|
// wrap in an IIFE for encapsulation |
|
(function () { |
|
|
|
// for simplicity create a global app object |
|
window.app = { |
|
Views: {}, |
|
Extensions: {}, |
|
Router: null, |
|
|
|
init: function () { |
|
// get an instance of the root App view |
|
this.getInstance(); |
|
|
|
// start backbone hash change listener |
|
Backbone.history.start(); |
|
|
|
}, |
|
|
|
// use a getter method to get the instance instead accessing this.instance |
|
// directly. Also, this prevents multiple instantiation of the application |
|
getInstance: function() { |
|
// creates a new instance of the root App |
|
// or returns the instance if it already exists |
|
if (!this.instance) { |
|
this.instance = new app.Views.App(); |
|
} |
|
|
|
return this.instance; |
|
} |
|
}; |
|
|
|
// on dom ready trigger the app's init method |
|
$(function() { |
|
window.app.init(); |
|
}); |
|
|
|
|
|
// ------------------------ |
|
// code for the application |
|
// |
|
|
|
// define a single router on the global app object |
|
app.Router = Backbone.Router.extend({ |
|
|
|
routes: { |
|
'detail': 'detail', |
|
'': 'home' |
|
}, |
|
|
|
home: function () { |
|
// make the Home view persist in memory and on the DOM |
|
if (!this.homeView) { |
|
this.homeView = new app.Views.Home(); |
|
} |
|
|
|
// pass the view to the Layout View for handling animations etc.. |
|
app.getInstance().goto(this.homeView); |
|
}, |
|
|
|
detail: function () { |
|
var view = new app.Views.Detail(); |
|
app.getInstance().goto(view); |
|
} |
|
|
|
}); |
|
|
|
// Base view class for providing transition capabilities |
|
// perhaps better named something like AnimView? |
|
app.Extensions.View = Backbone.View.extend({ |
|
|
|
initialize: function () { |
|
// this seems out of place even if this is the base view class for this app |
|
// refactor to move the router instantiation to happen after backbone.history.start() |
|
this.router = new app.Router(); |
|
}, |
|
|
|
// base render class that checks whether the the view is to be a 'page' |
|
// aka meant for transitions; This is somewhat of an anti-pattern in that |
|
// each view inheriting from this will have to trigger this render method |
|
// with a 'super' call. A better remedy is to provide a check for a method |
|
// like onRender() and trigger it with correct context so that views which |
|
// inherit from this can provide an onRender() method for any additional |
|
// rendering logic specific to that view. |
|
render: function(options) { |
|
|
|
// as part of refactor, show the current instance of the view using render |
|
console.debug('Render triggered for the ' + this.className + ' View with cid: ' + this.cid); |
|
|
|
options = options || {}; |
|
|
|
if (options.page === true) { |
|
this.$el.addClass('page'); |
|
} |
|
|
|
// From comment above, refactoring to use onRender() instead of override |
|
if (_.isFunction(this.onRender())) { |
|
// trigger whatever current/caller view's onRender() method |
|
this.onRender(); |
|
} |
|
|
|
return this; |
|
}, |
|
|
|
transitionIn: function (callback) { |
|
|
|
var view = this; |
|
|
|
var transitionIn = function () { |
|
|
|
view.$el.addClass(view.animateIn+' animated'); |
|
view.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function () { |
|
|
|
view.$el.removeClass(view.animateIn+' animated'); |
|
|
|
if (_.isFunction(callback)) { |
|
callback(); |
|
console.log('Callback triggered on transitionend for TransitionIn method'); |
|
} |
|
}); |
|
}; |
|
|
|
// setting the page class' css to position: fixed; obviates the need |
|
// for this and still allows transitions to work perfectly since pos |
|
// is absolute during animation |
|
_.delay(transitionIn, 0); |
|
}, |
|
|
|
transitionOut: function (callback) { |
|
|
|
var view = this; |
|
|
|
view.$el.addClass(view.animateOut+' animated'); |
|
view.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function () { |
|
|
|
view.$el.removeClass(view.animateOut+' animated'); |
|
|
|
if (_.isFunction(callback)) { |
|
callback(); // hard to track bug! He's binding to transitionend each time transitionOut called |
|
// resulting in the callback being triggered callback * num of times transitionOut |
|
// has executed |
|
console.log('Callback triggered on transitionend for TransitionOut method'); |
|
} |
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
// The root app view - attached to the body, allows handling DOM-wide events |
|
// and then through use of pub/sub or mixin backbone events we can notify other views. |
|
// Also, it acts as a Layout to contain all views that want to use transitions |
|
app.Views.App = app.Extensions.View.extend({ |
|
|
|
el: 'body', |
|
|
|
goto: function (view) { |
|
|
|
// cache the current view and the new view |
|
var previous = this.currentPage || null; |
|
var next = view; |
|
|
|
if (previous) { |
|
previous.transitionOut(function () { |
|
// only remove the old view if its not the Home view |
|
if (previous.$el.hasClass('home')) { |
|
console.log('Previous view is Home; not removing for it should persist'); |
|
} else { |
|
// otherwise cleanup all other views since we dont want them to persist |
|
previous.remove(); |
|
} |
|
}); |
|
} |
|
|
|
next.render({ page: true }); // render the next view |
|
this.$el.append( next.$el ); // append the next view to the body (the el for this root view) |
|
next.transitionIn(); |
|
this.currentPage = next; |
|
|
|
} |
|
|
|
}); |
|
|
|
// The initial view triggered on start (Home view) |
|
app.Views.Home = app.Extensions.View.extend({ |
|
|
|
className: 'home', |
|
template: null, |
|
|
|
initialize: function(options) { |
|
this.animateIn = 'fadeIn'; |
|
this.animateOut = 'iosFadeLeft'; |
|
|
|
// cache the template -- especially if your homeview may contain a collection or |
|
// act like a CollectionView; This prevents us from having to re-create the view |
|
// instance and re-fetch the collection if the apps primary purpose is focused around |
|
// the list view. |
|
this.template = _.template($('script[name=home]').html()); |
|
|
|
return this; |
|
}, |
|
|
|
/*render: function () { |
|
// fill this view's element with html from the template |
|
this.$el.html(this.template()); |
|
|
|
// trigger the parent class render method to handle |
|
return app.Extensions.View.prototype.render.apply(this, arguments); |
|
|
|
return this; |
|
}*/ |
|
|
|
onRender: function() { |
|
console.log('HomeView#onRender method triggered'); |
|
|
|
// fill this view's element with html from the template |
|
// and render it only once (we have no collection so no need to re-render since view persists) |
|
if (this.$el.is(':empty')) |
|
this.$el.html(this.template()); |
|
|
|
return this; |
|
} |
|
|
|
}); |
|
|
|
// the detail view triggered by /#detail |
|
app.Views.Detail = app.Extensions.View.extend({ |
|
|
|
className: 'detail', |
|
template: null, |
|
|
|
initialize: function(options) { |
|
this.animateIn = 'iosSlideInRight'; |
|
this.animateOut = 'slideOutRight'; |
|
|
|
// cache the template instead of grabbing it each time on render |
|
// in case we decide to persist this view instead of removing it |
|
this.template = _.template($('script[name=detail]').html()); |
|
}, |
|
|
|
/* replaced with onRender() so render work is accomplished in base class |
|
render: function () { |
|
this.$el.html(this.template()); |
|
|
|
// trigger the parent class' render (e.g. a call to 'super') |
|
return app.Extensions.View.prototype.render.apply(this, arguments); |
|
}*/ |
|
|
|
onRender: function() { |
|
console.debug('DetailView#onRender method triggered'); |
|
|
|
// fill this view's element with html from the template |
|
this.$el.html(this.template()); |
|
} |
|
|
|
}); |
|
|
|
})(); // end of IIFE with invocation |