I see a lot of examples using React with backbone, there is however some things that are still somewhat unclear to me. In nearly all examples they show how you can get your component to listen to a model or collection and update the view accordingly, this seems pretty straightforward, you can use the Backbone Mixin or you can setup some event listeners in "componentDidMount()".
What is unclear to me is how to handle the other way, ie when a user writes in some input field, I then want to set this same value on my model, which ultimately is what i validate and then save on the server.
With simple forms this is also pretty straightforward, you can have a callback for the onChange event, example:
return <div><input type="text" onChange={this.setPrice} /></div>
All good, in the setPrice function I can now do something like:
this.props.myModel.set('price', e.target.value);
This works, but two things that immediately strike me:
The set method will be called on the model every single key event, since Reacts "onChange" actually executes on every key event, when you type in the textbox.
My second concern is, this works good for simple forms, however we have forms that have upwards 30-40 different input fields, having an onChange event on all of these input boxes, checkboxes and what have you seems counterproductive.
Right now, we have a databinding in our Backbone Views that simply sets whatever the user types on these input fields on the model, this does not seem to be the way togo in React though since what would be updated if you use something like ReactLink is the properties inside "state" on the Component, not properties directly on the model.
Is there a best practice here, or is this "marriage" between React and Backbone simply not meant to be? It would seem as if you would need to somehow map each input field to a specific property on the model. I am not sure if this is a good thing todo with React.
Thanks
You can call the setPrice method onBlur instead of onChange so that you will update the state when the user clicks or tabs out of the field.
This is more efficient for longer forms in my opinion as you are guaranteed that the user will tab or click to the next field.
Related
I'm working with a form that may be a bit over-engineered, and I'm trying to write a script to step through the form and submit it. Most of the form is pretty hackable, but there's a 3-part date input which is just not responding to my attempts to manipulate it programmatically.
The date field works, if I click or tab to it and begin typing. But if I manually dispatch events, even ones that are identical to what it receives when I type and have exactly what the code seems to be looking for, I can't get it to hold onto its values and perform validation. I've tried a lot of variations of this. I've tried manually dispatching a custom event that matches a custom Vue event it should be listening for.
Is there a way to instead manipulate the data of the Vue component directly? To force it to have a certain "monthValue" for example, without intermediate events? I don't expect that there is, but hopefully I'm missing something. Please note that I do have the ability to refactor the form, but that should be an absolute last resort.
As far as I can tell, no, there isn't a way to do this. But it wasn't necessary.
The solution involved being more careful in looking at what the components were actually trying to do. In this case they were input elements using v-model, which is shorthand for a combination of #input (event listener) and :value attributes[1]. This meant that dispatching synthetic input events with the correct data attribute could convince Vue to accept the value and retain it (whereas any value dispatched in any keyboard event would be ignored entirely). This only addressed half of my problem with this particular form, but it is the correct solution for the question asked.
[1] https://vuejs.org/guide/essentials/forms.html
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 designing a page with forms for editing fields in a database. At first I was using FormView controls, but for various reasons I've changed them for DetailsView controls instead. These are so much quicker to code and the code is much tidier, because you just add a Boundfield for each field you want to display. No textboxes, labels, etc. So far so good.
However, the textboxes in my FormViews all had the onchange property specified, which called a JavaScript function. Basically it was the mechanism to alert a user that changes had been made and needed saving, and it worked perfectly.
Unfortunately, you can't add the onchange property to a DetailsView Boundfield. I've tried adding it programmatically in code behind (VB) in the DataBound event of the DetailsView, working on the theory that when the page is loaded the Boundfields are rendered as textboxes but, as expected, it doesn't work.
Can anyone suggest a way to replicate the functionality of the textboxes in my FormViews, calling a JavaScript function when text in a Boundfield is changed? I'd really like to stick with DetailsViews and Boundfields if I can. And if I could achieve this in code-behind, perhaps iterating through the fields to add the functionality, that'd mean just a few lines of code for each DetailsView, rather than adding it declaratively for every Boundfield (there are LOTS!).
This was way easier than I thought. It's possible to add the onchange property to the DetailsView itself. Once that's done, any change to any field within the DetailsView calls the JavaScript function.
There's a bit of story behind how we ran into this... Basically, we were calling trigger('change') on all of our form inputs to let other knockout observables know their value had been reset. But I really think it's a bug in Knockout. Asking here to see if anyone else has run into it (and StackOverflow is a much nicer interface than Knockout's google forums).
So if you have a hidden input who's value is data-bound to a computed observable and you call jQuery's trigger('change') on it, it wipes out the observable. If you drill into the code, you can see that on the view model object, the member object is replaced with a string of the last value on the computed observable before you triggered the change event.
JS fiddle showing the breakage in action: http://jsfiddle.net/2VvvE/1/
It uses console.log to output the object, so be warned if you try a browser without console (cough IE). You can see that the dependent observable is working fine until you hit the 'Break It' button, after which, the value stops updating and subsequent presses output the same thing. If you comment out the line with the trigger('change') on it and re-run the fiddle, you can see that it continues to work after each button press.
Apologies for not asking a real question - we already figured a work around where we only call trigger('change') on inputs that aren't hidden (pretty straightforward jquery selector in case anyone is curious):
$("#"+this.id+" form").each(function() {
$(this).validate().resetForm();
this.reset();
// Do some actions on all the inputs, then filter before calling the trigger
$(this).find('input,select').data('valid','true').filter(':not(:hidden)').trigger('change');
$(this).find('label,legend').removeClass('validated-error');
});
But I wanted a verdict: Knockout bug? Or am I doin' it wrong?
You should not bind a normal computed observable against a read/write binding like value. This is causing it to get overwritten in your model.
If you have to you can use a writeable computed observable. In this case you could even have a blank write function: http://jsfiddle.net/rniemeyer/2VvvE/2/
The actual answer though is that you really don't need to be triggering the change events on the fields. A better way to handle this is to do it from your view model. On any observable or computed observable you can call the valueHasMutated() function to notify all subscribers again with the latest value.
myObservable.valueHasMutated()
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.