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
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!
Disclaimer: Title is not super exact.
I have a service with a public method openCamera that calls a library in the global object and attaches eventlisteners to its HTML elements. When I call this service method from the HTML elements events, it works fine, but not when I call it via events attached to the window element. An example below:
class ImageService {
public static AttachEventOnce = false;
public openCamera() {
let camera = new JpegCamera('myContainer');
// captureBtn and videoBox are HTML elements generated from the library once instantiated
camera.captureBtn.addEventListener('click', () => this.openCamera()); // this works fine
camera.videoBox.addEventListener('resize', () => this.openCamera()); // doesn't enter here ie not calling it
if (!ImageService.AttachEventOnce) {
var that = this;
window.addEventListener('resize', () => that.openCamera()); // the buttons stop working
ImageService.AttachEventOnce = true;
}
};
}
The logic have been somewhat minified but more or less the same. I just want to call the service method again and again when window is resized. I don't care where I attach the listener (HTML element generated from the library or window).
My take: The window seem to retain the older object reference too as well as other listeners for other buttons which I think is causing the issue.
The window seem to retain the older object reference too as well as other listerers for other buttons which I think is causing the issue.
Correct — not because it's on window, but because you're using an arrow function and only hooking the event once, on the first instance where openCamera is called. So it doesn't matter whether that instance is discarded by everything else, it's the only instance that will receive that resize event. (There's also no reason for the var that = this; thing and then using that inside the function; arrow functions close over this like they do variables, so it does exactly what just using this within the function would do.)
It's not clear why you're doing that as opposed to hooking the event in an instance-specific way like you are the other events. If you remove the logic hooking it only once, you'll get the per-instance behavior.
Separately: It's odd to be attaching new event handlers every time the event occurs. You'll very quickly have them stacking up. The first time (say) you receive a click, you'll add a second click handler; the next time you receive a click, you'll receive two of them (one for each handler), and add two more handlers; and so on, doubling every time. This is bad enough with clicks, but disasterous with resize as there are a lot of resize events triggered when the window is being resized.
There are many contentcontrols in a document and I need to find out a way that the cursor is in which content control so that I will select that control and do the operation accordingly. I think by implementing onEnter and onExit events for contentcontrols , I can achieve it. But I don't know how to declare and invoke those eventhandlers in JavaScript API. Any help is really appreciated.
You would need to use a combination of APIs to implement that functionality with the current API set:
First add an event handler for the Document.selectionChanged event.
Every time the event fires, get the Range object corresponding to the selection in the document, using the Document.getSelection() API.
Check the range to see if there's a content control in it, using the Range.contentControls relationship.
-Michael (PM for add-ins)
Good question! We do have an onEnter event for content controls (we call it binding.selectionChanged. We also have a binding.dataChanged event who gets triggered if the user changes the content and exits the content control
so an alternative solution to what Michael proposed is to create bindings for each content control in the document and then register for such events.
you can achieve this by:
1. traversing the content control collection.(use body.contentControls collection)
2. for each content control, grab or set the title and use it to create a binding by named item. check the bindings.addFromNamedItem method.
3. on the callBack make sure to subscribe to the selectionChanged (or DataChanged) for the binding.
the create binding code and register to the events will look like this:
function CreateCCSelectionChangedEvent() {
Office.context.document.bindings.addFromNamedItemAsync("TitleOfTheContentControl", { id: 'Binding01' }, function (result) {
if (result.status == 'succeeded') {
result.value.addHandlerAsync(Office.EventType.BindingSelectionChanged, handler);
}
});
}
function handler() {
console.log("Event Triggered!");
}
Hope this helps!
Michael,
My company tried this approach a few years ago in a VSTO addin. It ended badly. The problem is the number of events you have to handle is horrific. The performance penalty is drastic and grows with the document size.
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 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);
});