I'm creating a simple Angular app that uses Ace editor (via ui-ace) for on-screen text editing. I want a handler to run when the cursor changes which will update a model object when the cursor is in a certain location. However, I also want to be able to click a button to move the cursor to a particular location (and also update the model object if necessary). Here's a jsfiddle that demonstrates the idea.
http://jsfiddle.net/fpzknzej/3/
The model object updates when the cursor is placed at the end of the word print on the second line. The problem is that the $scope.$apply() on line 30 will throw an in-progress error when the Move Cursor! button is pressed. However, without that line, the view bound to the model object will not update when the cursor is moved with the arrow keys.
My current understanding is that this is simply the wrong way to do this kind of thing and that I need to do something along the lines of wrapping the changeCursor event to operate solely in the angular world. However, I'm at a loss as to how to approach this task (custom directive seems to be the thing that comes up the most when searching for this type of thing?) and if there's a good resource for understanding how to interact with third-party event handlers within angular. Any pointers in the right direction would be appreciated.
Will try to make this as simple as possible.
All angular core event directives such as ng-click , ng-change etc will automatically call $apply() internally.
In your case with move cursor you start the digest with ng-click then have a bit of a circular issue where the external ace event triggered from within ng-click is also going to call $apply().
You really only want to call $apply() when events that change scope outside of the angular core need to update the view.
A short term workaround for your situation is to use $timeout() instead.
This will be added to the end of the current digest stack queue and will call $apply() once other digests are completed.
As for directive , yes, this code does belong in directive but you won't really be changing it's current structure ... just moving it to a different part of the app. For the short term that isn't going to change what is currently happening
Related
The custom component
I've created a custom component for the navigation of my app. It consists of an ul element and all its li elements are dynamically created based on the items in the router's navigation list. This is happening in the attached event of the component. Nothing special is going on here.
The custom attribute
But because I want to have a good looking fancy menu I also created a custom attribute and implemented it into the root ul element of the custom component. In the attached event of the custom attribute I'd like to do some dom manipulations so that my menu looks like a menu created by those cool kids.
The problem
Although the attached event of the custom attribute is fired AFTER the attached event of the custom component, the dynamically created li items are not part of the dom in the attached event of the custom attribute yet.
The question
My assumption was that on attached event the view of the actual component is attached to dom and that all components before are also attached to the dom. And when I am done with the attached event the html that has been dynamically created here is also attached. Am I mistaken?
On a side note
I am aware of using TaskQueue could solve my problem. However, I would like to know if there's a different approach/solution first because I believe that moving things in time could cause a chain of paradoxes leaving you in a maintenance nightmare.
Allow me take away your misconception about the TaskQueue.
Your idea of moving things in time causing maintenance issues would certainly apply to using setTimeout() because that incurs an actual delay, and forces execution onto the next event loop. queueTask() is more or less the same thing (it uses setTimeout() internally).
However, the TaskQueue's queueMicroTask() method works very differently.
When you call queueMicroTask(), unlike queueTask() and setTimeout(), the task is scheduled for immediate execution on the same event loop. This is a very robust mechanism with a guaranteed execution order, and it's generally considered good practice to use it within attached() before doing any DOM manipulation.
In fact, queueMicroTask() is used internally by Aurelia in various places (mostly in binding and templating-resources). Two notable places are:
Property- and collection observers use it to "delay" notifying subscribers until all other bindings have completed their internal updating work
The repeat attribute uses it to set an ignoreMutations flag (and unset it after the queue is flushed) to prevent infinite recursion while updating its inner collection
You can generally consider there to be two "phases" to the bind() and attached() hooks: a non-queued, and a queued phase. The queued phase is when components do work that relies on the whole component graph to first be done with some other (usually recursive) process.
queueMicroTask() does not delay execution, just pushes it to the end of the call stack
It's the functional equivalent of passing the function as a callback to the end of the call stack, but saves you the trouble of writing the spaghetti code required to locate that last call and wire it all up. It's super clean.
all its li elements are dynamically created based on the items in the
router's navigation list. This is happening in the attached event of
the component
See, whenever you create anything during attached(), you can't rely on that thing being there during another component's attached() as this depends on the order of compilation/composition. That's an internal matter. This is especially true for custom attributes. Custom attributes (particularly those in style libraries) use the TaskQueue all over the place because it's the only way they can rely on the DOM being done.
Using queueMicroTask() here will guarantee two things:
It's executed when aurelia is completely done with the "first pass" of attacheds and rendering
It's executed immediately when aurelia is done with that - not even a microsecond delayed.
The best (and perhaps only correct) way to address this is indeed by using the TaskQueue - I promise :)
I have written a directive which has a two way binding to bring in the text specification for a flow chart which is used to generate the actual objects (steps and connections) inside the directive. I have a $watch set up inside the directive to allow the controller to send in a new specification (e.g. loading a new chart), but I also want to have the directive make changes to the specification in response to user action (e.g. deleting a connection) so that the controller can save the changed specification.
I have a function in the directive which converts the chart objects back into a text specification, but if I simply replace the specification scope variable with the updated value, the original watch sees a change (which it thinks might have come from the controller) and so reloads the chart from the specification. This has the effect of breaking things like dragging elements around because the elements are being removed from the DOM and replaced by new ones.
What I would like to be able to do is temporarily suspend the $watch while I make my internal changes, or in some other way avoid the watch from triggering when the directive makes changes to it, only going off when the controller makes changes from outside. I tried to unbind and rebind the watch around making the change, but because the actual checking happens elsewhere in the cycle that does not work. An alternative solution I could use is to have two variables passed between the controller and directive, one going each way, but that is somewhat inelegant. Any better suggestions would be welcome.
When I do this, usually my $watch looks something like this:
$scope.$watch('MyVar',function(newval,oldval) {
if (oldval == newval) return;
if (newval == $scope.internalval) return;
// process here
})
For precisely the reason you outline. I don't believe you can turn the $watch off, so before I make an internal update to the watched variable, I update a tracking version of the variable to make sure I don't get infinitely-recursing changes. It seems like a pain, but the watched variables are finite and I always use a setter function so the code updating the internal value is only written once
I have a page that uses a Kendo MVVM approach for two different elements, one providing file search results, the other a document upload facility.
The problem I am encountering is to do with the change event that both elements use - it seems that when one control fires a change event it is then picked up by the other control, which then attempts to process the event and passes it on, at which point it is picked up by the second control's change handler which processes it and passes it on to the first control's change handler. As you might expect, after around 1500 repetitions of this cycle, I see a Uncaught RangeError: Maximum call stack size exceeded message as the JavaScript engine runs out of memory.
At first I thought the problem was that the container of the second model was contained within the first, but even if they are completely separate on the page it seems as though the problem still shows up, so now I'm wondering whether the problem is related to the event being global to the page.
It seems that anything I do in my event handler in terms of trying to stopPropagation or stopImmediatePropagation - or even to set the event to null altogether - makes no difference to this behaviour. Tracing the call stack I can see it looping through Kendo's trigger call then through the event binding on my object and jQuery's dispatch loops that lead it back to Kendo, where it triggers the event handler on the other observable object.
Removing my bindings does not affect the problem, the change event is still bounced back and forth between Kendo and jQuery in the same way, it just doesn't run through my code.
The answer here was not a direct consequence of Kendo itself, so it would have been hard to answer from the question as I set it.
Inside the Observable container that was raising this error, I was using Isotope for layout. The step I had missed was that I had a relationship like this:
Parent [Observable]
-> Container
-> Child
-> Child
-> Child
One of the things that Isotope brings to the party is that for each item in the child collection, it adds a reference to its parent object.
When the child is Observable that creates a structure like this:
Parent [Observable]
-> Container <--┐
-> Child ---|
-> Child ---|
-> Child ---┘
This is an ideal situation for events to be propagated from child to parent, but because the properties in question were being automagically added by the libraries in question it was very hard to troubleshoot.
The solution was to remove the Container layer from the Observable model - it didn't need to trigger anything on change and so I wrapped it in a simple getContainer() closure and used that everywhere I was previously using it as a property. This protected it from the Observable object, breaking the circular reference without harming the functionality.
It may also be relevant that as far as I can tell the initiating event was a DOM change event rather than one of Kendo's own events. The problem may have been avoidable by using a custom Kendo namespace but that would have been a significant change in a complex application and guaranteed to cause a lot of side effects.
I'm using Backbone.js. In my view, I have a textarea whose keyup is bound to a function like this (but see edit below):
this.model.save({text: self.$('textarea').val()}, {patch: true});
In the view's initialize function, I bind the model's change event to the view's render function:
initialize: function() {
this.listenTo(this.model, 'change', _.bind(this.render, this));
},
Trouble is, when the user types in the textarea, the following sequence of events occurs:
The keyup event fires.
The keyup handler calls save on the model.
The call to save triggers the model's model's change event.
The view, listening for the model's change event, calls render.
The textarea is replaced in the DOM.
The textarea is no longer focused, and the text cursor position is lost.
What is the best practice for situations like this, where a texarea's keyup event needs to trigger a sync? Some options I have considered:
Don't bind change to render. Disadvantage: If the model data changes due to anything other than the user typing, the textarea doesn't automatically update.
Read and remember the cursor position at the beginning of render. Set the cursor position at the end of render. Disadvantage: Depends on cursor manipulation features for which browser support is spotty.
In the keyup handler, set a temporary property on the view telling it not to re-render. Unset it after the model has been saved. Disadvantage: Feels like spaghetti code, fights against the structure of Backbone.
Are there any options I'm not seeing? Do you recommend one of the options above?
Edit:
I didn't want to distract from the main point, but since it came up in one of the answers: I'm not binding directly to keyup, but intermediating it with _.debounce. Thus, the event handler only runs once the user stops typing, as defined by a certain amount of time elapsing since the last keyup.
First of all I'd like to discourage this as it seems like really strange behaviour to save your model on keyup. If there is a use-case which really necessitates this I'd suggest using the input event at the very least - otherwise you'll end up saving the model every time the user presses even an arrow key, shift, ctrl etc.
I think you'll also want to debounce the input events by 500ms or so you're not actually saving the model every single keystroke.
To address your comment in point 1:
Disadvantage: If the model data changes due to anything other than the
user typing, the textarea doesn't automatically update
You need to ask yourself the likelihood of this happening and how important it is that the view is rerendered if this was to happen.
Finally, if you decide that this is indeed likely and it is important that the view is rerendered, then you can try something like this
http://jsfiddle.net/nuewwdmr/2/
One of the important parts here is the mapping of model attribute names to the name field of your inputs. What I've done here follows the sequence of events you described above. The difference is that when the model changes, we inspect the changed attributes and update the value of the corresponding element in the template.
This works fine in a very simple situation, the happy path, where the user is typing in a "normal" way into the input. If the user, however, decides to go back to the start of the input and change a letter to capitalize it, for example, the cursor will jump to end of the string after the change event in the model occurs.
The behaviour you require here is really two-way data-binding which is by no means trivial, especially with Backbone given just how little functionality a Backbone View has.
My advice would be your point 1
Don't bind change to render
Edit
If you want to look further into model / view binding you could take a look at two libraries:
stickit
epoxy
I've used stickit before and it's...fine. Not great. It's ok for simple bindings, for example binding a "top-level" model attribute to an input element. Once you get into nested attributes you'll run into problems and you'll then have to look into something like Backbone Deep Model.
Like I said, Backbone's View doesn't offer very much. If you've got the time I'd suggest looking into using React components in place of Backbone Views, or even look at some of the interesting stuff that ampersand have to offer.
I'm new to Backbone.js and am having trouble figuring out the proper architecture for a model-view relationship.
I have a view that holds an input box and a model that is supposed to take the contents of that input box and send it to the server.
My issue is that I don't always have a discreet DOM event that triggers a request for the view to update the model data, such as input.change. Sometimes the code itself needs to ask the model to send updates to the server.
I've thought of three solutions to this problem so far, I'm not sure if any if them are any good though:
Update the model on the input element's keypress event
Once the view is initialized with the model, have the view update/add a function to the model called 'get_input_value()' that returns the value of the input box
Whenever the application needs to request the model to update the server, first call a function in the view that updates all of the information that the user has typed into the view to the model.
Please bear in mind that this is a simplified example. The view contains child views as well, all of which hold a number of elements that the user can manipulate, the model needs to be updated with all of this information so that it can update the server.
Any help and input is appreciated! Thanks so much!
Edit :::
Base on machineghost's response, I now see that I did not articulate this problem correctly:
There is a DOM event, but the problem is that it doesn't necessarily originate from inside the view that uses the model. It may originate from the Router or another view and be triggered by a global event handler. Additionally, there is not a 1:1 View-Model relationship. This model is used by multiple views who express the model in different ways. So in this case, it seems like the command to update the server should not go through a View, but to the model itself. If that is the case, the model must be able to say "Sync me with my views!".
But I don't know how to do this without breaking the rules and thus creating other problems with architecture...
Ok this is kind of a subjective question, so forgive me if this just seems like me spouting off my two cents. And before I even answer your question, I have to admit I'm a bit skeptical that you:
don't always have a discreet DOM event
because pretty much anything the user can do triggers an event that you can watch for. For instance, if you want to wait until a user changes a text input there's change, but also (as you noted) the various key* events, plus there's blur (which is commonly used for this sort of thing). Between the 3(+) you should always be able to respond appropriately to the user's actions. It would only be if (say) you had to save the text input's contents every 3 seconds that it would truly be independent of DOM events.
So, without knowing your particulars, I just have to point out that something smells fishy there. But anyhow, as for your actual question, here's my take on your ideas:
Update the model on the input element's keypress event
This certainly would work, but just be sure to use the view to do the actual event handling/model setting; hooking up the onKeyPress handler in the model would be a bad idea
Overall this approach seems pretty standard, and fits the Backbone paradigm.
Once the view is initialized with the model, have the view update/add a function to the model called 'get_input_value()' that returns the value of the input box
I don't quite get how this helps your problem, plus it seems to put the concerns in the wrong place: the model should (ideally) have nothing to do with the DOM.
Whenever the application needs to request the model to update the server, first call a function in the view that updates all of the information that the user has typed into the view to the model.
Is the save happening every 5 minutes or something? If not, then it's presumably happening in response to the user's actions, and you should use an event handler to respond.
However, if you truly do need to make the sync independent of user actions, I'd recommend using a custom event to manage things. In other words, in your model's sync method put something like this.trigger('preSync'). Then, every view which uses that model can bind some sort of updateMyModelValue method, ie. this.model.on('preSync', this.updateMyModelValue, this);.
This way, your model code is never directly interacting with the DOM at all; instead, it just worries about the stuff it's supposed to worry about (the data) and the views pay attention for when they need to update that data from the DOM.
Hope that helps.
* EDIT (in response to your editing of your question) *
If that is the case, the model must be able to say "Sync me with my views!".
The general Backbone way for a model to say ... well, pretty much anything to its views is through events.
(Technically you could maintain a list of a model's views in the model itself, and then iterate through that list to tell the views to do things. Backbone is even un-opinionated enough to let you do that. However, from a maintainability standpoint that seems like a terrible approach to me.)
My example of a "presync" event (above) demonstrates how you'd use this technique; comment back if any of it is unclear.
Similarly, if you have an issue of:
View A catches an event
View B needs to do something in response to that event
You basically have two options:
1) You can tightly couple the two views. Let's say have a table view that creates row views, but needs to respond to events that happen in those rows. You can pass the table itself as an option to the row when you create it (new Row({table:this})), and then when those rows need to tell their table "an event happened" they can just do this.options.table.informThatAnEventHappened(). This is a great approach if the two views are inherently related, like a table and its rows. If not, a better approach is:
2) You can use events to communicate between the views. Let's say you have a title div at the top of the page, which needs to be updated whenever a "title" text input changes ... but that text input is way down the page and doesn't conceptually have much to do with the page's title (apart from setting it). The common point between these two elements (and their views) is the data, the text of the title itself.
Now imagine that titleDivView and titleSettingInputView both share a pageTitle model. When titleSettingInputView calls this.model.set('titleText', 'newTitle'), the titleDivView can listen for this.model.on('change:titleText', ...), and re-render itself appropriately in response. In this way two totally un-connected, de-coupled views can interact with each other, without creating a tangled web of inter-related code.
And of course, if there isn't a nice convenient "change:title" event to bind to, you can always make your own, as with the custom "presync" event I described above.