Quick summary of my problem: first rendering of the view contains the elements in the collection, and the second rendering doesn't. Read on for details...
Also of note: I do realize that the following code represents the happy path and that there is a lot of potential error handling code missing, etc, etc...that is intentional for the purpose of brevity.
I'm pretty new with backbone.js and am having some trouble figuring out the right way to implement a solution for the following scenario:
I have a page shelled out with two main regions to contain/display rendered content that will be the main layout for the application. In general, it looks something like this:
-------------------------------------------
| | |
| | |
| Content | Actions |
| Panel | Panel |
| | |
| | |
| | |
| | |
| | |
| | |
-------------------------------------------
I have an HTTP API that I'm getting data from, that actually provides the resource information for various modules in the form of JSON results from an OPTIONS call to the base URL for each module. For example, an OPTIONS request to http://my/api/donor-corps returns:
[
{
"Id":0,
"Name":"GetDonorCorps",
"Label":"Get Donor Corps",
"HelpText":"Returns a list of Donor Corps",
"HttpMethod":"GET",
"Url":"https://my/api/donor-corps",
"MimeType":"application/json",
"Resources":null
}
]
The application has a backbone collection that I'm calling DonorCorpsCollection that will be a read-only collection of DonorCorpModel objects that could potentially be rendered by multiple different backbone views and displayed in different ways on different pages of the application (eventually...later...that is not the case at the moment). The url property of the DonorCorpsCollection will need to be the Url property of the object with the "GetDonorCorps" Name property of the results of the initial OPTIONS call to get the API module resources (shown above).
The application has a menubar across the top that has links which, when clicked, will render the various pages of the app. For now, there are only two pages in the app, the "Home" page, and the "Pre-sort" page. In the "Home" view, both panels just have placeholder information in them, indicating that the user should click on a link on the menu bar to choose an action to take. When the user clicks on the "Pre-sort" page link, I just want to display a backbone view that renders the DonorCorpsCollection as an unordered list in the Actions Panel.
I'm currently using the JavaScript module pattern for organizing and encapsulating my application code, and my module currently looks something like this:
var myModule = (function () {
// Models
var DonorCorpModel = Backbone.Model.extend({ });
// Collections
var DonorCorpsCollection = Backbone.Collection.extend({ model : DonorCorpModel });
// Views
var DonorCorpsListView = Backbone.View.extend({
initialize : function () {
_.bindAll(this, 'render');
this.template = _.template($('#pre-sort-actions-template').html());
this.collection.bind('reset', this.render); // the collection is read-only and should never change, is this even necessary??
},
render : function () {
$(this.el).html(this.template({})); // not even exactly sure why, but this doesn't feel quite right
this.collection.each(function (donorCorp) {
var donorCorpBinView = new DonorCorpBinView({
model : donorCorp,
list : this.collection
});
this.$('.donor-corp-bins').append(donorCorpBinView.render().el);
});
return this;
}
});
var DonorCorpListItemView = Backbone.View.extend({
tagName : 'li',
className : 'donor-corp-bin',
initialize : function () {
_.bindAll(this, 'render');
this.template = _.template($('#pre-sort-actions-donor-corp-bin-view-template').html());
this.collection.bind('reset', this.render);
},
render : function () {
var content = this.template(this.model.toJSON());
$(this.el).html(content);
return this;
}
});
// Routing
var App = Backbone.Router.extend({
routes : {
'' : 'home',
'pre-sort', 'preSort'
},
initialize : function () { },
home : function () {
// ...
},
preSort : function () {
// what should this look like??
// I currently have something like...
if (donorCorps.length < 1) {
donorCorps.url = _.find(donorCorpResources, function (dc) { return dc.Name === "GetDonorCorps"; }).Url;
donorCorps.fetch();
}
$('#action-panel').empty().append(donorCorpsList.render().el);
}
})
// Private members
var donorCorpResources;
var donorCorps = new DonorCorpsCollection();
var donorCorpsList = new DonorCorpsListView({ collection : donorCorps });
// Public operations
return {
Init: function () { return init(); }
};
// Private operations
function init () {
getAppResources();
}
function getAppResources () {
$.ajax({
url: apiUrl + '/donor-corps',
type: 'OPTIONS',
contentType: 'application/json; charset=utf-8',
success: function (results) {
donorCorpResources = results;
}
});
}
}(myModule || {}));
Aaannnd finally, this is all using the following HTML:
...
<div class="row-fluid">
<div id="viewer" class="span8">
</div>
<div id="action-panel" class="span4">
</div>
</div>
...
<script id="pre-sort-actions-template" type="text/template">
<h2>Donor Corps</h2>
<ul class="donor-corp-bins"></ul>
</script>
<script id="pre-sort-actions-donor-corp-bin-view-template" type="text/template">
<div class="name"><%= Name %></div>
</script>
<script>
$(function () {
myModule.Init();
});
</script>
...
So far, I've been able to get this to work the first time I click on the "Pre-sort" menu link. When I click it, it renders the list of Donor Corps as expected. But, if I then click on the "Home" link, and then on the "Pre-sort" link again, this time I see the header, but the <ul class="donor-corp-bins"></ul> is empty with no list items in it. ... and I have no idea why or what I need to be doing differently. I feel like I understand backbone.js Views and Routers in general, but in practice, I'm apparently missing something.
In general, this seems like a fairly straight-forward scenario, and I don't feel like I'm trying to do anything exceptional here, but I can't get it working correctly, and can't seem to figure out why. An assist, or at least a nudge in the right direction, would be hugely appreciated.
So, I've figured out a solution here. Not sure if its the best or the right solution, but its one that works at least for now.
There seems to have been a couple of issues. Based on Rob Conery's feedback, I started looking at different options for rendering the collection with my view template, and this is what I came up with:
As I mentioned in the comment, we're using Handlebars for view templating, so this code uses that.
I ended up changing my view template to look like this:
<script id="pre-sort-actions-template" type="text/x-handlebars-template">
<h2>Donor Corps</h2>
<ul class="donor-corp-bins">
{{#each list-items}}
<li class="donor-corp-bin">{{this.Name}}</li>
{{/each}}
</ul>
</script>
Then, I removed DonorCorpListItemView altogether, and changed DonorCorpsListView to now look like this:
var DonorCorpsListView = Backbone.View.extend({
initialize : function () {
_.bindAll(this, 'render');
this.collection.bind('reset', this.render);
},
render : function () {
var data = { 'list-items' : this.collection.toJSON() };
var template = Handlebars.compile($('#pre-sort-actions-template').html());
var content = template(data);
$(this.el).html(content);
return this;
}
});
So, that seems to be working and doing what I need it to do now.
I clearly still have a lot to learn about how JS and Backbone.js work though, because the following DOES NOT WORK (throws an error)...and I have no idea why:
var DonorCorpsListView = Backbone.View.extend({
initialize : function () {
_.bindAll(this, 'render');
this.template = Handlebars.compile($('#pre-sort-actions-template').html());
this.collection.bind('reset', this.render);
},
render : function () {
var data = { 'list-items' : this.collection.toJSON() };
var content = this.template(data);
$(this.el).html(content);
return this;
}
});
Hopefully this will be helpful to someone.
Your rendering code looks odd to me for the DonorCorpListView - it looks like you're templating before you have any data. You need to compile that template with the data to get the HTML output.
So, the rendering logic should be somthing like:
var template = $([get the template).html();
template.compile(this.collection.toJSON());
this.el.append(template.render());
I used some pseudo code here as I'm not sure what your templating mechanism is (I like Handlebars myself).
Related
In spite of reading the marionette docs several times over, I am still not able to fully comprehend some aspects of it correctly.
I am creating a layout view 'AppLayout' as below:
var AppLayoutView = Marionette.LayoutView.extend({
regions: {
headerRegion: "#ecp_header",
bodyRegion: "#ecp_layout_region"
},
...
The html snippet for my app is having the two dom nodes for above defined regions:
<div id="ecp_header"></div>
<div class="container" id="ecp_layout_region">
<div class="row" id="ecp_body">
...
in app.js, my calling code is like this..
ECPApp.on('start', function() {
require(['controller_cp', 'header_view'], function(ControllerCP, HeaderView) {
console.log("On start event executing...");
// create a event aggregator vent object and attach to app.
ECPApp.vent = new Backbone.Wreqr.EventAggregator();
var appLayoutView = new AppLayoutView();
appLayoutView.render();
//appLayoutView.showLayout();
//$('div.toolbar > ul > li:first > a').tab('show');
if (Backbone.history) Backbone.history.start();
});
This gives me error Cannot render the template since it is null or undefined.
I thought that the default render() behavior of layout always looks for a template, so I rolled out my own version of render, as below:
render: function() {
var $self = this;
/* if no session exists, show welcome page */
var promise = ECPApp.request('entities:session');
promise.done(function(data) {
if (data.result==0) {
console.log('Valid session exists. Showing home page...!');
$self.showHome();
} else {
console.log('No session exists. Showing welcome page...!');
$self.showWelcome();
}
}).fail(function(status) {
console.log('No session exists. Showing welcome page...!');
$self.showWelcome();
});
return $self;
},
showWelcome: function() {
var self = this;
require(['header_view', 'welcome_view'],
function(HeaderView, WelcomeView) {
var headerView = new HeaderView();
var welcomeView = new WelcomeView();
self.bodyRegion.show(welcomeView);
});
}
This time, I get another error saying, An "el" #ecp_layout_region must exist in DOM. However I am sure that the element is existing in the DOM, as I can see it by checking in the debug console window. Running $('#ecp_layout_region') shows a valid element.
Marionette layout view is pretty confusing. Going forward I need multiple nested views. I am stuck here.
How is your template located? Is your template wrapped by <script type = “text/template”> tag?
It may look like this:
Inside your html, in head section:
<script type = “text/template” id="yourLayout">
<div id="ecp_header"></div>
<div class="container" id="ecp_layout_region">...</div>
</script>
And in Layout definition:
var AppLayoutView = Marionette.LayoutView.extend({
template: '#yourLayout'
...
});
I am new bee to backbone and i am trying with a below sample
http://jsfiddle.net/naveencgr/L3orucjm/
While loading i am getting this.el.html is not a function, let me know what is the cause of it.
HTML:
<div id="container"></div>
<script type="text/template" id="template">
<input type="text" name="Name" id="Name"></input>
<input type="button" name="Add" id="Add" value="Add"></input>
<div id="list"></div>
</script>
JavaScript:
NameModel = Backbone.Model.extend({
});
var nameModel = new NameModel();
nameModel.set('name','test');
alert(nameModel.get('name'));
NameView = Backbone.View.extend({
tagName:"li",
render: function(){
var template=_.template("<%=name%>", nameModel.toJSON());
return template;
}
});
var nameView = new NameView();
NameViewList = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
var template = _.template($("#template").html(), {});
this.el.html(template);
},
events : {
"click input#Add" :"addName",
},
addName : function() {
var name = $("#Name").val();
if (name.trim().length > 0) {
//nameModel.set("name", name);
$("#list").append(nameView.render());
}
}
});
var nameViewList = new NameViewList({el : $("div#container")});
You have numerous errors in your code. You know about one of them but not the others.
The one you know about is:
this.el.html(template);
A view's el is just a DOM node and that doesn't have an html function. The html function you're trying to use is part of jQuery so you'd want to call it on this.$el (which is just a cached version of $(this.el)):
this.$el.html(template);
Other problems:
Your fiddle is missing vars all over the place; don't say:
NameModel = ...
say:
var NameModel
to avoid accidental globals.
Your NameView is strange. It has tagName: 'li' so presumably it should be creating list elements but the render doesn't do anything with the view's el, it just returns a string of HTML that ends up inside a <div>. That <div> should be a <ul>:
<ul id="list"></ul>
A render function generally populates the view's el and, to allow chaining, returns this:
render: function() {
var template = _.template('<%= name %>');
this.$el.html(template(nameModel.toJSON()));
return this;
}
You are using Underscore's _.template incorrectly. You used to be able to say:
var h = _.template(template_source, data);
to compile the template and fill it in in one step but as of Underscore 1.7, the second argument to _.template is an options object. Now you need to compile and fill in the template in separate steps:
var t = _.template(template_source);
var h = t(data);
You'll see this change in the render above.
The way you're using your NameView is strange. Apparently you are trying to use one NameView to handle multiple names, this would work with your strange NameView#render implementation but it will fall apart once NameView has anything to do or once NameView is updated (as above) to be more conventional. You should create one NameView for each name you're displaying and each NameView should have its own NameModel as its model property. This would make NameView#render look like:
render: function() {
var template = _.template('<%= name %>');
this.$el.html(template(this.model.toJSON()));
return this;
}
and NameViewList#addName would look like:
addName: function() {
var name = this.$("#Name").val();
if(name.trim().length > 0) {
var nameView = new NameView({
model: new NameModel({ name: name })
});
this.$('#list').append(nameView.render().el);
}
}
You'll note that we're using NameView#render's new return value, this x.append(v.render().el) pattern is quite common and idiomatic in Backbone so it is a good practice. You should also notice that the search for #list is now limited to the view's el by using the view's this.$ function; this.$('#list') is equivalent to this.$el.find('#list') and doing things this way helps you keep your views self-contained.
In real life you'd probably put your new NameModel({ name: name }) instances in a collection somewhere and events on that collection would trigger the creation of new NameViews.
Applying all that to your fiddle gives you this functional and more idiomatic version:
http://jsfiddle.net/ambiguous/8x0ma9qo/
Background:
I am making changes to an application that uses backbone.js with Handlebars as the templating engine. After a change event fires I need to create html that is appended to the current DOM structure which is basically just a spit-out of information that is contained in the model. This change needed to fit in the already established application structure.
Issue:
I have created a new view that uses a Handlebars template and the model to create the html. I then instantiate that view and call the render function and append the output using JQuery. What I am noticing is that when the html is rendered the model that is passed in because attributes on the $el instead of filling in the template (like I think it should).
View I'm altering:
$.hart.TestView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile($('#templateOne').html()),
initialize: function () {
this.model.on('change', function () {
this.createMoreInfoHtml();
}, this);
},
selectSomething: function () {
this.$el.removeClass('policies');
this.createMoreInfoHtml(); //function created for new view stuff
},
createMoreInfoHtml: function () {
var id = this.$el.attr('data-id', this.model.get("ID"));
$('.info').each(function () {
if ($(this).parent().attr('data-id') == id
$(this).remove();
});
var view = new $.hart.NewView(this.model, Handlebars.compile($("#NewTemplate").html()));
$('h1', this.$el).after(view.render().el);
},
render: function () {
... //render logic
}
});
View I Created:
$.hart.NewView = Backbone.View.extend({
initialize: function (model, template) {
this.model = model;
this.template = template;
},
render: function () {
this.$el.html(this.template({ info: this.model }));
this.$el.addClass('.info');
return this;
}
});
Json the is the model:
{
"PetName":"Asdfasdf",
"DateOfBirth":"3/11/2011 12:00:00 AM",
"IsSpayNeutered":false,
"Sex":"F",
"SpeciesID":2,
"ID":"ac8a42d2-7fa7-e211-8ef8-000c2964b571"
}
The template
<script id="NewTemplate" type="text/html">
<span>Pet Name: </span>
<span>{{this.PetName}}</span>
</script>
So now to the question: What am I doing wrong? Why are the properties of the model being created as attributes on the $el instead of filling in the template? Can someone please direct me as to how to get the results I am looking for?
Let's skip the problem Jack noticed.
The way you're creating your view is just wrong. It may work as you get the expected arguments in the initialize function, but it has unexpected behaviors you don't see. See the View's constructor:
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
Now let's have a look at this _configure method:
_configure: function(options) {
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
_.extend(this, _.pick(options, viewOptions));
And of course...
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
Ok here we are... Basically when passing the model as the options argument, you're passing an object with an attributes key (the attributes of your model). But this attributes key is also used in the View to bind attributes to its element! Therefore the behavior your noticed.
Now, other wrong thing. You're compiling your template each time you create a new function, but not using it as a singleton either. Put your template in the view:
$.hart.NewView = Backbone.View.extend({
template: Handlebars.compile($("#NewTemplate").html(),
And change your view's creation to make the whole thing work:
new $.hart.NewView({model: this.model});
Oh, and get rid of this useless initialize method. You're just doing things Backbone already does.
I am working on my first RequireJS/Backbone app and I've hit a wall. There's a lot of code smell here, and I know I'm just missing on the pattern.
I have a route that shows all promotions, and one that shows a specific promotion (by Id):
showPromotions: function () {
var promotionsView = new PromotionsView();
},
editPromotion: function (promotionId) {
vent.trigger('promotion:show', promotionId);
}
In my promotions view initializer, I new up my PromotionsCollection & fetch. I also subscribe to the reset event on the collection. This calls addAll which ultimately builds a ul of all Promotions & appends it to a container div in the DOM.
define([
'jquery',
'underscore',
'backbone',
'app/vent',
'models/promotion/PromotionModel',
'views/promotions/Promotion',
'collections/promotions/PromotionsCollection',
'text!templates/promotions/promotionsListTemplate.html',
'views/promotions/Edit'
], function ($, _, Backbone, vent, PromotionModel, PromotionView, PromotionsCollection, promotionsListTemplate, PromotionEditView) {
var Promotions = Backbone.View.extend({
//el: ".main",
tagName: 'ul',
initialize: function () {
this.collection = new PromotionsCollection();
this.collection.on('reset', this.addAll, this);
this.collection.fetch();
},
render: function () {
$("#page").html(promotionsListTemplate);
return this;
},
addAll: function () {
//$("#page").html(promotionsListTemplate);
this.$el.empty().append('<li class="hide hero-unit NoCampaignsFound"><p>No campaigns found</p></li>');
this.collection.each(this.addOne, this);
this.render();
$("div.promotionsList").append(this.$el);
},
addOne: function (promotion) {
var promotionView = new PromotionView({ model: promotion });
this.$el.append(promotionView.render().el);
}
});
return Promotions;
});
Each promotion in the list has an edit button with a href of #promotion/edit/{id}. If I navigate first to the list page, and click edit, it works just fine. However, I cannot navigate straight to the edit page. I understand this is because I'm populating my collection in the initialize method on my View. I could have a "if collection.length == 0, fetch" type of call, but I prefer a design that doesn't have to perform this kind of check. My questions:
How do I make sure my collection is populated regardless of which route I took?
I'm calling render inside of my addAll method to pull in my template. I could certainly move that code in to addAll, but overall this code smells too. Should I have a "parent view" that's responsible for rendering the template itself, and instantiates my list/edit views as needed?
Thanks!
Here's one take. Just remember that there is more than one way to do this. In fact, this may not be the best one, but I do this myself, so maybe someone else can help us both!
First off though, you have a lot of imports in this js file. It's much easier to manage over time as you add/remove things if you import them like this:
define(function( require ){
// requirejs - too many includes to pass in the array
var $ = require('jquery'),
_ = require('underscore'),
Backbone = require('backbone'),
Ns = require('namespace'),
Auth = require('views/auth/Auth'),
SideNav = require('views/sidenav/SideNav'),
CustomerModel = require('models/customer/customer');
// blah blah blah...});
That's just a style suggestion though, your call. As for the collection business, something like this:
Forms.CustomerEdit = Backbone.View.extend({
template: _.template( CustomerEditTemplate ),
initialize: function( config ){
var view = this;
view.model.on('change',view.render,view);
},
deferredRender: function ( ) {
var view = this;
// needsRefresh decides if this model needs to be fetched.
// implement on the model itself when you extend from the backbone
// base model.
if ( view.model.needsRefresh() ) {
view.model.fetch();
} else {
view.render();
}
},
render:function () {
var view = this;
view.$el.html( view.template({rows:view.model.toJSON()}) );
return this;
}
});
CustomerEdit = Backbone.View.extend({
tagName: "div",
attributes: {"id":"customerEdit",
"data-role":"page"},
template: _.template( CustomerEditTemplate, {} ),
initialize: function( config ){
var view = this;
// config._id is passed in from the router, as you have done, aka promotionId
view._id = config._id;
// build basic dom structure
view.$el.append( view.template );
view._id = config._id;
// Customer.Foo.Bar would be an initialized collection that this view has
// access to. In this case, it might be a global or even a "private"
// object that is available in a closure
view.model = ( Customer.Foo.Bar ) ? Customer.Foo.Bar.get(view._id) : new CustomerModel({_id:view._id});
view.subViews = {sidenav:new Views.SideNav({parent:view}),
auth:new Views.Auth(),
editCustomer: new Forms.CustomerEdit({parent:view,
el:view.$('#editCustomer'),
model:view.model})
};
},
render:function () {
var view = this;
// render stuff as usual
view.$('div[data-role="sidetray"]').html( view.subViews.sidenav.render().el );
view.$('#security').html( view.subViews.auth.render().el );
// magic here. this subview will return quickly or fetch and return later
// either way, since you passed it an 'el' during init, it will update the dom
// independent of this (parent) view render call.
view.subViews.editCustomer.deferredRender();
return this;
}
Again, this is just one way and might be terribly wrong, but it's how I do it and it seems to work great. I usually put a "loading" message in the dom where the subview eventually renders with replacement html.
My question is around the best way to sub view a rendered view with Backbone.
Whilst there are a lot of blogs around the topic, I haven't been able to find any practical ideas that I can apply to this use case.
Backstory
On page load, backbone fetches and renders the remote model and remote page template.
The page template is made up of 3 fieldsets, each containing lots of readonly data and images, with an [edit] button.
+-----------------------+ +-----------------------+
| ID: ViewA | | ID: ViewB |
| | | |
| | | |
| EDIT | | EDIT |
+-----------------------+ +-----------------------+
+-----------------------+
| ID: ViewC |
| |
| |
| EDIT |
+-----------------------+
When [edit] is clicked by the user, I'd like a sub view created, the partial (underscore template) fetched, the existing model applied to it, and finally, it to replace the fieldset's innerHTML.
This will make the previously readonly content in the fieldset, editable and saveable by the user. Once saved (or cancelled) by the user, it should push back to the server, and re-render the readonly view.
For argument sake, let's say templates are hosted at /templates/edit/<fieldsetid>.html
Here is the existing code:
Model and Collection
// Model
window.Page = Backbone.Model.extend();
// Collection
window.PageCollection = Backbone.Collection.extend({
model: Page,
url: '/page/view.json'
});
The View
window.PageView = Backbone.View.extend({
initialize:function () {
this.model.bind("reset", this.render, this);
},
events:{
'click fieldset a[role=edit-fieldset]' : 'edit'
},
edit: function(e){
e.preventDefault();
// #TODO Do some subview awesomeness here?
},
render:function (eventName) {
_.each(this.model.models, function (data) {
$(this.el).append(this.template({data: data.attributes}));
}, this);
return this;
}
});
Router
var AppRouter = Backbone.Router.extend({
routes:{
"page/view":"pageView"
},
pageView:function (action) {
this.page = new PageCollection();
this.pageView = new PageView({model:this.page});
this.page.fetch();
$('#content').html(this.pageView.render().el);
}
});
Template Loader
// Borrowed from:
// http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/
// Should probably move to RequireJS and AMDs
utils.loadTemplate(['PageView'], function() {
var app = new AppRouter();
Backbone.history.start();
});
So with that in mind, what are your thoughts? Am I on the right track? Is there another pattern I should look at?
Thanks in advance.
The way I usually approach these situations is as follows (using the existing 'loadTemplate', Page and PageCollection architecture above):
// Model
window.Page = Backbone.Model.extend();
// Collection
window.PageCollection = Backbone.Collection.extend({
model: Page,
url: '/page/view.json'
});
window.PageView = Backbone.View.extend({
template: templates.pageViewTemplate,
render: function() {
var html = this.map(function(model) {
// Important! We don't need to store a reference to the FieldSetViews.
// Simply render them and by passing in the model, we can update the view
// as needed when the model changes.
//
// This is also good to reduce the potential for memory-leaks.
return new FieldSetView({
model: model
}).render();
});
// Do one DOM update.
this.$el.html(html);
return this.$el;
}
});
window.FieldSetView = Backbone.View.extend({
events: {
"click .edit": "_onEdit"
},
template: templates.fieldSetTemplate,
render: function() {
// Replace the top-level div created by backbone with that of your
// template (i.e fieldset). This gets around having to use
// the tagName property which should really be in your template.
this.setElement(this.template.render(this.model.toJSON()));
return this.$el;
},
_onEdit: function(e) {
// Handle edit mode here by passing the model to another view
// or by enhancing this view to handle both edit and view states.
}
});