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
Related
I am trying to programmatically update a currency field to run the value changed event which holds a numeric calculation. I want the value to set to zero using something like.
$('.tester').igCurrencyEditor("setFocus");
$('.tester').igCurrencyEditor('option','value', 0);
Then when I blur out, or not sure what to do here, the valueChanged event should trigger as per the API docs (It can be raised on lost focus or on spin events).
But I can't seem to trigger the value changed event, it only works when I manually click into the input and change the number.
The valueChanging and valueChanged events would trigger when a user interaction changes the displayInput value of the editor, and the corresponding valueInput value is different from the display input one. The editors have adopted the same approach as all other Ignite UI controls where events do not trigger on API calls, because when an API call is performed, the developer can choose whether to invoke their event handler after the API call, or not.
There's two things that you can do to invoke your event handler. First one is to cache the event handler method and invoke it manually:
$('.tester').igCurrencyEditor({
...
valueChanged: valueChanged,
...
});
function valueChanged(event, ui) {
// event handler
};
$('.tester').igCurrencyEditor("setFocus");
$('.tester').igCurrencyEditor('option','value', 0);
valueChanged(null, { /* if you need arguments */ });
The second one is to extend the currency editor and override the method that performs the check whether these events should be triggered, and make it always trigger the events:
$.widget("ui.igCurrencyEditorExtension", $.ui.igCurrencyEditor, {
_processValueChanging: function (value) {
this._triggerInternalValueChange(value);
}
}
The second approach requires you to switch to using the igCurrencyEditorExtension and may cause side effects, as the method performs other checks as well.
Anyways, what Alex Marinov has suggested should work, but it depends on your editor configuration, depending on whether you've set nullValue, allow null values in the editor, etc.
you need a function like this:
function clearValue() {
$('.tester').igCurrencyEditor('option','value', "");
$('.tester').igCurrencyEditor('field').blur();
}
The result will be that the displayed value inside the currency editor is "$0.00" and the valueChanged event is fired.
I have a scheduler with custom events. In the edit event popup, there is a cancel button. The scheduler is from a javascript library, so not of my creation, I only created its custom functionality.
So in the edit popup (which triggers edit event), I set a variable to hold the current state of the event data. Then if I make any changes and press cancel instead of save, the cancel event is triggered. In the cancel event, the 'currentState' event data is now changed to the new state of all the changed data, which I don't want. I need the old data so I can revert it all back, then refresh scheduler.
This is my cancel event:
cancel: function(e){
console.log('Cancelling', e);
var kendoEvent = new kendo.data.SchedulerEvent();
var schema = e.sender.dataSource.options.schema.model.fields;
console.log(e.sender.dataSource.hasChanges());
console.log(currentEventState);
$.each(schema, function(index, value){
//console.log(value);
console.log(index);
kendoEvent[index] = e.sender.oldEventData[index];
if(index == 'ownerId'){
kendoEvent.ownerId = currentEventState.ownerId[0].value;
}
});
console.log(kendoEvent);
console.log(getIndexById(kendoEvent.taskId));
eventData[getIndexById(kendoEvent.taskId)] = kendoEvent;
e.sender.dataSource.read();
if(e.event.id != '0'){
dontUpdate = 1;
//e.sender.dataSource.sync();
}
},
Where e.sender is the scheduler, and e.event is the event data (with any changes that were made). In the edit event the first thing I do is add oldEventData field to e.sender and set it to e.event. But... if changes are made to e.event AFTER e.sender.oldEventData is set, e.sender.oldEventData STILL gets changed to reflect all the changes made...
How might I preserve this old data so I can revert it back? I tried just doing a global variable instead of creating a new field in e.sender, but after setting the global variable to e.event, it still reflected any changes made >:(.
Discovered that cloning the data into the field e.sender.oldEventData prevented it from being tied to e.event. So e.event could change without messing with the old data.
This was achieved with a simple JSON.parse(JSON.stringify(e.event)).
I attempted Object.freeze(e.sender.oldEventData), which kept the old data, but made it completely unable to modify at all, so wasn't the best solution for me.
Currently I am working on a scenario that I need to change the data attached to a dragging element after the dragging starts. Basically the drop zones are input fields or textareas so i would like to use the native event.dataTransfer.setData since the native drag-drop can make the caret move with mouse.
Everything works perfectly at the beginning if i just call the setData() synchronously in the listener of dragstart event.
dragItem.addEventListener("dragstart",function(event){
event.dataTransfer.setData("text/plain","data set in dragstart");
})
However, my scenario could be that the data is from an asynchronouly callback function like an AJAX request. Then I attempted to call setData() in this callback function but nothing seems to be successfully set.
dragItem.addEventListener("dragstart",function(event){
event.dataTransfer.setData("text/plain","data set in dragstart");
//like a callback in Ajax or resolve function of a promise.
setTimeout(function(){
console.log("attempt to set data asynchonrously after drag start");
event.dataTransfer.setData("text/plain","asynchonrously set data");
//looks like failed, the console output is empty, even not the original set one
console.log(event.dataTransfer.getData("text/plain"));
},200)
})
I also tried to change the data in dragenter and even drop event listeners of the drop zones. But there was still no luck.
This plunker shows what i have tried.
Then i referred to the MDN API document to find offical api description of the dataTransfer object. But there is nothing about problems like asynchronously using setData after drag start. One really weird thing is that if i try to compare the two dataTransfer references in dragstart and drop event, the are NOT the same object. Now I have no clue what is actually happening.
So my questions are
Is it possible to set the data in dataTransfer after the dragging is started with the native APIs (without using event.preventDefault) ?
If the first question's answer is NO, what kind of workaround could I try? I could think of something about how to save and get the data to transfer. My main concern is that if event.preventDefault() is used on drop, it is not easy to get the caret move with mouse like the native dropping does.
Here is your answer.
Firstly,You can only set data in your dragstart event. So, every time any dragstart event starts it sets value and what ever you set asynchronouly will not get reflected no matter what.
So, one thing that you can do is have a global object and set that on drag start event like this:
var someObj = {
asd : 'something'
}
and set in you dragstart callback, like this:
dragItem.addEventListener("dragstart",function(event){
event.dataTransfer.setData("text/plain", someObj.asd);
dataTransferObject = event.dataTransfer;
setTimeout(function(){
console.log("attempt to set data asynchonrously after drag start");
//event.dataTransfer.setData("text/plain","asynchonrously set data");
someObj.asd = 'asynchonrously';
//looks like failed, the console output is empty
console.log(event.dataTransfer.getData("text/plain"));
}, 100)
})
Object is by default reference type.
Now, You can set someObj.asd to any value you want and you will see new value reflected. But there is a problem with this approach, value will be reflected after drop happens means after event has been ended.
So, to solve your problem what you can do is don't set any value on dragstart just set some value to someObj.asd on drag start and use someObj.asd on drop.
Here is a link of what i'm trying to explain:
https://plnkr.co/edit/SZhI9lGRI37eEd1nWfhn?p=preview
see console on drop event you will see reflected value there.
DON'T SEE UI JUST GO FOR CONSOLE
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);
});