Each row of my grid has a button for saving the model, and I need a way to highlight rows with pending edits. It's too easy to click around the grid without saving, and totally lose track of unsaved edits. Some users might also not notice the save button, and mistakenly think their edits save as soon as they click out of a cell.
This worked:
var MyRow = Backgrid.Row.extend({
initialize: function() {
MyRow.__super__.initialize.apply(this, arguments);
this.listenTo(this.model, 'change', function (model) {
this.$el.toggleClass('dirty', model.hasChanged());
});
}
});
Related
I need "good style" advice. I have a form which is populated from json. The code which populates the form is put inside render listener. The problem is, many form elements have change listeners, so when the form is populated these change listeners are triggered. I want to prevent this unwanted behavior.
// many form elements with change listeners come here
listeners:{
render:function(){
var frm=this.getForm();
Ext.Ajax.request({
url:'../handlers/instruct.handler.php?id='+id,
method:'POST',
params:{action:'params'},
success:function(result,request){
json=Ext.decode(result.responseText,1);
frm.setValues(json); // form population
// triggers change listeners
}
});
}
}
PS. I use ExtJs 4.2
You could suspend events on the fields:
var fields = form.getForm().getFields();
fields.each(function(f) {
f.suspendEvents();
});
form.setValues(json);
fields.each(function(f) {
f.resumeEvents();
});
I'm listing a collection with one check box and description. Now when user will click on submit button I want to validate that user should have clicked on at least one checkbox. I know there is an event "change" in collection but am not sure how to use it.
I want this collection should have a property of boolean type to check
whether the collection is changed or not.
Thanks for your prompt response.
I know i can use change event of collection. I wonder am correct in my solution. Here is my code. Note am registering the event after my view is rendered because on default I change some values.
complienceListView = Backbone.View.extend({
el: $('#complienceList'),
initialize: function () {
complienceList.bind("reset", this.render, this);
},
render: function () {
complienceList.each(function (complience) {
$(this.el).append(new complienceListItemView({ model: complience }).render().el);
}, this);
complienceList.on('change', function(){isDirty = true;})
return this;
}
});
var isDirty = false;
I'm developing a jQuery Backbone.js web application.
As it is in Adobe Flex, I have implemented 2 way data binding in my app for
input elements/widgets.
So, every input element/widget knows its corresponding model and model attribute name.
When the user hits tab or enter, the field value is automatically given to the model.
container.model.set(this.attrName, this.value, options); // command 1
In the other direction, when the model gets updated from the backend, the view of the
input element/widget should automatically get
updated:
container.model.bind("change:"+ this.attrName, this.updateView, this); // command 2
The problem is:
When the user hits enter and the model is automatically updated, also the "change:abc" is
triggered and this.updateView is called, not only when a new model comes from the
backend.
My solution until now was to pass an option "source: gui" when setting the model value when the user pressed enter (command 1), and to check for that in my updateView method. But I am not content with this solution anymore.
Does anybody have a better solution?
Thanks alot in advance
Wolfgang
Update:
When the option silent: true is passed, the validate method of the model is not called, so
that does not help. See Backbone.js source 0.9.2:
_validate: function(attrs, options) {
if (options.silent || !this.validate) return true;
From Backbone.js site:
A "change" event will be triggered, unless {silent: true} is passed as an option
options.silent = true;
container.model.set(this.attrName, this.value, options);
Update:
You added a new comment to your question, so I just complemented my answer to fix the new use case(validation flow) that you mentioned:
var ExtendedModel = Backbone.Model.extend({
uiChange : false,
uiSet: function (attributes, options, optional) {
this.uiChange = true;
this.set(attributes, options, optional);
this.uiChange = false;
}
});
var MyModel = ExtendedModel.extend({
});
var model = new MyModel();
model.on('change:name', function(){
console.log('this.uiChange: ', this.uiChange);
});
//simulates the server side set
model.set({name:'hello'});
//simulates the ui side set you must use it to set from UI
model.uiSet({name:'hello2'});
Two-way binding just means that:
When properties in the model get updated, so does the UI.
When UI elements get updated, the changes get propagated back to the
model.
Backbone doesn't have a "baked-in" implementation of 2 option (although you can certainly do it using event listeners)
In Backbone, we can easily achieve option 1 by binding a view's "render" method to its model's "change" event. To achieve option 2, you need to also add a change listener to the input element, and call model.set in the handler.
check (jsfiddle.net/sunnysm/Xm5eH/16)jsfiddle example with two-way binding set up in Backbone.
Backbone.ModelBinderplugin works great for providing Two-way data binding between your Backbone Views and Models. I wrote a blog post covering some essential features of this plugin Here is the direct link: http://niki4810.github.io/blog/2013/03/02/new-post/
I wanted to see what the bare bones code would be to have two-way binding with Backbone.js. This is what I came up with:
var TwoWayBoundView = Backbone.View.extend({
initialize: function(options) {
this.options = _.defaults(options || {}, this.options);
_.bindAll(this, "render");
this.model.on("change", this.render, this);
this.render();
},
events: {
"change input,textarea,select": "update"
},
// input updated
update: function(e) {
this.model.set(e.currentTarget.id, $(e.currentTarget).val());
},
// model updated...re-render
render: function(e) {
if (e){
var id = Object.keys(e.changed)[0];
$('#'+id).val(e.changed[id]);
}
else{
_.each(this.model.attributes, function(value, key){
$('#'+key).val(value);
});
}
}
});
And the usage:
var model = new Backbone.Model({ prop1: "uno 1", prop2: "dos 2", prop3: "3" });
var view = new TwoWayBoundView({
el: "#myContainer",
model: model
});
Here's a jsbin for it: http://jsbin.com/guvusal/edit?html,js,console,output
I've used libraries that do this, such as Epoxy.js (only 11k minified). And there are several others besides, which I would recommend long before using the proof of concept code above.
I would be interested in potential pitfalls and improvements that could be made with the TwoWayBoundView class above (but nothing beyond basic two-way binding please! i.e. I'm not looking for more features to add.)
I am using Extjs 4.1. I need to identify event on grid when data renderred and arrived.
I checked ''afterrender' event. But it fires to early
grid.on('afterrender', function () {
alert(333);
});
Please advice
You're correct grid rendered event would fire after grid was rendered not after the data is displayed. What about subscribing to the store load event?
store.on('load', function() {
...
})
If you simply need to know when the grid has first loaded data, use the Ext.grid.Panel viewready event, it fires when the data has been loaded in and the grid rows have been rendered.
Example:
this.control({
'mygridpanel': {
// select the first record
viewready: function(grid) {
var store = grid.getStore(),
view = grid.getView(),
selModel = grid.getSelectionModel();
if (store.getAt(0)) {
view.focus();
selModel.select(0);
}
},
},
});
This example is configured in the init function of a controller using the MVC pattern, I'm not sure if you are using MVC pattern? The snippet in your comment should work fine either way though.
Also this event passes the Ext.grid.Panel object that fired it as the first argument, as you can see from the example above, you can use that to get a reference to your grid and perform whatever logic you need to do on it in your handler.
I keep running into this problem over and over. I have a view with an input and I want to set and update things on every keyUp event. The problem is when set is called it triggers a change event which re-renders the view which causes the input to lose focus. So after the user types one character the input loses focus and they can't type anymore.
Another case where this happens is when the user clicks on an input I want to add a class to the div around the input so that it changes color. This of course causes the view to re-render and the input loses focus. I can't simply make a separate view for the input because the input is inside the div I want to re-render.
Here's a simple example.
itemView = Backbone.View.extend({
events: {
"keyup .itemInput": "inputKeyUp"
}
initialize: function(){
this.model.view = this;
this.bind('change', this.render());
this.render();
},
render: function(){
$(this.el).html( $(ich.itemView( this.model.toJSON() )) );
return this;
},
inputKeyUp: function(e) {
this.model.set({name: $(this.view.el).find('input[type=text]').first().val()});
},
});
So far I've gotten around it by using {silent:true} and updating things manually but this creates a mess.
You're basically getting yourself into a sort of infinite loop situation where you're binding your view too tightly to your model, and they're feeding back into each other.
When a user types into a browser text intput, they're already "updating the view". The view already represents the extra text.
So, when you update the model with those changes, you don't need the view to update AGAIN, as it already represents the current state.
So, in these cases, you really do want to use "silent", as you're just syncing the model with the current state of the UI, and don't need the model to inform the view to update.
As to how often to do this, I'm suspecting on keyup is probably excessive. You may want to do it on blur or, even, on some sort of "save" action.
As far as the other issue, I'm not sure why adding a class to an element would cause the view to re-render. Are you simply doing something like
this.$('input[type="text"]').addClass('active')
This shouldn't trigger your model's change event and cause render to run again.
Post comment:
You need to get more granular then.
In terms of rendering, break the individual rendering/updating of elements of the view into separate functions.
Bind property-specific change events ("change:name") to those more granular rendering functions so that they update the part of the view that you wish to change, but do not update the text input.
itemView = Backbone.View.extend({
events: {
"keyup .itemInput": "inputKeyUp"
}
initialize: function(){
this.model.view = this;
this.bind('change:name', this.update_other_stuff());
this.bind('change:selected', this.add_class());
this.render();
},
update_other_stuff: function(){
this.$('.some_other_thing').html("some desired change");
return this;
},
add_class: function(){
this.$('input[type=text]').first().addClass('active');
return this;
},
render: function(){
$(this.el).html( $(ich.itemView( this.model.toJSON() )) );
return this;
},
inputKeyUp: function(e) {
this.model.set({name: $(this.view.el).find('input[type=text]').first().val()});
},
});