Angular - Prevent event looping? - angularjs

I need your help as I am still having trouble with events handling.
Here is the concept :
To contextualize, let's say that I have visuals items, that I call "components".
A component is defined by 2 javascript objects. One is containing data (like position x, y, width, height, color, etc...). The other one is the view exploiting a 3rd party library named draw2d.
Draw2d uses it's own x y width height color etc... properties. Which mean that I need to "bind" values to always keep them equals.
For this purpose, I just use events. There are 'on' events in draw2d letting me watch theses modifications. For the angular side, I just use the ng-model directive directly on my data bindings. When a data is updated, I update draw2d view, and when the view is edited, I update my data.
This part is working correctly as this is not hard to handle it.
I then tried to implement multiple selection editing. For that, I store in an array every selected component. When a value from the view or from the data is edited, I trig an event which let every selected component to set the same data to the same value. Again, it's working great.
Where i'm starting to struggle hard, it's when I want to edit the position. I don't want every components to be on top of each other. For that, when I edit multiple x or y attributes, instead of positioning elements, I calculate the offset so that every components move with this offset only. Here is where it's not okay. Of course, when an offset is set, others components updates, also trigging their new value, so I'm stuck in a hell loop.
I would like to know if you know any pattern to handle events so that, when you have 2 view and a model, you can stop an event from propagate.
Here is my propagation snippet :
scope.canvas.on('figureEdited', function (canvas, event) {
if (scope.selectedComponents.length > 1) {
var key = event.key;
var value = event.value;
var originComponent = event.origin;
for (var i = 0; i < scope.selectedComponents.length; i++) {
var selectedComponent = scope.selectedComponents[i];
if (selectedComponent !== originComponent) {
if (key !== 'x' && key !== 'y') {
selectedComponent.setData(key, value);
} else {
var offset = value - event.oldValue;
selectedComponent.setData(key, selectedComponent.getData(key) + offset, true);
}
}
}
}
});
the issue is caused by
selectedComponent.setData(key, selectedComponent.getData(key) + offset, true);
It retriggers a figureEdited event, making every component move again by this offset, infinitely (stopped by 10 digest iterations).
I hope this is clear, sorry for the long text and thanks a lot for your help :)
Alann.

If the setData call triggers new events synchronously you can ignore events triggered while in your loop by setting an ignore flag and checking it at the start of the function.
var ignoreEvent = false;
scope.canvas.on('figureEdited', function (canvas, event) {
if (ignoreEvent) return;
ignoreEvent = true;
//...
ignoreEvent = false;
});

Related

Mutating object properties within an array with Polymer

I not sure how to solve this issue. I am sure someone will know this very quickly.
I have an array of objects and modifying a property. I have a firebase listener 'child_changed'. When firebase is updated need to update the array. Here is the code below.
dbRefList.on('child_changed', function(snap) {
var len = this.grocerylist.length;
for(var i=len; i--;) {
if(this.grocerylist[i].key === snap.key) {
this.set(['grocerylist', i, 'selected'], snap.val().selected);
}
}
this.notifyPath('grocerylist', this.grocerylist.slice());
}.bind(this));
When the array is modified I want the template repeat-dom to trigger. I know this.set will not trigger array mutation sub properties but again I am not sure how to solve this. I done research and tried so many solutions.
I can force a render on the template dom-repeat but I would prefer the data binding way.
So this code (just the this.set you have in there now) should cause the value of grocerylist.i.selected to update inside the dom-repeat (assuming it's bound in there so it's actually showing up).
What behavior are you seeing? Are you trying to filter or sort the list based on the selected value? In that case, you might need to add observe="selected" on the dom-repeat.
(Also—have you confirmed that the child-changed callback is being called with the this value you expect—the element—rather than window or something else?)
You should be able to force a refresh by doing this.grocerylist = this.grocerylist.slice() or this.set('grocerylist', this.grocerylist.slice()); ... notifyPath doesn't work here because notifyPath doesn't change the value, it notifies the element about a prior change (the second argument is effectively ignored).

Cursor (caret ) moves to beginning when call on change event on contenteditable div using angular js

The cursor gets moved to the beginning instead of being set to last type position when the change event is fired. I am using content editable div using angular js.
plunker
http://plnkr.co/edit/FFaWNZmgk00Etubtjgg8?p=preview
Why does the cursor position move to beginning?
I know this is an old question but, as I've been searching my whole working day to find a solution to this issue, I'd like to share this solution to people who might still looking for a working answer :
var el, el2, range, sel;
el = element[0];
range = document.createRange();
sel = window.getSelection();
if (el.childNodes.length > 0) {
el2 = el.childNodes[el.childNodes.length - 1];
range.setStartAfter(el2);
} else {
range.setStartAfter(el);
}
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
I got this code originally from akatov/angular-contenteditable directive, "moveCaretToEndOnChange" method.
and this worked for me.
I would assume Angular is rewriting the innerHTML property of the editable element, thus destroying and recreating all of the DOM tree within it, which results in the caret resetting to the start. See these answers for potential solutions:
https://stackoverflow.com/a/13950376/96100
https://stackoverflow.com/a/8240055/96100
In my case I was using the same reference of array for changing the innerHTML as was used on the DOM (two way binding). Hence the dom re-rendered and thus the cursor moved to the beginning.
The Solution was to make a deep copy of the array using JSON.parse(JSON.stringify(a)); where a is the array that stores the words that are contenteditable and then do the change
let filetexts =JSON.parse(JSON.stringify(this.filetexts));
// updating the current text
filetexts[alternativesIndex].Alternatives[0].Words[wordIndex].Word = html;
let data = { Text: JSON.stringify(filetexts) };
this.fileService.changeFileText(data, this.file.fileId).subscribe(res => {
});

Best way to save/update a resource with ($watch or save button, etc..)

Currently I run into the following problem,
we are working on a form heavy application and wanted a good user experience so we tried to stop have a save / update button everywhere.
Currently we try to $watch every Form Change, but this won't work correctly since it updates the scope when the model gets updated which causing problems on Decimal / Money Values.
What would you prefer? Still the messy save button or maybe something like Gmail did?
What are good methods to do this without save buttons.
/* EDITED */
Currently we use this method to update our form.
It first copys the scope in an object and checks if it is the same than the object that is set after the date got pulled.
$scope.$watch('task', function(scope) {
console.log($scope.updateForm);
scopeObject = angular.copy(scope);
if(scope !== undefined) {
if(!(_.isEqual(scopeObject, mainObject))){
//scope_copy.request_date = $filter('date')(new Date(scope.request_date), 'fullDate');
console.log('update');
scope.$update({pk: $routeParams.taskId}, function() {
scope.request_date = $filter('date')(scope.request_date);
mainObject = angular.copy(scope);
});
mainObject = angular.copy(scope);
}
}
}, true);
currently i think this code is somehow messy since it can't update decimal numbers.
but currently i don't have a better solution. (i don't want to use a Button to submit the form, it should be done interactivly).

How to set Ext.Form dirty state?

How to change the state dirty of a form to false?
Is there something like setDirty(false) method of Ext.Form.Panel?
UPD:
My task is to track Form dirty state for enabling docked SAVE button to become enabled only if there are any changes in the form.
I'm tracking dirtychange event with:
init: function() {
this.control({
'form': {
dirtychange: function(form) {
alert('change');
}
}
});
}
But when I'm loading a record through loadRecord() method into the Form I'm geting chage alert while loading record and Form becomes dirty after Record was loaded.
I'd like to reset dirty state of a Form right after Record loading and start tracking dirtychange event on record filled form.
Any ideas?
Correction
There is a property available which changes the original-value when a value get set.
trackResetOnLoad: true
See this JSFiddle
Old answer below might be quit inconvenient but I let it unchanged
First I have to say you will not find any such method
And that's because the form has not such a state, the fields have it. All what the form does is iterating over each field and check if it is dirty. If any field is dirty it will be checked if this value (the dirty mark) has changed since the last check and if so the dirtychange event is fired. Then this get saved as last check (wasDirty)
Now let's look at the fields cause they really trigger it:
They are checking onChange and onReset if the field is dirty by checking !me.isEqual(me.getValue(), me.originalValue) where originalValue is the value that was set the time initValue was called.
Now what's to do to 'set' the Form no longer 'dirty'
Iterate over all fields of the form by using getFields() on Ext.form.Basic now we will set the current value as original value (reset will now reset to this value) by calling initValue on each field and that's it.
wrong code removed
Edit
Well I've modified my last code from above to
var items = form.getForm().getFields().items,
i = 0,
len = items.length;
for(; i < len; i++) {
var c = items[i];
if(c.mixins && c.mixins.field && typeof c.mixins.field['initValue'] == 'function') {
c.mixins.field.initValue.apply(c);
c.wasDirty = false;
}
}
and forked a working example. The Button will stay inactive till any changes are detected and will again set inactive if you remove the changes. Play around with it I guess this is what you'Re looking for.
There is the method reset(). If used without an argument it retains the form data loaded with loadRecord().
So if you call reset() after loading the data, your form should not be dirty any more.
myForm.loadRecord(record);
myForm.reset();
I think, the dirtychange event will also be called by loading the data, but after the reset() you can check the form with isDirty().
A workaround here is to use a hidden field and set it's value to force the form dirty. We use this in widgets which do not contain form fields. To set the form dirty, we simply change the value of the hidden-field, to set it clean, we reset it to the original value.

extjsGirdPanel.getView().getRowClass does not work 2nd time

The handler of an extjsAction button calls a function with following code.
Add: function() {
var window = Ext.getCmp('wndAdd');
window.items.items[0].getStore().reload;
var Grid1 = Ext.getCmp('grdAll');
var grdStore2 = Ext.getCmp('grid2').getStore();
var i = 0;
var IDList = new Array();
for (i = 0; i < grdStore2.data.length; i++) {
IDList[i] =
grdStore2.data.items[i].data['ID'];
}
Grid1.getView().getRowClass = function(record, index) {
if (IDList.contains(record.data["ID"])) {
return 'disabled-row';
}
};
window.show();
}
But the getRowClass function works only on the first button click. does ot disable the row which gets added.
getRowClass only needs to be assigned one time. It is not a function that you call, it is a function called internally by the grid every time a row is rendered. Rather than assigning it inside an event handling function, it should be assigned ONE time, somewhere at the application level (e.g., wherever Grid1 itself is first configured would be the most logical place). This may or may not be your issue, depending on how your Add function is getting called, which is not clear. Bear in mind that since you rely on IDList inside getRowClass, you'll also have to have a reference to that variable that is in scope where the function is, and you will probably also have to add checks to make sure it is valid before accessing it.
You are also not showing where Grid1 is getting re-rendered. As explained above, getRowClass only executes when rows are rendered, so unless you are refreshing Grid1 somewhere not shown in your code, getRowClass will never be called.
FYI, while I'm glad that you found a solution that works for you, I'm not sure you understand getRowClass still. It does NOT get called only once -- it gets called EVERY time a grid row is re-rendered (which is anytime data changes). It was only getting called once in YOUR code because your code was not set up correctly.
I don't fully understand your use case, but regardless of the window being shown, the row striping would only need to change if the grid's underlying data actually changed. When you set up the getRowClass function properly, the grid takes care of calling it for you automatically, when it needs to, and it should "just work." There should be no need for your iteration code as written which just adds extra overhead.
Again, just FYI. :)
Yes, the getRowClass gets called only once when the grid is configured. But I wanted something that would fire everytimewindow.show() gets fired. I used the below code on window.onshow event.
for (var i = 0; i < Grid1.getStore().data.length; i++) {
var element = Ext.get(Grid1.getView().getRow(i));
var record = Grid1.getStore().getAt(i);
if (IdList.contains(record.data.ID)) {
element.addClass('disabled-row')
} else {
element.removeClass('disabled-row')
}
}
I will edit my response with an appropriate answer once supplied more information.
When are you firing getRowClass? It seems like you are creating the function but never actually calling to for a response.
Does the function error out, get called at all, or just doesn't do what you want?

Resources