<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[George Henderson]]></title><description><![CDATA[code musings from a front-end developer]]></description><link>http://wghenderson.com</link><generator>NodeJS RSS Module</generator><lastBuildDate>Mon, 27 Apr 2026 04:10:18 GMT</lastBuildDate><atom:link href="http://wghenderson.com/rss/" rel="self" type="application/rss+xml"/><author><![CDATA[George Henderson]]></author><ttl>60</ttl><item><title><![CDATA[Bootstrap + Lavaca]]></title><description><![CDATA[<p>This tutorial walks through integrating <a href='http://getbootstrap.com/' >Bootstrap</a> with Lavaca's <a href='https://github.com/mutualmobile/lavaca-starter/' >Starter Application</a>. A sample git repo of this tutorial's end result is available on my <a href='https://github.com/georgehenderson/lavaca-bootstrap' >GitHub</a>.</p>

<h2 id="installbootstrap">Install Bootstrap</h2>

<p>Installing dependencies in Lavaca is easy, thanks to <a href='http://bower.io/' >Bower</a>. Bower is great for managing client-side dependencies, in-fact Lavaca Starter uses Bower to install Lavaca's core library. To install Boostrap add a new dependency in the <code>bower.json</code> file.</p>

<pre><code>"dependencies": {
  "bootstrap": "~3.0.3",
  "lavaca": "latest"
}
</code></pre>

<p>Then open up a terminal and cd into the project's root and run:</p>

<pre><code>'$ bower install'
</code></pre>

<p>During the install, it may ask what version of jQuery to use. Select the first option to use Lavaca's jQuery version.</p>

<p><img src='http://wghenderson.com/content/images/2013/Dec/Screen_Shot_2013_12_20_at_8_37_59_AM.png'  alt="select jQuery version" /></p>

<h2 id="shimbootstrapforrequirejs">Shim Bootstrap for RequireJS</h2>

<p>Bootstrap provides some jQuery plugins to breath life into typical UI components like modals, popovers, and tooltips. These components can be shimmed individually or all at once. For simplicity lets shim the entire library. </p>

<p>In order for Bootstrap's JavaScript components to play nice with jQuery in a RequireJS setting we need to shim in the library. Open <code>src/www/js/app/boot.js</code> and add a path and shim entry.</p>

<pre><code>paths: {
  ...
  'bootstrap': '../components/bootstrap/dist/js/bootstrap'
},
shim: {
  ...
  bootstrap: {
    deps: ['$']
  }
}
</code></pre>

<p>This adds an alias for bootstrap's path to the components directory. The bootstrap module is now available by simply requiring it like <code>require('bootstrap')</code>. The shim entry allows us to define jQuery as a dependency of bootstrap, which prevents bootstrap from loading before jQuery.</p>

<p>The final step in adding Bootstrap's JavaScript components to our project is requiring the module. Since bootstrap just adds plugins to the jQuery namespace, we only need to require the module once. A logical place to add the module is in <code>src/www/js/app/app.js</code>.</p>

<pre><code>define(function(require) {
  var History = require('lavaca/net/History');
  var HomeController = require('./net/HomeController');
  var Connectivity = require('lavaca/net/Connectivity');
  var Application = require('lavaca/mvc/Application');
  var Translation = require('lavaca/util/Translation');
  var headerView = require('app/ui/views/controls/HeaderView');
  require('lavaca/ui/DustTemplate');
  require('hammer');
  require('bootstrap'); // &lt;-- Adds bootstrap's js components
  ...
</code></pre>

<h2 id="tolessornottoless">To LESS or not to LESS</h2>

<p>Lavaca Starter uses LESS out of the Box. Bootstrap gives the option of using LESS or compiled CSS. The LESS option is recommended since it is the most flexible and fits nicely into a typical Lavaca app workflow.</p>

<p>To use Bootstrap's LESS files, just import them into <code>src/www/css/app/app.less</code>.</p>

<pre><code>@import '../libs/libs.less';
@import '../../components/bootstrap/less/bootstrap.less';
@import '../../components/bootstrap/less/theme.less';
...
</code></pre>

<p>That's it!</p>

<p>If you don't plan to theme or make use of the LESS variables you can simply link <code>components/bootstrap/dist/css/bootstrap-min.css</code> and <code>components/bootstrap/dist/css/bootstrap-theme-min.css</code> into <code>src/www/index.html</code>. The only gotcha here is the components directory isn't copied by default during the build process, so your app won't work unless you alter the <code>Gruntfile.js</code>.</p>

<pre><code>copy: {
  www: [
    '&lt;%= paths.out.index %&gt;',
    '&lt;%= paths.out.css %&gt;/&lt;%= package.name %&gt;.css ',
    '&lt;%= paths.out.js %&gt;/&lt;%= package.name %&gt;.min.js',
    'configs/**/*',
    'assets/**/*',
    'messages/**/*',
    'config.xml',
    'components/bootstrap/dist/**/*' // &lt;-- Add boostrap dist directory to copy task
  ]
}
</code></pre>

<h2 id="conclusion">Conclusion</h2>

<p>The default styles for <code>HeaderView</code> and <code>HomeView</code> are not really bootstrap friendly, so you may see the Lavaca logo is misaligned when you run <code>grunt server</code>. These styles are meant to throwaway anyways, so feel free to dump them and start over. </p>

<p>Questions or Feedback? Hit me up in the comments or on twitter @wghenderson</p>]]></description><link>http://wghenderson.com/bootstrap-lavaca/</link><guid isPermaLink="false">8800d2fb-1730-49e9-a653-8c0f492b6c33</guid><dc:creator><![CDATA[George Henderson]]></dc:creator><pubDate>Fri, 20 Dec 2013 17:31:52 GMT</pubDate></item><item><title><![CDATA[Introducing Lavaca DevTools]]></title><description><![CDATA[<p><img src='http://wghenderson.com/content/images/2013/Dec/Screen_Shot_2013_12_04_at_9_35_44_AM.png'  alt="TodoMVC Grab" /></p>

<h4 id="lavacadevelopertoolsanextensionforchromedevtools">Lavaca Developer Tools an extension for Chrome DevTools</h4>

<p>This extension adds a Lavaca panel to Chrome DevTools allowing developers to take a bird's-eye view of their Lavaca application. It features tools for visualizing view hierarchy, inspecting models, and examining defined routes. This extension also adds sidebars to the elements panel for inspecting the view and model associated with the selected element.</p>

<p><a href='https://github.com/georgehenderson/lavaca-devtools' >View on Github</a></p>

<h3 id="quicksetup">Quick Setup</h3>

<ul>
<li>Download <a href='https://chrome.google.com/webstore/detail/lavaca-developer-tools/kbeolfhnclacoappfhlcnlngdonhhmjb?hl=en&amp;gl=US' >Lavaca Developer Tools</a> from the Chrome Web Store</li>
<li>Close and re-open developer tools if it's already open and refresh your browser</li>
</ul>

<p>Don't have a Lavaca app handy for testing. Try inspecting the TodoMVC sample app <a href='http://todomvc.com/labs/dependency-examples/lavaca_require/' >http://todomvc.com/labs/dependency-examples/lavaca_require/</a>.</p>

<h3 id="contributorsetup">Contributor Setup</h3>

<ul>
<li>Clone the repository at <a href='https://github.com/georgehenderson/lavaca-devtools.git' >https://github.com/georgehenderson/lavaca-devtools.git</a></li>
<li>run <code>bower install</code></li>
<li>Visit <a href='chrome://extensions' >chrome://extensions</a> in Chrome</li>
<li>Enable Developer mode</li>
<li>Click on 'Load unpacked extension...'</li>
<li>Choose the folder of the cloned repo</li>
<li>Close and re-open developer tools if it's already open and refresh your browser</li>
<li>Submit your pull requests :)</li>
</ul>

<h3 id="features">Features</h3>

<h4 id="viewstab">Views Tab</h4>

<p>This tab allows you visualize your application's view hierarchy and inspect the models associated with the rendered views. Hovering over each nested view will highlight its placement in the page. Clicking each view will show the selected view's model in the right column. <br />
<img src='http://wghenderson.com/content/images/2013/Dec/Screen_Shot_2013_12_04_at_9_35_44_AM.png'  alt="TodoMVC Views" /></p>

<h4 id="routestab">Routes Tab</h4>

<p>This tab shows the data associated with the defined routes for your application. Including the pattern, controller type, controller action, and additional parameters. <br />
<img src='http://wghenderson.com/content/images/2013/Dec/Screen_Shot_2013_12_04_at_10_11_19_AM.png'  alt="TodoMVC Routes" /></p>

<h4 id="optionstabanddebuggingoptimizedapps">Options Tab and Debugging Optimized Apps</h4>

<p>Debugging applications optimized with Lavaca's build process require a namespace before the application can be recognized. To add a namespace to your optimized Lavaca app, edit the applications <code>amd-dist</code> config in the Gruntfile.js. Add an <code>exports</code> option with the variable to assign to the <code>window</code>. Note applications that use require.js don't require a namespace, so during development this is not necessary.</p>

<pre><code>'amd-dist': {
  all: {
    options: {
      standalone: true,
      exports: 'myAppsNamespace'
    },
</code></pre>

<p>Create a new build running <code>grunt build</code> and deploy. Then add the namespace to the options tab, in this case the value would be <code>myAppsNamespace</code>. Once added, refresh the page and debug as usual. <br />
<img src='http://wghenderson.com/content/images/2013/Dec/Screen_Shot_2013_12_04_at_10_11_25_AM.png'  alt="TodoMVC Options" /></p>

<h4 id="elementspanel">Elements Panel</h4>

<p>The <code>Lavaca View Properties</code> and <code>Lavaca Model Properties</code> sidebars expose the current view and model in the selected elements scope. If the selected element is not associated with view it defaults to the view on the highest layer. <br />
<img src='http://wghenderson.com/content/images/2013/Dec/Screen_Shot_2013_12_04_at_10_12_01_AM.png'  alt="TodoMVC Elements" /></p>

<h4 id="consolevariables">Console Variables</h4>

<p>The sidebars also add the current view and model references to console.</p>

<ul>
<li><code>$view</code> The current view in the selected elements scope. Defaults to the view on the highest layer.</li>
<li><code>$model</code> The current view's model in the selected elements scope. Defaults to the model of the view on the highest layer.</li>
</ul>

<p>Questions or Feedback? Hit me up in the comments or on twitter <a href='https://twitter.com/wghenderson' >@wghenderson</a></p>]]></description><link>http://wghenderson.com/introducing-lavaca-developer-tools/</link><guid isPermaLink="false">562521d0-6d25-4bc5-81aa-90d41fa7f690</guid><dc:creator><![CDATA[George Henderson]]></dc:creator><pubDate>Wed, 04 Dec 2013 17:09:05 GMT</pubDate></item><item><title><![CDATA[Integrating Parse with Lavaca — Building Signup and Login Views]]></title><description><![CDATA[<p>The goal of this tutorial is to build a Lavaca app that contains an authentication flow for user signup, login, logout, and password reset with Parse. The end result should provide a seed for future projects or tutorials exploring <a href='http://parse.com/' >Parse</a> and <a href='http://getlavaca.com/' >Lavaca</a>.</p>

<h3 id="projectsetup">Project Setup</h3>

<p>If you haven't already, pull down a copy of <a href='https://github.com/mutualmobile/lavaca-starter' >lavaca-starter</a> and follow the <a href='https://github.com/mutualmobile/lavaca-starter' #getting-started">Getting Started</a> instructions to setup your dev environment. If you would rather just follow along the completed source of this tutorial, you can grab it from my <a href='https://github.com/georgehenderson/lavaca-parse-authflow' >github</a>.</p>

<p>You will also need to setup a new app at <a href='http://parse.com/' >http://Parse.com</a>. When creating your app, make note of the <code>Application ID</code> and <code>Javascript Key</code>, we will use these in our app to initialize the Parse JavaScript SDK.</p>

<h4 id="installdependencies">Install Dependencies</h4>

<p>All client-side dependencies in Lavaca should be managed by Bower where possible. For this tutorial we'll need to add the <a href='https://www.parse.com/docs/downloads' >Parse JavaScript SDK</a> and <a href='http://getbootstrap.com/' >bootstrap</a> as dependencies in <code>./bower.json</code>.</p>

<pre><code>"dependencies": {
  "lavaca": "https://github.com/mutualmobile/lavaca.git#dev",
  "parse": "https://parse.com/downloads/javascript/parse-1.2.12.js",
  "bootstrap": "~3.0.0"
}
</code></pre>

<p>Once the dependencies are added, open the terminal and cd into your project's root directory. Run <code>$ bower install</code> to update <code>./src/www/components</code> with the new dependecies.</p>

<p>In order to use these dependencies with RequireJS we need to update <code>./src/www/js/app/boot.js</code> to shim them in.</p>

<pre><code>paths: {
  ...
  'parse': '../components/parse/index',
  'bootstrap': '../components/bootstrap/dist/js/bootstrap'
},
shim: {
  ...
  parse: {
    exports: 'Parse'
  },
  bootstrap: {
    deps: ['$']
  }
}
</code></pre>

<p>Lastly, we need to import our bootstrap less into <code>./src/www/css/app/app.less</code> so app doesn't look so shitty.</p>

<pre><code>@import '../../components/bootstrap/less/bootstrap.less';
@import '../../components/bootstrap/less/theme.less';
</code></pre>

<h3 id="initializeparseandscaffoldroutes">Initialize Parse and Scaffold Routes</h3>

<p>Our app kicks off from <code>./src/www/js/app/app.js</code>. Edit the file to look like this.</p>

<pre><code>define(function(require) {
  var History = require('lavaca/net/History');
  var HomeController = require('./net/HomeController');
  var Connectivity = require('lavaca/net/Connectivity');
  var Application = require('lavaca/mvc/Application');
  var Translation = require('lavaca/util/Translation');
  var headerView = require('app/ui/views/controls/HeaderView');
  var AuthenticationController = require('app/net/AuthenticationController');
  var Parse = require('parse');
  require('lavaca/ui/DustTemplate');
  require('hammer');
  require('bootstrap');

  Parse.initialize({app_id}, {js_key});


  // Uncomment this section to use hash-based browser history instead of HTML5 history.
  // You should use hash-based history if there's no server-side component supporting your app's routes.
  History.overrideStandardsMode();

  /**
   * Global application-specific object
   * @class app
   * @extends Lavaca.mvc.Application
   */
  var app = new Application(function() {
    // Add routes
    this.router.add({
      '/': [HomeController, 'index'],
      '/logout': [AuthenticationController, 'logout', {bypassAuthentication: true}],
      '/login': [AuthenticationController, 'login', {bypassAuthentication: true}],
      '/signup': [AuthenticationController, 'signup', {bypassAuthentication: true}],
      '/forgot-password': [AuthenticationController, 'forgotPassword', {bypassAuthentication: true}]
    });
    // Initialize messages
    Translation.init('en_US');
    //render header
    headerView.render();
  });

  // Setup offline AJAX handler
  Connectivity.registerOfflineAjaxHandler(function() {
    var hasLoaded = Translation.hasLoaded;
    alert(hasLoaded ? Translation.get('error_offline') : 'No internet connection available. Please check your settings and connection and try again.');
  });

  return app;

});
</code></pre>

<p>You'll notice a few changes from the original file. First we require in a few dependencies needed by our application.</p>

<pre><code>var AuthenticationController = require('app/net/AuthenticationController');
var Parse = require('parse');
...
require('bootstrap')

Parse.initialize({app_id}, {js_key});
</code></pre>

<p>The <code>AuthenticationController</code> will contain the actions for handeling the auth flow, and <code>bootstrap</code> needs to be required once by our app so our responsive header is functional.</p>

<p>In the last line we initialize Parse JavaScript SDK, which we will use as a model and service layer for our application. Be sure to replace the <code>{app_id}</code> and <code>{js_key}</code> with your Parse app keys.</p>

<p>We also added some new routes to our application's router to stub out the complete authentication flow.</p>

<pre><code>this.router.add({
  '/': [HomeController, 'index'],
  '/logout': [AuthenticationController, 'logout', {bypassAuthentication: true}],
  '/login': [AuthenticationController, 'login', {bypassAuthentication: true}],
  '/signup': [AuthenticationController, 'signup', {bypassAuthentication: true}],
  '/forgot-password': [AuthenticationController, 'forgotPassword', {bypassAuthentication: true}]
});
</code></pre>

<p>Each route is mapped to a controller type with a specific action. Optionally you can pass data to the controller action. Note the <code>{bypassAuthentication: true}</code> flag, we will use this to unprotect these routes in <code>./src/www/js/app/net/BaseController</code>.</p>

<h3 id="authenticationcontrollerandlogic">Authentication Controller and Logic</h3>

<p>Before we stub out the <code>AuthenticationController</code>, lets add some logic to our <code>BaseController</code> to handle cases where the user is not logged in. Give this code a quick skim and then replace <code>./src/www/js/app/net/BaseController</code>.</p>

<pre><code>define(function(require) {

  var Controller = require('lavaca/mvc/Controller');
  var merge = require('mout/object/merge');
  var stateModel = require('app/models/StateModel');
  var Parse = require('parse');

  /**
   * Base controller
   * @class app.net.BaseController
   * @extends Lavaca.mvc.Controller
   */
  var BaseController = Controller.extend(function(){
      Controller.apply(this, arguments);
    }, {
    updateState: function(historyState, title, url, stateProps){
      var defaultStateProps = {pageTitle: title};
      this.history(historyState, title, url)();

      stateProps = merge(stateProps || {}, defaultStateProps);
      stateModel.apply(stateProps, true);
      stateModel.trigger('change');
    },
    exec: function(action, params) {
      var redirect = _shouldRedirect.call(this, action, params);

      if (redirect) {
        return this.redirect(redirect);
      } else {
        return Controller.prototype.exec.apply(this, arguments);
      }
    },
    isAuthenticated: function() {
      return !!Parse.User.current();
    }
  });

  function _shouldRedirect(action, params) {
    var redirect;
    if (!params.bypassAuthentication &amp;&amp; !this.isAuthenticated()) {
      redirect = '/login';
    }
    return redirect;
  }

  return BaseController;

});
</code></pre>

<p>You'll notice we overwrote <code>exec</code>. This method calls a controller action with its parameters and history state. It is also the logical place to test if a user has sufficient permissions to visit the specified controller action and redirect them to the login route.</p>

<p>We can check if there is currently an authenticated user by casting a boolean from the return value of <code>Parse.User.current()</code>.</p>

<pre><code>isAuthenticated: function() {
  return !!Parse.User.current();
}
</code></pre>

<p>Since we don't want to redirect all routes to the login screen, we added a flag to bypass authentication.</p>

<pre><code>if (!params.bypassAuthentication &amp;&amp; !this.isAuthenticated()) {
  redirect = '/login';
}
</code></pre>

<p>Next we need to create a new controller to realize the routes we scaffolded in <code>app.js</code>. Create a new file <code>./src/www/js/app/net/AuthenticationController</code> with the code below.</p>

<pre><code>define(function(require) {

  var BaseController = require('app/net/BaseController'),
      stateModel = require('app/models/StateModel'),
      LoginView = require('app/ui/views/LoginView'),
      SignupView = require('app/ui/views/SignupView'),
      ForgotPasswordView = require('app/ui/views/ForgotPasswordView'),
      Parse = require('parse');

  /**
   * Authentication controller
   * @class app.net.AuthenticationController
   * @extends app.net.BaseController
   */
  var AuthenticationController = BaseController.extend({

    logout: function(params, history) {
      Parse.User.logOut();
      stateModel.set('user', null);
      return this.redirect('/login');
    },

    login: function(params, history) {
      Parse.User.logOut();
      stateModel.set('user', null);
      return this
        .view(null, LoginView, {})
        .then(this.updateState(history, 'Login', params.url));
    },

    signup: function(params, history) {
      return this
        .view(null, SignupView, {})
        .then(this.updateState(history, 'Signup', params.url));
    },

    forgotPassword: function(params, history) {
      return this
        .view(null, ForgotPasswordView, {})
        .then(this.updateState(history, 'Forgot Password', params.url));
    }
  });

  return AuthenticationController;

});
</code></pre>

<p>In Lavaca controllers have three main responsibilities:</p>

<ul>
<li>Control the user's flow through the application</li>
<li>Control when data is loaded from, saved to, or modified on the server</li>
<li>Supply the data (model) to the visual presentation (view)</li>
</ul>

<p>The first thing we do is require the views used by the controller. We'll be adding these views to the project later.</p>

<pre><code>...
LoginView = require('app/ui/views/LoginView'),
SignupView = require('app/ui/views/SignupView'),
ForgotPasswordView = require('app/ui/views/ForgotPasswordView'),
...
</code></pre>

<p>The first action is to handle logout. Parse makes this easy for us with a simple method call. After the synchronous call to logout, we unset the user from the state model and redirect the user to the login screen. The <code>return</code> statement is important here. Every controller action should return a promise allowing the router to properly dispose of the controller.</p>

<pre><code>logout: function(params, history) {
  Parse.User.logOut();
  stateModel.set('user', null);
  return this.redirect('/login');
},
</code></pre>

<p>The login action ensures the user is logged out then presents the <code>LoginView</code> to <code>ViewManager</code> to load. Lastly, the action updates the history state with the state, title, and url. This common pattern shared by all of the other controller actions.</p>

<pre><code>login: function(params, history) {
  Parse.User.logOut();
  return this
    .view(null, LoginView, {})
    .then(this.updateState(history, 'Login', params.url));
},
</code></pre>

<h3 id="loginview">Login View</h3>

<p>Every view requires a template. Our login view template needs a form to collect the username and password. We also need links to the forgot password flow and account creation. Create a file called <code>./src/www/js/templates/login.html</code> with this markup.</p>

<pre><code>&lt;form class="form-signin"&gt;
  &lt;h2 class="form-heading"&gt;Login&lt;/h2&gt;
  &lt;input type="email" name="email" placeholder="Email" class="form-control"&gt;
  &lt;input type="password" name="password" placeholder="Password" class="form-control"&gt;
  &lt;button class="btn btn-lg btn-primary btn-block form-control"&gt;Login&lt;/button&gt;
  &lt;p class="login-note"&gt;
    &lt;a href='http://wghenderson.com/signup' &gt;Don't have an account?&lt;/a&gt;
  &lt;/p&gt;
  &lt;p class="login-note"&gt;
    &lt;a href='http://wghenderson.com/forgot-password' &gt;Forgot password?&lt;/a&gt;
  &lt;/p&gt;
&lt;/form&gt;
</code></pre>

<p>Let's also add some css to make this template a little less ugly. Create a new file <code>./src/www/css/app/LoginView.less</code> with these styles.</p>

<pre><code>.login.view {
  form {
    max-width: 330px;
    padding: 15px;
    margin: 0 auto;
    .form-heading, {
      margin-bottom: 10px;
    }
    .checkbox {
      font-weight: normal;
    }
    .form-control {
      position: relative;
      font-size: 16px;
      height: auto;
      padding: 10px;
      -webkit-box-sizing: border-box;
         -moz-box-sizing: border-box;
              box-sizing: border-box;
    }
    .form-control:focus {
      z-index: 2;
    }
    input[type="text"], input[type="email"] {
      margin-bottom: -1px;
      border-bottom-left-radius: 0;
      border-bottom-right-radius: 0;
    }
    input[type="password"] {
      margin-bottom: 10px;
      border-top-left-radius: 0;
      border-top-right-radius: 0;
    }
    button {
      margin: 10px 0;
    }
  }
  .login-note {
    text-align: right;
  }
}
</code></pre>

<p>Make sure to import the new less file into <code>./src/www/css/app/app.less</code> so it gets included.</p>

<pre><code>@import 'LoginView.less';
</code></pre>

<p>Now that we have a template and some styles, lets build a view to render our template and intercept the form's submit. Create a file at <code>./src/www/js/app/ui/views/LoginView.js</code>.</p>

<pre><code>define(function(require) {

  var BaseView = require('./BaseView');
  var router = require('lavaca/mvc/Router');
  var stateModel = require('app/models/StateModel');
  var Parse = require('parse');
  require('rdust!templates/login');

  /**
   * Example view type
   * @class app.ui.views.LoginView
   * @extends app.ui.views.BaseView
   */
  var LoginView = BaseView.extend(function() {
    BaseView.apply(this, arguments);
    this.mapEvent({
      form: {
        submit: this.onFormSubmit.bind(this)
      }
    });
  }, {
    /**
     * The name of the template used by the view
     * @property {String} template
     * @default 'login'
     */
    template: 'templates/login',
    /**
     * A class name added to the view container
     * @property {String} className
     * @default 'login'
     */
    className: 'login',
    onFormSubmit: function(e) {
      e.preventDefault();
      var form = this.el.find('form'),
          email = form.find('[name="email"]').val(),
          password = form.find('[name="password"]').val();
      Parse.User.logIn(email, password).then(function() {
        stateModel.set('user', Parse.User.current());
        router.exec('/');
      }, function(err) {
        console.log(err);
      });
    }

  });

  return LoginView;

});
</code></pre>

<p>In the constructor of the view we add an event listener to intercept the form submit event.</p>

<pre><code>this.mapEvent({
  form: {
    submit: this.onFormSubmit.bind(this)
  }
});
</code></pre>

<p>When the form is submitted, we grab the email and password values and login the user. If the login is successful, we set the current user on the state model broadcasting to the rest of the application that a user has logged in. Later in this tutorial we will use the state model to conditionally show a logout link when a user is logged in. Finally, we execute the index route to take the user home.</p>

<pre><code>onFormSubmit: function(e) {
  e.preventDefault();
  var form = this.el.find('form'),
      email = form.find('[name="email"]').val(),
      password = form.find('[name="password"]').val();
  Parse.User.logIn(email, password).then(function() {
    stateModel.set('user', Parse.User.current());
    router.exec('/');
  }, function(err) {
    console.log(err);
  });
}
</code></pre>

<p>If we were to try to load our application as it stands right now, RequireJS would error out trying to find our views. But, I am kind of interested what the app is looking like, so lets comment out the unused actions and views in our authentication controller.</p>

<pre><code>...
// SignupView = require('app/ui/views/SignupView'),
// ForgotPasswordView = require('app/ui/views/ForgotPasswordView'),

...

// signup: function(params, history) {
//   return this
//     .view(null, SignupView, {})
//     .then(this.updateState(history, 'Signup', params.url));
// },

// forgotPassword: function(params, history) {
//   return this
//     .view(null, ForgotPasswordView, {})
//     .then(this.updateState(history, 'Forgot Password', params.url));
// }

...
</code></pre>

<p>Then open a terminal and start the dev server, <code>$ grunt server</code>. The application should be running on <a href='http://localhost:8080/' >http://localhost:8080</a>.</p>

<p>Once your satisfied the app is coming along, go back to the authentication controller and uncomment the <code>signup</code> action and <code>SignupView</code> require.</p>

<h3 id="accountcreation">Account Creation</h3>

<p>The <code>SignupView</code> will require a very similar template to the <code>LoginView</code>. Create a new file <code>./src/www/js/templates/signup.html</code> with this markup.</p>

<pre><code>&lt;form&gt;
  &lt;h2 class="form-heading"&gt;Sign Up&lt;/h2&gt;
  &lt;input type="email" name="email" placeholder="Email" class="form-control"&gt;
  &lt;input type="password" name="password" placeholder="Password" class="form-control"&gt;
  &lt;button class="btn btn-lg btn-primary btn-block form-control"&gt;Signup&lt;/button&gt;
  &lt;p class="login-note"&gt;&lt;a href='http://wghenderson.com/login' &gt;Already have an account?&lt;/a&gt;&lt;/p&gt;
&lt;/form&gt;
</code></pre>

<p>The <code>SignupView</code> should also function very similarly to the <code>LoginView</code>, but instead of logging in the user on form submit we create a new account. Create a new file for the signup view at <code>./src/www/js/app/ui/views/SignupView.js</code>.</p>

<pre><code>define(function(require) {

  var BaseView = require('./BaseView');
  var router = require('lavaca/mvc/Router');
  var stateModel = require('app/models/StateModel');
  var Parse = require('parse');
  require('rdust!templates/signup');

  /**
   * Example view type
   * @class app.ui.views.SignupView
   * @extends app.ui.views.BaseView
   */
  var SignupView = BaseView.extend(function() {
    BaseView.apply(this, arguments);
    this.mapEvent({
      form: {
        submit: this.onFormSubmit.bind(this)
      }
    });
  }, {
    /**
     * The name of the template used by the view
     * @property {String} template
     * @default 'signup'
     */
    template: 'templates/signup',
    /**
     * A class name added to the view container
     * @property {String} className
     * @default 'signup'
     */
    className: 'signup login',
    onFormSubmit: function(e) {
      e.preventDefault();
      var form = this.el.find('form'),
          email = form.find('[name="email"]').val(),
          password = form.find('[name="password"]').val(),
          userModel = new Parse.User();
      userModel.set({
        username: email,
        password: password,
        email: email
      });
      userModel.signUp().then(function() {
        stateModel.set('user', Parse.User.current());
        router.exec('/');
      }, function(err) {
        console.log(err);
      });
    }

  });

  return SignupView;

});
</code></pre>

<p>Looks familiar huh. The interesting part is <code>onFormSubmit</code>.</p>

<pre><code>onFormSubmit: function(e) {
  e.preventDefault();
  var form = this.el.find('form'),
      email = form.find('[name="email"]').val(),
      password = form.find('[name="password"]').val(),
      userModel = new Parse.User();
  userModel.set({
    username: email,
    password: password,
    email: email
  });
  userModel.signUp().then(function() {
    stateModel.set('user', Parse.User.current());
    router.exec('/');
  }, function(err) {
    console.log(err);
  });
}
</code></pre>

<p>Here we create a new instance of a <code>Parse.User</code> and set some properties on that user. We then call the <code>signUp()</code> method, which posts our user data to Parse for account creation. On success, we update our state model with the current user. The newly created user is logged in automatically by Parse, so when we execute the index route the user should pass authentication without a redirect.</p>

<p>Once you have completed this step, the app should allow you to create a new account. Make sure your dev server is running a visit <a href='http://localhost:8080/signup' >http://localhost:8080/signup</a> and create an account.</p>

<h3 id="logoutandheader">Logout and Header</h3>

<p>Im sure you have noticed that once your logged in, you stay logged in even if you refresh the page. Parse stores the current user in LocalStorage under the key <code>Parse/{app_id}/currentUser</code>. To logout open the webinspector console and run.</p>

<pre><code>require('lavaca/mvc/Router').exec('/logout')
</code></pre>

<p>We can't expect our users to logout this way, so lets update the header to add a conditional logout link and look more bootstrappy. Replace <code>./src/www/js/templates/header.js</code> with:</p>

<pre><code>&lt;div class="navbar navbar-inverse navbar-fixed-top"&gt;
  &lt;div class="container"&gt;
    &lt;div class="navbar-header"&gt;
      &lt;button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse"&gt;
        &lt;span class="icon-bar"&gt;&lt;/span&gt;
        &lt;span class="icon-bar"&gt;&lt;/span&gt;
        &lt;span class="icon-bar"&gt;&lt;/span&gt;
      &lt;/button&gt;
      &lt;a class="navbar-brand" href='http://wghenderson.com/' &gt;Auth Flow&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="navbar-collapse collapse" style="height: 1px;"&gt;
      &lt;ul class="nav navbar-nav"&gt;
        {?user}
        &lt;li&gt;&lt;a href='http://wghenderson.com/logout' &gt;Logout&lt;/a&gt;&lt;/li&gt;
        {/user}
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>

<p>Note we add a logout link when a user is logged in.</p>

<pre><code>{?user}
&lt;li&gt;&lt;a href='http://wghenderson.com/logout' &gt;Logout&lt;/a&gt;&lt;/li&gt;
{/user}
</code></pre>

<p>In order for our <code>HeaderView</code> to be redrawn properly when a user logs in or out. We need to update the scope of the redraw method in <code>./src/www/js/app/ui/views/controls/HeaderView.js</code> to redraw the <code>.navbar-nav</code> when the model changes.</p>

<pre><code>onModelChange: function() {
  this.redraw('.navbar-nav');
}
</code></pre>

<p>In order to beautify the bootstrap header replace <code>./src/www/css/app/HeaderView.less</code> with some new styles.</p>

<pre><code>#nav-header {
  .navbar {
    min-height: 45px;
  }
  .navbar-collapse {
    overflow: hidden;
  }
  .navbar-toggle {
    margin-top: 5px;
    margin-bottom: 5px;
    margin-right: 10px;
  }
  .navbar-header {
    a {
      padding: 12px;
    }
  }
  .navbar-nav {
    margin-top: 0;
    margin-bottom: 0;
    li {
      a {
        padding: 12px;
      }
    }
  }
}
</code></pre>

<p>Lastly in <code>./src/www/css/app/main.less</code> lets fix the view roots height to work with the bootstrap header.</p>

<pre><code>top: 68px;
top: 6.8rem;
</code></pre>

<p>with </p>

<pre><code>top: 44px;
</code></pre>

<p>The app should now be 90% complete! Run the dev server and checkout your handywork.</p>

<h3 id="dontforgetaboutpasswordreset">Don't forget about password reset</h3>

<p>Password reset is a piece of functionality you don't miss until you need it. Luckily Parse makes it easy on us. Open up the <code>AuthenticationController</code> and uncomment the forgot password view and action.</p>

<p>Next we need to create a new template containing a password reset form; name it <code>./src/www/js/templates/password-reset.html</code>.</p>

<pre><code>&lt;form&gt;
  &lt;h2 class="form-heading"&gt;Forgot Password&lt;/h2&gt;
  &lt;input type="email" name="email" placeholder="Email" class="form-control"&gt;
  &lt;button class="btn btn-lg btn-primary btn-block form-control"&gt;Submit&lt;/button&gt;
  &lt;p class="login-note"&gt;&lt;a href='http://wghenderson.com/login' &gt;I remembered, back to login &amp;raquo;&lt;/a&gt;&lt;/p&gt;
  &lt;p class="login-note {noteType}"&gt;{note}&lt;/p&gt;
&lt;/form&gt;
</code></pre>

<p>The last <code>&lt;p&gt;</code> in the template will be used to message the user on a successful or failed reset attempt.</p>

<p>The view interaction for the <code>ForgotPasswordView</code> is setup just like the signup and login views. Create a new file <code>./src/www/js/app/ui/views/ForgotPasswordView.js</code>.</p>

<pre><code>define(function(require) {

  var BaseView = require('./BaseView');
  var Parse = require('parse');
  require('rdust!templates/forgot-password');

  /**
   * Example view type
   * @class app.ui.views.ForgotPasswordView
   * @extends app.ui.views.BaseView
   */
  var ForgotPasswordView = BaseView.extend(function() {
    BaseView.apply(this, arguments);
    this.mapEvent({
      form: {
        submit: this.onFormSubmit.bind(this)
      }
    });
  }, {
    /**
     * The name of the template used by the view
     * @property {String} template
     * @default 'login'
     */
    template: 'templates/forgot-password',
    /**
     * A class name added to the view container
     * @property {String} className
     * @default 'login'
     */
    className: 'forgot-password login',

    onFormSubmit: function(e) {
      e.preventDefault();
      var form = this.el.find('form'),
          email = form.find('[name="email"]').val();
      Parse.User.requestPasswordReset(email).then(function() {
        this.redraw('.login-note', {
          noteType: 'success',
          note: 'An email has been sent with instructions on how to reset your password.'
        });
      }.bind(this), function(error) {
        this.redraw('.login-note', {
          noteType: 'error',
          note: error.message
        });
      }.bind(this));
    }

  });

  return ForgotPasswordView;

});
</code></pre>

<p>Again, <code>onFormSubmit()</code>, is where the action lies. Parse provides the method <code>requestPasswordReset()</code>. When called with a valid email, an email is sent to the user to verify the request. The email contains a link to a password reset form hosted by Parse. </p>

<pre><code>onFormSubmit: function(e) {
  e.preventDefault();
  var form = this.el.find('form'),
      email = form.find('[name="email"]').val();
  Parse.User.requestPasswordReset(email).then(function() {
    this.redraw('.login-note', {
      noteType: 'success',
      note: 'An email has been sent with instructions on how to reset your password.'
    });
  }.bind(this), function(error) {
    this.redraw('.login-note', {
      noteType: 'error',
      note: error.message
    });
  }.bind(this));
}
</code></pre>

<p>On success or failure of the <code>requestPasswordReset()</code> method, we redraw the note with a message indicating the result.</p>

<h3 id="conclusion">Conclusion</h3>

<p>By the end of this tutorial you should have a working Lavaca application integrated with Parse that contains, account creation, login, logout, and password reset. Although this application relied on Parse as a service and model layer, the same patterns can be used to create a authentication flow with any backend system. Hopefully you learned something, if not sorry this was so long.</p>

<p>Get the source on my <a href='https://github.com/georgehenderson/lavaca-parse-authflow' >github</a></p>]]></description><link>http://wghenderson.com/integrating-parse-with-lavaca-—-part-i-building-login-and-signup-views/</link><guid isPermaLink="false">5529038a-c20b-4a50-9c66-ad61db4d5ef9</guid><dc:creator><![CDATA[George Henderson]]></dc:creator><pubDate>Fri, 22 Nov 2013 03:21:07 GMT</pubDate></item></channel></rss>