Backbone.js primer customisation issues - javascript

I've been trying to get my first Backbone.js app up and running, following the Backbone.js primer here.
I've followed the example through and now I'm trying to customise it for my purposes which are to simply retrieve and read a JSON file from my server. I don't need to be able to change or delete any of the data.
I've set up my html as per the primer below:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Backbone.js Primer</title>
<script type="text/javascript" src="./node_modules/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="./node_modules/underscore/underscore-min.js"></script>
<script type="text/javascript" src="./node_modules/backbone/backbone-min.js"></script>
<script type="text/javascript" src="./node_modules/moment/moment.js"></script>
<script type="text/javascript" src="./backbone.js"></script>
</head>
<body>
<div>
<h1>Transcripts Data</h1>
<div id="dailyTranscripts-app">
<ul class="dailyTranscripts-list"></ul>
</div>
</div>
</body>
</html>
I've then coded my backbone.js file as the primer describes below:
var yesterday = moment (new Date()).add(-1, 'days').format('YYYY-MM-DD')
var yesterdaysDataURL = 'https://mihndbotblob.blob.core.windows.net/mihndbot-transcripts/finalTranscripts/dailyTranscripts/' + yesterday + '.json'
// Model class for each transcript iten
var DailyTranscriptsModel = Backbone.Model.extend({
defaults: {
type: null,
MessageID: null,
MessageTime: null,
MessageChannel: null,
MessageSenderID: null,
MessageSenderName: null,
ConversationID: null,
MessageText: null,
MessageRecipientID: null,
QuickReplyDisplayText: null,
QuickReplyPayload: null,
Question: null,
Answer: null,
FollowUpPrompts: null
}
});
// Collection class for the DailyTransctipts list endpoint
var DailyTranscriptsCollection = Backbone.Collection.extend({
model: DailyTranscriptsModel,
url: yesterdaysDataURL
});
// View class for displaying each dailyTranscripts list item
var DailyTranscriptsListItemView = Backbone.View.extend({
tagName: 'li',
className: 'dailyTranscripts',
initialize: function () {
this.listenTo(this.model)
},
render: function () {
var html = '<b>Message ID: </b> ' + this.model.get('MessageID');
html += '<br><b>Message Time: </b>' + this.model.get('MessageTime');
this.$el.html(html);
return this;
}
});
// View class for rendering the list of all dailyTranscripts
var DailyTranscriptsListView = Backbone.View.extend({
el: '#dailyTranscripts-app',
initialize: function () {
this.listenTo(this.collection, 'sync', this.render);
},
render: function () {
var $list = this.$('ul.dailyTranscripts-list').empty();
this.collection.each(function (model) {
var item = new DailyTranscriptsListItemView({model: model});
$list.append(item.render().$el);
}, this);
return this;
}
});
// Create a new list collection, a list view, and then fetch list data:
var dailyTranscriptsList = new DailyTranscriptsCollection();
var dailyTranscriptsView = new DailyTranscriptsListView({collection: dailyTranscriptsList });
dailyTranscriptsList.fetch();
The major changes I've made to the code (apart from some customisations) are to remove the templates the primer uses to create the views (I couldn't get them working) and I've removed the Backbone CRUD elements as I only require my app to read data from the server, not update or delete it.
The issue I have is that whilst I'm pulling back the JSON file from the server, none of the data is rendering in the HTLM <div> as expected, it's just blank.
I know that Backbone.js is retrieving the data as when I add .then(function() {console.log(dailyTranscriptsList);}); to the final dailyTranscriptsList.fetch() call I can see the data in the browser console:

You need to wrap all of your backbone.js code within jQuery's .ready()
// backbone.js
$(document).ready(function () {
// all your backbone.js code here
})
This causes your js to run after the DOM is ready, so Backbone will know how to find the elements it needs in order for views to work.
You could also move <script type="text/javascript" src="./backbone.js"></script> to the end of the page, right before </body>

Related

Loading/Updating new data to a webpage and using Knockout to dynamically update UI

Working with the dynamic JavaScript Technology called Knockout, I would like to send new data to my webpage and ask Knockout to do the dynamic UI update for me.
The below example shows a very simple webpage that shows two Scores (P1 and P2). The JavaScript creates a ViewModel() using Knockout. Unfortunately, I only achieved this by providing a JSON-data property. And I don't know how to dynamically load and also dynamically update new score-data.
Now my question: How can I "inject" (i.e. load at first and update at any time) new data to my webpage and Knockout would update the Scores UI dynamically ?
I guess, I would need some sort of...
a) $.getJSON(".... for the initial loading!
b) Post-request (REST) call from anywhere for the data-update
But how do I do a) and b) ???
Thanks for any help on this.
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>iKK_ScoreMirror</title>
</head>
<script type='text/javascript' src='Knockout/knockout-3.4.2.js'></script>
<body>
<h3>Game</h3>
<p>Score P1 = <span data-bind="text: scoreP1"></span> </p>
<p>Score P2 = <span data-bind="text: scoreP2"></span> </p>
</body>
<script>
function ViewModel() {
var self = this;
// !!!!!!! Here is the json-data given fix
var jsonData = {
sP1: 13,
sP2: 23
};
self.scoreP1 = ko.observable(jsonData.sP1)
self.scoreP2 = ko.observable(jsonData.sP2)
};
var vm = new ViewModel();
ko.applyBindings(vm);
</script>
</html>
you mean something like this? here is a runnable fiddle https://jsfiddle.net/ca0xv62w/2/
function ViewModel() {
var self = this;
self.scoreP1 = ko.observable('13')
self.scoreP2 = ko.observable('23')
self.loadDataFromServer = function() {
$.ajax({
type: 'POST',
cache: false,
data: {
json: JSON.stringify(data)
},
url: '/echo/json/',
success: function(response) {
self.scoreP1(response.sP1);
self.scoreP2(response.sP2);
}
});
}
}

How can I integrate handlebars with backbone

I have a simple single page application which is for my own learning. The page has a UL of all the services that I provide. This list comes from a JSON. Next to each service there is a price for it, and a check-box which the user can select if he needs the service.
Under the list there is a total price value for all the services selected. Based on this, can you please tell me what wrong am I doing? I am trying to integrate the list of services as a handlebar template.
Here is the HTML
<head>
<meta charset="utf-8" />
<title>Your first Backbone.js App | Tutorialzine </title>
<!-- Google web fonts -->
<link href="http://fonts.googleapis.com/css?family=PT+Sans:400,700" rel='stylesheet' />
<!-- The main CSS file -->
<link href="http://localhost/backbone-demo/assets/css/style.css" rel="stylesheet" />
</head>
<body>
<form id="main" method="post" action="submit.php">
<h1>My Services</h1>
<div id="serviceTable"></div>
<ul id="services">
<script id="services-template" type="text/x-handlebars-template">
<!-- The services will be inserted here via handlebars-->
{{#each services}}
<li>
<input type="checkbox" value="1" name="{{title}}"/> {{title}}
<span>${{price}} </span>
</li>
{{/each}}
</script>
</ul>
<p id="total">total: <span>$0</span></p>
<input type="submit" id="order" value="Order" />
</form>
<!-- JavaScript Includes -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://localhost/backbone-demo/assets/js/libs/underscore.min.js"></script>
<script src="http://localhost/backbone-demo/assets/js/libs/backbone.min.js"></script>
<script src="http://localhost/backbone-demo/assets/js/libs/handlebars.js"></script>
<script src="http://localhost/backbone-demo/assets/js/script.js"></script>
</body>
Here is the Javascript:
$(function(){
// Create a model for the services
var Service = Backbone.Model.extend({
// Will contain three attributes.
// These are their default values
defaults:{
title: 'My service',
price: 100,
checked: false
},
// Helper function for checking/unchecking a service
toggle: function(){
this.set('checked', !this.get('checked'));
}
});
// Create a collection of services
var ServiceList = Backbone.Collection.extend({
// Will hold objects of the Service model
model: Service,
// Return an array only with the checked services
getChecked: function(){
return this.where({checked:true});
}
});
//Retrieve the list of services
$.ajax({
type: "GET",
url: "assets/services.json",
async: true,
success: function(response){
// Prefill the collection with a number of services.
var services = new ServiceList(response);
var ServiceView = Backbone.View.extend({
tagName: 'li',
events:{
'click': 'toggleService'
},
initialize: function(){
// Set up event listeners. The change backbone event
// is raised when a property changes (like the checked field)
this.listenTo(services, 'change', this.render);
},
render: function(){
var tpl = Handlebars.compile($("#services-template").html());
//console.log(this.$el.selector);
this.$el.html(tpl({services: services.toJSON()}));
//console.log(this.$el);
$('#serviceTable').append(this.$el);
//document.getElementById('serviceTable').innerHTML = serviceData;
// Create the HTML
/* this.$el.html('<input type="checkbox" value="1" name="' + this.model.get('title') + '" /> ' + this.model.get('title') + '<span>$' + this.model.get('price') + '</span>');
this.$('input').prop('checked', this.model.get('checked')); */
// Returning the object is a good practice
// that makes chaining possible
return this;
},
toggleService: function(){
this.model.toggle();
}
});
// The main view of the application
var App = Backbone.View.extend({
// Base the view on an existing element
el: $('#main'),
initialize: function(){
// Cache these selectors
this.total = $('#total span');
this.list = $('#services');
// Listen for the change event on the collection.
// This is equivalent to listening on every one of the
// service objects in the collection.
this.listenTo(services, 'change', this.render);
// Create views for every one of the services in the
// collection and add them to the page
services.each(function(service){
var view = new ServiceView({ model: service });
this.list.append(view.render().el);
}, this); // "this" is the context in the callback
},
render: function(){
// Calculate the total order amount by agregating
// the prices of only the checked elements
var total = 0;
console.log(services.getChecked());
Handlebars.each(services.getChecked(), function(elem){
console.log(total);
total += elem.get('price');
});
// Update the total price
this.total.text('$'+total);
return this;
}
});
new App();
}
});
});
Below is a workable snippet of your code. You have defined a ServiceView, which is created for each service in your set, however within this view, you are working with the collection as a whole, not this.model and your handlebars template has a for loop in it.
This means that you end up displaying the collection as many times as there are models in the collection. The example below duplicates the list twice as there are 2 records in the list.
There are 2 possible solutions:
Rename your ServiceView to ServicesView and remove the loop within App initialize.
Remove the loop in your template and change the call to this.$el.html(tpl(this.model.toJSON()));
// Create a model for the services
var Service = Backbone.Model.extend({
// Will contain three attributes.
// These are their default values
defaults:{
title: 'My service',
price: 100,
checked: false
},
// Helper function for checking/unchecking a service
toggle: function(){
this.set('checked', !this.get('checked'));
}
});
// Create a collection of services
var ServiceList = Backbone.Collection.extend({
// Will hold objects of the Service model
model: Service,
// Return an array only with the checked services
getChecked: function(){
return this.where({checked:true});
}
});
//Retrieve the list of services
$.ajax({
type: "GET",
url: "http://www.mocky.io/v2/588b429d300000d11afa8d97",
async: true,
success: function(response){
// Prefill the collection with a number of services.
var services = new ServiceList(response);
var ServiceView = Backbone.View.extend({
tagName: 'li',
events:{
'click': 'toggleService'
},
initialize: function(){
// Set up event listeners. The change backbone event
// is raised when a property changes (like the checked field)
this.listenTo(services, 'change', this.render);
},
render: function(){
var tpl = Handlebars.compile($("#services-template").html());
//console.log(this.$el.selector);
this.$el.html(tpl({services: services.toJSON()}));
//console.log(this.$el);
$('#serviceTable').append(this.$el);
//document.getElementById('serviceTable').innerHTML = serviceData;
// Create the HTML
/* this.$el.html('<input type="checkbox" value="1" name="' + this.model.get('title') + '" /> ' + this.model.get('title') + '<span>$' + this.model.get('price') + '</span>');
this.$('input').prop('checked', this.model.get('checked')); */
// Returning the object is a good practice
// that makes chaining possible
return this;
},
toggleService: function(){
this.model.toggle();
}
});
// The main view of the application
var App = Backbone.View.extend({
// Base the view on an existing element
el: $('#main'),
initialize: function(){
// Cache these selectors
this.total = $('#total span');
this.list = $('#services');
// Listen for the change event on the collection.
// This is equivalent to listening on every one of the
// service objects in the collection.
this.listenTo(services, 'change', this.render);
// Create views for every one of the services in the
// collection and add them to the page
services.each(function(service){
var view = new ServiceView({ model: service });
this.list.append(view.render().el);
}, this); // "this" is the context in the callback
},
render: function(){
// Calculate the total order amount by agregating
// the prices of only the checked elements
var total = 0;
console.log(services.getChecked());
Handlebars.each(services.getChecked(), function(elem){
console.log(total);
total += elem.get('price');
});
// Update the total price
this.total.text('$'+total);
return this;
}
});
new App();
}
});
<form id="main" method="post" action="submit.php">
<h1>My Services</h1>
<div id="serviceTable"></div>
<ul id="services">
<script id="services-template" type="text/x-handlebars-template">
<!-- The services will be inserted here via handlebars-->
{{#each services}}
<li>
<input type="checkbox" value="1" name="{{title}}"/> {{title}}
<span>${{price}} </span>
</li>
{{/each}}
</script>
</ul>
<p id="total">total: <span>$0</span></p>
<input type="submit" id="order" value="Order" />
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js"></script>

How to fetch data from Backbone collection to Template

i am just writing a simple backbone program. But i am not getting how to fetch data from backbone collection to backbone template. Complete code is written below:
<!doctype html>
<html>
<head>
<title>Backbone tutorial</title>
</head>
<body>
<div class="user">user</div>
<div class="page"></div>
<script type="text/template" id="user-list-template">
I am not able to get data on daya.key
<h1> <%= data.key %> </h1>
</script>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="underscore.js"></script>
<script type="text/javascript" src="backbone.js"></script>
<script>
This is my Collection code
var Album = Backbone.Collection.extend({
url : "/data.json"
});
This is my view code
var UserList= Backbone.View.extend({
el:'.page',
template:_.template($('#user-list-template').html()),
render : function(){
var that=this;
var album= new Album();
album.fetch({
success:function(album){
var _data = {data : album.models} ;
$(that.el).html(that.template(_data));
}
})
}
});
This is my Router code
var Router = Backbone.Router.extend({
routes: {
"": "home" // #help
}
});
var userList= new UserList();
var router = new Router();
router.on('route:home', function(){
userList.render();
});
Backbone.history.start();
</script>
</body>
</html>
and here is the data.json file
{ "key" : "value to print on template "}
Two modifications i would suggest
1.First check the data feild in your template. Since you are fetching data from the collection it will be array of models.
<h1> <%= data[0].key %> </h1>
or you can use a for loop iterating over the collections
2.The data.json file should look like this
[{"key" : "value"}]
Server needs to return a JSON array of model object for collection.fetch() request. So the data.json should look like this:
[{"key":"value to print on template"},{"key":"another value"}]
And try this collection view render implementation:
Model:
var User = new Backbone.Model.extend({});
Collection:
var Album = new Backbone.Collection.extend({
model: User,
url: "/data.json"
});
//create collection instance
var album = new Album();
View:
var UserList= Backbone.View.extend({
el:'.page',
template:_.template($('#user-list-template').html()),
initialize: function(){
//register a collection data add event handler
this.listenTo(album,"add",this.addUser);
//register a collection data remove event handler
this.listenTo(album,"remove",this.removeUser);
album.fetch();
},
render : function(){
},
addUser: function(user){ //apply model data to view template and append to view element
this.$el.append(this.template(user.toJSON()));
},
removeUser: function(user){
//view state update implementation when data have been removed from collection
}
});
Template:
<script type="text/template" id="user-list-template">
<h1><%= key %></h1>
</script>
div.user view will add/remove user-list view dynamically according to collection data manipulation.
Hope this helpful.

backbone collection. fetch() not rendering the view in mozilla

i am trying to learn backbone.js ( Backbone.js 1.0.0) this is my sample html page where iam using collection. fetch() method to get the collection,and it is displayed using view .i am getting result in
google chrome,but nothing is displayed in mozilla. i don't know the exact reason.
while i refere to backone site http://backbonejs.org/#Collection-fetch
it is qouted that :
Note that fetch should not be used to populate collections on page load — all models needed at load time should already be bootstrapped in to place. fetch is intended for lazily-loading models for interfaces that are not needed immediately: for example, documents with collections of notes that may be toggled open and closed.
is this is related with my issue?
this is my sample html page
<!DOCTYPE html>
<html>
<head>
<title>Backbone Application</title>
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/underscore.js" type="text/javascript"></script>
<script src="js/backbone.js" type="text/javascript"></script>
</head>
<body>
<div class="list"></div>
<script id="personTemplate" type="text/template">
<td> <strong><%= name %></strong></td>
<td>(<%= age %>) </td>
<td> <%= occupation %> </td>
</script>
<script type="text/javascript">
//Person Model
var Person = Backbone.Model.extend({
defaults: {
name: 'Guest User',
age: 30,
occupation: 'worker'
}
});
// A List of People
var PeopleCollection = Backbone.Collection.extend({
model: Person,
initialize: function(){
alert("intialise")
},
url:'/RestFul/rest/members/info',
});
// View for all people
var PeopleView = Backbone.View.extend({
tagName: 'table',
render: function(){
this.collection.each(function(person){
var personView = new PersonView({ model: person });
this.$el.append(personView.render().el); // calling render method manually..
}, this);
return this; // returning this for chaining..
}
});
// The View for a Person
var PersonView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#personTemplate').html()),
////////// initialize function is gone from there. So we need to call render method manually now..
render: function(){
this.$el.html( this.template(this.model.toJSON()));
return this; // returning this from render method..
}
});
var peopleCollection = new PeopleCollection();
//peopleCollection.fetch();
peopleCollection.fetch({ success: function () { console.log("collection fetched"); } });
//peopleCollection.fetch({context:collection}).done(function() {
// console.log(this.length)
// })
//console.log(peopleCollection.toJSON())
alert(JSON.stringify(peopleCollection));
var peopleView = new PeopleView({ collection: peopleCollection });
$(document.body).append(peopleView.render().el); // adding people view in DOM
</script>
</body>
</html>
any help will be appreciated
Try with
var fetching = peopleCollection.fetch({ success: function () { console.log("collection fetched"); } });
$.when(fetching).done(function(){
var peopleView = new PeopleView({ collection: peopleCollection });
$(document.body).append(peopleView.render().el); // adding people view in DOM
});
var fetching = peopleCollection.fetch({ success: function () {
var peopleView = new PeopleView({ collection: peopleCollection });
$(document.body).append(peopleView.render().el);
} });
I think we can call the view render inside the success callback

Event binding with views and AJAX-loaded collections

I'm starting out with Backbone.JS, and have a question about how this should work.
I have a page which should load a list of sports, and then render the view. Now, because the list of sports is loaded via AJAX, I need to bind events to the collection so that it's only rendered when the data from that collection is actually loaded.
Here's what I've got so far:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Test</title>
<script src="../../src/vendor/zepto.js" type="text/javascript" charset="utf-8"></script>
<script src="../../src/vendor/underscore.js" type="text/javascript" charset="utf-8"></script>
<script src="../../src/vendor/backbone.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="sportsList"></div>
<script type="text/template" id="sports-template">
<ul>
<% _.each(sports, function(sport){ %>
<li>
<%= sport.getDescription() %>
</li>
<% }); %>
</ul>
</script>
<script type="text/javascript" charset="utf-8" src="application.js"></script>
</body>
</html>
As for the javascript code:
var Sport = Backbone.Model.extend({
getDescription: function() {
return this.get('description');
}
});
var Sports = Backbone.Collection.extend({
model: Sport,
initialize: function(options) {
this.bind("refresh", function() {
options.view.render();
});
var sports = this;
$.ajax({
url: 'http://localhost:8080/?service=ListSportsService&callback=?',
dataType: 'jsonp',
success: function(data) {
sports.refresh(data);
}
});
}
});
var SportsView = Backbone.View.extend({
el: '#sportsList',
template: _.template($('#sports-template').html()),
initialize: function() {
this.model = new Sports({view: this});
},
render: function() {
$(this.el).html(this.template({sports: this.model.models}));
return this;
}
});
var SportsController = Backbone.Controller.extend({
routes: {
'sports': 'listSports'
},
listSports: function() {
new SportsView();
}
});
$(document).ready(function(){
window.app = new SportsController();
Backbone.history.start();
});
The part that really nags me is having to pass the view to the collection and binding the refresh event there so that I can render the view once everything is loaded.
My question is, is there a DRYer/simpler way of doing this that I'm missing?
Keep in mind I'm using ZeptoJS instead of jQuery.
Your collection should read
var Sports = Backbone.Collection.extend({
model: Sport,
url: '/?service=ListSportsService&callback=?'
});
Always use Backbone.sync functionality when possible (here by specifying the url on the collection and using fetch).
Your controller should read
var SportsController = Backbone.Controller.extend({
routes: {
'sports': 'listSports'
},
listSports: function() {
//create the view with the collection as a model
this.sports = new Sports();
this.view = new SportsView({model: this.sports});
this.sports.bind("refresh", this.view.render);
// fetch all the sports
this.sports.fetch();
}
});
Enjoy.

Categories

Resources