Reputation: 10613
Given a model:
MyModel = Backbone.Model.extend({
defaults: {
name: '',
age: -1,
height: '',
description: ''
}
});
and a View to render the model as a list:
MyView = Backbone.View.extend({
tagName: 'ul',
className: 'MyView',
render() {
var values = {
name: this.model.get('name'),
age: this.model.get('age'),
height: this.model.get('height'),
description: this.model.get('description')
}
var myTemplate = $('#MyView-Template').html();
var templateWithValues = _.template(myTemplate , values);
}
});
and a template loaded by the View:
<script type="text/template" id="MyView-Template">
<li class="name"><%= name %></li>
<li class="age"><%= age %></li>
<li class="name"><%= height%></li>
<li class="name"><%= description%></li>
</script>
everything works fine, although it is a contrived example, the real code has many, many more attributes in the model. The problem I'm experiencing is how to handle updates to the model.
I create an HTML form which has an appropriate input element for each field. The form is modelled and loaded as a template:
<script type="text/template" id="MyEditView-Template">
<input type"text" value="<%= name %>" /> <br />
<input type"text" value="<%= age%>" /> <br />
<input type"text" value="<%= height%>" /> <br />
<input type"text" value="<%= description%>" />
</script>
and loaded into a view:
MyEditView = Backbone.View.extend({
tagName: 'form',
className: 'MyEditView',
render() {
var values = {
name: this.model.get('name'),
age: this.model.get('age'),
height: this.model.get('height'),
description: this.model.get('description')
}
var myTemplate = $('#MyEditView-Template').html();
var templateWithValues = _.template(myTemplate , values);
}
});
When the user saves the form, the new values are set in the model (MyModel). However I do not want to re-render the entire original view, it takes too long and has many nested elements. I only want to update the HTML elements which have had their value changed in the model.
The problem is how can I elegantly link a model's attributes to HTML elements, so that I can do the following to an already rendered view:
A the moment I have a rather ugly solution of a JavaScript lookup table (just an object) which maps an attribute name onto an HTML element string:
var AttributesMap = {
name: {
htmlRef: 'li.name',
attributeName: 'name'
},
age: {
htmlRef: 'li.age',
attributeName: 'age'
}
...
}
This feels hacky and has resulted in some pretty bloated code.
Upvotes: 3
Views: 4141
Reputation: 2192
Well, Backbone use event delegation and one doesn't need re-subscribe for element events after replacing this.el content. But you destroy the DOM-subtree with every template synchronization, meaning you losing the state of your form. Just try subscribing your model for input/change events. So user types in input elements and form validates. Restoring the state of controls (let's say input[type=file]) would be challenging and resource-hungry.
I believe the best way is to go with a DOM-based template engine, that updates only target elements where it is necessary. E.g. mine is https://github.com/dsheiko/ng-template
You can have a template like this:
<form id="heroForm" novalidate>
<div class="form-group">
<label for="name">Name</label>
<input id="name" type="text" class="form-control" required >
<div class="alert alert-danger" data-ng-if="!name.valid">
Name is required
</div>
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select id="power" class="form-control" required>
<option data-ng-for="let p of powers" data-ng-text="p" >Nothing here</option>
</select>
<div class="alert alert-danger" data-ng-if="!power.valid">
Power is required
</div>
</div>
<button type="submit" class="btn btn-default" data-ng-prop="'disabled', !form.valid">Submit</button>
</form>
Here we bound models name
, power
and form
. Whenever their states change (e.g. while user is typing), the template reacts. It may hide/show error messages, or disable/enable submit button.
If it's interesting - how to bundle it with Backbone, here is a little free online book https://dsheiko.gitbooks.io/ng-backbone/
Upvotes: 0
Reputation: 155
I faced a similar problem where I wanted to have a template only show fields for which there was data. I approached the problem from the underscore template side because <%= undefinedKey %> throws an exception. The solution, for me, was to pass a wrapper object to the template that contains the model data. The wrapping of model data looks like:
this.$el.html(this.template({my_data: this.model.toJSON()}));
Your template checks for existence of the desired property:
<% if(my_data.phone) { %><p><%= my_data.phone %> </p><% } %>
You can automatically render the entire view each time the model changes. Using this method new values will appear and deleted values will disappear from the UI.
Some further information related to your requirements:
Iterate over a model's attributes. Determine which attributes have been modified.
If you want to know what attributes have changed since the last time a model's "change" event was fired you can use the Backbone Model's changedAttributes method.
Only update the UI for modified attributes. Hide UI for any previously rendered attributes which should no longer be shown.
In lieu of rendering the entire view for each attribute change that occurs you could surgically update only the portions of the UI that have changed attributes by having each UI field be a discrete Backbone View. All Views would be listening to a shared model for the change event of a specific model attribute:
this.model.on("change:phone", this.render, this);
Upvotes: 1
Reputation: 11125
there is actually two questions hidden in your post. You have problem with attributes of the model and you are not aware if how to subscribe to model change events. Luckily both of these are possible easily with backbone.js. Change your view code to below
render: function () {
var model = this.model;
$(this.el).empty().html(_.template(this.template, this.model.toJSON()))
return this;
}
where el
is property of the view that defines the container. toJSON() is a method you can invoke on the model to serialize it format that can be transferred over the wire.
Views are supposed to subscribe to model change events in their initialize function or more aptly use the delegated event support. When ever a model attribute changes a change
event is invoked on that model which you can subscribe to like here and example below.
window.ListView = Backbone.View.extend({
initialize: function () {
//pass model:your_model when creating instance
this.model.on("change:name", this.updateName, this);
this.model.on("change:age", this.changedAge, this);
},
render: function () {
var model = this.model;
$(this.el).empty().html(_.template(this.template, this.model.toJSON()))
return this;
},
updateName: function () {
alert("Models name has been changed");
},
changedAge: function () {
alert("Models age has been changed");
}
});
Upvotes: 6