AngularJS custom filter being called twice - javascript

I've created a custom filter using AngularJS that prints out the fruits that start with a p. As far as I can tell, I've implemented the custom filter correctly.
I'm printing out a message every time the filter is called but I'm curious to why my filter is being called twice.
Looking at similar problems on stackoverflow I found one person who had a similar issue however the problem wasn't answered and was a little different.
JSFiddle Solution
http://jsfiddle.net/ddemott/U3pVM/22606/
HTML Code
<body>
<div ng-controller="ExampleCtrl" ng-app="sampleApp">
<div class="showDiffTags" ng-repeat="val in values | myFilter:'p'">{{val.name}}</div>
</div>
</body>
AngularJS Code
angular.module('sampleApp', []).filter('myFilter', function() {
return function(items, firstLetter) {
var groups = [];
console.log("called function");
console.log(items.length);
for (var i = 0; i < items.length; i++) {
if (items[i].name.substring(0, 1) == firstLetter) {
groups.push(items[i]);
}
}
return groups;
}
}).controller('ExampleCtrl', function($scope) {
$scope.values = [{
name: 'apple'
}, {
name: 'banana'
}, {
name: 'orange'
}, {
name: 'avocado'
}, {
name: 'pineapple'
}, {
name: 'peach'
}, {
name: 'plum'
}, {
name: 'grapes'
}, {
name: 'mango'
}, {
name: 'papaya'
}, ];
});

That is correct behaviour and it's strongly coupled with how $digest cycle works
Every time model changes the $digest is run at least twice:
After model changes it runs the watchers and updates the models
To check if the first $digest made changes to model, if so another digest is called up to max ten iterations then angular throw errors.
There is nothing to worry unless you have a lot of functions in templates and unstable models (changing often)
I've updated your fiddle with simple button that updates model on scope
http://jsfiddle.net/U3pVM/22610/
<button ng-click="numModel = numModel + 1">
update model {{numModel}}
</button>
You will see that every time you click the button filter runs twice

Related

updating an item in a knockout bound list

I have a list of questions and each question has a list of answers and I am using knockout to display each question 1 at a time. What I am doing is setting up my model with the full list and then making a currentQuestion property observable and after each question is answered, I increment this to the next question. Problem is that I have to change some data on the question when the user hovers it but cant figure out how to make the answers observable.
I've put together a jsfiddle and what I want to do is change the answer text to 'modified' when the user clicks the answer.
How do I make the AnswerText observable so that when the click handler changes its value this is reflected in the UI.
Any ideas where I am going wrong would be appreciated.
jsfiddle code if below:
<div class="top">
<div data-bind="foreach: currentQuestion().Answers">
<div data-bind="click: $root.answerClicked">
<div data-bind="text: AnswerText"></div>
</div>
</div>
</div>
function MyVM() {
var self = this;
this.session = {
Questions: [
{
QuestionText: "Q1",
Answers: [
{
AnswerText: "Q1A1"
},
{
AnswerText: "Q1A2"
}
]
},
{
QuestionText: "Q2",
Answers: [
{
AnswerText: "Q2A1"
},
{
AnswerText: "Q2A2"
}
]
}
]
};
this.currentQuestion = ko.observable();
this.currentQuestion(self.session.Questions[1]);
this.answerClicked = function (selectedAnswer, event) {
alert('hello');
selectedAnswer.AnswerText = 'modified1';
selectedAnswer.AnswerText('modified');
};
}
var model = new MyVM();
ko.applyBindings(model);
Currently you're binding the UI to a static string. In order to make the UI reflect changes the string must be wrapped in an observable as you stated so it sounds like you were on the right track. All you need to do is use an observable in each answer object.
Answers: [
{
AnswerText: ko.observable("Q2A1")
},
{
AnswerText: ko.observable("Q2A2")
}
]
Then in the click function you'll want to get rid of the first assignment operator where it would be replacing the observable and use only the second line where a value is assigned to the observable instead.
this.answerClicked = function (selectedAnswer, event) {
alert('hello');
//selectedAnswer.AnswerText = 'modified1';
selectedAnswer.AnswerText('modified');
};

AngularJS Nested Object Array Pathway

I have a factory, which goes into a controller, and I am trying to get data from that display on an HTML page. I am having trouble specifying an Object's pathway however.
My Factory:
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
apis:
[{
accounts: [
{
v1: [
{
uri: Head+"/v1/accounts/",
item1: "AccountNumber",
item2: "MoneyInAccount"
}],
v2: [
{
uri: Head+"/v2/accounts/",
item1: "AccountNumber",
item2: "MoneyInAccount"
}]
}
],
customers: [
{
v1: [
{
uri: Head+"/v1/customers/",
item1: "CustomerName",
item2: "CustomerID",
item3: "CustomerEmail"
}]
}
]
}]
};
});
My Controller:
app.controller('APIController', function($scope, APIMethodService) {
$scope.title = "API";
$scope.apiList = APIMethodService;
$scope.accountList = $scope.apiList.accounts.v1;
$scope.accountList2 = $scope.apiList[0][0];
});
My HTML
<div ng-controller="APIController">
<div id="api" class="row">
<div class="col-xs-12">
<div class="row" style="font-size:20px">
{{title}} Page!
<table class="table table-striped">
<tr ng-repeat="api in apiList | orderBy:'uri' | filter:search">
<td>{{api.uri}}</td>
<td>{{api.item1}}</td>
<td>{{api.item2}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
The errors I get are in regards to the Controller trying to parse out the individual objects I wish to grab, like accounts or customers, and then any version v#, they may have.
So it will say something such as
TypeError: Cannot read property 'v1' of undefined
I just need some help specifying the proper pathways into my factory service.
You have a few problems. First, you are referring to the object returned from the factory incorrectly. APIMethodService is the factory that you're injecting, so you need to first reference the object that that factory is returning like this:
APIMethodService.apis
This will give you your entire JSON object.
From there, the rest of your object is made up of arrays of objects, so referring to 'v1' won't do you any good. You need to specify an index instead. If you want v1, you'll need:
APIMethodService.apis[0].accounts[0].v1
This will give you the v1 array, which again is an array of objects.
Customers would be:
APIMethodService.apis[0].customers[0].v1
The first problem you have is that the factory returns an object with a single property called apis. So basically this $scope.apiList.accounts.v1 should be $scope.apiList.apis.accounts.v1. Bu that's not all as this won't either work since dotting(.) into apis is an array you'd have to use the index. In this case it would be $scope.apiList.apis[0] and then you could .accounts[0].v1 which is also an array containing a single object.
Now if you can I would suggest to you that you'd change how you represent this data structure.
This is how you could do it.
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
accounts: {
v1: {
uri: Head+"/v1/accounts/",
items: ["AccountNumber","MoneyInAccount"]
},
v2: {
... // skipped for brevity
}
},
customer: {
... // code skipped for brevity
}
};
});
And then it's just a matter of dotting into your APIMethodService-object like APIMethodService.accounts.v1.items[0] if you want the AccountNumber method name.
Constructing your url could then be done like this.
var baseUrl = APIMethodService.accounts.v1.uri; // 'api.example.com'
var url = baseUrl + APIMethodService.accounts.v1.items[0]; // 'AccountNumber'
// url = "api.example.com/v1/accounts/AccountNumber"
Again, this is one way you could do it but this can be further enhanced upon. The examples I provided are simply for demo purposes and this is not in any way the only way to do it.
Expanding upon recieved comments/questions your service (and data representation) could now look like this.
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
accounts: {
v1: {
uri: Head+"/v1/accounts/",
items: [
{
name:'AccountNumber',
description:'Show the account number'
},
{
name:'AccountOwner',
description:'Show information about the owner of the account'
},
{
name:'MoneyInAccount',
description:'Show money in the Account'
}
]
},
v2: {
... // skipped for brevity
}
},
customer: {
... // code skipped for brevity
}
};
});
// Get descriptions
var accountNumberDescription = APIMethodService.accounts.v1.items[0].description; // 'Show the account number'
var accountOwnerDescription = APIMethodService.accounts.v1.items[1].description; // 'Show information about the owner of the account'
var moneyInAccountDescription = APIMethodService.accounts.v1.items[2].description; // 'Show money in the Account'
By using objects with properties like this it's alot easier to understand what you are trying to do. With arrays with indexes you'd have to know or take a look at the source to see what's going on. Here, someone viewing your code they can instantly understand that it is the description you are getting.

Associate multiple resources to service appointment entity using OData endpoint with javascript in CRM 2013

My goal is to create a service appointment record associated with multiple resources.
For this purpose I have followed this MSDN example.
The problem rises when I try to associate multiple resources for one particular service appointment, CRM server will store only the last one(1 record). Following demonstrates my code:
//attendees =[... array of resource ids]
var serviceAppointment = {
ScheduledStart: new Date("12/22/2014 4:53 PM"),
ScheduledEnd: new Date("12/22/2014 5:53 PM"),
Subject: "test service",
ServiceId:
{
//// hardcoded id for simplicity
Id: "6f795012-ca55-e411-aa38-00155d0a0948",
LogicalName: "service"
}
};
SDK.JQuery.createRecord(serviceAppointment,"ServiceAppointment"
,function(sa){
for(var i=0;i<attendees.length;i++)
{
var activityParty = {
PartyId:
{
Id: attendees[i],
LogicalName: "systemuser",
},
ActivityId:
{
Id: sa.ActivityId,
LogicalName: "serviceappointment",
},
ParticipationTypeMask:
{
//See http://msdn.microsoft.com/en-us/library/gg328549.aspx
//10 is for resource
Value: 10
}
};
SDK.JQuery.createRecord(activityParty,"ActivityParty", function(ap){debugger;},errorHandler);
}
}
,errorHandler);
As far as I debugged the code the create record is being executed properly without no exception. I believe I'm missing a configuration flag somewhere in my code and CRM is considering one to one association rather than one to many.
Any clues?
I could solve this issue by passing array of parties in service appointment record at the first place rather than inserting them one-by-one at the end. Here is the code which works:
var parties =[];
for(var i=0;i< e.event.attendees.length;i++)
{
var activityParty = {
PartyId:
{
Id: e.event.attendees[i],
LogicalName: "systemuser",
},
ParticipationTypeMask:
{
//See http://msdn.microsoft.com/en-us/library/gg328549.aspx
//10 is for resource
Value: 10
}
}
parties.push(activityParty);
}
var serviceAppointment = {
ScheduledStart: e.event.start,
ScheduledEnd: e.event.end,
Subject: e.event.title,
ServiceId:
{
Id: "6f795012-ca55-e411-aa38-00155d0a0948",
LogicalName: "service"
},
serviceappointment_activity_parties: parties,
};
SDK.JQuery.createRecord(serviceAppointment,"ServiceAppointment"
,function(sa){
debugger;
e.event.ActivityId = serviceAppointmentActivityId = sa.ActivityId;
}
,errorHandler);
}
hope it helps somebody out there.

Swapping data in Angular UI-Grid, new columns not visible when changing dataset externally

I've got a query tool I've been working on, which has an angular form that is filled out, and then when it's submitted it uses AJAX which returns JSON, which is then rendered into ui-grid, that JSON response looks like
{
"success": true,
"message": "",
"columns": ["first_name", "last_name", "company", "employed"]
"results": [
{first_name: "John", last_name: "Smith", company: "Abc Inc", employed: true},
{first_name: "Johnny", last_name: "Rocket", company: "Abc Inc", employed: true}]
}
I'm working on both the PHP and angular so I have full control over this JSON response if need be. I'm running into an issue when my JSON response from a first AJAX call is rendered, and then I run another, seperate AJAX call on the same page and get a new data set: this new data set does not render any of the columns that were not in the original data set. This is hugely problematic as the table is essentially cleared when none of the columns are the same, and I often need to load completely different data into ui-grid in this single page app.
When the JSON is recieved I simply bind the jsonResult.results to the old $scope.myData variable that ui-grid is bound to.
I've made a plunker isolating this issue. A dataset with a "punk" column is loaded, and then clicking "swap data" will try to load a dataset with "employee" column instead of "punk". I've so far looked into directives that will refresh or reload when the $scope.myData variable changes using $watch, and looked at finding something like $scope.columnDefs to let ui-grid know. Relatively new to angular and javascript so directives are still a bit over my head.
I have updated your plunker slightly:
$scope.swapData = function() {
if ($scope.gridOpts.data === data1) {
$scope.gridOpts.columnDefs = [
{ name:'firstName' },
{ name:'lastName' },
{ name:'company' },
{ name:'employee' }
];
$scope.gridOpts.data = data2;
//punk column changes to employee
}
else {
$scope.gridOpts.columnDefs = [
{ name:'firstName' },
{ name:'lastName' },
{ name:'company' },
{ name:'punk' }
];
$scope.gridOpts.data = data1;
//employee column changes to punk
}
};
http://plnkr.co/edit/OFt86knctJxcbtf2MwYI?p=preview
Since you have the columns in your json, it should be fairly easy to do.
One additional piece that I figured out with the help of Kevin Sage's answer and the plunker example... If you are using the backward-compatible "field" attribute the swapping does not work properly when there are field name overlaps between the two sets of column definitions. The column headers and the column widths are not rendered properly in this case. Using the "name" attribute of the column definition corrects this.
$scope.swapData = function() {
if ($scope.gridOpts.data === data1) {
$scope.gridOpts.columnDefs = [
{ field:'firstName' },
{ field:'lastName' },
{ field:'company' },
{ field:'employee' }
];
$scope.gridOpts.data = data2;
//punk column changes to employee
}
else {
$scope.gridOpts.columnDefs = [
{ field:'firstName' },
{ field:'lastName' },
{ field:'company' },
{ field:'punk' }
];
$scope.gridOpts.data = data1;
//employee column changes to punk
}
};
Example here: Plunker
My solution:
$http.get('url').success(function(res) {
// clear data
gridOptions.data.length = 0;
// update data in next digest
$timeout(function() {
gridOptions.data = res;
});
});

KnockoutJS losing previous value when loading options after value

I'm feeding my options off an AJAX request, while the value is in the selection initially. However Knockout seems to delete values that aren't in the options on binding.
Example: http://jsfiddle.net/EVzrH/
Knockout seems to use selectExtensions (line 1699 of v3) to read and write the selected option. In this new values are matched to indexes, and returned by again getting the index and matching to data.
How can I save my data from being lost?
Generally, I handle this by prepopulating the observableArray with the current value (no need for the text, since you wouldn't likely know it yet).
Like:
var viewModel = {
val: ko.observable(1),
opts: ko.observableArray([{ Id: 1 }])
};
Then, let the observableArray get populated with the actual values when it returns.
For a more generic solution, you could use a custom binding as described in the second part of this answer: Knockout js: Lazy load options for select
This would pre-populate the observableArray for you and take into account that you may or may not have optionsValue set.
I can see 2 possible options here. First is to fill opts arrray before applying bindings:
var viewModel = {
val: ko.observable(1),
opts: ko.observableArray([])
};
viewModel.opts([
{ Id: ko.observable(1), Text: ko.observable("abc") },
{ Id: ko.observable(2), Text: ko.observable("someVal") },
{ Id: ko.observable(3), Text: ko.observable("other") }
]);
ko.applyBindings(viewModel);
Here is fiddle: http://jsfiddle.net/EVzrH/1/
Or if for some reason you cannot populate it before applying bindings you can just save value and them assign it again:
var viewModel = {
val: ko.observable(1),
opts: ko.observableArray([])
};
var value = viewModel.val();
ko.applyBindings(viewModel);
viewModel.opts([
{ Id: ko.observable(1), Text: ko.observable("abc") },
{ Id: ko.observable(2), Text: ko.observable("someVal") },
{ Id: ko.observable(3), Text: ko.observable("other") }
]);
viewModel.val(value);
Here is a fiddle: http://jsfiddle.net/EVzrH/2/
Either set the value after populating the options, or subscribe to the options:
viewModel.opts.subscribe(function() {
viewModel.val(1);
});
http://jsfiddle.net/gCyP6/
I've managed to get it working the way I wanted by commenting out some of the knockout code to avoid ko.dependencyDetection.ignore.
http://jsfiddle.net/EVzrH/3/
ko.bindingHandlers['value']['update'] = function (element, valueAccessor) {
ko.bindingHandlers['options']['update'] = function (element, valueAccessor, allBindings) {
Only problem is that it isn't minified, so switching to the minified library does not work.

Categories

Resources