Skip to content

dzhdmitry/backbone-viewport

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

backbone-viewport

Tiny single page application framework. Based on jQuery and Backbone, it provides an API to create single page applications. Supports HTML5 history.pushState method and hash routing.

Viewport stores pages as Backbone's models with related Views. When [URL changes] user goes to some page, it retrieves URI, creates page Model, renders page View, and put model into Collection with unique URI, and shows it. If page for URI exists in Collection, it just shown.

Requirements

  • jQuery/Zepto/$-compatible framework can be used by Backbone.js
  • Backbone.js

Release

Development version

Minimized production version

Examples

Example with hash method

Classes

Viewport.View

Inherited from Backbone.View. Represents page view. View is rendered on any model's change.

Attributes

Name Type Default Description
tagName string 'div' Inherited from Backbone.View. Tag name of container element

Methods

.template(data)

Must be overridden. Receives model attributes and return rendered string.

It is common to use html templates in <script type="text/html"> elements and use underscore to render them. Here is an example how .template() may look like:

<script type="text/html" id="template-page-discovery">
    <h1><%= name %></h1>
    ...content...
</script>
var View = Viewport.View.extend({
    // ...
    template: function(data) {
        var template = $('#template-page-' + this.model.get("name")),
            templateFn = _.template(template.html());

        return templateFn(data);
    }
});

// Extending classes and configuring router...

// In Router's action:
router.go({ name: "discovery", content: "" /* , ... */ });

...will cause script[id="template-page-discovery"] will be rendered with data:

<h1>discovery</h1>
...content...
.render()

Renders model attributes by template.

.toggle(active)

Set display: block css style to page container if active=true, or display:none if false. Override it to use different behaviour.

Events

Event type Description
rendered Fires when .render was called.

Viewport.Model

Inherited from Backbone.Model. Contains page's attributes and data, which can be rendered in view.

Attributes

Name Type Default Description
uri string Generated automatically if not given. Unique identifier of every page. Viewport uses Backbone.history methods getPath() and getHash() for history.pushState and hash routing.
active boolean true Indicates visibility of a page. When changed, page container is set display: block css style if true, and display:none if false. Override Viewport.View.toggle() to use whatever behaviour.

All model's attributes are available in view.template().

Methods

.show()

Set page.active property to true. Triggers shown event.

.hide()

Set page.active property to false. Triggers hidden event.

.getFetchOptions()

Extend this method to provide options for ajax fetch call:

var Page = Viewport.Model.extend({
    // ...
    getFetchOptions: function() {
        var defaults = Page.__super__.getFetchOptions.call();

        return _.extend({}, defaults, {
            beforeSend: function() {
                // custom beforeSend callback
            },
            error: function() {
                // custom error handling
            },
            complete: function() {
                // custom callback on complete
            }
        });
    }
});

Events

Event type Description
shown Fires when page was hidden and became shown.
hidden Fires when page was shown and became hidden.
render Fires when page content needed to be rendered.
var Page = Viewport.Model.extend({
    initialize: function() {
        this.on("shown", this.onShown);
    },
    onShown: function() {
        // event handling
    }
});

Viewport.Collection

Inherited from Backbone.Collection. Stores pages. Accessing pages to add/toggle them. When page is added, new Viewport.View is created and linked to this page.

Properties

Name Type Default Description
el jQuery $('body') Pages container. Available after router initialized. Not allowed in extend().
model Backbone.Model Viewport.Model Type of model used by collection.
view Backbone.View Viewport.View Type of view used by collection. Will be created on collection.add().

Methods

.open(uri)

Open page with given uri and hide others. Find and show() page with uri, hide() other pages.

.pushPage(attributes, $el)

Create a page with given attributes and existing HTML-element ($el) and insert into collection. Useful for rendering start page without loading data. Returns added page.

Viewport.Router

Inherited from Backbone.Router. Listening to URI changes and handling assigned events. Contains Viewport.Collection and accessing it to handle pages.

Properties

Name Type Default Description
collection Backbone.Collection Viewport.Collection Type of collection used by Router.

Methods:

constructor / .initialize([options])

Creates new instance of Viewport.Router. If start=true, runs Backbone.history.start() when initialized and begin listening to URL changes. Options are:

Name Type Default Description
el jQuery $('body') Container of pages' views.
start boolean true Start to listen URI changes when initialized (Run Router.start()).
pushState boolean false Defines which type of routing to use: history.pushState or hash. Will be transmitted to Backbone.history.start().
silent boolean false Tells router not to navigate (in case if page is already rendered). Will be transmitted to Backbone.history.start().
root string '/' Make sense only if pushState=true Will be transmitted to Backbone.history.start().
pages Object[] [] Initial array of pages.
var Router = Viewport.Router.extend({
    // extend default router by routes and methods...
});

var router = new Router({
    el: $('#viewport'),
    pushState: true,
    root: '/path/custom/'
});
.start([options])

Run Backbone.history.start() with pushState, root and silent provided in constructor and overridden by provided directly.

.stop()

Stop watching uri changes (Run Backbone.history.stop()).

.go(attributes[, options])

Read document uri and activate page with given attributes (PlainObject). If page not exists in collection, it will be created with given attributes, and added to collection. URI is generated automatically:

  • If pushState=true: full URI of document
  • If pushState=false: hash part of URI

If page has been already added in collection, it will be just shown, none of attributes will be updated and view will not be re-rendered.

Options are:

Name Type Default Description
force boolean false Update properties of model and make it re-render its view if page is already exists.
load boolean false (For pushState:true) Make request to server with url=page's uri (run Viewport.Model.fetch()), update model with received data

Viewport.Router.go() is not supposed to be used anywhere except for Router's actions.

.navigate(fragment[, options])

Backbone.Router.navigate inherited from Backbone.Router. Use it in code to navigate to some page:

router.navigate("product/1", {
    trigger: true
});

Extending classes

Viewport classes are unsuitable for end use and must be extended. At least, .template() must be provided for Viewport.View:

var View = Viewport.View.extend({
    className: 'my-page-class',
    template: function(data) {
        return _.template('<h1>Page title: <%= title %><h1> ...content... ', data);
    }
});

Viewport.Collection must be extended to use View (also provide model if extended):

var Collection = Viewport.Collection.extend({
    view: View
});

Router must be extended to use Collection and set routes and handlers. Refer Backbone.Router.routes for routing syntax.

var Router = Viewport.Router.extend({
    collection: Collection,
    routes: {
        '': 'home',
        'discovery': 'discovery',
        'product/:name': 'product'
    },
    home: function() {
        this.go({ title: 'Home &ndash; My SPA' });
    },
    discovery: function() {
        this.go({ title: 'Discovery &ndash; My SPA' });
    }
    product: function(name) {
        this.go({ title: name + ' &ndash; My SPA', productName: name });
    }
});

Now application classes are ready to use, but it is not started yet. To start the application, create instance of extended Router:

var router = new Router({
    // options
});

Refer Router.initialize() for options.

Routing process

When Router is initialized, or when URI is changed, Router begin to look for matching routes through routes property and if found, runs route's action. In an action, you can analyze route params, and maybe load some data from server to get some data required by page. this in actions points to current router, so this.go() can be called to open a page with its attributes and data.

Viewport considers each page as unique for different uri. But it may be useful to have one page for many uris, e.g one homepage for / and /#!/. To make it, provide uri manually when go to a page:

var Router = Viewport.Router.extend({
    // ...
    routes: {
        '': 'home',
        '#!/': 'home'
        // other routes
    },
    home: function() {
        this.go({
            uri: '/'
            // page properties
        });
    }
    // other actions
});

Handling hyperlinks

Viewport does not handle any events on hyperlinks. They must be handled manually. Possible example:

// Ensure router is instance of Viewport.Router
$(document).on('click', 'a.spa-link', function(e) {
    if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
        e.preventDefault();

        var url = $(this).attr("href").replace(/^\//,'').replace('\#\!\/','');

        router.navigate(url, {
            trigger: true
        });

        return false;
    }
});

In this example, all hyperlinks with spa-link class will be handled to use Viewport router instead of common behavior.

Updating title

Viewport does not update browser page's title automatically. Page title can be changed on Page shown event:

var Page = Viewport.Model.extend({
    initialize: function() {
        this.on("shown", function() {
            $("title").html(this.get("title"));
        });
    }
});