KnockoutJS custom binding calling click event during bind, not on click - javascript

I`m attempting to bind an observable array of people two a two column responsive layout with click events using knockoutjs.
I have created a custom binding called TwoCol that loops through the array, and appends nodes to the DOM to create my suggested layout, but the click events are giving me trouble when I try to apply them in a custom binding nested in a loop.
I have played with it quite a bit, and encountered all types of results, but where I`m at now is calling my ~click~ event during binding, rather than on click.
http://jsfiddle.net/5SPVm/6/
HTML:
<div data-bind="TwoCol: Friends" id="" style="padding: 20px">
JAVASCRIPT:
function FriendsModel() {
var self = this;
this.Friends = ko.observableArray();
this.SelectedFriend = "";
this.SetSelected = function (person) {
alert(person);
self.SelectedFriend = person;
}
}
function isOdd(num) {
return num % 2;
}
ko.bindingHandlers.TwoCol = {
update: function (elem, valueAccessor) {
var i = 0;
var rowDiv;
var vFriends = ko.utils.unwrapObservable(valueAccessor());
$(elem).html('');
while (i < vFriends.length) {
//create row container every other iteration
if (!isOdd(i)) {
rowDiv = document.createElement("div");
$(rowDiv).addClass("row-fluid");
elem.appendChild(rowDiv);
}
//add column for every iteration
var colDiv = document.createElement("div");
$(colDiv).addClass("span6");
rowDiv.appendChild(colDiv);
//actual code has fairly complex button html here
var htmlDiv = document.createElement("div");
var htmlButton = vFriends[i]
htmlDiv.innerHTML = htmlButton;
colDiv.appendChild(htmlDiv);
//i think i need to add the event to the template too?
//$(htmlDiv).attr("data-bind", "click: { alert: $data }")
//it seems that the SetSelected Method is called while looping
ko.applyBindingsToDescendants(htmlDiv, { click: friends.SetSelected(vFriends[i]) });
i++;
}
return { controlsDescendantBindings: true };
}
}
var friends = new FriendsModel();
friends.Friends.push('bob');
friends.Friends.push('rob');
friends.Friends.push('mob');
friends.Friends.push('lob');
ko.applyBindings(friends);

I don't think you're using ko.applyBindingsToDescendants correctly. I admit I'm a little confused as to the meaning of some of the values in your code, so I may have interpreted something incorrectly.
Here's a fiddle where I think it's working the way you intended:
http://jsfiddle.net/5SPVm/7/
http://jsfiddle.net/5SPVm/8/
Notice if manually control descendant bindings (return { controlsDescendantBindings: true };), you need to set that up in the init callback, instead of update. The update callback is too late for that.
Quick rundown of the changes (edited):
Moved the controlsDescendantBindings into the init binding callback
Added the necessary parameter names to the binding param list to access additional values.
I re-enabled the html.attr call. Notice that now, because the binding context is set to the actual item, the SetSelected method doesn't exist at that level anymore, so it is necessary to use $parent.SetSelected.
$(htmlDiv).attr("data-bind", "click: $parent.SetSelected")
Fixed the ko.applyBindingsToDescendants call. This method takes a binding context, which is created from the current binding context, and also takes the element to apply the binding to. You don't want to reapply the binding, which is why this whole thing needs to be in the init handler.
var childBindingContext = bindingContext.createChildContext(vFriends[i]);
ko.applyBindingsToDescendants(childBindingContext, colDiv);

Related

Module pattern in JavaScript

I'm practicing my JS skills (I'm new at it). I'm trying to get the specific element that triggered the event and display it in a span element. But I don't know what I'm doing wrong, when I click the button nothing happens.
This is for a calculator program that I'm doing but using module pattern I think it's called.
var Calculator = {
init: function(){
var button = document.querySelectorAll("[class^='button']");
button.onclick = this.writeEvent;
},
write: function (element){
document.getElementById("display").innerHTML = element;
},
writeEvent: function(event){
write(target.event)
}
}
Calculator.init();
There are several problems with the posted code.
var button = document.querySelectorAll("[class^='button']");
button.onclick = this.writeEvent;
The result of querySelectorAll is a NodeList.
Assigning to its onclick property will not achieve what you want.
You want to assign to the onclick property of each individual node.
But actually that's not so simple, we'll need to come back to this.
writeEvent: function(event){
write(target.event)
}
One problem here is that target is undefined.
Surely you meant event.target.
Another problem is that write is also undefined.
Perhaps you meant this.write,
but that won't actually work well.
The problem is that when writeEvent is called from a click event,
it won't be called on the object,
so this will not be bound to the calculator object,
and the this.write call will raise an exception.
There's a way to overcome this,
by binding the onclick handler function to the object when setting it.
Putting the above together:
var Calculator = {
init: function() {
var nodeList = document.querySelectorAll("[class^='button']");
var callback = this.writeEvent.bind(this);
nodeList.forEach(item => item.onclick = callback);
},
write: function(element) {
document.getElementById("display").innerHTML = element;
},
writeEvent: function(event) {
this.write(event.target);
}
}
Calculator.init();

Remove all listener of "document" element [duplicate]

Just question: Is there any way to completely remove all events of an object, e.g. a div?
EDIT: I'm adding per div.addEventListener('click',eventReturner(),false); an event.
function eventReturner() {
return function() {
dosomething();
};
}
EDIT2: I found a way, which is working, but not possible to use for my case:
var returnedFunction;
function addit() {
var div = document.getElementById('div');
returnedFunction = eventReturner();
div.addEventListener('click',returnedFunction,false); //You HAVE to take here a var and not the direct call to eventReturner(), because the function address must be the same, and it would change, if the function was called again.
}
function removeit() {
var div = document.getElementById('div');
div.removeEventListener('click',returnedFunction,false);
}
I am not sure what you mean with remove all events. Remove all handlers for a specific type of event or all event handlers for one type?
Remove all event handlers
If you want to remove all event handlers (of any type), you could clone the element and replace it with its clone:
var clone = element.cloneNode(true);
Note: This will preserve attributes and children, but it will not preserve any changes to DOM properties.
Remove "anonymous" event handlers of specific type
The other way is to use removeEventListener() but I guess you already tried this and it didn't work. Here is the catch:
Calling addEventListener to an anonymous function creates a new listener each time. Calling removeEventListener to an anonymous function has no effect. An anonymous function creates a unique object each time it is called, it is not a reference to an existing object though it may call one. When adding an event listener in this manner be sure it is added only once, it is permanent (cannot be removed) until the object it was added to, is destroyed.
You are essentially passing an anonymous function to addEventListener as eventReturner returns a function.
You have two possibilities to solve this:
Don't use a function that returns a function. Use the function directly:
function handler() {
dosomething();
}
div.addEventListener('click',handler,false);
Create a wrapper for addEventListener that stores a reference to the returned function and create some weird removeAllEvents function:
var _eventHandlers = {}; // somewhere global
const addListener = (node, event, handler, capture = false) => {
if (!(event in _eventHandlers)) {
_eventHandlers[event] = []
}
// here we track the events and their nodes (note that we cannot
// use node as Object keys, as they'd get coerced into a string
_eventHandlers[event].push({ node: node, handler: handler, capture: capture })
node.addEventListener(event, handler, capture)
}
const removeAllListeners = (targetNode, event) => {
// remove listeners from the matching nodes
_eventHandlers[event]
.filter(({ node }) => node === targetNode)
.forEach(({ node, handler, capture }) => node.removeEventListener(event, handler, capture))
// update _eventHandlers global
_eventHandlers[event] = _eventHandlers[event].filter(
({ node }) => node !== targetNode,
)
}
And then you could use it with:
addListener(div, 'click', eventReturner(), false)
// and later
removeAllListeners(div, 'click')
DEMO
Note: If your code runs for a long time and you are creating and removing a lot of elements, you would have to make sure to remove the elements contained in _eventHandlers when you destroy them.
This will remove all listeners from children but will be slow for large pages. Brutally simple to write.
element.outerHTML = element.outerHTML;
Use the event listener's own function remove(). For example:
getEventListeners().click.forEach((e)=>{e.remove()})
As corwin.amber says, there are differences between Webkit an others.
In Chrome:
getEventListeners(document);
Which gives you an Object with all the existing event listeners:
Object
click: Array[1]
closePopups: Array[1]
keyup: Array[1]
mouseout: Array[1]
mouseover: Array[1]
...
From here you can reach the listener you want to remove:
getEventListeners(document).copy[0].remove();
So All the event listeners:
for(var eventType in getEventListeners(document)) {
getEventListeners(document)[eventType].forEach(
function(o) { o.remove(); }
)
}
In Firefox
Is a little bit different because it uses a listener wrapper that contains no remove function. You have to get the listener you want to remove:
document.removeEventListener("copy", getEventListeners(document).copy[0].listener)
All the event listeners:
for(var eventType in getEventListeners(document)) {
getEventListeners(document)[eventType].forEach(
function(o) { document.removeEventListener(eventType, o.listener) }
)
}
I stumbled with this post trying to disable the annoying copy protection of a news website.
Enjoy!
You can add a hook function to intercept all calls to addEventHandler. The hook will push the handler to a list that can be used for cleanup. For example,
if (EventTarget.prototype.original_addEventListener == null) {
EventTarget.prototype.original_addEventListener = EventTarget.prototype.addEventListener;
function addEventListener_hook(typ, fn, opt) {
console.log('--- add event listener',this.nodeName,typ);
this.all_handlers = this.all_handlers || [];
this.all_handlers.push({typ,fn,opt});
this.original_addEventListener(typ, fn, opt);
}
EventTarget.prototype.addEventListener = addEventListener_hook;
}
You should insert this code near the top of your main web page (e.g. index.html). During cleanup, you can loop thru all_handlers, and call removeEventHandler for each. Don't worry about calling removeEventHandler multiple times with the same function. It is harmless.
For example,
function cleanup(elem) {
for (let t in elem) if (t.startsWith('on') && elem[t] != null) {
elem[t] = null;
console.log('cleanup removed listener from '+elem.nodeName,t);
}
for (let t of elem.all_handlers || []) {
elem.removeEventListener(t.typ, t.fn, t.opt);
console.log('cleanup removed listener from '+elem.nodeName,t.typ);
}
}
Note: for IE use Element instead of EventTarget, and change => to function, and various other things.
Clone the element and replace the element with its clone. Events are not cloned.
elem.replaceWith(elem.cloneNode(true));
This uses Node.cloneNode() to clone the elem DOM object, which ignores all event handlers (though, as Jan Turoň's answer notes, attributes like onclick="…" will remain). It then uses Element.replaceWith() to replace elem with that clone. Simple assignment to an anonymous clone wasn't working for me.
This should be faster and cleaner than redefining elem.outerHTML with itself (as proposed by pabombs's answer) but may be slower than answers that iterate through and purge each listener (noting that getEventListeners() seems available exclusively in Chrome's dev console—not elsewhere in Chrome, not at all on Firefox). Presumably, at some higher volume of listeners to clear, this non-loop solution becomes faster.
(This is a simplification of Felix Kling's answer with help from asheroto's comment to it.)
you can add function and remove all other click by assign them
btn1 = document.querySelector(".btn-1")
btn1.addEventListener("click" , _=>{console.log("hello")})
btn1.addEventListener("click" , _=>{console.log("How Are you ?")})
btn2 = document.querySelector(".btn-2")
btn2.onclick = _=>{console.log("Hello")}
btn2.onclick = _=>{console.log("Bye")}
<button class="btn-1">Hello to Me</button>
<button class="btn-2">Hello to Bye</button>
You can indeed remove all event handlers by cloning the node as #FelixKling suggests in his answer, however don't forget that
attribute event handlers are not affected by cloning
Having element like this
<div id="test" onclick="alert(42)">test</div>
will still alert on click after cloning. To remove this sort of events, you need to use removeAttribute method, in general
const removeAttEvents = el =>
[...el.attributes].forEach(att =>
att.name.startsWith("on") && el.removeAttribute(att.name)
);
Then having the test element above, calling removeAttEvents(test) gets rid of the click handler.
To complete the answers, here are real-world examples of removing events when you are visiting websites and don't have control over the HTML and JavaScript code generated.
Some annoying websites are preventing you to copy-paste usernames on login forms, which could easily be bypassed if the onpaste event was added with the onpaste="return false" HTML attribute.
In this case we just need to right click on the input field, select "Inspect element" in a browser like Firefox and remove the HTML attribute.
However, if the event was added through JavaScript like this:
document.getElementById("lyca_login_mobile_no").onpaste = function(){return false};
We will have to remove the event through JavaScript also:
document.getElementById("lyca_login_mobile_no").onpaste = null;
In my example, I used the ID "lyca_login_mobile_no" since it was the text input ID used by the website I was visiting.
Another way to remove the event (which will also remove all the events) is to remove the node and create a new one, like we have to do if addEventListener was used to add events using an anonymous function that we cannot remove with removeEventListener.
This can also be done through the browser console by inspecting an element, copying the HTML code, removing the HTML code and then pasting the HTML code at the same place.
It can also be done faster and automated through JavaScript:
var oldNode = document.getElementById("lyca_login_mobile_no");
var newNode = oldNode.cloneNode(true);
oldNode.parentNode.insertBefore(newNode, oldNode);
oldNode.parentNode.removeChild(oldNode);
Update: if the web app is made using a JavaScript framework like Angular, it looks the previous solutions are not working or breaking the app.
Another workaround to allow pasting would be to set the value through JavaScript:
document.getElementById("lyca_login_mobile_no").value = "username";
At the moment, I don't know if there is a way to remove all form validation and restriction events without breaking an app written entirely in JavaScript like Angular.
Update 2: There is also a way to remove a specific event that was added with addEventListener on a website we don't own, by using the getEventListeners function combined to removeEventListener like mentioned in the answer of Jmakuc. If getEventListeners does not exist like on Firefox, you can use a polyfill and inject the script on the page with Greasemonkey addon: https://github.com/colxi/getEventListeners/issues/1
The only easy way I found and worked is this:
Let's say we want to add 2 event listeners
const element = document.getElementById("element");
element.addEventListener('mouseover',
()=>{
// some task
});
element.addEventListener('mouseout',
()=>{
// some task
});
Now you can remove both of the elements by simply:
element.replaceWith(element.cloneNode(true));
Removing all the events on document:
One liner:
for (key in getEventListeners(document)) { getEventListeners(document)[key].forEach(function(c) { c.remove() }) }
Pretty version:
for (key in getEventListeners(document)) {
getEventListeners(document)[key].forEach(function(c) {
c.remove()
})
}
angular has a polyfill for this issue, you can check. I did not understand much but maybe it can help.
const REMOVE_ALL_LISTENERS_EVENT_LISTENER = 'removeAllListeners';
proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER] = function () {
const target = this || _global;
const eventName = arguments[0];
if (!eventName) {
const keys = Object.keys(target);
for (let i = 0; i < keys.length; i++) {
const prop = keys[i];
const match = EVENT_NAME_SYMBOL_REGX.exec(prop);
let evtName = match && match[1];
// in nodejs EventEmitter, removeListener event is
// used for monitoring the removeListener call,
// so just keep removeListener eventListener until
// all other eventListeners are removed
if (evtName && evtName !== 'removeListener') {
this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, evtName);
}
}
// remove removeListener listener finally
this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, 'removeListener');
}
else {
const symbolEventNames = zoneSymbolEventNames$1[eventName];
if (symbolEventNames) {
const symbolEventName = symbolEventNames[FALSE_STR];
const symbolCaptureEventName = symbolEventNames[TRUE_STR];
const tasks = target[symbolEventName];
const captureTasks = target[symbolCaptureEventName];
if (tasks) {
const removeTasks = tasks.slice();
for (let i = 0; i < removeTasks.length; i++) {
const task = removeTasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
}
}
if (captureTasks) {
const removeTasks = captureTasks.slice();
for (let i = 0; i < removeTasks.length; i++) {
const task = removeTasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
}
}
}
}
if (returnTarget) {
return this;
}
};
....
You can add a helper function that clears event listener for example
function clearEventListener(element) {
const clonedElement = element.cloneNode(true);
element.replaceWith(clonedElement);
return clonedElement;
}
just pass in the element to the function and that's it...
Sub-class of EventTarget from the JavaScript WebAPI. Supports removing events without specifying a handler function reference.
class SmartEventTarget extends EventTarget {
constructor() {
super();
this.handlers = {};
}
addEventListener(name, handler) {
super.addEventListener(name, handler);
if (!this.handlers[name]) {
this.handlers[name] = new Set();
}
this.handlers[name].add(handler);
}
removeEventListener(name, handler) {
if (handler) {
super.removeEventListener(name, handler);
this.handlers[name].delete(handler);
} else {
this.handlers[name].forEach(h => {
super.removeEventListener(name, h)
});
this.handlers[name].clear();
}
}
removeAllListeners(name) {
if (name) {
this.removeEventListener(name, null);
} else {
Object.keys(this.handlers).map(name => {
this.removeEventListener(name, null);
});
this.handlers = {};
}
}
}
See this Gist for unit tests. You can run the tests by simply copying the code from the Gist into your browser JS console and pressing enter.
Be sure to read strange JS from the internet before blindly pasting it into your console.
https://gist.github.com/angstyloop/504414aba95b61b98be0db580cb2a3b0
I know this is an old question but for me the only thing that worked was:
parentOfTheElement.innerHTML = parentOfTheElement.innerHTML;
While the other solutions do in fact remove all the listeners, I had problems adding new ones when using either the outerHTML trick or cloneNode()
May be the browser will do it for you if you do something like:
Copy the div and its attributes and insert it before the old one, then move the content from the old to the new and delete the old?
One method is to add a new event listener that calls e.stopImmediatePropagation().
var div = getElementsByTagName('div')[0]; /* first div found; you can use getElementById for more specific element */
div.onclick = null; // OR:
div.onclick = function(){};
//edit
I didn't knew what method are you using for attaching events. For addEventListener you can use this:
div.removeEventListener('click',functionName,false); // functionName is the name of your callback function
more details

KnockoutJS not picking up jQuery .change() event

I've read every related post on this and spent last two days trying to figure out what I am doing wrong here without much success.
Working with this JS fiddle here as an example: http://jsfiddle.net/rniemeyer/dtpfv/ I am trying to implement a dirty flag. The only difference is that I am changing a data inside a regular span vs an input filed and using mapping plugin vs manually assigning observables.
$(document).ready(function(){
ko.dirtyFlag = function(root, isInitiallyDirty) {
var result = function() {},
_initialState = ko.observable(ko.toJSON(root)),
_isInitiallyDirty = ko.observable(isInitiallyDirty);
result.isDirty = ko.computed(function() {
return _isInitiallyDirty() || _initialState() !== ko.toJSON(root);
});
result.reset = function() {
_initialState(ko.toJSON(root));
_isInitiallyDirty(false);
};
return result;
};
$.getJSON('/environments/data.json', function(jsondata) {
var mapping = {
create: function (options) {
var innerModel = ko.mapping.fromJS(options.data);
for (var i=0; i < innerModel.deployments().length; i++) {
innerModel.deployments()[i].dirtyFlag = new ko.dirtyFlag(innerModel.deployments()[i]);
}
return innerModel;
}
}
var viewModel = ko.mapping.fromJS(jsondata, mapping);
self.save = function() {
console.log("Sending changes to server: " + ko.toJSON(this.dirtyItems));
};
self.dirtyItems = ko.computed(function() {
for (var i = 0; i < viewModel().length; i++ ) {
return ko.utils.arrayFilter(viewModel()[i].deployments(), function(deployment) {
return deployment.dirtyFlag.isDirty();
});
}
}, viewModel);
self.isDirty = ko.computed(function() {
return self.dirtyItems().length > 0;
}, viewModel);
self.changeTag = function (data, event) {
// Neither .change() nor .trigger('change') work for me
$(event.target).parents().eq(4).find('span.uneditable-input').text(data.tag).change()
// This value never changes.
console.log('Dirty on Change: '+self.dirtyItems().length)
}
ko.applyBindings(viewModel);
});
})
Here is stripped down piece of the HTML that triggers the changeTag() function, which replaces current_tag() with a selection from the drop down menu. This, however, does not trigger KnockoutJS update.
<div>
<span data-bind="text: current_tag() }"></span>
<div>
<button data-toggle="dropdown">Select Tag</button>
<ul>
<!-- ko foreach: $.parseJSON(component.available_tags() || "null") -->
<li></li>
<!-- /ko -->
</ul>
</div>
</div>
I am on day two of trying to figure this out. Any idea what I am doing wrong here? Am i supposed to use an input field and not a regular span element? Do i need to be changing values my viewModel directly instead of using jQuery to manipulate DOM? (I have actually tried that, but changing viewModel and then re-binding to it seems to slow things down, unless I am doing it wrong)
Thank you.
As said in the comments, it is considered 'bad Knockout practise' to update a value through jQuery, when you can also update the observable directly. Knockout promotes a data-driven approach.
In response to your last comment (not sure yet how to answer comments on stack overflow): the reason the UI isn't picking up the change is because you assigned the value wrong:
var x = ko.observable(1); // x is now observable
x = 3; // x is no longer observable. After all, you've assigned the value 3 to it. It is now just a number
x(3); // this is what you're after. x is still an observable, and you assigned a new value to it by using Knockout's parentheses syntax. If x is bound to the ui somewhere, you'll see the value 3 appear
So you want to do
jsondata[environment()].deployments[deployment()].current_tag(ko.dataFor(event.target).tag);

Assign a Function Argument with a Loop

I have an array of list items in a piece of Javascript code. I would like to assign an onclick event handler to each one. Each handler would be the same function, but with a different input argument. Right now I have:
function contentfill(i) {
box = document.getElementById("text");
box.style.background="rgba(0,0,0,0.8)";
var content = new Array();
contentdivs = document.querySelectorAll("#contentfill>div");
box.innerHTML = contentdivs[i].innerHTML;
}
li[3].onclick = function() {contentfill(0);};
li[4].onclick = function() {contentfill(1);};
li[5].onclick = function() {contentfill(2);};
This works well enough, but I would like to achieve the same thing with a loop, for example:
for(i=3;i<=5;i++) {
j=i-3;
li[i].onclick = function() {contentfill(j);};
}
This, however, does not work. Since j seems to be defined as 2 at the end of the loop, each time I click, it only seems to call contentfill(2).
For an alternative approach, consider having each of the elements aware of what argument it should be using.
for (var i = 0; i < 3; i++) {
var el = li[i + 3];
el.dataset.contentIndex = i;
el.addEventListener('click', contentfill);
}
Then contentfill would have to extract the argument from .dataset instead of taking an argument, of course. (This is the same mechanism as jQuery's $.data.)
I tend to prefer this since (a) it doesn't generate tons of tiny wrappers, (b) it allows me to later examine and possibly change the "arguments", and (c) it lets me predefine them in the document using data- attributes. Effectively changes them from function arguments into behavior.
The value of i - 3 should be bound to the click handler function; a closure can provide this functionality:
li[i].onclick = (function(j) {
return function() {
contentfill(j);
}
)(i - 3));
Btw, it's better practice to use addEventListener or attachEvent to register click handlers.

Event/Class Design Pattern (Prototype)

I've bumped up against this before and I need the help of someone smarter than me!
How can I attach an event of an element to a method of a class? More specifically, the same class? Is there a better way?
Using a Qt widget style approach, I've been trying to create self-contained "widget" objects in javascript. I'm using the Prototype javascript framework and building my widget as a class. Then, using Event.observe, I've tried to attach the event to a method of the class. But the Event assignment unbinds the assigned method. Here's an example of a simple table I'm trying to build that has clickable column headers:
Objectify.Grid.Table = Class.create({
initialize: function(headers) {
this.columns = headers;
this.rows = [];
},
addRow: function(GridData) {
var len = this.rows.push(GridData);
return len-1;
},
getRow: function(rowIndex) {
return this.rows[rowIndex];
},
build: function(parent) {
this.mainTable = new Element('table',{'class':'Objectify-Grid'});
$(parent).update(this.mainTable);
var tableBody = new Element('tbody',{});
this.mainTable.update(tableBody);
var headerRow = new Element('tr',{'class':'Objectify-Grid-header-row'});
tableBody.update(headerRow);
this.columns.each(function(val,id) {
var hcell = new Element('td',{'class':'Objectify-Grid-header-cell'}).update(val);
headerRow.insert(hcell);
// EVENT ASSIGNMENT //
hcell.observe('click',this.respondToClick);
/////////////////////
}.bind(this));
this.rows.each(function(GridData,id) {
var row = new Element('tr',{'class':'Objectify-Grid-row','id':'Objectify-Grid-row'+id});
tableBody.insert(row);
this.columns.each(function(columnName,index) {
var cell = new Element('td',{'class':'Objectify-Grid-cell'}).update(GridData.getValue(columnName));
row.insert(cell);
});
}.bind(this));
},
// RECEIVING METHOD //
respondToClick: function(event) {
var columnName = event.element().innerHTML;
// "this" is no longer bound in this method
this.sortColumnAsc(columnName); // [ERROR]
}
});
I think that you should do this :
hcell.observe('click',this.respondToClick.bindAsEventListener(this));
and the second argument of the "each" method is the object to bind the function to, so you can do this:
array.each(someFunction, this);
instead of
array.each(someFunction.bind(this));
I'm not too familiar with Prototype, but what does this refer to in the respondToClick event handler? If I had to guess, I'd say it referred to the clicked DOM element, and not the object instance you wanted.
Perhaps you can assign the grid to a property the DOM element and reuse that property during the respondToClick function, as in:
myDOMElement.someProperty = grid;
//and later
this.somePropery.sortColumnAsc(columnName);

Categories

Resources