I am creating multiple objects and binding to the same handler. My requirement is I want to send particular value to handle when it is called.
Code:
sliderObj = [];
for(i=0;i<3;i++)
{
sliderObj[i] = this._turntableSlider = new SliderView();
sliderObj[i].on("change:value", this._handleChangeSpeedSlider);
}
When handler is called for sliderObj[0] want to send value 0 similarly 1 for sliderObj[1] etc.
Please help me out how to do that.
Try using .bind():
sliderObj[i].on("change:value", this._handleChangeSpeedSlider.bind(this, i));
Simple demo
Prefer listenTo over on.
Passing the index
Simplest solution would be to wrap the callback into an anonymous function.
But since we're looping and value of i will change, we need to create a simple function which returns a new function using the frozen value of i. (This is similar to using TJ's bind answer)
function getChangeSpeedHandler(index) {
return function() {
this._handleChangeSpeedSlider(index);
}
}
for (i = 0; i < 3; i++) {
var slider = sliderObj[i] = new SliderView();
this.listenTo(slider, "change:value", getChangeSpeedHandler(i));
}
Passing the slider view
Assuming the value of i in the handler is only used to get the right sliderObj, you could pass the slider view directly.
function getChangeSpeedHandler(slider) {
return function() {
this._handleChangeSpeedSlider(slider);
}
}
for (i = 0; i < 3; i++) {
var slider = sliderObj[i] = new SliderView();
this.listenTo(slider, "change:value", getChangeSpeedHandler(slider));
}
Delegating the event handling to the slider view
If you want to handle changes inside the SliderView, it should handle the event itself.
this.listenTo(this.model, 'change:value', this.onValueChange);
Triggering custom events
If it's really the parent that needs to know the slider and their number is dynamic, you could pass the i value to the SliderView and then it could trigger a custom event.
In the slider view:
var SliderView = Backbone.View.extend({
initialize: function(options) {
options = options || {};
this.index = options.index;
// ...
},
onValueChange: function(model, value, options) {
this.trigger('slider:change', value, this.index, this);
},
});
Then in the parent:
initialize: function() {
for (i = 0; i < 3; i++) {
var slider = sliderObj[i] = new SliderView({ index: i });
this.listenTo(slider, "slider:change", this._handleChangeSpeedSlider);
}
},
_handleChangeSpeedSlider: function(value, index, sliderView) {
// handle the change
}
Related
I'm using this structure to create one-time click events:
function structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
for(var i = 0; i < this.numElements; i++) {
this.elements[i].addEventListener('click', this.elementClicked.bind(this));
}
}
The handler of those events is implemented as follows:
structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) {
this.elements[i].removeEventListener('click', arguments.callee);
}
};
The idea is to fire the handler once if any of the registered elements gets clicked, and then unregister the event from each of those elements
Unfortunately the handler still gets fired everytime I click on one of the registered items
I'm aware anonymous functions can't be used to reference the same object, but specifying arguments.callee or the entire name of the referenced function still didn't help the cause
An alternative is to make your objects implement the EventListener interface. You can do this by adding a handleEvent method to the .prototype of the constructor, and then passing the object itself in place of the event handler.
function Structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
for(var i = 0; i < this.numElements; i++) { // v-- pass the object
this.elements[i].addEventListener('click', this);
}
}
// Implement the interface; gets invoked when an event occurs
Structure.prototype.handleEvent = function(e) {
// Used a switch statement in anticipation of other event types
switch (e.type) {
case "click":
this.elementClicked(e);
break;
}
};
Structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) { // v-- pass the object
this.elements[i].removeEventListener('click', this);
}
};
Now there's no longer any need to use .bind(). Instead the value of this in handleEvent will be the bound object. You can still get the element to which the handler was bound via e.currentTarget.
Each time you call...
this.elements[i].addEventListener('click', this.elementClicked.bind(this));
... bind creates another instance of a method. It uses this.elementClicked, true, but otherwise is a completely different function. That's why you won't drop it with remoteEventListener called on this.elementClicked.
What's the workarounds? One possible option - passing { once: true } as addEventListener param - has been given in the comments, but it's not supported by IE and Edge (and most likely won't be supported by the Safari you encounter in the nearest future). Here's another approach:
function Structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
// reassign a bound method onto instance:
this.elementClicked = this.elementClicked.bind(this);
for(var i = 0; i < this.numElements; i++) {
this.elements[i].addEventListener('click', this.elementClicked);
}
}
Structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) {
this.elements[i].removeEventListener('click', this.elementClicked);
}
};
Now you create a bound elementClicked method for each instance of structure object, having its context set permanently.
I'm trying to reuse an object I created to dynamically create more than one slider on a page.
My idea was to create an array and push my slider object there as often as needed, so I could access it by id. Unfortunatelly it doesn't work. Hope someone can point me in the right direction ...
So what I have is this;
var slider = {
"init":function(slide_it){
this.parent = $(slide_it);
/Count Elements and create a navigation depending on the count etc./
},
"otherstuff":{...}
}
In my (document).ready function I create an array and fill it up with different slider objects, add Ids to an accordion and call the init function:
var slide_array = [];
var accordion_sections = $('#accordion > div').length;
for(var i = 0; i < accordion_sections; i++){
slide_array.push(slider);
$('#accordion').children('div').eq(i).attr('id', 'slide_it_'+ i);
slide_array[i].init($('#slide_it_' + i).find('.slider'));
}
Then I have a button with class="next" and I call a function within the slider
$('.next').click(function(){
slide_array[0].otherstuff();
});
My plan is to get the parent of .next and its id so that I can use slide_array[parentID].otherstuff();
But ... it's not working propperly when I call the init function inside the for loop more then once.
More weird, some functions calls seem to work, other have no effect.
What am I doing wrong?
You can use Object.create.
var s1 = Object.create(slider),
s2 = Object.create(slider);
s1.init(...);
s2.init(...);
If you return this from init your will be able to chain like:
var s1 = Object.create(slider).init(...);
However at this point I would just ditch the object literal and use constructors, since this is what you need.
function Slider(slide_it) {
this.parent = $(slide_it);
}
Slider.prototype = {
constructor: Slider,
otherStuff: function () {}
};
var s1 = new Slider(...),
s2 = new Slider(...);
Write a function to return the object:
function slider() {
return {
"init":function(slide_it){
this.parent = $(slide_it);
/Count Elements and create a navigation depending on the count etc./
},
"otherstuff":{...}
};
}
Then:
slide_array.push( slider() );
That'll give you a separate object every time. In you're version, you're filling the array with references to the same single object.
Why not just turn that into a jQuery plugin ?
jQuery.fn.slider = function(options) {
return this.each(function() {
var sliderElem = $(this),
settings = $.extend({
speed : 3000,
something : 'other thing'
}, options);
otherStuff(sliderElem);
});
function otherStuff(elem) {
}
}
$('#accordion > div').slider();
No iteration or jumping through hoops, just call it on the collection and it creates a new slider for each element ?
I have a menu class loading data from a received json file.
in the constructor I build the menu, so I have a for loop with this (extracted part) js:
for (var i = 0; i<data.length; i++)
{
var btn = $('<div>'+data[i].label+'</div>').appendTo(object.container);
btn.click(function()
{
if($('.blockingFrame').length == 0)//pas de blocking
{
data[i].action()
}
});
}
Now, obviously this doesn't work because on runtime data[i] doesn't exist anymore...
data[i].action contains a valid js function.
This works, but doesn't contains the condition..:
for (var i = 0; i<data.length; i++)
{
var btn = $('<div>'+data[i].label+'</div>').appendTo(object.container);
btn.click(data[i].action);
}
So I thought I could store this action inside the jquery object and call it like this, but it doesn't work:
for (var i = 0; i<data.length; i++)
{
var btn = $('<div>'+data[i].label+'</div>').appendTo(object.container);
btn.action = data[i].action;
btn.click(function()
{
if($('.blockingFrame').length == 0)//pas de blocking
{
$(this).action();
}
});
}
A partial solution I came u with, was to store the action in another event, like dblclick, and trigger a dblclick inside the condition, but this seams ugly.
Any idea how to do this?
for loops don't work properly with closures. Consider using iterator methods instead:
$.each(data, function(index, elem) {
var btn = $('<div>'+elem.label+'</div>').appendTo(object.container);
btn.click(function()
{
if($('.blockingFrame').length == 0)//pas de blocking
{
elem.action()
}
});
}
Iterators are usually more elegant and compact than for loops, especially when you have them nested.
The reason why your last snippet doesn't work if that btn = $(...) is a temporary jquery object and disappears once you leave the scope, with everything you have assigned to it. When later a click handler is being invoked, you create a new jquery object via $(this), which doesn't carry your changes from the previous step. If you want to keep any data attached permanently to an element, use the data method - but in this case there's no need for that.
Use an immediately-executing function to create a closure that holds i.
for (var i = 0; i<data.length; i++) {
var btn = $('<div>'+data[i].label+'</div>').appendTo(object.container);
btn.click(function(i) {
return function() {
if($('.blockingFrame').length == 0)//pas de blocking {
data[i].action();
}
}(i));
}
I am trying to set an onclick event for thumbnails that are dynamically populated from a database. I need to set the function to handle an argument, which is the id of the bigger picture the thumbnail represents. The code I have now sets all the thumbnails to point to #18. If you see in the for-loop, it is supposed to die at 17:
for (var i = 0; i < 18; i++) {
document.getElementById('tat' + i).onclick = function() { display(i); };
}
(My thumbnail <img />s all have id="tat0", id="tat1", id="tat2", id="tat3" etc.)
(display() loads the larger pic that the thumbnail represents into a separate element)
Each thumbnail gets this onclick function, so I know the for loop is accessing each one by its ID properly (stepping through for each i) so why are all the display(i) being assigned to 18? Can you assign an onclick function to handle parameters?
You need a closure function to generate your handlers.
function genHandler( param ) {
return function() {
// use all params in here
display( param );
}
}
and then assign your events similarly
for (var i = 0; i < 18; i++) {
document.getElementById('tat' + i).onclick = genHandler( i );
}
It might also work, if you just add 'i' as a parameter to your function.
Wrapping your onclick handler in a function will create a closure that carrys the current scope with it.
for (var i = 0; i < 18; i++) {
document.getElementById('tat' + i).onclick = (function(a) {
return (function() {
display(a);
});
})(i);
}
I'm very new to JQuery, and I'm having some trouble
function Clients(guid)
{
var that = this;
this.guid = guid;
this.container = $("#Clients_" + that.guid);
this.LoadClients = function () {
var ids = that.container.find("#clients-tbl").getDataIDs();
for (var i = 0; i < ids.length; i++) {
var row = that.container.find("#clients-tbl").getRowData(ids[i]);
var imgView = "<img src='../../Content/Images/vcard.png' style='cursor:pointer;' alt='Open case' onclick=OnClickImage(" + ids[i] + "); />";
that.container.find("#clients-tbl").setRowData(ids[i], { CasesButtons: imgView });
}
}
this.CreateClientsGrid = function () {
var clientsGrid = that.container.find("#widget-clients-tbl").jqGrid({
.....
ondblClickRow:function(rowid)
{
---
}
loadComplete: function () {
that.LoadClients();
}
}
this.OnClickImage=function(idClient){
....
}
this.Init = function () {
that.CreateClientsGrid();
};
this.Init();
}
The problem is with onclick, because OnClickImage is not global function.
How can I use OnClickImage function?
You can bind to the click event in different ways. For example you can follow the way from the answer. By the way, it works much more quickly as getRowData and setRowData. Moreover you should save the result of that.container.find("#clients-tbl") operation in a variable outside of the loop and use use the variable inside the loop. JavaScript is dynamic language and every operation even ids.length will be done every time.
One more way would to use onCellSelect event without click event binding. See the answer which describe the approach and gives the corresponding demo.