I have a div container element and inside this container element I have multiple templates. I want to selectively load templates based on the json returned from backend. It's a trivial problem but facing issues doing this. Some logical code.
<div id = "container">
<div class = "row page">
<script type = "text/template" id = "template1">
<div id = "template1_id">
</div>
</script>
<script type = "text/template" id = "template2">
<div id = "template2_id">
</div>
</script>
</div>
</div>
In my Backbone View I'm doing something like:
var someView = Backbone.view.extend({
el: '.page'
render: function() {
el: '.page';
var template1 = _.template($('#template1').html());
this.$el.html(template1);
//get Json from backend and render the template within the fetch method
var collection1 = new someCollection();
someCollection.url = "blah";
someCollection.fetch ( function() {
success: function() {
var template2 = _.template($('#template2').html());
$('#template1_id').html(template2);
}
});
},
});
Only the first template is rendered and not the second one. Am I doing something fundamentally wrong here ?
If you are rendering both of the templates into one container, you should be using $.append, rather than $.html, so both will be appended consequently.
you have a typo(mismatched quote)
$('#template1_id").html(template2);
should be
$("#template1_id").html(template2);
Related
I'm trying to add new DOM element dynamically which has click event handler - already defined function. Here's the code:
HTML
<div ng-app="miniapp" ng-controller="Ctrl">
<div id="div" ng-click="addingEvent()">DIV </div>
</div>
JavaScript
var $scope;
var app = angular.module('miniapp', []);
function Ctrl($scope) {
$scope.addingEvent = function(){
var myEl = angular.element( document.querySelector( '#div' ) );
myEl.append('<a ng-click="afterEvent()" href="#">anchor addedd </br> </a>');
};
$scope.afterEvent = function(){
alert('after event');
};
};
Here's the demo. As you see, while the anchor is added, the click event is not handled correctly. How to fix it?
It's not enough to just append an element into DOM: you'll have to let Angular know about it. One possible approach (listing only the controller here for brevity):
function Ctrl($scope, $compile) {
var anchorTemplate = '<a ng-click="afterEvent()" href="#">anchor addedd </br> </a>';
$scope.addingEvent = function(){
var myEl = angular.element( document.querySelector( '#div' ) );
var $anchor = angular.element(anchorTemplate);
$compile($anchor)($scope);
myEl.after($anchor);
};
}
Here's the demo to play with, with some good practices applied as well. Yet, as a matter of fact, I'd rather have this functionality implemented as a directive (component in terms of Angular 1.5).
So, I have modified your code using ng-if statement. There is no need to remove or add elements in DOM using old JS ways.
HTML
<div ng-app="miniapp" ng-controller="Ctrl">
<div id="div" ng-click="addingEvent()">DIV <span><a ng-click="afterEvent()" href="#" ng-if="showAnchor">anchor addedd </a></span></div>
</div>
SCRIPT
var $scope;
var app = angular.module('miniapp', []);
function Ctrl($scope) {
$scope.showAnchor=false;
$scope.addingEvent = function(){
$scope.showAnchor=true;
};
$scope.afterEvent = function(){
alert('after event');
};
};
Solved Jsfiddle demo updated
myEl.append($compile('<a ng-click="afterEvent()" href="#">anchor addedd </br> </a>')($scope));
This will work
I've been trying to no end to try and create a custom filter template for Kendo grid, when the grid is in filterMode: "row". I can get the HTML to appear, but the angular directive is never compiled.
This is the filter configuration for the column:
filterable: {
cell: {
template: function getTemplate(args) {
var container = $("<span>").appendTo(args.element.parent());
var buttonTemplate = kendoGridService.compileTemplate({
template: "modules/common/templates/kendo/columns/days-of-week-edit.tpl.html",
data: options
});
container.append(buttonTemplate);
},
showOperators: false
}
}
Here is the compile template function mentioned above:
compileTemplate: function compileTemplate(options) {
var template = $templateCache.get(options.template);
var templateFn = kendo.template(template);
var result = templateFn(options.data);
return result;
}
Here is what I see in the HTML:
<span class="k-operator-hidden">
<input data-bind="value: value">
<span>
<days-of-week-edit data-item="dataItem"></days-of-week-edit>
</span>
</span>
And finally, this is what I see in the UI. Not that the default input field is there, but nothing else is rendered.
Seems like you also have to compile your template with Angular. Try using $compile service:
var template = $templateCache.get(options.template);
var templateFn = kendo.template(template);
var result = templateFn(options.data);
$compile(result)($scope);
return result;
Just make sure you have a valid $scope object with the dataItem property inside
this is below is code which i am using for simple task add , view is not getting render, i am not able to find the mistake ,
<!doctype html>
<html lang="en">
<head>
<title>Calculator</title>
<link rel="stylesheet" href="styles/bootstrap/css/bootstrap.min.css">
<script src="js/libs/jquery-1.9.1.min.js"></script>
<script src="js/libs/underscore-min.js"></script>
<script src="js/libs/backbone-min.js"></script>
<script type="text/template" id="display-template">
<div class="row">
<div class="span4">
<%=content%>
</div>
</div>
</script>
<script language="javascript">
var cdate;
var tasks={};
var app = app || {};
// App view responsible for rendering app
app.TaskView = Backbone.View.extend({
el: $('#tasks'),
template: _.template($('#display-template').html()),
initialize: function () {
this.render();
},
render: function () {
console.log("render called");
console.log(this.template());
this.$el.html(this.template());
}
});
app.task = Backbone.Model.extend({
defaults:{
content:null
}
});
app.bUsers = Backbone.Collection.extend({
model : app.task,
initialize: function(models, args) {
this.bind('add', this.renderone);
this.bind('remove', this.destroy); },
renderone:function(user){
console.log(user);
var view = new app.TaskView({model: user});
},
destroy:function(user){
$(user.view.el).remove();
}
});
app.Users = new app.bUsers();
$(document).ready(function() {
cdate=new Date();
$("#cdate").html(new Date());
$("#pre").click(function(){
cdate=new Date(cdate.getTime()-(1*24*3600*1000));
$("#cdate").html(cdate);
});
$("#next").click(function(){
cdate=new Date(cdate.getTime()+(1*24*3600*1000));
$("#cdate").html(cdate);
});
$("#submit").click(function(){
if(tasks[cdate]==undefined) tasks[cdate]=[];
tasks[cdate].push($("#task").val());
// app.appView = new app.TaskView({
// model: new app.task({"content":$("#task").val()})
// });
var data ={"content":$("#task").val()};
app.Users.add(data);
});
});
</script>
</head>
<body>
<a id="pre" href="#">Prev</a>
<div id="cdate"></div>
<a id="next" href="#">Next</a>
<input type="text" id="task" ></input>
<input type="button" value="submit" id="submit" ></input>
<div id="tasks"></div>
</body>
Oye, you've got a few problems.
To answer your specific question, your render method of your view should take your view's model instance and get something from this.model.toJSON() it to get at its data to pass to the template method (toJSON really returns "JSONable" objects).
But that's not all.
Besides a few html issues, you also have stylistic problems.
Collections generally should not be concerned with views, only data (*). Views should be concerned with with collections and models. Collections communicate to views via event binding, which I see you are doing. However, for reuse purposes, you may have more than one combination views that might want to listen to events in the collection. By setting up the event binding in the collection, you've effectively limited your collection for only one use.
Views can do alot. There's not much reason to manually add DOM event handlers when you can code the view to do it for you.
I haven't written Backbone in a little while (not by choice!), but generally found it was a good idea to have a view dedicated to the collection, and then have a separate model view that the collection view might create or destroy based upon whatever events took place.
Here's a bit of a cleanup of your code to give you a starting example:
http://jsfiddle.net/jfcox/SmPNv/
HTML:
<a id="pre" href="#">Prev</a>
<div id="cdate"> </div>
<a id="next" href="#">Next</a>
<input type="text" id="task" />
<input type="button" value="add" id="submit" />
<div id="tasks"></div>
Backbone definitions:
var defs = defs || {};
//first define the data handlers
defs.Task = Backbone.Model.extend({
defaults: function () {
return {
content: null,
addDate: (new Date()).toString()
};
}
});
defs.Users = Backbone.Collection.extend({
model: defs.Task
});
// App view responsible for rendering app
defs.SingleTaskView = Backbone.View.extend({
//since we can't control where the js is loaded, go ahead and make the template inline for jsFiddle demo.
tagName: 'div',
template: _.template('<div class="row"> <div class="span4"><%=content%></div> <em><%=addDate%></em> <button class="remove"> remove</remove> </div>'),
events: {
"click button.remove": "remove"
},
initialize: function (opts) {
this.model.on('change', this.render, this);
},
render: function () {
console.log("render called");
var modelBare = this.model.toJSON();
return this.$el.html(this.template(modelBare));
},
remove: function () {
//removes from local collection, does not delete on server
//for that, you'd want `this.model.destroy`
this.collection.remove(this.model);
//removes this view's element.
this.$el.remove();
}
})
defs.TasksView = Backbone.View.extend({
el: 'body',
events: {
"click #pre": "doPrevious",
"click #next ": "doNext",
"click #submit ": "doSubmit"
},
cdate: null,
initialize: function (opts) {
this.cdate = new Date();
this.render();
this.collection.on('add', this.renderone, this);
},
render: function () {
$("#cdate").html(this.cdate.toString());
},
doPrevious: function () {
this.cdate = new Date(this.cdate.getTime() - (1 * 24 * 3600 * 1000));
$("#cdate").html(this.cdate.toString());
},
doNext: function () {
this.cdate = new Date(this.cdate.getTime() + (1 * 24 * 3600 * 1000));
$("#cdate").html(this.cdate.toString());
},
doSubmit: function () {
var data = {
"content": $("#task").val()
};
this.collection.add([data]);
},
renderone: function (userModel) {
var view = new defs.SingleTaskView({
model: userModel,
collection: this.collection
});
this.$el.find('#tasks').append(view.render());
}
});
The application, itself.
var app = app || {};
app.users = new defs.Users();
(function ($) {
$(document).ready(function () {
app.usersview = new defs.TasksView({
collection: app.users
});
});
})(jQuery);
(*) This is a guideline, not an absolute rule, of course. If you think a collection might work as some sort of workflow manager, etc, that might be fine, but that's an advanced topic.
Edit: I included the template inline, partially for reasons that I don't trust jsFiddle with inline "text" scripts. I'm not recommending any way to handle that, just that's how I did it here.
I am having problems understanding how to render a collection in a view using a template. Here is my code:
<div id="mydiv"></div>
<script type="text/template" id="details">
<ul>
<% _.each(?, function(person) { %>
<li><%= person.name %></li>
<% }); %>
</ul>
</script>
<script>
var m = Backbone.Model.extend();
var c = Backbone.Collection.extend({
url: 'retrieve.php',
model: m
});
var v = Backbone.View.extend({
el : $('#mydiv'),
template : _.template($("#details").html()),
initialize : function() {
var coll = new c();
coll.fetch({success: function(){alert(JSON.stringify(coll));} });
this.render();
},
render : function() {
//what do I put here?
return this;
}
});
var view = new v();
I am confused about how to get the data returned from my php file into the template. What code do I need in the view and ._each? My php code is returning:
[{"id":"1","name":"John","age":"5"},{"id":"2","name":"Jane","age":"2"}]
and I see this in the alert().
You should call your render function from the success handler as a collection.fetch returns result asynchronously (you can also bind render function to a collection reset event).
var v = Backbone.View.extend({
el : '#mydiv',
template : _.template($("#details").html()),
initialize : function() {
var self = this,
coll = new c();
coll.fetch({ success: function(){ self.render() ; } });
},
render : function() {
// the persons will be "visible" in your template
this.$el.html(this.template({ persons: this.coll.toJSON() }));
return this;
}
});
And in the template refer to the same persons object
<script type="text/template" id="details">
<ul>
<% _.each(persons, function(person) { %>
<li><%= person.name %></li>
<% }); %>
</ul>
</script>
Update:
I've created the working fiddle, but I had to modify the original source code as I can't use your retrieve.php endpoint
What you have asked is a generic question of how to use a collection to generate a view. Most of them are comfortable with generating a view with a model, but not with a collection. I followed the following tutorial Binding a collection to a view. Might be helpful for you also.
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.