Saturday, November 23, 2013

Forward and Backward Sliding Region in Marionette

I recently worked on a project where the client had a panel or region on the page and they had a series of views they'd like to show in it in a set order, with the ability to go back.  Kind of like a wizard.  When you did one action, you'd get the next view, but you could go back as well.

I was able to implement this easily using Marionette Regions, but it was missing something.  The continuity of the views was lost as Marionette would replace an old view with the new immediately.  It wasn't the worst thing in the world, and it was mitigated by another 'control' view that helped the user navigate and know where they were.  However, I thought it would be a better experience if it appeared as if the views were sliding forward and backward as well, much like the OSX finder application when navigating directories.  

A quick search revealed that this was possible by just extending Marionette.Region and writing your own custom one.  Code for this region is below:


/*global define*/
/*jshint newcap:false */
define(function (require) {
    "use strict";
    var Marionette = require('marionette'),
        _ = require('underscore'),
        Properties = require('properties'),
        Q = require('q');

    var forward = true,
        backward = false,
        flyIn = true,
        flyOut = false;
    var Region = Marionette.Region.extend({
        initialize : function(){

        },
        //  Overridden show function that uses jquery animations to slide items in and out based on a
        // 'direction', which defaults to 'forward' or true if not present
        // views in this region should have left properties that can move them off the screen.
        show: function (view, direction) {
            var region = this;
            direction = _.isUndefined(direction, forward) ? forward : direction;
            this.ensureEl();

            var isViewClosed = view.isClosed || _.isUndefined(view.$el);

            var isDifferentView = view !== this.currentView;
            var closePromise;
            if (isDifferentView) {
                closePromise = region.close(direction);
            } else {
                closePromise = Q();
            }
            return closePromise.then(function () {
                view.render();
                var openPromise;
                if (isDifferentView || isViewClosed) {
                    openPromise = region.open(view, direction);
                } else {
                    openPromise = Q();
                }
                return openPromise.then(function () {
                    region.currentView = view;

                    Marionette.triggerMethod.call(region, "show", view);
                    Marionette.triggerMethod.call(view, "show");
                });


            })
                .fail(function (error) {
                    console.error(error.stack ? error.stack : error);
                });

        },

        open: function (view, direction) {
            // src  example
//           this.$el.empty().append(view.el);
            var region = this;

            this.$el.html(view.el);

            var outerWidth =  view.$el.outerWidth();
            if(direction === backward){
                outerWidth = -outerWidth;
            }
            view.$el.css({
                left : outerWidth,
                opacity : 0
            });

            return this.slide(view,direction, flyIn)
                .then(function(){
                    region.$el.perfectScrollbar();
                });

        },

        // Close the current view, if there is one. If there is no
        // current view, it does nothing and returns immediately.
        close: function (direction) {
            var view = this.currentView;
            var region = this;
            if (!view || view.isClosed) {
                return Q();
            }
            this.$el.perfectScrollbar('destroy');
            return this.slide(view,direction,flyOut)
                .then(function(){
                    // call 'close' or 'remove', depending on which is found
                    if (view.close) {
                        view.close();
                    }
                    else if (view.remove) {
                        view.remove();
                    }

                    Marionette.triggerMethod.call(region, "close");

                    delete region.currentView;
                });

        },


        slide : function(view, forwardorBackward, flyInOrOut){
            var deferred = Q.defer();
            var animationProps = {
                opacity : flyInOrOut ? 1 : 0
            };
            if(flyInOrOut === flyIn && forwardorBackward === forward){
                animationProps.left = 0;
            }
            if(flyInOrOut === flyOut && forwardorBackward === forward){
                animationProps.left = parseInt(view.$el.css('left'), 10) === 0 ? -view.$el.outerWidth() : 0;
            }
            if(flyInOrOut === flyIn && forwardorBackward === backward){
                animationProps.left = 0;
            }
            if(flyInOrOut === flyOut && forwardorBackward === backward){
                animationProps.left = parseInt(view.$el.css('left'), 10) === 0 ? view.$el.outerWidth() : 0;
            }

            view.$el.animate(animationProps,
                {
                    duration : Properties.slidingAnimationDuration,
                    complete: function () {
                        deferred.resolve();
                    },
                    fail: function () {
                        deferred.reject(arguments);
                    }
                });

            return deferred.promise;
        }
    });

    return Region;
});


A couple of things to note:

  • I'm defining this as an AMD module, if that's not your cup of tea, it shouldn't be that hard for you to remove it.
  • I like promises, a lot. :)  I'm using the popular Q.js library here to help me control the flow.  Again, if not your cup of tea, you can substitute in promise library of your choice or convert to pure callback style.  
  • I was using the jquery plugin perfect scrollbar.  This could easily be taken out if it's not your thing as well.  Same goes for the "Properties", just a way to keep this configurable in my app, it's only controlling the duration of the slide.
  • There is a requirement that views being shown by this region implement a positioning style that allows them to be shifted left by css properties.  I defined a CSS class called 'slide-animate' with style:  "slide-animate { position relative; left: 0px;}" and placed it on all the views that will be managed by this region.
Let's dive through the code just a bit so we understand what's happening.  First standing on the shoulders of others, the flow of code is taken from the Marionette's Region so as to be compatible.  So a lot of the code for "ensureEL()" or checking to verify a view is closed is straight from that.  

The meat is really in the show method which is responsible for closing the old view and opening the new one.  The one tricky part is that the view needs to be completely rendered so that jQuery's outerWidth() call will work correctly to determine how far we need to slide in or out.  So we render the view, and place it in the DOM, then immediately shift it out of view with the "left" property (in the case we're moving 'foward' its moved positively, otherwise negatively).  Then it's just a matter of animating in the left (and opacity for style) attribute to bring it into or out of view.  Promises are used to help me control the flow better.

A couple of things could make this better, I'm not 100% happy with my use of booleans to move forward or backward, so that could be done better, and I could probably extract out the dependency on perfectscrollbar, but I wanted to get this out there quickly since I haven't blogged in awhile.  If this is useful, I could see it being a nice Marionette plugin for the community.  Let me know what you think in the comments below!


Sunday, July 7, 2013

Setting up Many to Many relationships in Backbone Relational

We're using Backbone Relational in our application.  We actually switched to relational after trying to manage those relationships ourselves via parse and initialize methods but we found that to be really annoying and frustrating.  Relational has some nice benefits, automatically fetching relationships, keeping a store of objects and enforcing relationships.  One thing it doesn't do though is Many to Many relationships.  I'd like to share how we went about doing that in case other people would find it useful.

The path forward is recommended on relational's documentation, and a classic way of handling many-to-many, try to split it into two one-to-many relationships.  The intent is that your domain model really should be split up into two relationships, but sometimes you still need to have that many-to-many.

The best way to deal with this without re-writing Relational or using another library (none of which currently do Many To Many relationships) is to break up the Many To Many into two One To Many relationships.

Our Example.

Let's keep it simple, A has many B's (plural of 'b', pronounced 'bees'), B has many A's.


Normally you can only setup one of those relationships, and Relational will not allow you to make another.  If you try it anyway, or don't realize you even have this type of relationship, Relational will actually remove the relationship from the first model that gets it and set it up on the other one.  This can cause annoying errors where things are null when they shouldn't be. 

If you want to keep it many to many, you need to set up a "join" object, in fact this is doing exactly what docs would say to do, but the docs suggest you have some sort of concept for this join table, and if you do have a concept for this, then by all means do so.  


The Implementation


First off, pick a parent relationship, we'll use A -> B in this case.  Then for every A model, you construct a join object AB.  That join object then has a hasMany object to to B.  

So, let's say we have our definition for B, it is pretty basic, just a regular model:

var models = {}
models.ModelB = Backbone.RelationalModel.extend({
    urlRoot: 'someUrl'
});
models.CollectionB = Backbone.Collection.extend({
    model: models.ModelB
});
Next, we need to build our 'Join' model and collection
// Join table
models.ABJoin = Backbone.RelationalModel.extend({
    relations: [
        {
            type: Backbone.HasOne,
            key: 'b',
            relatedModel: models.ModelB,
            reverseRelation: {
                key: 'abJoins',
                includeInJSON: false
            }
        }
    ],

    toJSON: function () {
        return this.get('b').id;
    }
});

models.ABJoinCollection = Backbone.Collection.extend({
    model: models.ABJoin
});
This is where it starts to get more interesting. What we're defining here is a go-between of A to B. This object will have a 'b' reference AND an 'a' reference (we'll get to that later). Each join object will have many A's and many B's. We still want to be able to have Backbone handle saves, deletes, and posts correctly though. To that end, we override the toJSON() to return what we want directly from 'b'. This support's our model of returning the id. You could have it delegate to 'b's toJSON() method if you wish. Finally, we need to define our A model:
models.ModelA = Backbone.RelationalModel.extend({
    url: 'someUrl',
    relations: [
        {
            type: Backbone.HasMany,
            key: 'abJoins',
            relatedModel: models.ABJoin,
            collectionType: models.ABJoinCollection,
            keyDestination: 'bs',
            autoFetch: false,
            reverseRelation: {
                key: 'a',
                includeInJSON: true
            }
        }
    ],
    parse: function (response) {
        // plural of 'b'
        var bs = response.bs;
        if (bs) {
            response.abJoins = _.map(bs, function (b) {
                return {b: b};
            });
            delete response.bs;
        }

        return response;
    }
});

The relation defined above says that A will have many join objects, with the reverse relation (defaulting to one to many) being named 'a'. That's how the join object also has an 'a' reference (in addition to the 'b' reference). There are a couple of other things of note here. Notice the key destination 'bs'. This is also to support Backbone Sync, essentially telling Relational that when you toJSON the join table, call the result 'bs'. You may also notice the key is 'abJoins', but we don't want the server to be aware of this join object, in all likelihood the server is sending you an array of 'b' objects. That's where the overridden parse method comes in. Parse get's the raw JSON and allows us to tweak it. So what we do is take that JSON and wrap it (using underscore lib here) in a join object JSON. You may ask why the 'if' check there. Well, a weird thing with Relational we found was that parse would be called multiple times, so we had to be a bit defensive.

Edit:  One thing not to forget when passing your json to your models or collection is to set the {parse : true} option!

That's pretty much it.  We've been using this for a couple of months now.  It's nice in that it works, but it definitely doesn't feel completely right.  There's a lot of boilerplate that will need to go on for every relationship like this.  However until something better comes along, or data models stop having many-to-many relationships, it will do.

Edit:  I spent some time making an executable example on the above.  Here's the jsFiddle:  http://jsfiddle.net/mmacaula/XaESG/2/

Sunday, June 9, 2013

Development Terminal Coolness

Hello!  Inspired by a talk at KCDC about pushing yourself to the next level professionally, I've decided to start up a blog where I talk about technical things that I do that might be useful to a wider audience.  So without further ado...

My Development Setup

We use Node.js with Express to serve up our applications and we use Grunt to build our application and do all the stuff to package our app.   Minify, compile Less, uglify, run JSHint, even run our tests.  My goal is to have more immediate feedback on my coding so if I do something wrong, I get an error immediately.  So for example, say I'm coding and something I do breaks a test, I should get a warning immediately so I can be aware of my mistake and correct it.

Grunt "watch"

Grunt has a plugin called 'watch' that will get us started.  To use it, you need to include 'grunt-contrib-watch' in your package.json file and then configure it like so in your GruntFile.js:
... 
// A task that runs in the background 'watching' for changes to code.
watch: {
    css : {
        files: ['client/assets/less/*.less', 'client/assets/css/**/*.css'],
        tasks: ['css']
    },
    debug: {
        files: ['client/app/**/*', 'client/assets/**/*'],
        tasks: ['assemble']
    },
    test : {
        files : ['client/app/**/*','client/test/**/*'],
        tasks: ['jshint','jasmine', 'mochacli']
    }
},
...
Here you can see my 'watch' configuration, which is actually 3 of them for the 3 different things I want to do. 'css' watches all my less and css files and when one of them changes grunt will trigger the 'css' task. The test subtask does the same thing, this time it watches every file in the app folder as well as the test folder and then runs 3 tasks, 'jshint', 'jasmine', and 'mochacli'. Pretty cool. To run this, just type

grunt watch
Which will 'watch' all the subtasks we defined above. If we only want to watch css, you can run
grunt watch:css

Grunt Parallel

This is all well and good, but what if I want to watch the css and the tests, but also run my server to serve up my application at the same time?  watch is a task which never ends, so I can't type
grunt watch:css runapp:development
or anything like that. Since watch:css will never end, runapp:development (which starts up my express web server) will never be run.  I could open a second terminal window and do runapp:development in there, but what if I also want to run my tests in a server via "runapp:test"?  I'd have to open another window
Enter Grunt-parallel, which lets you run multiple tasks at the same time. To install
npm install grunt-parallel --save-dev
Then, load it up in your GruntFile.js
grunt.loadNpmTasks('grunt-parallel');
With that done, we'll load configure it to run our application (via the runapp:development task) and also run the watch tasks that we want.
... 
parallel : {
    development : {
        options : {
            grunt:true,
            stream:true
        },
        tasks : ['watch:test', 'watch:css','runapp:development', 'runapp:test']

    }
}
...
Here we see a "development" task, which could be named anything, with several tasks running. First, our two watch tasks, then we have our runapp:development and finally we have our runapp:test task which starts up another express instance serving up our jasmine test files! This server is on a different port to prevent conflicts.  Now if a test fails, I'll get an error in my teminal.  Sometimes the output in the test errors are not very helpful and I need to debug more.  If I need to, I can easily jump to a browser and debug the tests from there because the tests are also being served up there!

The two options spelled above, "grunt:true" and "stream:true" which means that all my sub-tasks are grunt tasks and they're all never-ending so stream the output to the console for me.

Taking it to the next level with iTerm2

If you're using a Mac and like the power of the terminal, you should definitely check out iTerm2 link.  It has a whole host of very cool features, paste history, a way to go back in time, easily customizable color profiles, mouseless selection, plus more.  But the one I want to highlight today is its ability to trigger notifications on your desktop based on some output from your terminal.  You can use growl or since its a paid app now, you can install a program called "terminal-notifier" which provides a command line interface for publishing notifications to the desktop.  To install just run (assuming you have ruby installed):
sudo gem install terminal-notifier

Then, once it's installed, open up the iterm2 Preferences -> Profiles -> Advanced -> Triggers -> Edit and add the following trigger.  For the regular expression I used: "Warning: Task ".*" failed" which Grunt uses for whenever a task fails.  For the action use "Run Command" and for parameters put in:


terminal-notifier -message "\0 click to open tests in browser" -title "Oh no!" -open http://localhost:8888/

There are many more options you can use, to check them out, visit the project homepage (link) or check out this article for more of a summary.  What the command above does is take the regex we defined earlier and place that in the "\0" sequence in the message.  The -open flag lets you kick off a browser window to wherever you'd like.  In my case, I am pointing it to my (currently running) test server so that just by clicking the link It will open up a web page to what I broke in the tests (assuming it is a test I broke and not the jshint task).

Putting it all together

I now have a very nice single-terminal-window environment where I keep my less compiled on every change, run my tests and linter on every change and get notified immediately via the terminal and also the Mac notification bar that something has gone wrong!  Pretty slick!