Sunday, December 29, 2013

Multi-Column Sort in Backbone Collections

Amongst the features that I wish Backbone implemented a little more fully is collection sorting. Its great that it can accommodate several approached (like the attribute name or a function) so you can implement more complex sorting schemes. However, sorting a set of data is probably fairly well defined and, in most cases, will be a combination of one or more columns in either ascending or descending order. Generally, one column is enough so the built-in string variation is sufficient and easy to use. However, that will only give you ascending order. If you want descending order, you'll need a sorting function to reverse the comparison. A generalized version of that function might look like this:


   comparator: function(a, b) {

      var a = a.get( this.sortColumn ),
          b = b.get( this.sortColumn );

      if ( a == b ) return 0;

      if ( this.sortDirection == 'asc' ) {
         return a > b;
      } else {
         return a < b;
      }
   }



That's great for one column, but what if you want several columns to be considered in the sorting?

Sunday, December 22, 2013

Using Bootstrap Dropdown Buttons as a Form Select Box


The Bootstrap Button Dropdown is a nice control that can be customized to do a variety of tasks above and beyond what a normal select box can achieve. You're not limited by presenting a list of options to the user in the menu. You can add additional functionality to the menu like a search box and other logic. However, by default, the Button Dropdown is really just a menu of actions not really designed to represent a selected data value. Wiring up the control to behave like a select box is not too difficult. A little Javascript is necessary to handle a selection and change the label on the button:

<div class="btn-group btn-input clearfix">
  <button type="button" class="btn btn-default dropdown-toggle form-control" data-toggle="dropdown">
    <span data-bind="label">Select One</span> <span class="caret"></span>
  </button>
  <ul class="dropdown-menu" role="menu">
    <li><a href="#">Item 1</a></li>
    <li><a href="#">Another item</a></li>
    <li><a href="#">This is a longer item that will not fit properly</a></li>
  </ul>
</div>


Using that markup will enable the built-in dropdown widget so the menu will open when the button is clicked. In addition to that default behavior, add a listener for click events in the menu:

   $( document.body ).on( 'click', '.dropdown-menu li', function( event ) {

      var $target = $( event.currentTarget );

      $target.closest( '.btn-group' )
         .find( '[data-bind="label"]' ).text( $target.text() )
            .end()
         .children( '.dropdown-toggle' ).dropdown( 'toggle' );

      return false;

   });


When an item is selected, this code will change the button label and hide the menu. Now we have a select box, however, the styling on Dropdown Button does not match the styling on other form controls. When the text in the button is too short, the button shrinks to fit. When that text is too long, the button expands outside the bounds of its parent:



A and B represent the default behavior. C and D show what happens if you simply add a .form-control class to the button. And E and F illustrate the desired behavior after adding some additional styles to the .btn-group class and contents of the button (the text and caret spans):

.btn-input {
   display: block;
}

.btn-input .btn.form-control {
    text-align: left;
}

.btn-input .btn.form-control span:first-child {
   left: 10px;
   overflow: hidden;
   position: absolute;
   right: 25px;
}

.btn-input .btn.form-control .caret {
   margin-top: -1px;
   position: absolute;
   right: 10px;
   top: 50%;
}



The two main difficulties I faced making this work properly was the display: inline-block on the .btn-group and the overflow of the text inside the button. The most significant change in the styles is using absolute positioning inside the button to align the label and caret to better control their size and position. I placed this example in a jsFiddle if you'd like to experiment with it more. If these modified controls are contained inside a grid cell, they will probably work pretty well. Other use cases may require additional tweaks to make functional.

Sunday, December 15, 2013

Working with Variable Height CSS Floats in Responsive Layouts

The Bootstrap grid system is a great toolkit for constructing fluid, responsive page layouts. Typically, you place enough .col-* elements into a .row parent so the total of the column sizes equals 12. However, nothing prevents you from adding as many columns to a row parent as you'd like. They will simply flow to the next row after reaching the sizing of 12. I was curious if I could use one row parent and layout out a 3 column grid of Bootstrap panels with equal width that would simply flow to the next row every 3 panels. The problem I encountered was while each panel my be equal width, it is not equal height. Using CSS floats to layout the content with variable height elements results in the content not necessarily wrapping to the beginning of the next row. Instead, they could float right below the last element on the previous row or anywhere else depending on the height of the previous elements:



Sunday, December 8, 2013

Working with CSS-Only Height Transitions and Content Styling

For some reason working with height in HTML is a challenge. DIVs conveniently fill their parent's width, but height is a different story. Adding a transition to the height becomes even more challenging and has caused me some recent grief. Even after finding a solution, I was still not completely happy with the result and spent some additional time finding a reasonable approach to make the content look correct in both the collapsed and expanded states.

As part of the solution, I'd like to limit the amount of Javascript to only what is necessary to toggle classes on the element that should be expanded/collapsed. Everything else will be defined via CSS to create the animation and style the content:

   $( '.expandable .toggle' ).click(function( e ) {

      var $target = $(this),
          $exp = $target.closest( '.expandable' );

      $target.toggleClass( 'dropup' );
      $exp.toggleClass( 'in' );

      return false;
   });


The code assumes there is a base class expandable assigned to element(s) that should change size and, contained within those elements, is some kind of toggle element to allow the user to switch between the expanded and collapsed views. Now we need to figure out what styles need to be defined to make it work.

What doesn't work

Trying to transition between an explicit height and auto will not work:

.expandable-bad {
   height: 48px;
   overflow: hidden;
   transition: height 0.55s ease-in-out 0s;
}

.expandable-bad.in {
   height: auto;
}


Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.


While the boxes do expand/collapse, there is no smooth transition between those states. Percentages only work inside a parent with a fixed height. However, I may not know how tall the content will be fully expanded and don't want to assume one height. I could calculate the height on each box, but if I'm going to stick to my goal of not using Javascript to calculate anything, I'll have to find an alternative CSS-only solution.

What does work

Instead of height, we can use max-height to solve our problem:

.expandable {
   max-height: 48px;
   overflow: hidden;
   transition: max-height 0.55s ease-in-out 0s;
}

.expandable.in {
   max-height: 1000px;
}




This mimics auto height because, if the height is not needed, the container will not be sized to the specified height. It will only be constrained once it reaches the defined maximum. Fortunately, this style can also be animated with a CSS transition so this looks like a viable solution as long as you don't exceed the defined maximum. The only thing you do need to know with this approach is how tall the content should start. If you're trying to fit a certain number of lines of text, you'll have to do some work to find the correct value.

Better clipping

Notice how the text doesn't hide until it reaches the border of the container? It would be nice if it would clip with the matching 15px top padding while in the collapsed state. The best way I found to achieve this is to use the :after pseudo-element to add and style content to cover the bottom text. As an extra visual cue, I added an ellipsis to the clipping box to signify there is more content in the box. However, once I do that, I need it to hide when the content is expanded so I'll add a transition to match the one defined in the expandable class:


/* add to existing expandable */
.expandable {
   position: relative;
   ...
}

.more:after {
   background-color: #FFFFFF;
   bottom: 0;
   content: "...";
   display: block;
   height: 15px;
   padding-bottom: 10px;
   position: absolute;
   width: 100%;
   transition: bottom 0.55s ease-in-out 0s;
}

.more.in:after {
   bottom: -25px;
}


This isn't a necessary addition to the height transition. But I think it makes the collapsed content look better:



I've been trying to use transitions whenever possible. Sometimes you have to be a little creative to make them work, but overall, they can reduce the amount of code required to create the effect.

Monday, December 2, 2013

Dynamic Collapsible Menus Based on Page Scroll Position

Navigation menus can eat up page real estate pretty quickly. While the user is interacting with the navigation, it makes sense to use the space necessary to allow them to make a selection. However, once they start to scroll into the content of the page, that navigation bar can become a nuisance. A lot of sites have started using the scroll position of the page as an indicator that the user has stop navigating and is now focused on the page content. As the scroll occurs, the navigation bar collapses into a compact representation of the menus that still allow navigating, but provide either less options or less verbose labeling. Google+ is good examples of a site that employs this technique and I was interested in creating a similar effect myself.

I decided to start with my jQuery UI Scrollable widget to monitor a certain element to determine when to toggle between the full and collapsed navigation menu. The full example presented here is one of the Scrollable widget's demo pages. The reason I chose this route is because the widget conveniently triggers one event when a target element is inside or outside the defined view port: