I have a model cell with an attribute foo.
I have a listener thats bound to "change:foo" like so:
cell.listenTo(cell, "change:foo", function(){
if(cell.foo == "bar"){
doSomething()
}
});
Now, since the listener is bound at a point in time when foo may already be "bar", I want to manually trigger change:foo in order for the code to run, after the listener is bound. I'm unable to bind the listener earlier due to constraints from my application.
I found a Backbone.js cheat sheet which claimed I could just use cell.change() to trigger change:attribute for each attribute of the model, but that just yields Uncaught TypeError: cell.change is not a function. I am aware that I can trigger the change event by calling cell.trigger("change") but that does not trigger change:foo.
I could change my listener to listen for "change" instead of "change:foo" but if it's at all possible I'd like to stick with "change:foo" for performance reasons.
You can pass an arbitrary string to trigger, most notably the event you want, change:foo
Try
var cell = new Backbone.Model({
foo: 'bar'
});
cell.on('change:foo', function() {
console.log('foo changed', cell.get('foo'));
});
cell.trigger('change:foo');
And a demo http://jsfiddle.net/nikoshr/tk33rme2/
Related
I want to get the returned value of a clicked button, and then use it to make an if statement. All the answers that I read here about people trying to do that are either very old using old script that doesn't work anymore, or not the same case.
function remove() {
if (document.getElementById("removing").value == true) {
document.getElementById("test").style.backgroundColor = "red";
}
}
<div id="test">test</div>
<button id="removing" onclick="remove()">Remove a word</button>
I have tried using the value property, and onclick, but non of them equal true when the button is clicked.
I tried using alert to display the value, but it displays nothing.
Does clicking a button actually returns a value, and if so, what is it?
DOM Events are handled by an EventListener's callback function.
Thus such a handler function, if triggered by an event and forwarded by an event listener's handleEvent method, always will be invoked with an event object as this function's single argument.
All information related to this event are carried by the event itself. Its properties can be read and some even can be written/changed.
It is obvious from the provided example that the OP wants to assure that an event handler has been triggered by just a specific html element. Thus any valid approach just needs to look into the event's currentTarget property ...
// the way the OP might want to handle the problem.
function handleRemoveWord(evt) {
const elmNode = evt.currentTarget;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node properties.
if (
(elmNode.tagName.toUpperCase() === 'BUTTON')
&& (elmNode.id === 'remove')
) {
document
.getElementById('test')
.style.backgroundColor = 'red';
}
}
// another way the OP might want to handle the problem.
function handleRemoveAnotherWord(evt) {
// `this` referres to the element which got
// bound to the handler via `addEventListener`.
const targetNode = this;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node references.
if (targetNode === evt.currentTarget) {
document
.getElementById('test')
.style.backgroundColor = 'cyan';
}
}
// an alternative way of solving the problem
// of always being assured about the correct
// element having triggering the event handling.
function handleRestoreWordWithBoundContext(evt) {
const context = this;
const { elmTest, elmRestore } = context;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node references.
if (elmRestore === evt.currentTarget) {
elmTest.style.backgroundColor = '';
}
}
function initialize() {
// the way(s) the OP might want to handle the problem.
document
.getElementById('remove')
.addEventListener('click', handleRemoveWord);
document
.querySelector('#removeAnother')
.addEventListener('click', handleRemoveAnotherWord);
// an alternative way of soving the problem
// of always being assured about the correct
// element having triggering the event handling.
const elmTest = document.querySelector('#test');
const elmRestore = document.querySelector('#restore');
elmRestore.addEventListener(
'click',
handleRestoreWordWithBoundContext.bind({
elmTest,
elmRestore,
})
);
}
initialize();
<div id="test">test</div>
<button id="remove">Remove a word</button>
<button id="removeAnother">Remove another word</button>
<button id="restore">Restore a word</button>
As one might have noticed, the example features a third button with yet another way of implementing an event handler. This additional handler assumes its this context to carry additional information. This is possible by invoking bind on this handler function and providing exactly the information one wants to be present as the event handlers this context. Every time this function specific method is invoked it creates another function which does have access to the bound information via the this keyword.
Simply change background color onClick of button as:
function remove() {
document.getElementById("test").style.backgroundColor = "red";
}
<div id="test">test</div>
<button id="removing" onclick="remove()">Remove a word</button>
Dispatching Javascript event handlers always returns true, even if it returns false, which we all know is used to prevent default behaviour of an event. We don't usually use the return values of Event handlers or even return anything for that matter.
In your case, I think you're trying to acess the value of the currentTarget element(the button 'removing' in your case). For this you can use the event object, which gets passed on as parameter to your event handler.
event.currentTarget is a way of referencing the element on which an event is being dispatched(triggered) on. It's just like using 'this' inside the event handler, except it also works on arrow functions.
So do something like this:
function remove(event) {
let button = event.currentTarget;
if (buttton.value) {
document.getElementById("test").style.backgroundColor ="red";
}
}
and in HTML,
<div id="test">test</div>
<button id="removing" onclick="remove(event)">Remove a word</button>
Notice I've used remove(event).
Edit Based on comment below:
Using onclick will require you to create you a global 'remove' function.
If you do, '...onclick="remove(event)" what it basically does is creates the function below, a wrapper basically:
// In the global scope
[reference element].onclick = () => {
remove(event);
}
So you must have a global 'remove' function. So this won't work in modules cause each modules have their own top level scope. And you're gonna wanna have to use modules if you plan to work on sophisticated projects.
NOTE Using inline 'onclick' attributes in html has following disadvantages on heavy requests from a comment below:
-separation of concern : You usually don't want to mix up your UI logic(what happens on clicking a button) with presentation. You want a clear split between content, style and script.
-only one handler can be assigned using onclick.
-if an event is specified inline, the JS is specified as a string (attribute values are always strings) and evaluated when the event fires.(extra wrapper code builds internally).
-as I've mentioned before, you are faced with having to reference named functions. This is not ideal and has implications on the function needing to be global which will really bite you back when you use modules.
In short, handle events centrally via the dedicated addEventListener API.
In ArcGIS JS API I need to trigger a method of my class when the processing after an extent change is complete. I didn't find any special event for this. extent-change triggers too early, when the data are not loaded yet. update-end is triggered also by the function I want to call, making an endless loop. So what I need is to link the two events together, the second one just once. But how?
Most of my events look like this:
this.events.push(on(this.map, "extent-change", lang.hitch(this, this._foo)));
This is not suitable for event linking, so I need to make the linked event some other way. What I've tried:
_foo: function () {
function fooEvent() {
this._bar();
dojo.disconnect(updateHandle);
}
var updateHandle = dojo.connect(map, "onUpdateEnd", fooEvent());
}
_bar is the method I want to run on the end of the extent change. However, this in the event handler means something else, not the class containing the function. I also tried the classname I declared in the declare statement, but with no luck.
_foo() and _bar() are in the same class (let's call it "foobar/baz"). However, inside of the fooEvent() is not its subclass as I hoped - when I try to use this.inherited within it, it's undefined. Another way I try is to add event argument to the handler, but it's undefined as well. So unless there is some better way, I need to understand how to get the object of "foobar/baz" class.
Another way I tried was to use lang.hitch once more, in one of the following ways:
//through the cluster event
var updateHandle = dojo.connect(map, "onUpdateEnd", lang.hitch(this, clusterEvent));
//or directly calling _bar()
var updateHandle = dojo.connect(map, "onUpdateEnd", { langh:lang.hitch(this, this._bar), disc:dojo.disconnect(updateHandle)});
//or through on, leaving a rouge event listener
dojo.on(map, "onUpdateEnd", lang.hitch(this, this._bar));
None of them returns any clear error and though the _bar() method seemed to work for some time, it doesn't work now - this is true for all three of the previous. I don't understand what these listeners do.
I solved this issue by flattening the event listeners. First, I made a flag _reloadFlag, initialized in the constructor as false and then changed to true whenever I want to call _bar, like in _foo. _bar now starts with a test of _reloadFlag either setting it to false or returning nothing. The event listeners now look like this:
this.events.push(on(this.map, "extent-change", lang.hitch(this, this._foo)));
this.events.push(on(this.map, "update-end", lang.hitch(this, this._bar)));
I have a User model(Backbone.js) and I want to update its settings attribute and then save it to the server. Settings is in JSON format, and the way I have it set up is that settings is the string version and settingsJSON is the object version. I bind functions to the change event of each so that when one changes, it updates the other.
The problem I am having, is that the save method is running before the changed handler is finished running. Is there any way i could ensure that all event handlers for that model are complete or something like that?
how I'm calling it:
currentUser.get('settingsJSON').apps = appsEnabled;
currentUser.save();
My event handlers:
Initialize: function() {
var that = this;
this.on("change:settingsJSON", function(model){
model.set({settings: JSON.stringify(model.get('settingsJSON'))});
});
this.on("change:settings", function(model){
model.set({settingsJSON: JSON.parse(model.get('settings'))});
});
}
#fencliff:
The change event is firing when I run this and works properly, I had it print the new settings string to the console.
Are you sure that they are called synchronously? I added console.log('changed') to the end of the .on(change) and put console.log('saved') directly after currentUser.save() and every time the console read:
saved
changed
For now I have just made it so that I stringily the JSON and save it to settings directly before I save and that works fine.
Backbone events are executed synchronously. That means that unless you (or some library) has overridden some part of the event handling, the change handlers will have processed as soon as you execute the next line of code.
In you code example there is another problem. When you call
user.get('settingsJSON').apps = appsEnabled;
The change event will not fire, because the value of settingsJSON has not been changed, merely the contents of the object were modified. The model.attributes.settingsJSON is still the same object as before.
The events are fired only when you call set on the property, and the new value is a different object. For example:
user.set('settingsJSON', _.extend({}, user.get('settingsJSON'), {apps:appsEnabled});
Another problem, it would seem, is that your event handlers, if triggered, would cause the change event being fired twice for the property which was first set:
this.on("change:settingsJSON", function(model){
//-> changes settings, and set triggers change
model.set({settings: JSON.stringify(model.get('settingsJSON'))});
});
this.on("change:settings", function(model){
//-> changes settingsJSON, and set triggers change
model.set({settingsJSON: JSON.parse(model.get('settings'))});
});
To solve that issue, call set with {silent:true} or modify the model.attributes hash directly.
Edited with corrections by #muistooshort.
Edited again with further corrections
JQuery has great support for custom events - .bind("foo", function(e).... However what if the mechanic of triggering the event is not ready yet and has to be constructed only on those elements that have the event bound on?
For example I want a scrollin event that gets fired when an element is scrolled into a viewport. To do this, I would onscroll have to check all the elements and trigger scrollin on those that were outside the viewport and now are inside. This is not acceptable.
There are some tricks to speed it up. For example one of the plugins for this checks all the elements in "private" $.cache and does the checking only on those that have scrollin event bound.
But that's also ugly. What I need is an additional callback for the binding of the event (additional to the callback for handling) that would take care of the scroll management, that is to put the element(s) into some elementsCheckOnScrol cache array.
I'm looking for something like:
$.delegateBind("scrollin", function(jqSelection) { ... });
element.bind("scrollin", function(e) {..}); //Calls ^ after internal bind management
Edit: This would be nice api!
$.bind("bind", function(onWhat) { ... })
:-)
If I'm not misunderstanding you, you could patch the bind method like this:
(function($) {
var oldBind = $.fn.bind;
$.fn.bind = function(name) {
if(name === "scrollin") {
delegateFunction(this);
}
oldBind.apply(this, arguments);
};
})(jQuery);
What it does is checking whether a scrollin is being bound, and if so, calls your delegate function. After that it simply calls the original bind function which does all jQuery things like it does regularly.
After having added this code, you could use it like this: http://jsfiddle.net/pimvdb/g4k2G/.
function delegateFunction(selection) {
alert(selection.length);
}
$('a').bind('scrollin', function() {});
Note that this does not support object literals being passed to .bind (only (name, func)), but you could implement that as well.
I found an $.event.special API, but I don't know "how much" public it is. It is not in the docs and has been changed at least once before. http://benalman.com/news/2010/03/jquery-special-events/
The following doesn't work... (at least not in Firefox: document.getElementById('linkid').click() is not a function)
<script type="text/javascript">
function doOnClick() {
document.getElementById('linkid').click();
//Should alert('/testlocation');
}
</script>
<a id="linkid" href="/testlocation" onclick="alert(this.href);">Testlink</a>
You need to apply the event handler in the context of that element:
var elem = document.getElementById("linkid");
if (typeof elem.onclick == "function") {
elem.onclick.apply(elem);
}
Otherwise this would reference the context the above code is executed in.
The best way to solve this is to use Vanilla JS, but if you are already using jQuery, there´s a very easy solution:
<script type="text/javascript">
function doOnClick() {
$('#linkid').click();
}
</script>
<a id="linkid" href="/testlocation" onclick="alert(this.href);">Testlink</a>
Tested in IE8-10, Chrome, Firefox.
To trigger an event you basically just call the event handler for that
element. Slight change from your code.
var a = document.getElementById("element");
var evnt = a["onclick"];
if (typeof(evnt) == "function") {
evnt.call(a);
}
Granted, OP stated very similarly that this didn't work, but it did for me. Based on the notes in my source, it seems it was implemented around the time, or after, OP's post. Perhaps it's more standard now.
document.getElementsByName('MyElementsName')[0].click();
In my case, my button didn't have an ID. If your element has an id, preferably use the following (untested).
document.getElementById('MyElementsId').click();
I originally tried this method and it didn't work. After Googling I came back and realized my element was by name, and didn't have an ID. Double check you're calling the right attribute.
Source: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click
$("#linkid").trigger("click");
Old thread, but the question is still relevant, so...
(1) The example in your question now DOES work in Firefox. However in addition to calling the event handler (which displays an alert), it ALSO clicks on the link, causing navigation (once the alert is dismissed).
(2) To JUST call the event handler (without triggering navigation) merely replace:
document.getElementById('linkid').click();
with
document.getElementById('linkid').onclick();
Have a look at the handleEvent method
https://developer.mozilla.org/en-US/docs/Web/API/EventListener
"Raw" Javascript:
function MyObj() {
this.abc = "ABC";
}
MyObj.prototype.handleEvent = function(e) {
console.log("caught event: "+e.type);
console.log(this.abc);
}
var myObj = new MyObj();
document.querySelector("#myElement").addEventListener('click', myObj);
Now click on your element (with id "myElement") and it should print the following in the console:
caught event: click
ABC
This allows you to have an object method as event handler, and have access to all the object properties in that method.
You can't just pass a method of an object to addEventListener directly (like that: element.addEventListener('click',myObj.myMethod);) and expect myMethod to act as if I was normally called on the object. I am guessing that any function passed to addEventListener is somehow copied instead of being referenced. For example, if you pass an event listener function reference to addEventListener (in the form of a variable) then unset this reference, the event listener is still executed when events are caught.
Another (less elegant) workaround to pass a method as event listener and stil this and still have access to object properties within the event listener would be something like that:
// see above for definition of MyObj
var myObj = new MyObj();
document.querySelector("#myElement").addEventListener('click', myObj.handleEvent.bind(myObj));
If you're using this purely to reference the function in the onclick attribute, this seems like a very bad idea. Inline events are a bad idea in general.
I would suggest the following:
function addEvent(elm, evType, fn, useCapture) {
if (elm.addEventListener) {
elm.addEventListener(evType, fn, useCapture);
return true;
}
else if (elm.attachEvent) {
var r = elm.attachEvent('on' + evType, fn);
return r;
}
else {
elm['on' + evType] = fn;
}
}
handler = function(){
showHref(el);
}
showHref = function(el) {
alert(el.href);
}
var el = document.getElementById('linkid');
addEvent(el, 'click', handler);
If you want to call the same function from other javascript code, simulating a click to call the function is not the best way. Consider:
function doOnClick() {
showHref(document.getElementById('linkid'));
}
In general I would recommend against calling the event handlers 'manually'.
It's unclear what gets executed because of multiple registered
listeners
Danger to get into a recursive and infinite event-loop (click A
triggering Click B, triggering click A, etc.)
Redundant updates to the DOM
Hard to distinguish actual changes in the view caused by the user from changes made as initialisation code (which should be run only once).
Better is to figure out what exactly you want to have happen, put that in a function and call that manually AND register it as event listener.