Backbone validate function not getting called - backbone.js

My validate function is not getting called in the code below:-
var Vehicle = Backbone.Model.extend({
color: 'green',
validate: function (attrs) {
var validColors = ['white', 'red', 'blue', 'yellow'];
var colorIsValid = function (attrs) {
if (!attrs.color) return true;
return _.contains(validColors, attrs.color);
}
if(!colorIsValid(attrs)) {
return "color must be one of: " +validColors.join(",");
}
}
});
var car = new Vehicle();
car.on('error', function (model, error) {
console.log(error);
});
console.log(car.get('color'));
car.set('color', 'muave');
Please see fiddle
http://jsfiddle.net/vineet85/Fa8jr/5/
Can someone tell me why the validate function is not getting called?

In Backbone.js validate is called automatically on save but not on set.
If you want validations to run when setting a value you will need to use the validate option. e.g.
car.set('color', 'muave', {validate: true});
See http://backbonejs.org/#Model-validate
The error event is triggered when an error occurs, typically on the server, when trying to save the object. See http://backbonejs.org/#Events-catalog
If you want to catch validation failures try handling the invalid event:
car.on('invalid', function (model, error) {
console.log(error);
});

Seems need to write some extra code for to run the validate.
like below:
car.set('color', 'muave', {validate:true});

Related

Quill Editor, The 'on' method works only once

I have the following, script for the QuillEditor(I have multiple editors):
var editors = {};
function editors() {
var toolbarOptions = [
[{'list': 'ordered'}, {'list': 'bullet'}],
];
data.forEach(function (el) {
editors[el] = new Quill(el['editor'], {modules: {toolbar: toolbarOptions}, theme: 'snow', scrollingContainer:el['quill'] });
editors[el].on('text-change', copyText(el['text'], editors[el]));
});
}
}
function copyText(text, editor) {
text.innerHTML = editor.root.innerHTML;
console.log(text.innerHTML)
}
To use it in backend, I'm copying the text from the editor to a textarea copyText(el['text'].
I need to always work, but it is coping text/html, only once when the function is executed. I'm expecting editors[el].on('text-change', to work like an event listener.
The scrollingContainer, doesn't a scroll. I check that the target exist, is the parent of the editor.
I am not sure if this part of the error but you have an extra } after after the editors function.
The main problem here is that instead of setting the event listener you are running the event listener which is why it is running only once.
So change the event-listener line to:
editors[el].on('text-change', function () {
copyText(el['text'], editors[el]);
});
I don't generally like creating functions in other functions and especially inside loops so I would recommend creating a function factory function that will create a new function for each element.
function createListener(text, editor) {
return function(){
copyText(text, editor);
};
}
And call it like this:
editors[el].on('text-change', createListener(el['text'], editors[el]));

Jasmine: Testing a function returning a promise

I am writing tests using Jasmine for my angular application. All the tests are passing. My class looks like follows:
class xyz implements ng.IComponentController {
private myList: ng.IPromise<MyList[]> ;
//declare necessary variables
/* #ngInject */
constructor(private ListService: ListService,
) {
this.myList = this.ListService.getList();
}
public onChange(): void {
this.isNameUnique(this.name).then(function(unique){
scope.isUnique = unique;
scope.errorNameInput = !reg.test(scope.name) || !scope.isUnique;
scope.myFunction({
//do something
});
});
}
public isNameUnique(name: string): ng.IPromise<boolean> {
return this.myList
.then(
(names) => {
_.mapValues(names, function(name){
return name.uuid.toLowerCase();
});
return (_.findIndex(names, { uuid : uuid.toLowerCase() }) === -1) ? true : false;
});
}
}
Here, I am using ListService to pre-populate my list in the constructor itself (so it calls the service only once). Then, in my onChange method, I am checking
if a name is unique or not. The isNameUnique is returning a boolean promise.
Now, I'm trying to get 100% coverage for my test. I'm getting confused about testing isNameUnique method here. My first test is:
(Assuming myList is a json similar to response I will get from service)
this.$scope.myFunction = jasmine.createSpy('myFunction');
it('should ...', function() {
this.view.find(NAME_INPUT).val('blue').change(); // my view element.
this.getList.resolve(myList);
this.controller.isNameUnique('blue').then(function (unique) {
expect(unique).toEqual(false); //since blue is already in my json
expect(this.controller.errorNameInput).toEqual(true); //since its not unique, errornameinput will be set to true
expect(this.$scope.myFunction).toHaveBeenCalled();
});
});
I would expect this test to cover the line: scope.errorNameInput = !reg.test(scope.name) || !scope.isUnique and invocation of myFunction() but it still shows uncovered. Not sure why.
Please let me know if you see anything else wrong since I'm quite new to Angular and Jasmine. Thanks.
You need to call $scope.$digest() to cause your promise to resolve in your test. There is a handy tutorial that discusses this in depth here
Hope that helps!

I'm not calling $apply explicitly but still get Error: [$rootScope:inprog] $apply already in progress

In a angular factory I have a method to create a new item, which has a connection to a user and a price to add to that users "items" array (like a shopping cart). So I have to see if the user is present in my the local users array if not then on the server and if not then create the user.
Code looks like this:
var saveItem = function (item) {
var user = filterUserById(item.ownerId);
if (user) {
user.createItem(item);
} else {
repository.getUserById(item.ownerId).then(
function (serverUser) {
var userViewModel = repository.getUserViewModel(serverUser);
userViewModel.createItem(item);
users.push(userViewModel);
}
, function () {
user = {
id: item.ownerId,
items: [
createItemDto(item)
]
};
repository.createUser({ id: user.id }, user);
users.push(repository.getUserViewModel(user));
});
}
};
No matter which of the "cases" occurs (user was found localy, on the server or was created and added) I get an error:
Error: [$rootScope:inprog] $apply already in progress
http://errors.angularjs.org/1.3.0-beta.18/$rootScope/inprog?p0=%24apply
I recon this may have to do with the fact that I'm using resources in my repository, but I don't think resource should (since it's a part of angular..). Here's the user.createItem method, code:
user.createItem = function (item) {
var resource = userResource
, itemDto = createItemDto(item)
, command = [{
Type: 'add',
Name: 'items',
Value: itemDto
}];
resource.createItem({ id: item.ownerId }, command);
this.items.push(itemDto);
};
Y U NO WERK!? PLS HLP! :'(
P.S. I don't have any explicit calls to apply, compile or digest anywhere in my code.
Found the problem! I had put a small code line to set focus on the correct input after the item was added and form was emptied. This consisted of a
$('selector').focus();
This was colliding with digest cycle... Solution:
$timeout($('selector').focus());
Try wrapping your call to user.createItem(item) in a $timeout function:
$timeout(function() {
user.createItem(item);
}, 0);
It's possible you could be triggering some other call to $scope.$apply() some other way.
Alternatively, try using $scope.$evalAsync(function())
Here's some good info: inprog

Nested backbone model results in infinite recursion when saving

This problem just seemed to appear while I updated to Backbone 1.1. I have a nested Backbone model:
var ProblemSet = Backbone.Model.extend({
defaults: {
name: "",
open_date: "",
due_date: ""},
parse: function (response) {
response.name = response.set_id;
response.problems = new ProblemList(response.problems);
return response;
}
});
var ProblemList = Backbone.Collection.extend({
model: Problem
});
I initially load in a ProblemSetList, which is a collection of ProblemSet models in my page. Any changes to the open_date or due_date fields of any ProblemSet, first go to the server and update that property, then returns. This fires another change event on the ProblemSet.
It appears that all subsequent returns from the server fires another change event and the changed attribute is the "problems" attribute. This results in infinite recursive calls.
The problem appears to come from the part of set method of Backbone.Model (code listed here from line 339)
// For each `set` attribute, update or delete the current value.
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
}
// Trigger all relevant attribute changes.
if (!silent) {
if (changes.length) this._pending = true;
for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
}
The comparison on the problems attribute returns false from _.isEqual() and therefore fires a change event.
My question is: is this the right way to do a nested Backbone model? I had something similar working in Backbone 1.1. Other thoughts about how to proceed to avoid this issue?
You reinstantiate your problems attribute each time your model.fetch completes, the objects are different and thus trigger a new cycle.
What I usually do to handle nested models:
use a model property outside of the attributes handled by Backbone,
instantiate it in the initialize function,
set or reset this object in the parent parse function and return a response omitting the set data
Something like this:
var ProblemSet = Backbone.Model.extend({
defaults: {
name: "",
open_date: "",
due_date: ""
},
initialize: function (opts) {
var pbs = (opts && opts.problems) ? opts.problems : [];
this.problems = new ProblemList(pbs);
},
parse: function (response) {
response.name = response.set_id;
if (response.problems)
this.problems.set(response.problems);
return _.omit(response, 'problems');
}
});
parse gets called on fetch and save (according to backbone documentation), this might cause your infinite loop. I don't think that the parse function is the right place to create the new ProblemsList sub-collection, do it in the initialize function of your model instead.

Accessing NotMapped Computed Properties Using BreezeJS and AngularJS

I've followed the examples laid out in http://www.breezejs.com/documentation/extending-entities, and the DocCode test labeled "unmapped property can be set by server class calculated property".
When I try and access the unmapped property, it has the same value as it did when it was set in the constructor. I have verified that the server is returning the unmapped property. It is possible that I don't know the proper way to access the unmapped property when using angularjs with breezejs.
See below on the two lines I've commented as "empty string, value expected".
breeze.NamingConvention.camelCase.setAsDefault();
breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);
var ms = new breeze.MetadataStore();
var manager = new breeze.EntityManager({
serviceName: 'breeze/Projects',
metadataStore: ms
});
manager.enableSaveQueuing(true);
manager.fetchMetadata()
.then(function () {
var Sheet = function ()
{
this.previewUrl = "";
}
ms.registerEntityTypeCtor("Sheet", Sheet);
var query = new breeze.EntityQuery("Project")
.withParameters({ id: $stateParams.projectId });
manager
.executeQuery(query)
.then(function (data)
{
var p = data.results[0]; // project
var s = p.sheets[0];
console.log(s["previewUrl"]); // empty string, value expected
console.log(s.previewUrl); // empty string, value expected
})
.fail(function (e)
{
console.log(e.message);
alert(e);
});
});
Edit: Additional Info
I've figured out that this is caused by the camelCase naming convention. I was able to fix the bug in breeze.js by altering the getPropertyFromServerRaw function to this:
function getPropertyFromServerRaw(rawEntity, dp) {
return rawEntity[dp.nameOnServer || dp.isUnmapped && rawEntity.entityType.metadataStore.namingConvention.clientPropertyNameToServer(dp.name)];
}
Hopefully this fix can make it into the next version of breeze.js

Resources