Showing posts with label Javascript. Show all posts
Showing posts with label Javascript. Show all posts

Tuesday, January 13, 2015

So you want a dashboard?

I recently completed a project for Blue Cross Blue Shield of North Carolina in which we designed and developed a member engagement dashboard, called BlueConnect. Dashboards these days have quite a few features such as draggable tiles, responsive media queries for tablet and mobile devices, animated charts and graphics, third party API services, and maybe even social media integration. I have been swamped with work supporting a project such as this, but I wanted to take some time to jot down some notes and share some insights.

I won't get into the details on everything, but I will just share a few high level concepts. First, some general advice on code structure/team organization.

  • Make sure to leverage a version control system and agree on it first as a team. I highly recommend Vincent Driessen's git flow documented here.
  • Use a build/automation system like Grunt or Gulp. It will save you time at every step of the way and make your code optimized.
  • Choose a client side framework that is robust, and ideally supports testing. We went with Angular because of the widespread community support. Some Angular JS libraries we found useful:
  • For structuring your CSS, I highly recommend splitting your media queries into separate files so that they can be conditionally loaded, and modified for IE. We chose to organize these by breakpoint, and this helped tremendously with code organization.
Now, for the dashboard itself. 

  • First, decide on base tile sizes. Handling a multitude of asynchronous requests in a tile dashboard can be a nightmare if you don't have proper templating and a sound structural approach. For this reason, we first developed base templates upon which our actual content would be loaded, and that could be used for early DOM rendering and tile placement. This can easily be done using Angular's ng-repeat and ng-include directives. These base templates were constrained to "single-wide" and "double-wide" tile for our use case, but these could easily be expanded to as many variations as you need. I recommend choosing sizes that flow easily across different screens for best results, and if you want to meet smaller mobile devices like the iPhone 4, choose a tile size under 320px. Another nice thing about Angular's two way binding/ng-repeat, is that it will automatically re-render tiles quickly if you programmatically change the list (say from outside events, or a change in viewport size).
  • Choose a bin-packing library. These are useful for arranging items within a space in the most efficient way. There are a few of these out there, but the best one I've seen by far is Packery JS. These make your dashboard elements fit nicely, and provide support for things like draggability. There is some great examples of this with Angular here and here and here.
  • After the base content has loaded and you have initialized Packery, you'll want to render your content within your base template (possibly using ng-include or other Angular mechanisms). Ng-include supports variable templates, so we simply passed the name of our content templates in our tile array object. I recommend showing the user a loading spinner for any asynchronous loaded content (such as social media or third party feeds) in your base template. You can do this easily using Angular-Spinner and libraries such as imagesLoaded for any longer loading graphics. 
Lastly, consider animations for your app. These may require some special handling, but for simple animations, you can make use of Animate.css. We chose to add a delayed "fade In" effect for the tiles, which nicely presented them to the user as their content is loaded. Another useful library for animating graphs is Highcharts.js. This library certainly gives your application a dashboard "feel" as your user is presented with animated data charts.

There you have it...a dashboard is waiting ahead! If you have any trouble or questions, please feel free to contact me. 

Monday, March 10, 2014

Amazing Builds with Grunt.js

Continuing with my previous post on my new app Urban Champ, I recently optimized my build process using Grunt.js.

Grunt is an amazing build tool which can be used to do a variety of things. Currently I am using Grunt tasks for concatenating and minifying css files, running the RequireJS build optimizer, preprocessing HTML (to do things like point your built HTML at compiled/minified scripts), and image minification. Grunt is also extremely useful for separating your build targets (such as development and production) and then firing up a server on your localhost. I really like the livereload grunt "watch" task which will automatically reload your server as you change source files in your editor.

My hats off to the Grunt team! I will definitely be keeping a close eye on future Yeoman generators using Grunt. This is an extremely powerful tool for all you web devs out there!

Node grunt packages installed:
{
  "name": "UrbanChamp",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-concat": "^0.3.0",
    "grunt-contrib-uglify": "^0.4.0",
    "grunt-contrib-cssmin": "^0.9.0",
    "grunt-contrib-requirejs": "^0.4.3",
    "grunt-contrib-clean": "^0.5.0",
    "grunt-open": "^0.2.3",
    "grunt-contrib-connect": "^0.7.1",
    "grunt-contrib-watch": "^0.5.3",
    "grunt-contrib-livereload": "^0.1.2",
    "grunt-uncss": "^0.2.0",
    "grunt-processhtml": "^0.3.0",
    "connect-modrewrite": "^0.6.3-pre",
    "grunt-contrib-imagemin": "^0.5.0"
  }
}

Gruntfile.js:
'use strict';
var modRewrite = require('connect-modrewrite');
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};


module.exports = function (grunt) {
    grunt.initConfig({
      clean: {
        build: {
          src: ['build']
        },
        css: {
          src: ['build/App/css/*.css','!build/App/css/tidy.min.css']
        },
        images: {
          src: ['build/App/images/*']
        }
      },
      watch: {
          livereload: {
              files: [
                  'app/App/*.html',
                  '{.tmp,app}/App/css/{,*/}*.css',
                  '{.tmp,app}/App/js/{,*/}*.js',
                  'app/App/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
              ],
              tasks: ['livereload']
          }
      },  
      connect: {
          options: {
              port: 8888,
              // change this to '0.0.0.0' to access the server from outside
              hostname: 'localhost'
          },
          livereload: {
              options: {
                  middleware: function (connect) {
                      return [
                          modRewrite(
             ['!\\.html|\\.js|\\.svg|\\.css|\\.png|\\.jpg$ /index.html [L]']),
                          lrSnippet,
                          mountFolder(connect, '.tmp'),
                          mountFolder(connect, 'app')
                      ];
                  }
              }
          },
          dist: {
              options: {
                  middleware: function (connect) {
                      return [
                          mountFolder(connect, 'build')
                      ];
                  }
              }
          }
      },          
      open: {
          server: {
              path: 'http://localhost:8888'
          }
      },      
      requirejs: {
        dist: {
         options: {
           appDir: 'app',
            baseUrl: 'App/js', 
            optimize: 'uglify',
            preserveLicenseComments: false,        
            mainConfigFile: 'app/App/js/main.js',
            dir: 'build'
         }
        }
      },
      concat: {
        dist: {
          src: ['app/App/css/*'],
          dest: 'build/App/css/tidy.css'
        }
      },
      cssmin: {
          dist: {
            src:'build/App/css/tidy.css',
            dest: 'build/App/css/tidy.min.css'
          }
      },      
      processhtml: {
        dist: {
          files: {
            'build/index.html': ['app/index.html']
          }
        }
      },
      imagemin:{
        dynamic: {
          files: [{
            expand: true,                  // Enable dynamic expansion
            cwd: 'app/App/images',                   // Src matches are relative to this path
            src: ['**/*.{png,jpg,gif}'],   // Actual patterns to match
            dest: 'build/app/images'                  // Destination path prefix
          }]          
        }

      }            
});


// load plugins
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-open');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-processhtml');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-imagemin');


// register at least this one task
grunt.registerTask('build', ['clean:build','requirejs', 'concat','cssmin','processhtml', 'clean:css', 'clean:images', 'imagemin']);
grunt.registerTask('default', ['build']);

grunt.registerTask('server', function (target) {
    if (target === 'dist') {
        return grunt.task.run(['build', 'open:server', 'connect:dist:keepalive']);
    }

grunt.task.run(['open:server', 'connect:livereload:keepalive', 'watch']);
});
};

Monday, July 29, 2013

Backbone Marionette apps with Parse!

I have recently completed a simple Backbone Marionette application using the Parse SDK as a stoarge back-end and cloud hosting environment. As far as I know, this is the first "Todo" template out there using these frameworks. You can see the application here:
http://marionette-todo.parseapp.com/

And the source code here:
https://github.com/priley86/parse-marionette-todo

There is also an ASP.NET MVC4 template for those that prefer a Visual Studio development environment:
 https://github.com/priley86/marionette-mvc4-bootstrap-template/tree/Parse-Backend

All of the documentation about the frameworks and setup is listed on the github markdown file, but I wanted to point out a few things about this template and the benefit from using this approach.

First, you vastly cut down on application development and setup time by using the Parse back-end. Parse handles all of the object storage for you and sets up flexible APIs for you automatically when you save objects to your Parse application via the SDK. No more time wasted worrying about the database, user management, or an API layer. It's all done. Oh, and by the way, they'll host your web application for you. It's all free while you are developing, and very cheap to get enterprise scale hosting and custom domains. Start-up doesn't get any easier than this.

Second, Marionette is a very extensible plugin for Backbone which makes your single page app development much smoother. You save lines of code (because Marionette handles the trivial things like template rendering), and your application becomes easier to manage and maintain. Marionette takes care of the things you frequently do, and most of the things that Backbone forgets to, like event aggregation, view structuring (for collections and models), view layouts, and much more.

A few things to point out in each of the templates that I found important as I wrote it. There isn't enough time to cover everything on Parse and Backbone-Marionette, but the links below will help get you familiar with them:

Marionette
https://github.com/marionettejs/backbone.marionette

Parse

First, you will want to make sure you take care of your Parse initialization in it's own Require JS module. Running this in another module or outside of Require will lead to errors. Also, make sure you set the Parse jquery reference to your application's jquery reference since Parse depends on this:

/* Parse.js */
define(
  ['jquery'
], function ($) {
      "use strict";

      Parse.$ = $;
      Parse.initialize("YOUR_APP_ID", "YOUR_JAVASCRIPT_KEY");
      return Parse;
  }
);

Next, notice that our collections and models are no longer Backbone collections and models, but Parse collections and models:

 /* Todo.js */
define(['Parse'], function (Parse) {
    'use strict';

    return Parse.Object.extend({
        className: "Todo",
        defaults: {
            title: '',
            completed: false,
            created: 0
        }
     ...

/* TodoList.js */
define(['Parse', '../models/Todo'], function (Parse, Todo) {
    'use strict';

    return Parse.Collection.extend({
        model: Todo,
    ...

Now that we have our Parse models and collections, it is important to remember to set the current user and their access control list (created in the login view) before creating any new persisted models

 /* Header.js */
define(['marionette', 'tpl!../templates/header.html'], function (Marionette, header) {
  "use strict";

  return Marionette.ItemView.extend({
      ...
       if (evt.which === ENTER_KEY && todoText) {
        this.collection.create({
            title: todoText,
            user: Parse.User.current(),
            ACL: new Parse.ACL(Parse.User.current())
        });

And when querying your Parse user's persisted objects, set the query parameter's accordingly:

 /* Layout.js */
        ...
        onShow: function () {
            
            //instantiate our collection instance for each of our views
            var viewOptions = {
                collection : this.todoList
            };

            this.headerRegion.show(new HeaderView(viewOptions));
            this.mainRegion.show(new TodoListCompositeView(viewOptions));
            this.footerRegion.show(new FooterView(viewOptions));
            
            // Setup the query for the collection to look for todos from the current user
            this.todoList.query = new Parse.Query(Todo);
            this.todoList.query.equalTo("user", Parse.User.current());

            // Fetch all the todo items for this user
            this.todoList.fetch();

If you do this correctly, you will see the user's objects created successfully with the user specified:





Lastly, there is an easy guide for deploying your application to Parse. A few quick shell commands to deploy your app to the Parse cloud, and your app is up and running. Guide:
https://parse.com/docs/cloud_code_guide#hosting

Way to simple, right? I will be blogging more about the Parse SDK in the future as I develop some amazing cloud applications! Parse has way too many features to cover in one blog post, so stay tuned!

Saturday, June 8, 2013

MVC4-Marionette TodoMVC Template

It has been some time since I last posted, and honestly I'm not sure how long it will be until the next.... But I try to leave my interesting findings on this blog when I have the time. Lately I have been totally devoted to various single page application frameworks, and all of us in the development community have seen a great deal of popularity in this area lately. Single page javascript applications are fast, do most of their work on the client browser, and scale easily. They also make applications much more responsive because most of your client/server requests are lessened to the JSON model data returned by the asynchronous ajax requests your application makes after the initial page load. Google's Gmail is a great example of a SPA. The days of reloading/resubmitting lots of html from the server to the browser after each page request are coming to a close.

There are a large number of SPA frameworks gaining steam, notably Durandal, Backbone, and Angular JS. Each of these SPA variants often share many of the same core ideas, approaches, and libraries for implementing SPAs:

Typically there is the following components in a good SPA framework:

  • A templating library for interactive HTML templates (example library: Underscore js)
  • A binding library for DOM/HTML & Javascript application interaction. This typically handles things like application events, callbacks, model HTML bindings (example libraries: Knockout, Backbone)
  • A routing library for handling client side application routing (example libraries: Sammy js, Backbone)
  • AMD libraries for module definitions. These enable you to use common patterns for defining separated modules and make up for some of the inherent limitations of the javascript language. An example widely used in most SPAs today is Require js. 
  • Methods for synchronizing or serializing your model data to and from the server (whether this is via a service or directly to database file) OR HTML5's local storage (which has become quite useful for many test applications). Some example libraries for this include Breeze js and Backbone js. 
As you can see, Backbone is a pretty extensive framework for building single page apps, thus the reason it is being used by many of the most popular websites in the world today. There are also many extensions available (such as Marionette) which simplify the development process and reduce the amount of repeatable code you need to write.

I have recently begun work on a simple SPA for a client and decided to share my template for the rest of the community. It makes use of the following frameworks:

  • Underscore
  • Backbone
  • Marionette
  • Twitter Bootstrap styling
  • Require
  • MVC4/Web API

I have not included any Web API methods in this application (as it makes use of a Backbone local storage library), but the frameworks are provided in the default MVC4 project in Visual Studio 2012. The application provided is a simple refactorization of Jarrod Overson's Backbone/Marionette TodoMVC application. This approach differs in that it aims to allow for ample division in structure for specific pages within your SPA.

The main reason I wanted to make this open to the public was because I was not able to find a Backbone/Marionette MVC template which makes use of Microsoft's nuGet package management tools in Visual Studio. NuGet is most widely used with Microsoft applications and makes package management easy because of its integration with Visual Studio.Often JS libraries pulled from various sources can be difficult to manage and maintain from version to version. NuGet attempts to resolve these issues by managing dependencies between libraries.

Backbone and Marionette have been upgraded to the latest stable versions in this template (Backbone v1.0.0 and Marionette 1.03.1).

My template:
https://github.com/priley86/marionette-mvc4-bootstrap-template

Jarrod Overson's (older) template:
https://github.com/jsoverson/todomvc/tree/master/labs/dependency-examples/backbone_marionette_require

I will likely add additional findings to this template in the future as I come upon them, so please stay tuned.

Enjoy!