Backbone repeated templates with different types - backbone.js

I am trying to figure out the best way to structure the following using Backbone views and templates. We have a collection of "messages", but the messages may be of different types, each with it's own view. So, the underlying collection might look like:
{
{ id: 1, name="message one", type="customer-message" },
{ id: 2, name="message two", type="support-message" },
{ id: 3, name="attachment one", type="attachment" }
}
and the resulting page output would be something like:
<ul>
<li class="message customer-message"></li>
<li class="message support-message"></li>
<li class="message attachment"></li>
</ul>
such that each different li class would have entirely different structure/content.
What I'm trying to figure out is how to set up the templates and Views to a) handle nesting and b) handle the fact that the inner template differs depending on the type.
<script type="text/template" id="chat-template">
<ul>
<!--how to reference other templates here?-->
</ul>
</script>
<script type="text/template" id="customer-message-template">
</script>
<script type="text/template" id="support-message-template">
</script>
<script type="text/template" id="attachment-template">
</script>
Not looking for a full solution here - just hoping there's an example someone can point me to.
Thanks!

Take a look at Backbone.Marionette which provides View "subclasses" specifically for handling collection scenarios like this. http://marionettejs.com/

Here's what I ended up doing - essentially using the convention over configuration approach (hat-tip to Robert Levy for Marionette - not sure this couldn't also work with straight backbone, but I think Marionette's CollectionView does make this easier):
MyApp.MessageView = Backbone.Marionette.ItemView.extend({
tagName: 'li',
getTemplate: function() {
var type = this.model.get("type");
return "#" + type + "-template";
}
});
MyApp.MessageCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: MyApp.MessageView,
tagName: 'ul'
});

Related

Add a wrapper around a fieldGroup

I have create the following jsbin
Notice how the fieldGroup does not display because of the wrapper setting. If you remove/comment out the wrapper then the fields properly display.
Ultimately, I am pushing objects into this fieldGroup from a service call. I want each of these items within the group to be a <li> to the overall <ul>. So I wrapped each individual field with the <li> wrapper and I was planning on wrapping the entire fieldGroup with the <ul> wrapper. But Formly doesn't seem to like this approach.
What am I doing wrong? Or is there a better way of making this list?
Unfortunately fieldGroup doesn't support wrappers, and adding them would mean adding complexity to angular-formly.
Fortunately there's a simple way to accomplish the same results using custom templates:
app.config(function (formlyConfigProvider) {
// set templates here
formlyConfigProvider.setType({
name: 'nested',
template: '<formly-form model="model[options.key]" fields="options.data.fields"></formly-form>'
});
formlyConfigProvider.setWrapper({
name: 'panel',
types: ['nested'],
templateUrl: 'panel.html'
});
});
See full example: http://angular-formly.com/#/example/other/nested-formly-forms
I could be mistaken, but are you trying to dynamically create a list based on data that is pulled in from a service?
If you are...
Create a custom formly type, that has a template with an ng-repeat on the li, within the ul. You can then put a controller on the type and pass in your service to iterate on.
Here is an example of what I am thinking:
formlyConfig.setType({
name: 'myList',
templateUrl: 'myList.view.html',
controller: ['ListService', function(ListService) {
var vm = this;
ListService.getList().then(function(list) {
vm.list = list.data
vm.specific = list.data.specificItems
}
}]
});
<div type="text/ng-template", id="myList.view.html">
<ul>
<li ng-repeat="item for item in vm.list">{{item.name}}</li>
</ul>
<ul>
<li ng-repeat="item for item in vm.specific">{{item.name}}</li>
</ul>`
</div>`

Parse Backbone.js Handlebars [Object object]

Rather new to handlbars.js but my #each is functional until I try to access a nested attribute. Specifically author.name
<!-- Handlebars.js Blog Template -->
<script id="posts-tpl" type="text/x-handlebars-template">
{{#each post}}
<div class="blog-post">
<h2 class="blog-post-title">{{title}}</h2>
<p class="blog-post-meta">{{author.name}}</p>
<div>{{{body}}}</div>
</div>
{{/each}}
</script>
Chrome Javascript console shows author as having attribute 'name' with the correct data.
The Backbone.js view appears to be working also:
var PostsView = Parse.View.extend({
template: Handlebars.compile($('#posts-tpl').html()),
render: function(){
var collection = { post: this.collection.toJSON() };
this.$el.html(this.template(collection));
}
I have a full example at http://picze.co/blog
If you take a look at what you're passing into the template, you'll see the issue.
In your render function, try inspecting what this.collection.toJSON(); actually is.
Basically, it looks something like this:
[
{
author: {
__type: "pointer",
className: "_User",
objectId: "JCAjG1AIN0"
},
title: "WTFBBQ",
body: "<p><h3>test</h3> egg on face. This cannot be good. </p>"
}
]
Looks like the author is missing the name attribute
EDIT
Seems like the name attribute exists in the model but it's nested as another model so when you call .toJSON() you won't get it.
One way would be to include the author model as well:
var collection = { post: this.collection.toJSON() };
for (var i=0; i<collection.post.length; i++) {
if (collection.post[i].author) {
collection.post[i].author = this.collection.at(i).get('author').toJSON();
}
}
I'm not familiar with parse.js so this may not be the best solution, but it should work

Using External Views with Backbone.js

So I have seen other posts pertaining to this question but regardless of my efforts to incorporate their suggestions, I don't seem to be able to solve the issue. So here is the code:
What am I doing wrong here and what is the best way to get the application started?
Entire Application:
var Person = Backbone.Model.extend({
defaults : {
name : ""
}
});
var PersonView = Backbone.View.extend({
el : $('body'),
tagName: 'li',
initialize : function() {
this.render();
},
render : function(){
var template = _.template( $("#person").html(), {} );
this.$el.html(template);
return this;
}
});
HTML w/ Template:
<script type="text/template" id="person">
<h1><%= name %></h1>
</script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script type="text/javascript" src="js/underscore.js"></script>
<script type="text/javascript" src="js/backbone.js"></script>
<script type="text/javascript" src="app.js"></script>
At first glance, it looks like you're defining the DOM location incorrectly.
el : $('body'),
tagName: 'li',
el should be the jQuery selector string rather than the object
el : 'body'
but then, this.$el will be $('body') effectively, so this.$el.html(template) will try to put data into the <body> tag.
It looks like, from the tagName: 'li' that you want to have multiple PersonViews, so I suggest not defining the el property at all, then appending this.$el once the view has been rendered to whatever parent element you need it in.
On further review, "entire application" suggests that you're not initialising anything.
The Javascript you have there is defining a Person class and a PersonView class. Presumably, you will want to instantiate one or more objects of the Person class and then also a PersonView for each Person, this is achieved by using new, as mu is too short mentions.
Effectively, the code you have is the setup, now you need to start it.
var joe = new Person;
var joeView = new PersonView({ person: joe });
That snippet assumes that you'll be expecting { person: joe } in PersonView, which you're not, but the errors thrown should point you in a better direction.

backbone read/edit views based on user group

I have created a simple backbone app, which works well.
Now I need to solve a problem, that I'm having trouble with. So I seek advice.
I need to represent my models/collections read-only or editable,on a per user/group basis.
My initial thought was, create two templates (read and edit) and their respective views. (psuedo-code):
var appRouter = Backbone.Router.extend({
routes: {
'' : 'schedule',
}
schedule: function() {
this.collection = new Collection();
this.collection.fetch({success:function(resp) {
if (resp.group == 'allowedToEdit')
myview = new editView(this.collection);
else
myview = new readView(this.collection);
}});
});
This approach winds up with me having to duplicate templates:
<script type="text/template" id="edit-template">
<div class="myclass">
<input class="fn" type="text" value="<%= (fn) != '' ? fn : 'default' %>">
</div>
</script>
<script type="text/template" id="static-template">
<div class="myclass">
<div class="fn"><%= fn %></div>
</div>
</script>
Would it be better to in-line javascript for selecting an input or div tag instead or perhaps there is a better solution I'm not thinking of?
This is what I ended up doing:
I created a user model that contains user preferences and boolean values (returned from the database based on user permissions) if a user is allowed to edit or not.
The resulting code is:
var appRouter = Backbone.Router.extend({
routes: {
'' : 'schedule',
}
schedule: function() {
this.collection = new Collection();
this.collection.fetch({
success:function(resp) {
myview = user.schedule() ? new editView(this.collection)
: new readView(this.collection);
myview.render();
}});
});
The function user.schedule() just returns the boolean value that is associated with the requested route.
The permissions on the back-end are restricted by group - so if the user changes these boolean values manually
to access the editable page - they still don't have authorization on the back-end to manipulate data.
Also, in my case, the editable/static views are quite different so I created two separate templates.

Backbone, not "this.el" wrapping

I do an extensive use of templates, and I like to use full contained templates. I mean that I want to see in the template code all the DOM elements including the root one, like this:
<script type="text/template" id="template-card">
<div class="card box" id="card-<%= id %>">
<h2><%= title %></h2>
<div><%= name %></div>
</div>
</script>
But what Backbone likes is having a template like this:
<script type="text/template" id="template-card">
<h2><%= title %></h2>
<div><%= name %></div>
</script>
And defining the root element and its attributes in the JS code. What I think is ugly and confusing.
So, any good way to avoiding my Backbone View to wrapper my template with an extra DOM element?
I have been checking this issue thread: https://github.com/documentcloud/backbone/issues/546 and I understand there is not any official way to do it.. but maybe you can recommend me a non official way.
You can take advantage of view.setElement to render a complete template and use it as the view element.
setElement view.setElement(element)
If you'd like to apply a Backbone view to a different DOM element, use setElement, which will
also create the cached $el reference and move the view's delegated
events from the old element to the new one
Two points you have to account for:
setElement calls undelegateEvents, taking care of the view events, but be careful to remove all other events you might have set yourself.
setElement doesn't inject the element into the DOM, you have to handle that yourself.
That said, your view could look like this
var FullTemplateView = Backbone.View.extend({
render: function () {
var html, $oldel = this.$el, $newel;
html = /**however you build your html : by a template, hardcoded, ... **/;
$newel = $(html);
// rebind and replace the element in the view
this.setElement($newel);
// reinject the element in the DOM
$oldel.replaceWith($newel);
return this;
}
});
And a working example to play with http://jsfiddle.net/gNBLV/7/
Now you can also define a view's tagName as a function and create a class like this:
var MyView = Backbone.View.extend({
template: '#my-template',
tagName: function() {
// inspect the template to retrieve the tag name
},
render: function() {
// render the template and append its contents to the current element
}
});
Here's a working example
Backbone.Decarative.Views provides you with an alternative way to do this, without having to rely on setElement. For more, check out my answer here.

Resources