I've got a view Foo, with an input field, and a wrapper view Bar, with a button.
When I write something in the input and then press the button, the 'change' event on Foo's model should be triggered, and then the 'click' button on Bar's, yet only the first do happen. Is there a way to trigger both, preferably at the correct order?
Thanks!
To reiterate, and confirm your question/setup. Based on your question, this is the setup you have. You want to change the Foo.model THEN trigger the click event on the Bar.
<div id="Bar">
<div id="Foo">
<input type="text">
</div>
<button id="barButton">Update</button>
</div>
It's a little bit of an odd setup. #alexanderb illustrates it the way I also think of the whole problem but I'll explain what I'd do in your scenario assuming you know something I don't about the context of the situation! :-)
If you want the change to be triggered on the Foo.model followed by the click event on the Bar button, all you need to do manually trigger the button click from your Foo view. To do this, you can do it one of several ways. The first is to directly trigger the event of your parent (Bar) view.
// When you create your Foo subview, you pass in the parent view so you can access it
var foo = new Foo({
'parent': this // this = the barView assuming you're creating FooView inside BarView
});
// In Foo after you set your model attribute, you access the parentView
this.options.parent.$el.find('#barButton').click(); // Manually trigger the click event
// or
this.options.parent.onButtonClick() // Manually run the event handler bypassing the event
This satisfies your request to have the change event followed by the button click event in the DOM context you provided. (Not sure why in this case but I can imagine some scenarios where something similar might be desired.)
The second way of doing it is to use an event aggregator pattern which allows your views to send and receive events to one another. In this scenario, what happens is your Foo view, after updating the model triggers an event that is listened to by your parent Bar view, upon which it executes some handler.
You can read more about event aggregator pattern in Backbone here:
Event Aggregator Explanation
To be honest, I really think that alexanderb's answer is the best one for this situation unless there is something more we don't know about your special setup. But, I hope you can see and compare the two and get an idea of when and why one setup might be appropriate given a particular context.
Hm, I'm a little confused. If you don't use any backbone plugis, the workflow is different.
If you press something on a screen, an event occures on view, not in model. You have to listen to that event and update the model accordingly. If you set new value in model, change model event will occur.
Say, you have a view Foo it's subview Bar and model Boo. Both views are using the same model objects.
In Bar view,
events: {
'click .button': 'onButtonClicked';
}
onButtonClicked: function () {
var value = this.$el('.input').val();
this.model.set({ someValue: value});
}
In Foo view, you can listen for model changes
initialize: function () {
this.model.on('change:someValue', this.onSomeValueChanged);
}
In this.onSomeValueChanged you can do, what ever you like.
Related
I have a region, and I need to execute some code once the view is rendered. So the first time I call myRegion.show(myView)The code that goes inside onShow in myView does the work.
The same piece of code doesn't work onRender or at initialize. The problem comes, when after a change in myView I call this.render() to reflect the changes. This time, "onShow" is not firing.
I think that I can trigger an event from the view, catch it at the parent view and call again to myRegion.show(myView). But it looks like a tricky solution, and I don't know if there could be a better way to manage this situation.
onShow callback is called only once when you insert your view into the region.
I think in your case you should use onDomRefresh. Marionette calls it when view is showed into region and when it is re-rendered (but only when it is showed already). So you can make all your DOM dependent code in it.
You can read more in docs: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#view-domrefresh--ondomrefresh-event
When we working on a extjs MVC project, we can handle a view event either inside the view or controller. How do we decide what events been handled in the view and what events been handled in the controller. What is the best practice.
For example, I need to pop up a window when a button is clicked. Should the creation of the window goes to controller or resides in the container view object.
One thing you can do is utilize the control() method within your controller definition. For example, take this code,
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'viewport > panel': {
render: this.onPanelRendered
},
'#editform button[action=save]': {
click: this.onClick
}
});
}
});
This is from the MVC example. You'll notice that it is using the ComponentQuery Language (http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.ComponentQuery) to select a set of Ext objects to assign the render() event. This is saying, for all panels in the viewport, call onPanelRendered() for the render() event. So in your case, you want to select some buttons. So I added another example that selects some form with an id of editform and then select the button that contains the property action = save. You can get very specific with these component queries. I would suggest reading ComponentQuery portion of the API to see further examples. This depends on how you are attempting to select the buttons.
If you are new to MVC maybe you should only listen to events in the controller, once you have developed a couple of projects and you've gained discipline and enjoyed the benefits of listening events in a controller then you would be able to mix it up (when to use a handler and when to use a controller is gonna become obvious).
E.g If you have a FormPanel and you are only gonna listen to a button's click perhaps it doesn't make much sense to create a controller just for that, but in the other hand if you have a grid with a toolbar, action buttons, itemClick's etc then it would be a good idea to have a controller for that view.
The fact that you are using MVC does not mean you are not allowed to use handlers anymore, but do it wisely.
Best regards.
Simple question: What is the best way to fire an event 'only once'?
I have a Collection in backbone with multiple models. Sometimes multiple models' attributes are updated at once, firing multiple 'change' events on the Collection.
What is the best way to fire the 'change' event only once, grouping all attribute changes together?
My best idea at the moment is using a timer but this will only capture the first model attribute change.
All suggestions, ideas and solutions are valued. Thank you :).
Context:
In this case the event fires a very computationally intensive function, therefore multiple runs must be avoided.
Whenever you are updating the attributes on the models in bulk, pass {silent:true} as an option, which will suppress any events from being fired.
Then trigger a custom event for which a view or multiple views listening to the same.
You can "debounce" the method in your collection that responds to change events, though you'll have to manually figure out which models changed:
Col = Backbone.Collection.extend({
modelChanged: _.debounce(function() {
// handle model changes
}, delayInMs),
initialize: function() {
this.collection.on('change', this.modelChanged, this);
}
})
You can try resetting the collection, rather than adding the elements. See Collection reset method description:
reset collection.reset([models], [options])
Adding and removing models one at a time is all well and good, but sometimes you have so many models to change that you'd rather just update the collection in bulk. Use reset to replace a collection with a new list of models (or attribute hashes), triggering a single "reset" event at the end. Returns the newly-set models. For convenience, within a "reset" event, the list of any previous models is available as options.previousModels.
As documentation says, if you use reset, instead of add you will get a single reset event in the end.
I have a navigation view with one button in the toolbar. Based on the view pushed, the button's label and functionality should chang. I've managed to do this by creating many buttons and activating them as needed (hide/show)
Instead of doing that approach I'd like to have just one button and in the controller change the text and action. Something along these lines:
this.getButton().setHtml("new text");
this.getButton().action = "newaction";
setHtml works, but setting the action doesn't. Examining the button in the console, I see the action changes but when I click it, it responds to the previous action.
Any suggestions on how to approach this?
Thanks
You should use setText instead of setHtml that, err... Doesn't seem to exist! And setHandler to change the handler function.
Alternatively, since you say that you're working in a controller, you can attach a function to the click event of the button and, inside this listener function, decide what action to execute in the current context.
I'm working on a application which has sidebar on the right side. That displays collection. Each model of the collection has select behavior.
In the top center of the page I have one independant backbone view which acts like "info bar" and this view should update it's infomation when I select one of the views in the sidebar
How can I do it? I think that the each view in the sidebar should have "selected" event with model argument, and my question is how can I listen that change in my info bar view in Backbone.js???
This sounds like something that would be served well by an event aggregator pattern. Derick Bailey posted a really nice article on this matter which you can read here.
Event Aggregator Pattern
I extended my Backbone.View so that all views have the event aggregator object available to them like this.
Backbone.View.prototype.eventAggregator = _.extend({}, Backbone.Events);
Basically when your model view is selected, your view.eventAggregator will trigger some custom event.
sidebarView.eventAggregator.trigger('selected', this.model);
or something of this sort. With Backbone events, you can pass parameters through the trigger function to your event listener. In the above example I passed the model with the specific event.
In your main view, you'd be listening for this event.
mainView.eventAggregator.on('selected', myFunction, this);
myFunction: function(model) {
// Some code to execute - model is available through param
}
It's a pretty useful pattern. Just don't forget to unbind the event when you close out your mainView. :-)