In this global change event, is there a way I can detect which attribute was changed?
myModel.on('change', function(model) {
// Which attribute changed?
});
I tried the following:
Using myModel.previousAttributes() but it always returned latest values... I guess it only updates after a server interaction.
Iterating trough attributes and using myModel.hasChanged(attr) but it always returned false.
It's there a way to accomplish this?
You can use model.changedAttributes
changedAttributes model.changedAttributes([attributes])
Retrieve a hash of only the model's attributes that have changed, or false if
there are none.
Optionally, an external attributes hash can be passed
in, returning the attributes in that hash which differ from the model.
This can be used to figure out which portions of a view should be
updated, or what calls need to be made to sync the changes to the
server
For example,
var m = new Backbone.Model({
att1: 'a',
att2: 'b',
att3: 'c'
});
m.on('change', function() {
console.log(m.changedAttributes());
console.log(_.keys(m.changedAttributes()));
});
m.set({
att1: 'd',
att3: 'e'
});
And a demo http://jsfiddle.net/nikoshr/NYnqM/
Related
I'm using Angular Schema Form and I want to set a model property to null when a field value changes. I've tried using onChange in the form definition like so
{
key: '7_11',
type: 'radios',
titleMap: [{value: 'no', name: 'No'}, {value: 'yes', name: 'Yes'}],
onChange: function(modelValue,form) {
if (!modelValue) {
// model['8_1'] = null
}
}
}
Notice that the model property I'm trying to set is bound to a different field than the field that has changed
I can't do it in the manner indicated by the comment, because model is not in scope in the onChange listener.
Update
A second example is this Plunker demo. Say we want to clear the 2 checkboxes (by removing the corresponding properties from the model) whenever some text is entered in the Name field, how could this be achieved?
I should also point out that for reasons I won't bore you with, I can't implement this using a conditional and destroyStrategy.
Well, it is not uncommon (because it is kind of natural) to set the model and the form inside the scope of the same controller.
If that is the case, you can set X by $scope.model.X = modelValue.
Or perhaps you are in a position where you can't reach the scope of the model, in that case it is more complicated. On the other hand I would recommend to do it all in the same place.
(Sometimes that is not possible. For example, I have dynamic forms and schemas in my application, however in those cases I use generalized functionality like the filter functionality in ASFDS or ASF conditions and avoid hard-coding UI using onChange) .
For example, this would set foo2 if foo is changed in the plunkr:
onChange: function(modelValue,form) {
if (modelValue) {
$scope.model.foo2 = ["Yes"];
}
},
What I'm trying to build is a SPA containing multiple selects on one page, where each select has it's own model. Choosing different options in the select boxes may show/hide other fields on the page and modify data for other selects (load different sets). Finally options in two selects may change the url (so when page is loaded with a proper address, those two options will be preselected).
Now I'm wondering what'll be the best approach here.
First. Is it worth to switch to ui-router in this case ?
Second. I need to write a custom directive for this select, that will have the following functionality
load data collection
display data and remember selection
reload it's data collection when other select triggered it
reload data for other selects
Now I've written directives before but never anything this (I think) complex. That's why few questions come to my mind.
How can I bind different data to my directive ?
Should this be just a single massive complex directive or divide it to smaller parts (like one directive for showing closed select box and another one to show the list and so on) ?
How can I trigger event when data should be changed and listen to a similar event from another select. Via controller ?
Thanks in advance for all your help.
What I can suggest is keep it MVC. Here is one of the solution -
Create a controller to store model data (here $scope.selectOptions inside controller)
Pass this values to 'select directive' instance to display
Whenever user select the value in directive, pass that selected value to controller (lets say in controller $scope.selectedValue is holding that value)
In controller add $watch on $scope.selectedValue and in its callback call for different data set for another select option. (lets say storing that data set in $scope.anotherSelectOption )
Pass this $scope.anotherSelectOption to '2nd directive' instance
Whenever user select the value in 2nd directive, pass that selected value to controller (lets say in controller $scope.anotherSelectedValue is holding that value)
In controller add $watch on $scope.anotherSelectedValue and in its callback change url thing you want to do
Here's your HTML will look like -
<div ng-controller="MyCtrl">
<select-directive selection-option="selectOptions", selected-option="selectedValue"> </select-directive>
<select-directive selection-option="anotherSelectOptions", selected-option="anotherSelectedValue"> </select-directive>
</div>
Here's your controller, it will look something like this -
yourApp.controller('MyCtrl', function($scope) {
$scope.selectOptions = []; // add init objects for select
$scope.selectedValue = null;
$scope.$watch('selectedValue', function() {
// here load new values for $scope.anotherSelectOptions
$scope.anotherSelectOptions = [] // add init objects for select
$scope.anotherSelectedValue = null;
});
$scope.$watch('anotherSelectedValue', function() {
// here add code for change URL
});
});
Here's your directive scope is
yourApp.directive('selectDirective', function() {
return {
templateUrl: 'views/directives/select.html',
restrict: 'E',
scope: {
selectionOption: '=',
selectedOption: '='
}
};
});
I am attempting to use Backbone Validation with Backbone Stickit, I wish to validate one attribute at a time as the user enters them. However, when a user enters a value all attributes on the model get validated instead of just the one the user has changed. What am I doing wrong?
My View:
bindings:{
'#username' : {
observe:'username',
setOptions: {
validate:true
}
},
'#email' : {
observe:'email',
setOptions: {
validate:true
}
},
'#firstname' : {
observe:'firstName',
setOptions: {
validate:true
}
},
.......
onShow: function(){
Backbone.Validation.bind(this, {
valid: function(view, attr) {
alert('VALID - ' + attr);
},
invalid: function(view, attr, error) {
alert('INVALID - ' + attr);
}
});
this.stickit();
},
Everything you pass through setOptions is used when setting the value in the model (1). When you pass validate: true to the set function of a Backbone model it will validate the values in the model as well as the values passed to the set function (2) meaning it will validate the whole model every time you set a new value causing the problem you're seeing now. You're not doing anything wrong.
You could probably solve this by splitting up your validation into multiple separate functions and calling only the required ones on attribute change and then changing the validate function to call all those separate functions to validate the entire model.
This happened to me as well. In my case, I was setting default values in the model as '' (blank). Removed them and it worked
For this to work you should remove defaults (at least for the attributes your validating) values from your model
Try using the backbone.validation forceUpdate parameter on your backbone.stickit setOptions object in yout view bindings. That worked for me and I had a kind of similar problem.
Just like yousefcisco mentioned, backbone will validate all the attributes in the model on set or save, depending of your options passed, but in my case is not that I needed to validate each attribute separately, but the attributes didn't get set even if only one attribute was invalid, then I tried the forceUpdate: true, and it did its magic.
check it here: http://thedersen.com/projects/backbone-validation/#configuration/force-update
Assuming I have an object in the rootScope:
$scope.mydict = {
'apple': {'properties':{'color':'red', 'texture': 'mm'}, 'physics': {'bouncy': 'no', 'impact': 'breaks'}},
'pear': {'properties':{'color':'red', 'texture': 'mm'}, 'physics': {'bouncy': 'no', 'impact': 'breaks'}}
}
Assuming depending on how the user interacts, this $scope may have more and more fruits indicated by the key. How would I be able to $watch for changes to the dictionary for any new items that are added and process them?
I tried:
$watch("mydict" ...)
Though it complains about mydict not being there and doesnt seem to allow me to watch dictionaries. Note that the $scope.mydict is initialized by another service i defined to get the resource from to populate that variable in the $scope, so it might not exist in the directive that is responsible for $watch-ing the data changing.
What I want to do is for every new fruit that gets added to "mydict", output the "properties.texture" of ONLY that item. If it is possible to watch and see if any new frutis added even have a "properties.texture" before doing anything(i.e. alert), that would be even better. Though I am not sure if this is possible.
The Backbone documentation says:
Model.set will fail if validation fails - it won't set the value therefore it won't trigger any callback. We can pass { silent: true } to Model.set - then it will set the value but won't trigger any callback neither.
So,
Why does Backbone Model require a valid state to simply set an attribute value? What if we want to set attributes as the user interacts with the UI, but the model is not valid yet? It means change callbacks are unavailable unless we pass { silent: true } then manually trigger the change?!
Please say you know a better way of handling this :)
I'm not sure how to answer the Why questions but you could say that there are arguments for why it is good that set runs validations. For instance, it makes it dead simple to do client side validation in real time.
If your problem could be solved by only validating the value that is currently being changed by the user, you can do that by combining your validate method with the hasChanged method.
For example something like this:
Backbone.Model.extend({
defaults : { name : "" },
validate : function (attrs) {
var errors = {};
if(this.hasChanged("name") && attr.name.length == 0) {
errors.name = "Need a name yo!";
}
//...
if(_.keys(errors).length > 0) {
return errors;
}
}
})
In Backbone whenever you call set on model , It keeps track of what attributes of model has been changed and what attributes are newly added.Calling validate allows it be more efficient in doing it .Passing {silent:true} as options in set function causes validate and change to not execute so if doesnt fire any change events.
If you want to set attributes as the user interacts with the UI, but the model is not valid yet
In this case you can set the change in a plain object make sure object keys are sames as model's attribute and then at some point just set in your model.
var uiChanges = {name:'x'}; //just fill it with your changes
ur_model.set(uiModel); //then set it ,this way it fires change events only once
To check the diff between your plain object and model you can use the
ur_model.changedAttributes(uiChanges);
changedAttributes -
Return an object containing all the attributes that have changed, or false if there are no changed attributes.
You can further use it save only those attributes that have changed rather than saving entire model again.