vuejs ajax request using a v-model in a v-for - arrays

I have a set of data (todos array) that displays in my model, and I am rendering this data in a list.
I am trying to implement a functionality on the list that whenever click on any item on the list, the selected variable's value should be updated with that item'sforumID, and whenever a different item is clicked the selected variable should update accordingly.
I tried adding a v-model, but that breaks. So need some direction on how can I achieve this.
new Vue({
el: "#app",
data: {
myId:"",
selected:"",
todos: [{"ForumId":41830,"Name":"test","Description":{"Text":"","Html":""},"ShowDescriptionInTopics":false,"AllowAnonymous":false,"IsLocked":false,"IsHidden":false,"RequiresApproval":false,"MustPostToParticipate":false,"DisplayInCalendar":false,"DisplayPostDatesInCalendar":false,"StartDate":null,"EndDate":null,"PostStartDate":null,"PostEndDate":null,"StartDateAvailabilityType":null,"EndDateAvailabilityType":null},{"ForumId":41863,"Name":"new forum","Description":{"Text":"","Html":""},"ShowDescriptionInTopics":false,"AllowAnonymous":false,"IsLocked":false,"IsHidden":false,"RequiresApproval":false,"MustPostToParticipate":false,"DisplayInCalendar":false,"DisplayPostDatesInCalendar":false,"StartDate":null,"EndDate":null,"PostStartDate":null,"PostEndDate":null,"StartDateAvailabilityType":null,"EndDateAvailabilityType":null}]
},
methods: {
myMethod1() {
var vm = this;
$.ajax({
url: "https://example.com/" + vm.myId +"/blogs/"+selected+"/topics/",
type: 'Get',
headers: {
accept: "application/json;odata=verbose"
},
success: function (data) {
console.log(data);
vm.Topics=data
}
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<li v-for="(item, index) in todos">{{item.Name}}
</li>
</div>

We can use the v-model directive to create two-way data bindings on form input, textarea, select elements, and custom components.
The following error in your code also proves the above definition.
<a v-model="selected">: v-model is not supported on this element type.
If you are working with contenteditable, it's recommended to wrap a
library dedicated to that purpose inside a custom component.
Now, as you said, " item on the list when clicked, grabs the forumID found in the dataset and replaces the selected value with that forumID changing whenever a different item in the list is clicked"
This simply stated that you only want to update the selected variable with the currently clicked item's forumID which directly means-
no two-way-binding is required == no v-model is required.
So, why not use only a click event to update the selected variable?
Below is the demo in which when you click on any list item, the selected variable will update with that item's forumID.
new Vue({
el: "#app",
data() {
return {
myId: "",
selected: "",
todos: [{
ForumId: 41830,
Name: "test",
Description: {
Text: "",
Html: ""
},
ShowDescriptionInTopics: false,
AllowAnonymous: false,
IsLocked: false,
IsHidden: false,
RequiresApproval: false,
MustPostToParticipate: false,
DisplayInCalendar: false,
DisplayPostDatesInCalendar: false,
StartDate: null,
EndDate: null,
PostStartDate: null,
PostEndDate: null,
StartDateAvailabilityType: null,
EndDateAvailabilityType: null,
},
{
ForumId: 41863,
Name: "new forum",
Description: {
Text: "",
Html: ""
},
ShowDescriptionInTopics: false,
AllowAnonymous: false,
IsLocked: false,
IsHidden: false,
RequiresApproval: false,
MustPostToParticipate: false,
DisplayInCalendar: false,
DisplayPostDatesInCalendar: false,
StartDate: null,
EndDate: null,
PostStartDate: null,
PostEndDate: null,
StartDateAvailabilityType: null,
EndDateAvailabilityType: null,
},
],
};
},
methods: {
myMethod1() {
var vm = this;
$.ajax({
url: "https://example.com/" + vm.myId + "/blogs/" + vm.selected + "/topics/",
type: 'Get',
headers: {
accept: "application/json;odata=verbose"
},
success: function(data) {
console.log(data);
vm.Topics = data
}
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<div style="margin-bottom:20px;">Click on any item</div>
<li v-for="(item, index) in todos" :key="index">
{{item.Name}}
</li>
<div v-if="selected" style="margin-top:20px;">
Updated selected variable - {{selected}}
</div>
</div>
Try this also-
I called two actions together on the anchor tag's click, First, update the selected variable, and second, call myMethod1 function.
You can also call myMethod1 and pass forumId to it and update the selected variable inside it-
{{item.Name}}
And
myMethod1(forum_id) {
var vm = this;
vm.selected = forum_id
// YOUR REST CODE HERE
}

Related

Angularjs - Array items dependent property not updating

I have this scope array variable
$scope.menuItems = [
{
name: 'Login',
url:'#/login',
isAvailable: true
},
{
name: 'Register',
url: '#/register',
isAvailable: $scope.global.roleId == null
},
{
name: 'My Restaurants',
url: '#/myrestaurants',
isAvailable: $scope.global.roleId == constants.OWNER_USER_ROLE_ID
},
{
name: 'Create Restaurant',
url: '#/createrestaurant',
isAvailable: $scope.global.roleId == constants.OWNER_USER_ROLE_ID
},
{
name: 'Logout',
url: '#/logout',
isAvailable: $scope.global.roleId != null
}
];
This object is dependent on another scope variable $scope.global.roleId
I am loading menu from this array like this
<ul class="nav navbar-nav">
<li ng-repeat="n in menuItems | filter:{isAvailable:true}">
{{n.name}}
</li>
</ul>
And I want it to update menus automatically when $scope.global.roleId is updated.
To be noted, I am updating this variable $scope.global.roleId from a child controller and this field is getting updated properly but it is not affecting the array field isAvailable.
I checked both the variables $scope.global.roleId and $scope.menuItems in log and $scope.global.roleId is updating properly everytime but not $scope.menuItems.isAvailable field which is dependent on former
What wrong am I doing or expecting? And what is the right way to achieve this?
Achieved it like this, converting variable to property
{
name: 'My Restaurants',
url: '#/myrestaurants',
get isAvailable() {
return $scope.global.roleId == constants.OWNER_USER_ROLE_ID;
}
},

Angular-Strap Alerts: Unable to capture when alert.hide() triggers

I am trying to implement an automatic alert into my AngularJS application using Angular-Strap and the $alert service. So far so good, however, I am left with an issue I can't seem to resolve.
Is there a way I can I set a callback to capture when the $alert is hidden either using the duration property or the alert.hide() command? I want to run a function when the alert goes into a hidden state.
My code snippets looks like this:
var alertContent = '';
// Define the critical alert
var criticalAlert = $alert({
templateUrl: 'templates/critical.alert.tpl.html',
title: ' Critical Alert Detected!',
content: alertContent,
container: 'body',
type: 'danger',
dismissable: false,
duration: '20',
show: false
});
...
alertContent = 'Foo Bar!';
...
criticalAlert.$promise.then(criticalAlert.hide);
...
$scope.$on('alert.hide', function() {
console.log('Alert Hidden');
});
The result of the $promise, you can pass your own function, eg:
criticalAlert.$promise.then(function(){criticalAlert.hide();console.log('Alert Hidden'); })
More about $promise anglular $q
UPDATE
You can use $watch.
Of course, it's not a nice solution, but the best I found. At least, it works!
var alert = $alert({
title: ' Critical Alert Detected!',
content: 'its Alert!',
container: 'body',
type: 'danger',
dismissable: false,
duration: '5',
show: false,
});
$scope.$watch(function(){return alert.$isShown;},function(val){
if(val===true)
console.log('alert opened',val);
else
console.log('alert closed',val);
});
alert.$promise.then(alert.show);
UPDATED 2
We can use event of angular.
Live example on jsfiddle.
.controller('ExampleController', function($scope, $alert) {
$scope.alert = $alert({
title: ' Critical Alert Detected!',
content: 'its Alert!',
container: '#divForAlert',
type: 'danger',
dismissable: false,
duration: '5',
show: false,
prefixEvent:"mymodal",
});
$scope.alert.$promise.then(function() {
$scope.alert.show();
});
$scope.$root.$on('mymodal.hide.before', function(event) {
console.log('hide before',event);
});
$scope.$root.$on('mymodal.hide', function(alert) {
console.log('hide',alert);
});});
And some important html
<div ng-controller="ExampleController">
<h3>
ExampleController
</h3>
<form name="ExampleForm" id="ExampleForm">
<div id="divForAlert">
</div>
</form>

Polymer 1.0: How can I add paper-card heading content dynamically

Is there a way I can create paper-card heading dynamically using some property inside custom element? Following is what I tried but didn't work. Probably this is not the way to achieve what I want:( I googled for a couple of hours but ended up with nothing!
Custom Element
<script>
(function () {
'use strict';
Polymer({
is: 'nearest-customers',
properties: {
customers: {
type: Array,
value: [],
notify: true
},
cardViewMaxRecords: {
type: Number,
notify: true
},
showFullCustomerList: {
type: Boolean,
value: false,
notify: true
},
headingContent: {
type: String,
value: 'Custom card heading'
}
},
ready: function () {
this.heading.textContent = this.headingContent
},
});
})();
</script>
HTML
<nearest-customers id="nearestCustomers" card-view-max-records="3"></nearest-customers>
...
...
...
<script type="text/javascript">
window.addEventListener('WebComponentsReady', function (e) {
var nearestCustomers = document.querySelector("#nearestCustomers");
nearestCustomers.headingContent= "<a href='someurl'><iron-icon icon='fa:arrow-left'></iron-icon></a> This is a new content";
}
</script>
My objective is to put an iron-icon before the heading text and the icon can be used as a link to somewhere.
Thanks in advance
I'm sure there's a better way, but I just added the styles and structure:
<div class="header paper-card">
<div class="title-text paper-card">
<iron-icon icon="book"></iron-icon> Reading List
</div>
</div>

Angular Bootstrap Accordion issue

I am trying to create accordion to loop my data.it is partially working, but I need to dynamically add new part into accordion. at beginning I need to open the first one, but after user save it and click add,I need to open the second one, and close others. My code is:
<accordion close-others="oneAtATime">
<accordion-group
heading="{{destination.length}}"
is-open="status.isFirstOpen"
is-disabled="status.isFirstDisabled"
ng-repeat="destination in mileage.destionations">
<select ng-model='destination.Type'
id='type'
ng-options='Type for Type in mileageTypes'
ng-init='mileageTypes[0]'
ng-change='updateReimbur(destination)'>
</select>
<select ng-model='destination.Reimbursable'
id='reimbursable'
disabled="true"
ng-options='reimbursable for reimbursable in mileageReimbursment'
ng-init='mileageReimbursment[0]'>
</select>
</accordion-group>
</accordion>
JS:
$scope.mileage.destionations = [{
Type: '',
Reimbursable: "Yes",
Distance: true,
Odometer: false,
Total: 0,
From: '',
To: ''
}];
$scope.addNewDestionation = function () {
$scope.NewDestionation = {
type: '',
reimbursable: "Yes",
Distance: true,
Odometer: false,
total: 0,
From: '',
To: ''
}
$scope.mileage.destionations.push($scope.NewDestionation);
}
$scope.status = {
isFirstOpen: true,
isFirstDisabled: false
};
How can I always leave the last one(New one) open and close the others?
I'm sure you will get that working, by following these hints :
Replace close-others="oneAtATime" with close-others="true".
On all the repeated elements, you are writing : is-open="status.isFirstOpen", which is equivalent to is-open="true". This is your main mistake, as you're saying all groups should be opened.
Try to maintain a reference to the opened group. You could maintain an array of statuses but something like that will also do the trick, and avoid you to maintain all statuses :
is-open="status[$index].isOpen"
is-disabled="status[$index].isDisabled"
$index is an angular variable that references the index of the repeated element in the array. I leave you the js logic for the maintenance of the status object.
For the sake of style, correct the typo in destionations (destinations), initialize your default new destination in a variable outside the function addNewDestionation, and push that variable. Like that :
var newDestination = {
type: '',
reimbursable: 'Yes',
Distance: true,
Odometer: false,
total: 0,
From: '',
To: ''
};
$scope.addNewDestionation = function () {
$scope.mileage.destionations.push(newDestination);
}
close-others attribute takes a boolean value. You should either define
$scope.oneAtATime = true
or
<accordion close-others = "true">
If I've undestood it correctly you could do it like this.
Add a property openState to your destination object and change it like you need it. So keeping the second active could be done be setting every state to false except the second one.
It's similar to 2. form Michael's answer and I also think creating a status variable to keep track of the open states is probably better here.
Please have a look at the demo below (it's a reduced version of your code to keep things easier to read) or here at jsfiddle.
angular.module('demoApp', ['ui.bootstrap'])
.controller('mainController', MainController);
function MainController($scope) {
var itemCount = 0; // just to have an increasing title
$scope.oneAtATime = true;
$scope.mileage = {};
$scope.mileage.destionations = [{
Type: '',
Reimbursable: "Yes",
Distance: true,
Odometer: false,
total: itemCount,
From: '',
To: '',
openState: true
}];
$scope.addNewDestination = function () {
var index = $scope.mileage.destionations.length,
openState = (index == 1);
angular.forEach($scope.mileage.destionations, function(destination, index) {
// turn all off except second
destination.openState = (index == 1);
});
itemCount++;
var newDestination = {
type: '',
reimbursable: "Yes",
Distance: true,
Odometer: false,
total: itemCount,
From: '',
To: '',
openState: openState
};
$scope.mileage.destionations.push(newDestination);
}
$scope.status = {
isFirstOpen: true,
isFirstDisabled: false
};
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.2/ui-bootstrap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.2/ui-bootstrap-tpls.js"></script>
<div ng-app="demoApp" ng-controller="mainController">
<accordion close-others="oneAtATime">
<accordion-group is-open="destination.openState" heading="{{destination.total}}" ng-repeat="destination in mileage.destionations">
{{destination|json}}
</accordion-group>
</accordion>
<button ng-click="addNewDestination()">add</button>
</div>

Angular Grouping Directive starting point

I'am trying to create a grouping and filtering mechanism with several predefined filters. I have a collection of undefined rules and some predefined grouping actions, for example "relativeDate" (today, tomorrow, yesterday, this week, ...), "boolean" or . The set of actions should be expandable.
I've managed to get this working in a controller. But I want to outsource this into a directive to get this working with other object collections. The Problem is: I need to specify the template of the list dynamically.
Imagine the following collections:
$scope.memosReceived = [
{ id: 1, from: 'Henry Ford', title: 'Want your Model T?', received: '2015-05-04T12:30:00', read: true },
{ id: 2, from: 'Oliver Newton', title: 'Look at this!', received: '2015-06-15T08:00:00', read: true }
...
];
$scope.memosSent = [
{ id: 1, to: 'Henry Ford', title: 'That is an old car', sent: '2015-05-04T12:35:21', read: true },
{ id: 2, to: 'Oliver Newton', title: 'Stop Spam', sent: '2015-06-15T08:01:47', read: false }
...
];
Now the markup should be like the following:
<div ng-controller="controller">
<h2>Received</h2>
<grouped-list ng-model="memosReceived" item-var="received" grouping-options="groupingReceived">
<h2>{{ received.title }} <sub>by {{ received.from }}</h2>
</grouped-list>
<h2>Sent</h2>
<grouped-list ng-model="memosSent" item-var="sent" grouping-options="groupingSent">
<h2>{{ sent.title }} <sub>to {{ sent.to }}</h2>
</grouped-list>
</div>
Options could be like:
$scope.groupingReceived = [
{ modelKey: 'received', action: 'relativeDate', options: { [.. passed to grouping action, like value->name mapping ..] },
{ modelKey: 'read', action: 'boolean', options: { [...] } }];
$scope.groupingSent = [
{ modelKey: 'sent', action: 'relativeDate', options: { [.. passed to grouping action, like value->name mapping ..] },
{ modelKey: 'read', action: 'boolean', options: { [...] } }];
The rendered HTML should look like this "PseudoHtml":
<div ng-controller="controller">
<h2>Received</h2>
<div class="grouped-list">
<div class="filter-section">
<button ng-click="openFilters = !openFilters>Open Filters</button>
<div class="filter-options" ng-hide="!openFilters">
<h4>Group by</h4>
[selectbox given group actions] [selectbox sort ascending or descending]
<h4>Filter by</h4>
[build filters by similar to group actions given filter actions]
</div>
</div>
<div class="group">
<div class="group-header">
<h3>Yesterday</h3>
</div>
<ul class="group-list">
<li ng-repeat="received in ngModel | customFilters">
<!-- something like transclusion starts here -->
<h2>{{ received.title }} <sub>by {{ received.from }}</h2>
<!-- something like transclusion ends here -->
</li>
</ul>
</div>
<div class="group">
<div class="group-header">
<h3>Last Week</h3>
</div>
<ul class="group-list">
<li ng-repeat="received in ngModel | customFilters">
<!-- something like transclusion starts here -->
<h2>{{ received.title }} <sub>by {{ received.from }}</h2>
<!-- something like transclusion ends here -->
</li>
</ul>
</div>
</div>
<h2>Sent</h2>
<div class="grouped-list">
[... same like above ...]
</div>
</div>
I am really struggeling how to achieve this behavior, where to store the several parts of the logic (e.g. the grouping actions, the custom filters) and how to transclude this correctly.
Maybe someone can give me a good starting point for that.
You could create a custom filter and call it from the controller of your directive.
Inside of this filter you can decide which filter action should be triggered by passing parameters to the filter.
I would call it from the controller instead of the template because there you can easier chain your filters.
Please have a look at the demo below or in this jsfiddle.
During adding my code to SO I've detected a bug (not displaying the item) in my code with a newer AngularJS version. Not sure what it is but with 1.2.1 it's working.
I'll check this later. Seems like a scoping issue.
angular.module('demoApp', [])
.filter('aw-group', function($filter) {
var filterMethods = {
relativeDate: function(input, action) {
console.log('relative date called', input);
return input; // do the translation to relative date here
},
filterByNumber: function(input, action, options) {
// if you need mor parameters
return $filter('filter')(input, options.number);
},
otherFilter: {
}
};
return function(input, action, options) {
return filterMethods[action](input, action, options);
};
})
.directive('groupedList', function () {
return {
restrict: 'E',
scope: {
model: '=',
itemVar: '=',
filter: '='
},
transclude: true,
template: '<ul><li ng-repeat="item in filteredModel" ng-transclude>'+
'</li></ul>',
controller: function($scope, $filter) {
//console.log($scope.filter);
$scope.filteredModel = $filter('aw-group')($scope.model, 'filterByNumber', { number: 2 }); // passing action from $scope.filter.action as second parameter, third is an options object
}
};
})
.controller('mainController', function () {
this.data = [{
title: 'Test1',
from: 'tester1'
}, {
title: 'Test2',
from: 'tester1'
}, {
title: 'Test3',
from: 'tester1'
}, ];
this.groupingReceived = [{
modelKey: 'received',
action: 'relativeDate',
options: {},
modelKey: 'read',
action: 'boolean',
options: {}
}];
this.memosReceived = [{
id: 1,
from: 'Henry Ford',
title: 'Want your Model T?',
received: '2015-05-04T12:30:00',
read: true
}, {
id: 2,
from: 'Oliver Newton',
title: 'Look at this!',
received: '2015-06-15T08:00:00',
read: true
}];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainController as ctrl">
<grouped-list model="ctrl.data" item-var="received" filter="ctrl.groupingReceived">
<h2>{{item.title}}<sub>{{item.from}}</sub></h2>
</grouped-list>
</div>

Resources