Tuesday, June 24, 2014

A Backbone-JQuery Mobile skeleton project -

I've been playing around with integrating Backbone and jQuery Mobile. I'm planning on using them as the framework for implementing a mobile version of FuelMyRoute.

I started with Christophe Coenraets' backbone-jquerymobile sample app and made changes to bring it up to date with the most recent versions of jQuery Mobile and jQuery. I also made a couple different design choices. In this post I'll go over the changes I made.

jQuery.on

A couple of jQuery's event listener methods were removed in 1.9: live and bind. I updated several event listeners, like the mobileinit event listener, to use jQuery.on instead.

mobileinit

To configure jQuery Mobile, you set up a listener for the mobileinit event and set properties on the $.mobile object. Here's what I have so far.

$(document).on("mobileinit", function () {
    $.mobile.ajaxEnabled = false;
    $.mobile.linkBindingEnabled = false;
    $.mobile.hashListeningEnabled = false;
    $.mobile.pushStateEnabled = false;
    $.mobile.autoInitializePage = false;
});

This is identical to Coenraets' config except that I also added setting autoInitializePage to false. We'll get to that in a bit. The other settings are there to turn off the routing and link hijacking behaviour in jQuery Mobile so we can use Backbone for routing instead. Coenraets explains in his blog post on the topic and the jQuery Mobile project also has notes on integrating with Backbone.

autoInitializePage

By default jQuery Mobile will call initializePage() when it starts up. initializePage() is important because it initializes the pagecontainer and sets up navigation events. It will try to find the first div with data-role="page" and change to that page. If there isn't a page in the DOM, it will create an initial dummy page. This generally works out fine if you are using jQuery Mobile in the more typical way where you have multiple pages defined in the one page. However, with our Backbone integration, there isn't any page until the router runs and injects the first page into the DOM so it doesn't make sense to automatically call initializePage().

So what I did was set autoInitializePage to false. The Router will call initializePage() after it adds the first page to the DOM.

var AppRouter = Backbone.Router.extend({

    ...
    changePage:function (page) {
        // Render and add page to DOM once
        if ($('#'+page.id).length === 0) {
            $('body').append(page.render().$el);
        }
        if (this.firstPage) {
            // We turned off $.mobile.autoInitializePage, but now that we've
            // added our first page to the DOM, we can now call initializePage.
            $.mobile.initializePage();
            this.firstPage = false;
        }
        $( ":mobile-pagecontainer" ).pagecontainer( "change", page.$el,
                { changeHash: false });
    }
    ...
});

Managing Pages as Views

There are a couple different ways to go when managing page Views. You could remove them and recreate them each time or you could not remove them and just let jQuery Mobile hide and show them. Coenraets went with the "recreate each time" approach. I went with the approach of letting jQuery Mobile hide and show pages. My page Views are rendered and then added to the DOM once.

I did this because it seems a better fit with jQuery Mobile. Also I think there is a performance benefit on mobile to keeping DOM manipulations to a minimum.

However, I am still concerned about my page Views being bound to models even when they aren't being shown. Offscreen pages might re-render unnecessarily due to model/collection updates.

I might add to my router the ability to let pages know when they are being hidden or shown and that would allow a page to deal with offscreen rendering performance issues. One thing a page View might do is unbind its listeners when being hidden and rebind them when being shown again, which might be appropriate if intermediate events can be safely ignored. Another thing a page View might do when not being shown is continue to listen for events but defer any re-rendering until actually shown. I'll explore this more as needed, but I need a real world example to guide the design.

Enhancing pages

I did a little exploration into how you would handle jQuery Mobile enhancement if you re-rendered page. Basically, whenever render() is called, the page needs to be re-enhanced. jQuery Mobile will automatically enhance a page the first time it is changed to, but after that you need to call it yourself. I implemented a View base class I call PageView that has an enhance method. It allows you to implement render like so:

render: function () {
    // do some rendering
    // ...
    this.enhance(); // See PageView.enhance for details
    return this;
}

Keep in mind, though, re-rendering and re-enhancing the entire page is discouraged for performance reasons.

Minor stuff

  • $.mobile.changePage is deprecated. I switched to the change method on the pagecontainer. See the changePage method on the AppRouter above.
  • Instead of <div data-role="content">, use <div class="ui-content" role="main">
  • Contrary to Coenraets, I didn't find I needed to disable the first page transition, so I removed that code.
  • I also switched to project to use Bower and RequireJS to manage dependencies.
  • I never got around to dealing with transitions. Delegating routing to Backbone means you miss out on the declarative transition stuff in jQuery Mobile. I'm thinking that eventually I might add a transitions hash to the Router that would allow you to define page to page transitions, something like
    transitions: {
        'page1:page2':  'slide',
        // etc...
    }
    

Sunday, June 01, 2014

Git client setup

These are some settings and configuration I've found useful so far while working with Git.

Git global configuration

git config --global user.name "Marcus Christie"
git config --global user.email marc.christie@gmail.com

Can also set an editor to use for commit messages

git config --global core.editor gvim

Or with MacVim on OS X

git config --global core.editor 'mvim -f'

However, I think I prefer to edit commit messages in the terminal.

You can also use gvimdiff as a merge tool, but I haven't tried this yet.

Excludes file

Set up a global excludes file in ~/.config/git/ignore. My entries so far:

*.swp
.DS_Store
Session.vim

Whitespace

On Windows you want

git config --global core.autoclrf true

I think this is the default on Windows also, so this may be unnecessary.

Credential helper

This tip is mainly for when you are working with a repo over HTTPS, for example, with GitHub, and you authenticate with a username and password. You don't want to have to type passwords over and over. For this you can specify a credential helper that will securely store your password. On OS X:

git config --global credential.helper osxkeychain

On Windows, download git-credential-store. Then start up Git Bash, go to the directory where you downloaded the executable and then run it from the command line

$ git-credential-winstore.exe

Aliases

Shortcuts so you can run git ci -m "fixed bug" instead of typing out commit

git config --global alias.ci commit
git config --global alias.st status
git config --global alias.co checkout

Bash completions

On Windows, Git Bash comes with these already configured. On OS X, I downloaded git-completion.bash, dropped it in ~/bin and in .bash_profile I have

## Git completions
source ~/bin/git-completion.bash

Web viewer

On OS X:

git config --global instaweb.httpd webrick

Now you can start up a web viewer with

git instaweb

As a Mercurial refugee, I got used to hg serve so I like to have a web view.

Haven't tried doing this on Windows yet.

push.default

Set push.default to simple:

git config --global push.default simple

This setting affects what happens when you do a git push without specifing a remote or branch to push. With simple git will push the current branch to the upstream tracked branch. This mode also is the new default in Git 2.0.

Make sure that when you first push a branch to a remote server that you also set it as the upstream tracked branch. You can do that with the -u option:

git push -u origin mybranch

From then on every push can be done simply with

git push

merge.defaultToUpstream

Set merge.defaultToUpstream to true

git config --global merge.defaultToUpstream true

This setting affects what happens when you do a git merge without specifying a branch to merge from. With this set to true, git will merge in changes from the upstream remote tracking branch, which is generally what you want.

Anything else?

Are there any other things you do to configure Git on a new machine? Let me know in the comments or on Twitter.