Sunday, February 15, 2015

From the Notebook: An Ember Bootstrap Modal Dialog and How to Deal with state in Ember controllers

Bootstrap Modal Dialog for Ember

To create a Bootstrap styled modal dialog for an Ember app at work, I mostly followed the Ember cookbook entry on modal dialogs. One difference is using Bootstrap for the modal dialog, of course. Here are the steps I took.

  1. Added a modal outlet to the application template

    {{outlet}}
    {{outlet modal}}
    
  2. On my ApplicationRoute.js I have the following:

    actions: {
        openModal: function(modalName, model) {
    
            return this.render(modalName, {
    
                into: 'application',
                outlet: 'modal',
                model: model
            });
        },
    
        closeModal: function() {
    
            return this.disconnectOutlet({
    
                outlet: 'modal',
                parentView: 'application'
            });
        }
    }
    

    This allows me to pass a model to the modal. One implication of this is that the modal view needs to have a corresponding Controller so that Ember can set the model on that Controller. It appears that by default Ember Routes will supply an ArrayController or ObjectController for routes that don't have one defined, but the same doesn't hold for these non-route views.

  3. I then defined a modal-dialog component, but also split out modal-body and modal-footer components since those are the ones I'm expecting to customize from modal dialog to modal dialog.

    <script type="text/x-handlebars" data-template-name="components/modal-dialog">
        <div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="dialogTitle" aria-hidden="true">
            <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal"
                        aria-label="Close"> <span
                        aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title" id="dialogTitle">{{title}}</h4>
                </div>
                {{yield}}
            </div>
            </div>
        </div>
    </script>
    
    <script type="text/x-handlebars" data-template-name="components/modal-body">
        <div class="modal-body">
            {{yield}}
        </div>
    </script>
    
    <script type="text/x-handlebars" data-template-name="components/modal-footer">
        <div class="modal-footer">
            {{yield}}
        </div>
    </script>
    
  4. For the component code I need to call .modal() on the modal dialog to pop it up. I also wanted to handle the Bootstrap modal closed event and then remove the modal from the DOM (i.e., disconnect the outlet). Removing the modal from the DOM is not strictly necessary I guess, but I think it is nice to remove it from the DOM when it is no longer needed. If it is rendering a model, there's no point for the hidden modal to re-render when that model changes.

    App.ModalDialogComponent = Ember.Component.extend({
        sendCloseAction: function(){
            this.sendAction('close');
        },
        didInsertElement: function() {
            this.$('.modal').modal();
            this.$('.modal').on('hidden.bs.modal', this.sendCloseAction.bind(this));
        },
        willDestroyElement: function() {
            this.$('.modal').off('hidden.bs.modal');
        }
    });
    
  5. To use it, just need to create a view that uses the modal-dialog component.

    <script type="text/x-handlebars" data-template-name="myModal">
        {{#modal-dialog close="closeModal" title=title}}
            {{#modal-body}}
                {{message}}
            {{/modal-body}}
            {{#modal-footer}}
                <button type="button" class="btn btn-default" {{action 'save'}}>Save</button>
                <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
            {{/modal-footer}}
        {{/modal-dialog}}
    </script>
    

    One problem I ran into was that Components can only send actions based on names that they are given. My ModalDialogComponent can't call this.sendAction('closeModal') and have that be handled by the ApplicationRoute. Instead ModalDialogComponent calls this.sendAction('close') where the actual named action to send is specified in the view using the component. Here in the myModal template I'm specifying that for close the component should send the closeModal action. See Sending Actions from Components to Your Application for more details.

Ember Controllers Are Singletons

One surprising thing about Ember for me as I've been learning over the past couple months is that Controllers are (kind of, sort of) singletons. Ember instantiates a controller for a route once. So any state in a Controller is sticky. But often times you'll want to reset the state in your Controller when switching from one model to another. So what to do?

Found a couple of interesting blogs regarding this problem:

Both suggest resetting the controller in Route's setupController as one way to address this problem. (Note: Ember.Route also has a resetController hook; haven't used it but seems to cover exactly this need.) Balint Erdi's post has an interesting idea about having the reset logic in the Controller itself and having it observe some property that can be used to trigger a reset.

When thinking about Controller state and Routes, and where to put this state, it occurs to me that there are 3 kinds of view state. A view might want to take advantage of all 3 types of view state.

  1. Transient view state

    This is view state that you don't want to be sticky at all. Maybe expanding/collapsing an accordion type view, or form validation error display.

    Basically, if the model changes, you want to reset this the transient state. For this you can define a resetState function that is called whenever the model changes.

    resetState: function(){
        // reset transient stuff here
    }.observes("model")
    
  2. Sticky state, but not serialized to the URL

    This would be view state that you want to be sticky so that when the user moves from view to view this state remains. However, for whatever reason, you don't want to serialize this state to a Route URL. This is the default way that Ember works.

    Can't actually think of a good example here. Maybe a sub-view that you expand or show and as you move through different models you want to keep showing that sub-view?

  3. Sticky state that should be serialized to the URL

    If you have some view state that you want to be persistent, then you should really think about moving that to the Route and serializing to the URL. If that works for your use case, then you can do that and move that view state out of the Controller entirely.

To me, #1 is a more common type of view state than #2, so it seems weird that the default for Ember Controllers is #2. However, the fact that Ember Controllers are singletons makes #2 possible and then one just needs to reset state to make #1 work. If Ember Controllers weren't singletons, its hard to see where #2 style view state would be stored.

Odds and Ends

  • Console2: a better Windows console. I played around with setting up Console2 to get a better console window than cmd.exe. The following were helpful resources

    Here's what I like about Console2:

    • You can configure it to copy on select and paste with a right click.
    • You can resize the window (but you have to configure it to have more columns first, which is kind of weird). I sometimes like to have the console take up a full screen.
    • It is tabbed.
    • You can configure it to start a cmd shell or Git bash. I have Git bash as a default and hitting Ctrl+Shift+T opens a new Git bash tab.
  • Ember dot notation. Learned that in Ember instead of doing

    this.get('foo').get('bar').get('baz');
    

    You can do

    this.get('foo.bar.baz');
    

    If bar is not defined, then the first one would fail but the second would return undefined.

  • Git: counting words in a specific revision of a file. I wanted to be able to count how many words are in a previous version of a blog post. There might be a better way to do this, but here's how I did it.

    1. Get the file's blob hash. You can do

      git log --raw -- path/to/file
      

      Seems the easiest way. This prints the before and after blob hash for each revision

      ...
      :100644 100644 c5d00fe... 2403611... M  path/to/file
      ...
      

      Where 2403611 is the blob hash for this revision and c5d00fe is the blob hash for the previous revision.

    2. cat the blob and count the words. Getting the blob hash was the hard part, now we can simply do

      git cat-file -p 2403611 | wc -w
      

Sunday, February 08, 2015

Responsive Webapp Conclusions

I've spent a few weeks now reading and thinking about whether it makes sense to use responsive web design techniques to build a single page web application (SPA) that works well on mobile and desktop computers. I started with my initial thoughts and reviewed arguments for responsive webapps and against responsive webapps. I haven't done as much reading as I would like and there is still a lot of good stuff out there to read and learn from. But I think I have learned enough to come to some conclusions.

Recommendation

The first step in deciding whether to go responsive or not with a single page web application is to first design the ideal mobile UI and the ideal desktop UI for your app. Obviously phones and desktop computers have different real estate to work with, but when you are designing an ideal UI for your app think also about the different use cases that users on mobile versus desktop computers will have. Don't just think "how do I make this work on small and large screens". For example, consider an email campaign management app. On desktop the UI would likely be optimized for creating new email messages or templates and bulk loading and editing of email lists. On a mobile device the UI would be more geared to checking the status of email campaigns: open rates, bounceback rates, unsubscribes. Design a UI that makes the most sense for your users on both platforms.

Now, if the UI is basically the same except for layout, then Responsive Web Design techniques could be a good fit. You can use media queries to adapt the layout of the app on different devices.

However, you might still not choose to go responsive. Why?

  1. You might want to optimize what is downloaded on mobile devices. Not only to achieve a smaller download size but also to exclude running JavaScript code that would be a performance burden on mobile devices.
  2. You want the flexibility to diverge the UIs in the future. Sure, today, maybe before you even have any users, you think that the ideal mobile and desktop UIs only differ in layout. But apps should evolve based on feedback from users and as they grow new capabilities. (This point might actually be the nail in the coffin for me regarding responsive SPAs. It seems far more pragmatic to start by having separate mobile and desktop UIs.)
Other recommendations
Only two device classes: phone and desktop (or small and large)

There used to be such a clear cut divide between phones and tablets, but any more I don't see much point in developing separate UIs for them. They share a lot of the same considerations:

  • modest computational resources
  • on mobile networks
  • access to sensors like GPS

I think it is sufficient to just have two device classes.

One thing that does seem reasonable is to use responsive web design techniques to have your mobile UI adapt to larger screen area. For example, you could implement the kind of UI like in the iPad Mail app where in portrait you see only the email message but in landscape you see a list of emails in that folder on the left and the email message on the right.

Also, allow the user to switch between the phone and desktop UIs if they desire. A theme running throughout these blog posts is to try to not make any hard assumptions and this is, to me, one of the big advantages of the device class approach. You might think that the user will best be served by the mobile UI but that user may want the desktop UI. (This also dovetails nicely with thinking about different use cases on mobile and desktop: the user may be on a desktop computer but want to use the mobile UI because it is optimized for the kind of task the user currently has in mind.)

Make everything optimized for touch

Whether you go responsive or take the device classes approach, make everything optimized for touch. Size controls so that they can be tapped on and respond to touch events.

An alternative to responsive webapps: Device Classes

This week I spent some time with Boris Smus' article, A non-responsive approach to building cross-device webapps. As the title suggests, Boris presents an alternative to using media queries and responsive design for building webapps that work on a range of devices. It's a good article and I recommend reading it. In this blog post I'll review his arguments and give my thoughts on the matter.

To start with, Boris sees a few problems with Media Queries (or at least, with using just media queries to adapt layout for different screen sizes):

  • All devices get the same assets: the same JavaScript, CSS, images etc. So there is a good chance that devices are downloading a lot more than they would absolutely need to.
  • All devices start with the same DOM. To get the DOM to look one way on one device and look another way on a different device could lead to overly complicated CSS.
  • Not much flexibility to customize the UI for different types of devices. You can do it with a media query approach but if you have a mobile view that is just completely different from a desktop view, you will have duplicate views and the application code has to be careful to handle both views at once, depending on screen size. That is, while creating a custom view for a certain type of device, you have to think about how it will work for all other types of devices.

Smus recommends developing separate UIs for three different device classes:

  1. mobile phones (small screen width + touch)
  2. tablets (large screen width + touch)
  3. desktops (no touch)

He suggests using device width and whether the device is touch enabled to distinguish between these device classes. How reasonable are these criteria?

As one of the commenters mentions, hindsight is 20/20. We live in an era of touchscreen desktop Windows 8 computers, and they don't appear to be going anywhere. We can no longer assume that lack of touch support equals desktop or vice versa. One of my big takeaways from this whole investigation is that all controls should be designed to be touch friendly.

What about the small screen/large screen divide? As I wrote in my previous post, we now have many large phones and small tablets such that the height of a large phone can easily exceed the width of a tablet. I still think a line can be drawn somewhere and I do think that it is useful to design a mobile friendly version of a webapp. But that line is continually moving and blurring.

Okay, now that we have 3 device classes (personally I only see two: mobile and desktop), what does Smus recommend for detecting and serving up these different experiences? Smus compares doing server side and client side detection and comes down on client side detection, mostly because it is feature detection based (more future proof) instead of user agent based. I agree there.

Smus suggests using Device.js (apparently one of his open source projects) to do this client side detection.

He also suggests using a MVC framework and only making separate View classes for the difference device classes. This allows you to get a lot of reuse while also being able to fully tailor the UI for a particular device class.

Overall, I'm impressed with this approach. If your application is best implemented with a different UI on mobile versus desktop, I think this is probably the best approach. Responsive Web Design seems to be a good fit only when the UI is basically the same on mobile and desktop with only the layout needing to be a little different. For example, at Walker Information, we have a survey application. On mobile and on desktop, the essential UI is the same, with only layout differing. Each page has a list of questions and controls for providing answers, with Back and Next buttons to navigate through the survey. This is a good candidate for being designed responsively. As a counter example, consider something like a webapp for MailChimp. On desktop it might be optimized for creating emails and setting up campaigns whereas on mobile the UI might be quite different and be focused on monitoring open rates, bounceback rates, etc. In this case, having two completely different apps would be a better fit.

There are some downsides with this separate app for device classes approach:

  • If the device class is part of the URL then bookmarks, shared links would include it as part of the URL. It would be nice to have canonical URLs.
  • Separate views for the device classes is extra work and has its own maintenance overhead
  • Doesn't respond to orientation changes or browser resizing
    • However, it would be possible to combine both approaches. You might create a separate view for the mobile device class that itself uses media queries to adjust the UI between phone-like sizes and tablet-like sizes.

On a closing note, there is one other thing I like about Smus' approach. He recommends having links in the app to the different device classes. This way if you are on a mobile phone and really want the desktop view, you can get to it. I think this is in line with the thinking from the last blog post that challenges the consensual hallucination that we as web developers tend to participate in. Our assumption about what is mobile, what is a desktop computer are being challenged all the time. It would be good to make fewer assumptions. Letting a user switch to a non-default device class, to me, fits in with that line of thinking. We only have a limited understanding of the kinds of devices that exist today. Hopefully the devices that arrive in the future will surprise us and challenge our assumptions. And hopefully our webapps will be ready.

Additional Resources:

  • Really liked this post by Brad Frost that Smus links to. He makes the point that while media queries are great, the most important thing to optimize on mobile is performance.