Backbone JS multiple level navigation example - javascript

I'm trying to construct a solid Backbone JS experiment, where I have a local JSON data file which contains my pages (a project I'm doing has this sort of requirement anyhow). And I've coded this example so I can have endless nested subpages in the pages data. It seems to be working great. But when it comes to the URLs, I'm a little stuck.
How do I approach giving this multiple level navigation example totally dynamic URLs? What I mean is, correctly using the url property of the models and collections to construct the right URLs for all the top level and nested elements. Is it even possible? I just can't think how to do it.
See a live demo of where I am now:
http://littlejim.co.uk/code/backbone/multiple-level-navigation-experiment/
Just so it's easier, the source code is below...
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Multiple Level Navigation Experiment</title>
<script type="text/javascript" src="../../media/scripts/jquery-1.5.1.min.js"></script>
<script type="text/javascript" src="../../media/scripts/underscore-min.js"></script>
<script type="text/javascript" src="../../media/scripts/backbone-min.js"></script>
<script type="text/javascript" src="application.js"></script>
<script type="text/javascript">
// wait for the DOM to load
$(document).ready(function() {
App.initialize();
});
</script>
</head>
<body>
<div id="header">
<h1>Multiple Level Navigation Experiment</h1>
<p>Want to get this page structure pulled from JSON locally and have a fully functional multiple level nested navigation with correct anchors.</p>
</div>
<div id="article">
<!-- dynamic content here -->
</div>
</body>
</html>
content.json
{
"pages": [
{
"id": 1,
"title": "Home",
"slug": "home"
},
{
"id": 2,
"title": "Services",
"slug": "services",
"subpages": [
{
"id": 1,
"title": "Details",
"slug": "details",
"subpages": [
{
"id": 1,
"title": "This",
"slug": "this"
},
{
"id": 2,
"title": "That",
"slug": "that"
}
]
},
{
"id": 2,
"title": "Honest Service",
"slug": "honest-service"
},
{
"id": 3,
"title": "What We Do",
"slug": "what-we-do"
}
]
},
{
"id": 3,
"title": "Contact Us",
"slug": "contact-us"
}
]
}
application.js
// global app class
window.App = {
Data: {},
Controller: {},
Model: {},
Collection: {},
View: {},
initialize : function () {
$.ajax({
url: "data/content.json",
dataType: "json",
success: function(json) {
App.Data.Pages = json.pages;
new App.Controller.Main();
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
}
}
// main controller class
// when called it should have 'data' in JSON format passed to it
App.Controller.Main = Backbone.Controller.extend({
initialize: function() {
var pagesCollection = new App.Collection.Pages(App.Data.Pages);
var pagesView = new App.View.Pages({collection: pagesCollection});
$('#article').html(pagesView.render().el);
}
});
// pages model class
App.Model.Page = Backbone.Model.extend({
initialize: function() {
if (!_.isUndefined(this.get("subpages"))) {
this.subpages = new App.Collection.Pages(this.get("subpages"));
} // end if
this.view = new App.View.Page({model: this});
},
});
// page collection class
App.Collection.Pages = Backbone.Collection.extend({
model: App.Model.Page
});
// single page view class
App.View.Page = Backbone.View.extend({
tagName: "li",
initialize: function() {
_.bindAll(this, "render");
},
render: function() {
$(this.el).html(_.template("<%=title%>", {title: this.model.get("title")}));
return this;
}
});
// multiple pages view class
App.View.Pages = Backbone.View.extend({
tagName: "ul",
initialize: function() {
_.bindAll(this, "render");
},
render: function() {
var that = this;
this.collection.each(function(page) {
$(that.el).append(page.view.render().el);
if (!_.isUndefined(page.subpages)) {
var subpagesView = new App.View.Pages({collection: page.subpages});
$(that.el).append(subpagesView.render().el);
} // end if
});
return that;
}
});
I'm just needing so right direction on how to do the URLs properly. The idea I'm wanting is that I can setup my controller for routes so it can expect any page of any nested level. The models, collections and nested collections should be able to generate their URLs on their own, but the hash URL must reflect the level.
Ideally, this navigation would go to URLs like these:
http://example.com/#pages/services
http://example.com/#pages/services/details
http://example.com/#pages/services/details/this
...the URLs using the "slug" from the content.json data. Does any of this make sense? I'm pretty new to Backbone JS and just want to do things right.
Thanks, James

Here's my favorite solution to this problem: use PathJS !

Why not just parse out the slug?
So you can have a single route in the Backbone.Controller that looks like this:
'pages/:id' : showPage
And then showPage looks like:
showPage(id) : function(id) {
parse out the string 'services/details/etc'
look up the slug data based on that IE pages['services']['details']['etc']
}
or if the pages actually need to be processed differently, you can setup multiple routes, ever more granular like this:
'pages/:id' : showPage
'pages/:id/:nest' : showNestedPage
'pages/:id/:nest/:more' : showNestedMorePage

Related

Lazy load json data from template engine (handlebars)

I am trying to lazy load content that I am getting from a json file using handlebars.js as a template engine.
I want to lazy load each .projects-list div on scroll.
Here is my code.
HTML:
<div class="projects-list">
<script id="projects-template" type="text/x-handlebars-template">​
{{#each this}}
<h1>{{name}}</h1>
<img class="lazy" src="{{image.small}}" height="130" alt="{{name}}"/>
<img class="lazy" data-src="{{image.small}}" height="130" alt="{{name}}"/>
{{/each}}
</script>
</div>
JS:
$(function () {
// Get project data from json file.
$.getJSON("projects.json", function (data) {
// Write the data into our global variable.
projects = data;
// Call a function to create HTML for all the products.
generateAllProjectsHTML(projects);
});
// It fills up the projects list via a handlebars template.
function generateAllProjectsHTML(data) {
var list = $('.projects-list');
var theTemplateScript = $("#projects-template").html();
//Compile the template​
var theTemplate = Handlebars.compile(theTemplateScript);
list.append(theTemplate(data));
}
$('.lazy').lazy({
effect: "fadeIn",
effectTime: 5000,
threshold: 0
});
});
JSON:
[
{
"id": 1,
"name": "Example Name 1",
"image": {
"small": "assets/images/example1.jpg",
"large": "assets/images/example2.jpg"
}
},
{
"id": 2,
"name": "Example Name 2",
"image": {
"small": "assets/images/example3.jpg",
"large": "assets/images/example4.jpg"
}
}
]
I am trying to use this plugin: http://jquery.eisbehr.de/lazy/ but I am open to any suggestions.
Thanks for taking the time to look, any help is greatly appreciated!
The problem seems to be the order of your script and the timings. It would be a race-condition. You should initialize Lazy right after the template has been loaded. That should solve the behavior.
You can even compress your script. And remove the jQuery ready states in the script, it is not needed here.
So the result would look like this:
$.getJSON("projects.json", function(data) {
var theTemplateScript = $("#projects-template").html();
var theTemplate = Handlebars.compile(theTemplateScript);
$("#projects-list").append(theTemplate(data));
$(".lazy").lazy({
effect: "fadeIn",
effectTime: 5000,
threshold: 0
});
});

How can we consume JSON data using OPENUI5/SAPUI5?

I am new to SAPUI5/OPENUI5.
I am trying out a sample program to consume json data from a domain and display it in my openui5 table. I have tried two methods to get the data and bind it to table control.But I am not able to generate the table with the json data.
Please let me know my mistake in the code.
And also please refer me some links to understand the concept in a better way.
Thanks in advance.
Please find the two approaches below :
JSON Data :
[
{
"name": "Rajesh"
},
{
"name": "Kunal Jauhari"
},
{
"name": "Ashish Singh"
},
{
"name": "Ansuman Parhi"
},
{
"name": "Arup Kumar"
},
{
"name": "Deepak Malviya"
},
{
"name": "Seshu"
},
{
"name": "Ankush Datey"
},
{
"name": "Tapesh Syawaria"
},
{
"name": "Mahesh"
},
{
"name": "Vinay Joshi"
},
{
"name": "Ardhendu Karna"
},
{
"name": "Abhishek Shukla"
},
{
"name": "Kashim"
},
{
"name": "Vinayak"
}
]
Approach 1 : I am using a php file to echo the JSON data and use it in my ui5 screen.
When I access the run the php file individually, it generates the data and prints the data on screen.
Error I get is getJSON is not called.
Code :
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8'/>
<script src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-libs="sap.ui.commons,sap.ui.table"
data-sap-ui-theme="sap_bluecrystal">
</script>
<!-- add sap.ui.table,sap.ui.ux3 and/or other libraries to 'data-sap-ui-libs' if required -->
<script>
var json_url = "http://mydomain/teamdetails_ui5.php?t=6";
$.ajax({
url : json_url,
jsonpCallback : 'getJSON',
contentType : "application/json",
dataType: 'jsonp',
success: function(data,textStatus,jqXHR) {
oModel.setData({data: data});
sap.ui.getCore().setModel(oModel);
var oTable1 = new sap.ui.table.Table({
title : "Players List",
visibleRowCount : 3,
selectionMode : sap.ui.table.SelectionMode.Single,
navigationMode : sap.ui.table.NavigationMode.Paginator,
});
//Define the columns and the control templates to be used
oTable1.addColumn(new sap.ui.table.Column({
label : new sap.ui.commons.Label({
text : "Player Name"
}),
template : new sap.ui.commons.TextView().bindProperty(
"text", "name"),
width : "10px"
}));
oTable1.setModel(oModel);
oTable1.bindRows("/oModel");
oTable1.placeAt('table_cont');
},
error : function(jqXHR,textStatus,errorThrown) {
alert("Oh no, an error occurred");
alert(jqXHR);
alert(textStatus);
alert(errorThrown);
}
});
</script>
</head>
<body class="sapUiBody" role="application">
<div id="table_cont"></div>
</body>
</html>
Approach 2 : I am trying to access the JSON file directly on my domain and access the data.
Code is the same as above except url.
Url is used for this approach is (mydomain/players.json) where players.json contain the above json data.
Please help me in understanding the concept of JSON data handling.
Regards,
Rajan
First of all: SAPUI5 is built onto jQuery, yes. But there should be no need to use jQuery inside your SAPUI5 Application.
Use a JSONModel to load JSON-Data. Also the JSONModel can load the data from URL.
See the Documentation
this will look like:
// create a "json" Model
var oModel = new sap.ui.model.json.JSONModel();
// load data from URL
oModel.loadData('http://mydomain/teamdetails_ui5.php?t=6');
after this you can register this model in your sap.ui.core with:
sap.ui.getCore().setModel(oModel);
after this line every control can use the data from this model by simple binding-syntax.
Now lets create the table:
// create your table
var oTable1 = new sap.ui.table.Table({
title : "Players List",
visibleRowCount : 3,
selectionMode : sap.ui.table.SelectionMode.Single,
navigationMode : sap.ui.table.NavigationMode.Paginator,
// bind the core-model to this table by aggregating player-Array
rows: '{/player}'
});
beware of the part with "rows: '{/player}'". This is the only thing that has to be done to get the data from the model inside your table.
now finish the demo by adding the column and add the table to the DOM:
// define the columns and the control templates to be used
oTable1.addColumn(new sap.ui.table.Column({
label : new sap.ui.commons.Label({
text : "Player Name"
}),
template : new sap.ui.commons.TextView({
text: '{name}'
}),
width : "10px"
}));
//place at DOM
oTable1.placeAt('content');
Thats it. If it doesn't work, here is a running DEMO.

Unable to output from JSON file - Backbone

Trying to fetch JSON data when the div is clicked, but not able to see the output. I am using Backbone Collection to pull json data. Tried to output json data to console and also within another div. The content from json file is also listed below.
<div class = "test">Click </div>
<div class = "new_test"> </div>
JS
var myModel = Backbone.Model.extend();
var myCollection = Backbone.Collection.extend({
model: myModel,
url : "myjson.json"
})
var jobs = new myCollection();
var myView = Backbone.View.extend({
el : 'div',
events : {
'click div.test' : 'render'
},
initialize : function(){
jobs.fetch();
},
render : function(){
jobs.each(function(myModel){
var _comp = myModel.get('company');
$('div.new_test').html(_comp);
console.log(_comp)
})
}
})
Json File :
[
{
"company": "Ford",
"Type": "Automobile"
},
{
"company": "Nike",
"Type": "Sports"
}
]
You have to call the render function of your view to see the results. You cannot instantiate a collection object and expect to see results.
Code hasn't been tested.
var myView = Backbone.View.extend({
el : 'div',
events : {
'click div.test' : 'render'
},
initialize : function(){
jobs.fetch();
this.render();
},
render : function(){
jobs.each(function(myModel){
var _comp = myModel.get('company');
$('div.new_test').html(_comp);
console.log(_comp)
})
}
})
var yourView = new myView();
I have the same problem a few weeks ago.
Check path for Your JSON.
For example when You have structure of directories like this:
-js
- collection
collection.js
- json
myjson.json
Backbone collection You set like this:
url: 'js/json/event.json',
Check in Firefox, in Chrome we have a Cross-browser thinking
and check this:
jobs.fetch({
reset: true,
success: function (model, attributes) {
//check here if json is loaded
}
});

Click event not firing

I've looked around for a while now and have not been able to find anything that suggests what the cause of this is.
My code:
var faqView = Backbone.View.extend({
tagName: 'div',
id: 'faq-list',
initialize: function() {
var view = this;
this.collection = new faqCollection();
this.collection.fetch({
success: function(collection, response) {
collection.each(function(faq){
view.$el.append(_.template($('script#faq_item').html(),{faq: faq.attributes}));
});
},
error: function(collection, response) {
view.$el.html("<p>Unable to get the FAQ items.<br>Please try again later.</p>");
}
});
},
render: function() {
this.$el.appendTo('div#container');
return this;
},
events: {
'click h3': 'toggleAnswer'
},
toggleAnswer: function(event) {
console.log(this);
console.log(event);
}
});
var router = Backbone.Router.extend({
routes: {
"faq": "faq",
"*other": "defaultRoute"
},
faqView: {},
initialize: function() {
this.faqView = new faqView();
},
defaultRoute: function() {
this.resetPage();
},
faq: function() {
this.resetPage();
$('body').addClass('page-faq');
this.faqView.render();
},
resetPage: function() {
$('body').removeClass('page-faq');
this.faqView.remove();
}
});
The above code is included as the last items in the <body>. The HTML is as follows.
<body>
<div id="container">
</div>
<script type="text/template" id="faq_item">
<h3 class="contracted"><span>{{faq.question}}</span></h3>
<p style="display: none;">{{faq.answer}}</p>
</script>
<script type="text/javascript" src="./js/models.js"></script>
<script type="text/javascript" src="./js/views.js"></script>
<script type="text/javascript" src="./js/collection.js"></script>
<script type="text/javascript" src="./js/router.js"></script>
<script type="text/javascript">
//<![CDATA[
$(function() {
var app = new router;
Backbone.history.start();
});
//]]>
</script>
</body>
All the required elements exist (as far as I can tell) and I'm not manually setting the el attribute of the View. I'm lost as to why the events are not binding/firing when the <h3> is clicked.
Edit No errors thrown and the functionality works if I don't use the router and create the view by it self. e.g.
var app = new faqView();
app.render();
Your problem is in your router. Right here in fact:
resetPage: function() {
$('body').removeClass('page-faq');
this.faqView.remove();
}
View#remove is just jQuery's remove on the view's el by default and that:
[...] method takes elements out of the DOM. [...] all bound events and jQuery data associated with the elements are removed
So once you this.faqView.remove(), the delegate handler that drives the view's events is gone.
The usual approach is to create and destroy views as needed instead of creating a view and caching it for later. Your router should look more like this:
var router = Backbone.Router.extend({
routes: {
"faq": "faq",
"*other": "defaultRoute"
},
defaultRoute: function() {
this.resetPage();
},
faq: function() {
this.resetPage();
$('body').addClass('page-faq');
this.view = new faqView();
this.view.render();
},
resetPage: function() {
$('body').removeClass('page-faq');
if(this.view)
this.view.remove();
}
});
Demo: http://jsfiddle.net/ambiguous/aDtDT/
You could try messing around with detach inside an overridden remove method in faqView as well but there's really no need to have an instance of faqView around all the time: create it when you need it and remove it when you don't.

Am I using the Model & Views correct in backbone.js

I'm starting to learn backbone.js and I've built my first page and I want to know If I'm going down the 'correct' path (as much as there is ever a correct path in software).
Is it possible to get the model properties (attributes) to automatically bind to the html elements?
The html:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>settings page</title>
<link rel="stylesheet" type="text/css" href="../Content/theme.css" />
<script language="javascript" src="../Scripts/jquery-1.7.1.js"></script>
<script language="javascript" src="../Scripts/underscore.js"></script>
<script language="javascript" src="../Scripts/backbone.js"></script>
<script language="javascript" src="../Scripts/settings.js"></script>
</head>
<body>
<div style="width:95%;margin:10px;padding:10px;background-color:#ffffff;color:#000000;padding-bottom:8px;padding-right:5px;padding-top:4px;float:left;">
<h1>
Settings...
</h1>
Server URL (cloud based API):
<br />
<input id="settings-service-url" type="text" size="100" />
<br />
<br />
Timeout:
<br />
<input id="settings-timeout" type="text" size="100" />
<br />
<br />
<button id="update-settings">Update Settings</button>
</div>
</body>
</html>
Javascript:
$(document).ready(function () {
if (typeof console == "undefined") {
window.console = { log: function () { } };
}
Settings = Backbone.Model.extend({
defaults: {
ServiceUrl: "",
Timeout: 0
},
url: function () {
return '/settings';
},
replaceServiceUrlAttr: function (url) {
this.set({ WisdomServiceUrl: url });
},
replaceTimeoutAttr: function (timeout) {
this.set({ Timeout: timeout });
}
});
SettingsView = Backbone.View.extend({
tagName: 'li',
events: {
'click #update-settings': 'updateSettings'
},
initialize: function () {
_.bindAll(this, 'render');
this.settings = new Settings;
this.settings.fetch({ success: function () {
view.render(view.settings);
}
});
},
updateSettings: function () {
view.settings.replaceServiceUrlAttr($('#settings-service-url').val());
view.settings.replaceTimeoutAttr($('#settings-timeout').val());
view.settings.save();
},
render: function (model) {
$('#settings-wisdom-service-url').val(model.get("WisdomServiceUrl"));
$('#settings-timeout').val(model.get("Timeout"));
}
});
var view = new SettingsView({ el: 'body' });
});
There are a mistake in your view. First of all, it's common practice to pass the model as parameter when you create a new view:
var view = new SettingsView({ "el": "body", "model": new Settings() });
now you can access your model by this.model in your view.
Next thing is the use of the variable view in your view. Using Backbone's View means you can have multiple instances of one View class. So calling new SettingsView() creates an instance of your view. Let's think about having two instances of your view:
var view = new SettingsView({ "el": "body", "model": new Settings() });
var view1 = new SettingsView({ "el": "body", "model": new Settings() });
Whenever you call view.settings.save(); in one of your instances it will always call the method in the first view instance because it's bound the variable name "view". So all you have to do use this instead of view:
SettingsView = Backbone.View.extend({
tagName: 'li',
events: {
'click #update-settings': 'updateSettings'
},
initialize: function () {
this.settings = new Settings;
this.settings.fetch({ success: _.bind(function () {
//to get this work here we have to bind "this",
//otherwise "this" would be the success function itself
this.render(view.settings);
}, this)
});
},
updateSettings: function () {
this.model.replaceServiceUrlAttr($('#settings-service-url').val());
this.model.replaceTimeoutAttr($('#settings-timeout').val());
this.model.save();
},
render: function () {
$('#settings-wisdom-service-url').val(this.model.get("WisdomServiceUrl"));
$('#settings-timeout').val(this.model.get("Timeout"));
}
});
Using both settings methods in your model doesn't make much sense at the moment as they just call set. So you could call set on the model directly.
Also using tagName: 'li' and inserting an element will not work as you expected. Using tagName only has an effect if you don't insert an element into the constructor. In this case backbone will create a new element using the tagName. Otherwise the element of the view is the one you passed into the constructor.

Categories