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)));
Related
I have the following buttons:
<button id="abcd" onclick="something()">click</button>
and the following functions are attached to this button apart from the one in its html definition.
$('#abcd').on('click',function(){alert("abcd");});
$('#abcd').on('click',function(){
someAjaxCallWithCallback;
});
Now I want a new function with another ajax call to execute on this button's click, before the above mentioned functions. This new function determines whether the remaining functions would be called or not based on what data is recieved by the ajax call. That is, this pre function should complete its execution before giving control over to the rest of the functions and also determine whether they would run or not.
As an example, without changing the existing validation logics and button code, I have to add a new pre-validation function and similarly and post validation function.
I have a bindFirst method using which I can at least bring my new function to the beginning of the call stack but I have not been able to contain its execution and control further delegation because of callbacks.
If I understand correctly, you are looking for the way to do this, without modifying html and already existing js, only by adding new js-code.
First of all, if onclick handler is set and you want to control it, you should disable it on page load (maybe, saving it to some variable):
$(document).ready(function() {
var onclick = $("#abcd").attr("onclick").split("(")[0];
//to run it in future: window[onclick]();
$("#abcd").attr("onclick", "");
});
Edit: I changed my answer a little, previous approach didn't work.
Now you need to remove all already existing handlers. If number of handlers you want to control is limited, constant and known to you, you can simply call them in if-else after pre-validation inside your pre-function. If you want something more flexible, you are able to get all the handlers before removing, save them and then call them in a loop.
For that "flexible" solution in the end of $(document).ready(); you save all already existing handlers to an array and disable them. Then you write your pre-function and leave it as the only handler.
var handlers = ($._data($("#abcd")[0], "events")["click"]).slice();
$("#abcd").off("click");
$("#abcd").click(function() {
//this is your pre-func
//some code
handlers[1].handler.call();
});
Try console.log($._data($("#abcd")[0], "events")) to see, what it is.
Finally just run your post-function and do whatever you need, using conditions.
So, the general algorithm is as follows:
Disable onclick
Save all handlers
Disable all handlers
Run pre-func first
Run handlers you want to be executed
Run post-func
In fact, you just make your pre-func the only handler, which can run all other handlers you may need.
Although Alex was spot on, I just wanted to add more details to cover certain cases that were left open.
class preClass{
constructor(name,id){
if($(id) && $(id)[0] && $(id)[0]['on'+name])
{
var existing = $(id)[0]['on'+name]
$(id).bindFirst(name,existing);
$(id).removeAttr('on'+name)
alert("here");
}
if($._data($(id)[0],"events")){
this.handlers = $._data($(id)[0],"events")[name].slice();
}
else
{
this.handlers = null;
}
this.id = id;
this.name = name;
}
generatePreMethod(fn,data)
{
$(this.id).off(this.name);
$(this.id).bindFirst(this.name,function(){
$.when(fn()).then(execAll(data));
});
}
}
function exec(item,index){
item.handler.call()
}
function execAll(handlers){
return function(){ handlers.forEach(exec);}
}
This more or less takes care of all the cases.
Please let me know if there is something I missed!
Commuity,
I have a problem with a specific piece of code I am writing currently. The problem is that i want to delegate the animations. (E.g. I have 3 objects, the first is animated, after that the second and after that the third)
The point is I am trying to make it modular so i can't hard code anything in there.
On start we have following situation:
parentDiv is supposed to contain the objects you want to "fadeIn"
prevDiv is the object which was "fadedIn" recently
onclickDisabled is a class to remove "click" events temporarily
fadeInAnim is the class to trigger needed animation
function prepareChildrenForFadeInAnimation(parentDiv, prevId){
var object = 0;
parentDiv.children().each(function(){
$(this).addClass('onclickDisabled');
addAnimationAfterAnimationEnd($(this)[object].id, prevId);
prevId = $(this)[object].id;
});
}
function addAnimationAfterAnimationEnd(currentSelection, prevSelection){
document.getElementById(prevSelection).addEventListener("webkitAnimationEnd", triggerNextAnimation(currentSelection, prevSelection));
document.getElementById(prevSelection).addEventListener("animationend", triggerNextAnimation(currentSelection, prevSelection));
}
function triggerNextAnimation(selection, prevSelection){
selection = $('#'+selection);
selection.addClass('fadeInAnim');
prevSelection = $('#'+prevSelection);
prevSelection.removeClass('onclickDisabled');
}
I have a feeling that in addAnimationAfterAnimationEnd() the function triggerNextAnimation() is fired, but I don't have a clue how to prevent this since I need to submit parameters (or do I?).
If one of you guys could help me out I would be overjoyed!
PS: I am using "Chrome" and "Firefox" for testing.
The JavaScript at Question is calling the function triggerNextAnimation(currentSelection, prevSelection) immediately instead of referencing the function to be called when event is dispatche at the object.
You can use Function.prototype.bind() or $.proxy() to reference the function which should be called when the event is dispatched and with this set to a specific object and pass parameters.
Also, it is not necessary to use vendor prefix if you are already using jQuery. You can use .on("animationend")
document.getElementById(prevSelection).addEventListener("animationend",
triggerNextAnimation.bind(null, currentSelection, prevSelection))
The problem was that the function, which was supposed to trigger after certain animation, was simply executed.
document.getElementById(prevSelection).addEventListener("animationend",
triggerNextAnimation.bind(null, currentSelection, prevSelection));
The code above solves this problem. "Function.bind()" enables me to specificaly call the function after an event
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/
I have a Dojo SubmitButton with jsId="saveParamButtonWidget". I overrided its onClick method by putting:
saveParamButtonWidget.onClick = editParam
I defined the editParam() function like this:
function editParam(eventObj) {
dojo.stopEvent(eventObj);
// ...
}
dojo.stopEvent() is supposed to stop event bubbling and default processing. However, the browser will submit the form anyway. I also tried with the following:
function editParam(eventObj) {
eventObj.stopPropagation();
eventObj.preventDefault();
// ...
}
Same thing. The only way I've managed to prevent form submission is by returning "false" from the event handler:
function editParam(eventObj) {
// ...
return false;
}
Can someone tell me why the first two ways did not work? Thanks.
Okay, after doing some digging through the source, I believe I can answer your question definitively.
The reason dojo.stopEvent() doesn't work, but return false does, is entirely due to how dijit.form.Button is coded. If you're interested, it's time for a little field trip. Keep your hard hats on.
When a dijit.form.Button is clicked...
The button's _onButtonClick method is invoked. (This is hooked up in the template, to the special ondijitclick event which captures not only mouse click but also certain keypresses, for a11y purposes.)
The _onButtonClick method first invokes the _onClick method, which, presuming the button is not disabled (which it's not in this case), invokes and returns the result of the onClick method. This is of particular interest since it's the method you're overriding!
Coming back to _onButtonClick, if _onClick returned precisely false (e.g. if your onClick handler returned false), _onButtonClick immediately bails out. This is why returning false makes your code work as desired. But what happens if it doesn't bail out there? Let's follow the trail further...
Next, _onButtonClick checks whether this button not a descendant of an actual HTML form, but is a descendant of a widget with an _onSubmit method (duck-typing). I'm assuming that in your case it is inside a real form (dijit.form.Form counts), so we'll skip over this. (I am under the impression that this code path wouldn't actually end up submitting, whereas yours apparently does.)
One final condition is checked: if the button has a valueNode defined (it does), the click method of this node is invoked. Unfortunately, this produces an entirely new event object on an invisible input type="submit" node under your form, and thus anything you tried to tell the original event is rendered immaterial, and the form goes on to submit! This is why dojo.stopEvent did not work - this code in dijit.form.Button pays it absolutely no heed.
I cooked this up as a somewhat-limited proof of concept (be sure to open firebug/etc. to get the logs): http://jsfiddle.net/Bf5H8/
Perhaps this is something that should be logged as a bug, but I suppose the initial thought may have been that supporting the well-known return false mechanism would be enough.
All this being said, it's quite possible that overriding onSubmit of the form is more in-line with your interests than overriding the button's onClick anyway (as S.Jones suggested), but at least this should solve the mystery.
Interesting question. +1
I believe you have to use dojo.connect to connect your function to a DOM event to get access to those methods with an event object.
See: The Event Object (DojoTollkit.org Reference Guide)
The Event Object
When you connect a function to a DOM
event with dojo.connect,
Dojo passes your function a normalized
event object. This means that,
regardless of the client's browser,
you can count on a set of standard
attributes about the event and a set
of methods to manipulate the event.
Assume that your function has been
called by dojo.connect and takes an
argument named event, like:
dojo.connect(dojo.byId("node"), "onclick", function(event){
// the var 'event' is available, and is the normalized object
});
...
Dojo normalizes the following methods with an event object:
event.preventDefault — prevent an event's default behavior (e.g., a link from loading a new page)
event.stopPropagation — prevent an event from triggering a parent node's event
Additionally, dojo.stopEvent(event)
will prevent both default behavior any
any propagation (bubbling) of an
event.
That said, placing a function like the one below in your form to perform some logic before submitting it, is a fairly clean, easily understood & maintainable approach.
<script type="dojo/method" event="onSubmit">
if (!this.validate()) { // or whatever else you'd like to evaluate
// insert calls here...
return false;
}
return true;
<script>
Cheers.
I had the same issue for using dojo.stopEvent
This issue is solved the form submission issue like this - here it is a simple form used to connect through dojo:
this.formId = dojo.byId("formId");
dojo.connect(this.formId, 'onsubmit', function(evt) {
var val_main = validate_this_form(0);
if(val_main == false)
dojo.stopEvent(evt);
});