Quick question regarding delegateEvents in a View - can we specify multiple event bindings in a single event definition?
For example, instead of:
'keyup .text-input': 'textEvents',
'keydown .text-input': 'textEvents',
'focusin .text-input': 'textEvents',
'focusout .text-input': 'textEvents',
'click .text-input': 'textEvents',
...
Is this possible?
'keyup keydown focusin focusout click .text-input': 'textEvents',
No, you can't do that. From the fine manual:
Events are written in the format {"event selector": "callback"}
The event is implicitly a single word (just like in jQuery and the DOM events) whereas selector can be any jQuery-style selector. Furthermore, the keys in this.events are parsed using this regex:
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
so the event is the first component and only the first component.
You could build the events object yourself and call delegateEvents by hand with something like this:
var catch = ['keyup', 'keydown', 'focusin', 'focusout', 'click'];
var events = { };
_(catch).each(function(ev) { events[ev + ' .text-input'] = 'textEvents' });
this.delegateEvents(events);
Related
I am working with standalone (not mobile) and I think it is _getScroll method for reaching it.
how to implement it here qooxdoo selectbox example
I found similar for mobile implementing virtual scrolling list console.log says container._getScroll is not a function.
The idea is to get scrollbar from a widget, the scrollbar you are needed is NativeScrollbar of the widget qx.ui.list.List. Then add event handler for a "scroll" event. In handler u have to compare current position of scroll and maximum.
Try the code below (eg copy and paste into the Qooxdoo playground).
qx.Class.define("SelectBoxWithScrollEndEvent", {
extend: qx.ui.form.SelectBox,
construct: function(){
this.base(arguments);
this.__setupScroll();
},
events: {
"scrollEndHappened": "qx.event.type.Event"
},
members: {
__setupScroll: function(){
const list = this.getChildControl("list");
const scrollbar = list.getChildControl("scrollbar-y");
scrollbar.addListener("scroll", function(e){
if (scrollbar.getMaximum() === scrollbar.getPosition()){
this.fireEvent("scrollEndHappened");
}}, this);
}
}
});
const box = new SelectBoxWithScrollEndEvent();
const data = new qx.data.Array([1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5]);
const controller = new qx.data.controller.List(data, box);
box.addListener("scrollEndHappened", function(){
alert("SCROLL HAPPENED ALERT");
}, this);
this.getRoot().add(box);
I'm using Marionette 2.4 and have a layoutView which is listening to an event in the childView. When the event fires I search for an existing model within the collection and if it is not there I create a new model and add it to the collection. If it is found I remove the model from the collection. The problem is that the event seems to be firing twice. The first time it fires, it will create the model, but then as it is firing twice, it then finds the newly created model in the collection and then removes it.
var layout = Marionette.LayoutView.extend({
childEvents: {
'channel:selected': 'onChildviewChannelSelected'
},
onChildviewChannelSelected: function (childView, args) {
var linkCollection = this.getRegion('regionWithCollectionView').currentView.collection;
var modelToUpdate = linkCollection.where({channel: args.currentTarget.value});
if(modelToUpdate) {
this.removeModel(linkCollection, modelToUpdate);
} else {
this.addModel(linkCollection, args.currentTarget.value);
}
},
removeModel: function (collection, model) {
collection.remove(model);
},
addModel: function (collection, channel) {
var newEntity = new MyApp.Entities.Link();
newEntity.set('channel', channel);
collection.add(newEntity);
}
});
and here is the child view that fires the 'channel:selected' event....
var childView = Marionette.ItemView.extend({
events: {
'change input[type="checkbox"]': 'channelSelected'
},
channelSelected: function(args) {
this.triggerMethod('channel:selected', args);
}
});
Any idea why the childView fires the 'channel:selected' event twice?
It isn't the view that holds the collection that is being added to, but perhaps there is something that happens when a collection is added to that it will trigger the event again for some reason.
It looks like your function is getting fired twice because of Marionette's "childview* event bubbling". From the documentation:
When a child view within a collection view triggers an event, that
event will bubble up through the parent collection view with
"childview:" prepended to the event name.
That is, if a child view triggers "do:something", the parent
collection view will then trigger "childview:do:something".
This means that "childview:channel:selected" is already being triggered on your layoutview (which means that the onChildviewChannelSelected function is automatically executed on the parent view if it exists http://marionettejs.com/docs/v2.4.7/marionette.functions.html#marionettetriggermethod).
It seems there are a couple potential workarounds. 1 - don't specify a childEvents handler if your handler/function name follows Marionette conventions.
var LayoutView = Marionette.LayoutView.extend({
template: false,
el: '.container',
regions: {
'regionWithCollectionView': '.collection-view-container'
},
onChildviewChannelSelected: function (childView, args) {
console.log("layoutview::channelSelected - child " + childView.model.get('channel') + " selected");
}
});
Fiddle showing workaround #1: https://jsfiddle.net/kjftf919/
2 - Rename your LayoutView's childview function handler to something that doesn't conflict with Marionette's automatic event bubbling.
var LayoutView = Marionette.LayoutView.extend({
template: false,
el: '.container',
regions: {
'regionWithCollectionView': '.collection-view-container'
},
childEvents: {
'channel:selected':'channelSelected'
},
channelSelected: function (childView, args) {
console.log("layoutview::channelSelected - child " + childView.model.get('channel') + " selected");
}
});
Fiddle showing workaround #2: https://jsfiddle.net/kac0rw6j/
I'm using Backbone.ModelBinder in a Backbone.js Marionette project. I've a scenario which I can't work out how to use ModelBinder to automatically update my model/UI.
My model has a 'status' string attribute, with multiple states. In this example I'll show the code for two: 'soon', 'someday'
In my UI I have a list on which I use click events to set the model status, and update classes to highlight the relevant link in the UI.
<dd id="status-soon"><a>Soon</a></dd>
<dd id="status-someday" class="active"><a>Someday</a></dd>
events: {
'click #status-soon': 'setStatusSoon',
'click #status-someday': 'setStatusSomeday'
},
setStatusSoon: function () {
this.model.set('status', 'soon');
this.$el.find('.status dd').removeClass('active');
this.$el.find('#status-soon').addClass('active');
},
... etc
It feels like I doing this a long-winded and clunky way! The code bloat increases with the number of states I need to support. What's the best way of achieving the same outcome with ModelBinder?
You could probably simplify things with a data attribute, something like this:
<dd data-status="soon" class="set-status"><a>Soon</a></dd>
<dd data-status="someday" class="set-status active"><a>Someday</a></dd>
and then:
events: {
'click .set-status': 'setStatus'
},
setStatus: function(ev) {
var $target = $(ev.target);
var status = $target.data('status');
this.model.set('status', status);
this.$el.find('.status dd.set-status').removeClass('active');
$target.addClass('active');
}
You might not need the set-status class, just keying things on the <dd>s might be sufficient; I prefer separating my event handling from the nitty gritty element details though.
Unfortunately, it is going to be pretty difficult to do exactly what you want with ModelBinder. The main reason being that ModelBinder wants to provide the same value for all elements that are part of a single selector. So doing this with ModelBinder, while possible, is going to be pretty verbose as well.
The cleanup offered by mu is likely to be better than trying to use ModelBinder. 1) because you need a click handler to do the this.model.set no matter what and 2) you would need individual bindings for ModelBinder because the converter function is called once for a single selector and then the value is set on all matching elements (rather than looping through each one).
But if you do want to try and do something with ModelBinder it would look something like this:
onRender : function () {
var converter = function (direction, value) {
return (value == "soon" ? "active" : "");
};
var bindings = {
status : {selector : "#status-soon", elAttribute : "class", converter : converter}
};
this.modelBinder.bind(this.model, this.el, bindings);
}
This would do what you want. Of course the down side as I said above is that you will need multiple selector bindings. You could generalize the converter using this.boundEls[0] but you will still need the separate bindings for it to work.
In case you want to access to the bound element, it is possible to declare 'html' as elAttrbute, modify the element and return its html with converter function:
onRender : function () {
var converter = function (direction, value, attribute, model, els) {
return $(els[0]).toggleClass('active', value === 'soon').html();
};
var bindings = {
status : {
selector : "#status-soon",
elAttribute : "html",
converter : converter
}
};
this.modelBinder.bind(this.model, this.el, bindings);
}
In my router I require a view like this:
require(['views/business-detail-view'],
function(BusinessDetailView) {
var businessDetailView = new BusinessDetailView({collection: businessOverviewCollection.models[id], id: id});
businessDetailView.render();
}
);
and in the view I'm binding events like this:
events: {
'click #about-btn' : 'aboutHandler',
'click #contact-btn' : 'contactHandler',
'click #deals-btn' : 'dealsHandler',
'click #map-btn' : 'mapHandler'
},
Now the issue is that if the view gets rendered the first times the callbacks are invoked ones. But if the view needs to be rendered again in some other place the callbacks are invoked twice and so on.
How can I prevent this or am I doing something wrong?
UPDATE:
In the meantime I have changed the code in my router to:
if ( !businessDetailView ) {
require(['views/business-detail-view'],
function(BusinessDetailView) {
businessDetailView = new BusinessDetailView({collection: businessOverviewCollection.models[id]});
businessDetailView.render();
}
);
}
else {
businessDetailView.collection = businessOverviewCollection.models[id];
businessDetailView.render();
}
which seem to solve the issue, but I'm still to new to backbone this know whether this is a valid pattern.
At some point in your view you probably clear out the existing HTML on the page and replace it with new HTML. When you clear out old HTML you should also clear out old event handlers so they aren't laying around. For example, in your view when you want to render your newHtml you could do:
#$el.off().html(newHtml)
I have my view set up with some events and I want to reference, for example, the button element that was clicked because it has a data attribute I need. I want to do it like so:
events: {
'click #testGadget': 'fireEvent',
...
},
fireEvent: function(){
var x = $(this).data('iCanHaz')
}
But the 'this' variable is scoped to the view itself. I know there's a way to accomplish what I'm looking to do but I can't seem to word my question in a way that returns any google hits.
Can simply be done with the event.target property:
fireEvent: function(e){
var x = $(e.target).data('iCanHaz')
}
see Event Object doc