Sunday, April 28, 2013

Filtering in Backbone: Let the Model Do the Work

I had previously posted about different approaches to filtering Backbone collections. However, as I spent time trying to solve my problem I kept finding that the solutions did not offer the flexibility I needed. I liked the overall approach but felt that it might be better to decouple the collections of filters from the application of the filters. The solution I arrived at was to move the decision of whether a filter matched into the model. A controller pattern would tie the collection process to the application process to manage the different models and collections involved in the process.


Below is a simple example that illustrates the main points of the pattern:

  • The model contains all the logic to decide if it matches the provided selection criteria

  • There is a source collection that contains all the models that can be selected

  • A second collection is reset with the array of matching models returned by the source's filter function

  • A controller manages the interactions between the different components





var Team = Backbone.Model.extend({

defaults: {
name: '',
score: 0
},

selector: null,

match: function ( select ) {
this.selector = select;
return this.get('score') > select.score;
}
});

var Teams = = Backbone.Collection.extend({
model: Team
});

var selector = { score: 50 },
source = new Teams([
{ name: 'Rockets', score: 27 },
{ name: 'Colts', score: 67 },
{ name: 'Giants', score: 55 }
]),
selected = new Teams(),
matcher = function( model ) { return model.match(selector); };

selected.reset( source.filter(matcher) );



What I like about this approach is that the process of applying the filter is very simple and repeatable. The complexity can be deferred to the model which, as long as it understands the contents of the filter arguments, can be designed to test if it matches the selection criteria in whatever way is applicable to the target solution. It also leverages all the built-in functionality already available in Backbone which also reduces the amount of required code to build it. Finally, it has the added benefit of pushing the selector down to each model. This can come in handy later if you'd like to validate a model still matches when its updated or use it to format the attributes during rendering (ie highlighting matching text in a regex search).

Sometimes a bigger, more functional example helps solidify the idea. I built a tiny contact lookup list which has an input box to search all the available attributes in the model. You can filter by name, organization, phone, and email. The definition of the model has slightly more complex matching logic that will look for the provided search text in each of the four columns:



App.Models.Contact = Backbone.Model.extend({

defaults: {
display_name: '',
organization: '',
primary_email: '',
primary_phone: ''
},

selector: null,

match: function ( select ) {

this.selector = select;

return (
select.search_text.length == 0 ||
( select.search_text.length > 0 &&
_.chain(
_.values(
_.pick(this.attributes, 'display_name', 'organization', 'primary_phone', 'primary_email')
)
)
.any(
function( s ) {
return s.toLowerCase().indexOf(select.search_text.toLowerCase()) > -1;
}
)
.value()
)
);
}

});


Since this is searching several different columns, I'd like to highlight the search text where it was found in each item so the user can quickly see why it is included in the results. Since the formatting is injecting HTML content into the data, it doesn't really belong in the model. However, since the model recorded the selector used to find it, the view that renders each item can use it to apply the formatting:


App.Views.ContactItem = Backbone.View.extend({

template: null,

tagName: 'ul',
className: 'ui-list-item',

initialize: function ( options ) {
this.template = _.template($('#contact-item').html());
},

render: function () {

this.$el.html(this.template( this.formattedJSON() ));

this._super();

return this;
},

formattedJSON: function () {

var item = this.model,
select = item.selector,
data = item.toJSON(),
check = new RegExp( $.ui.autocomplete.escapeRegex(select.search_text), 'i' );;

if ( select.search_text.length > 0 ) {

for ( var d in data ) {

if ( data[d] && check.test(data[d]) ) {

data[d] = data[d].replace(
new RegExp(
'(?![^&;]+;)(?!<[^<>]*)(' +
$.ui.autocomplete.escapeRegex(select.search_text) +
')(?![^<>]*>)(?![^&;]+;)', "gi"),
'<span class="ui-search-highlight">$1</span>');
}
}
}

return data;
}

});


Now the whole thing can be wired up inside a controller object that will coordinated the views, models, and collections required to make it all work:



App.Controller = Controller.extend({

filter: null,
source: null,
selected: null,

container: null,

layoutView: null,
filterForm: null,
resultList: null,

initialize: function ( options ) {
var options = options || {};

this.container = options.container || 'body';
},

start: function () {

this.source = new App.Collections.Contacts( localData );
this.selected = new App.Collections.Contacts();
this.filter = new Backbone.Model({ search_text: '' });

this.layoutView = new App.Views.ContactLayout();
this.filterForm = new App.Views.ContactFilter({ model: this.filter });
this.resultList = new App.Views.ContactList({ collection: this.selected });

$(this.container).append( this.layoutView.render().$el );
this.layoutView.$('.ui-list-options').append( this.filterForm.render().$el );
this.layoutView.$('.ui-list-content').append( this.resultList.render().$el );

this.listenTo(this.filter, 'change', this.applyFilters);
},

applyFilters: function () {

var selector = this.filter.toJSON();

this.selected.reset( this.source.filter(function ( model ) {

return model.match( selector );

}));
}

});


This controller is bit simplified but you can see the different elements of the pattern by keeping it flatten out. The filter is now a simple model that can be bound to an input box. By leveraging some autobinding magic, we can listen for changes to the filter model to trigger the actual filtering on the list.

This example only works with a small set of local data. It can be extended to query a remote data source. In that scenario, the source collection would be setup to fetch from the server under certain conditions to retrieve a larger dataset that can further be filtered locally. You can attach events to the source's request and sync events to show/hide a wait spinner and rebuild the selected collection after the fetch is complete. The only other consideration worth noting is that the model assumes it will be a member in one filter collection. This might pose a problem is you are using multiple filtering collections that are not mutually exclusive. If that happens, you'd either need to record the context of the filter (which collection it came from) or find an alternative approach.

Saturday, April 20, 2013

Programmatic Get/Set of jQuery UI Buttonsets and Checkbox/Radio Group Values

I much prefer the look of a jQuery UI Buttonset over a list of radio buttons. Mixing Buttonsets with groups of check boxes enables a consistent look with easy control over the styling. However, if you need to manipulate the state of the underlying form controls, it becomes a little less clear how to handle that situation.

I've never been a fan of the inconsistent interface to getting/setting DOM form elements. jQuery only insulates us slightly from the problem. You can safely say $('input').val() and that will return the contents of the input box if its type is "text". A check box or radio button don't work the same way. Calling the val() function on one of these elements won't tell you if its checked. Instead, it will return the value of the value attribute. The honor of determining the state of the check box or radio button is reserved for the prop() function. Using prop('checked') allows us to determine if a checkbox or radio button is checked. Additionally, jQuery sets up the handy :checked selector to find elements that are checked.

So how does this affect a Buttonset tied to a radio group? Knowing the above will allow you to interact with radio buttons programmatically like the Buttonset widget does internally. Since there is no value() function built in to the Buttonset widget, you have to write your own get/set function. After some trial and error, I found the following worked quite well:


<div id="mygroup">
<input type="radio" name="mygroup" id="option1" value="1" /><label for="option1">Option 1</label>
<input type="radio" name="mygroup" id="option2" value="2" /><label for="option2">Option 2</label>
<input type="radio" name="mygroup" id="option3" value="3" /><label for="option3">Option 3</label>
</div>


The Buttonset widget needs a container element for the inputs it will manage. Additionally, each input needs a label element so the buttons render properly. The input attribute name="mygroup" establishes the radio group so the Buttonset will toggle the state correctly. Creating the Buttonset is easily done by selecting the containing div:


$('#mygroup').buttonset();


By itself, you don't need to do anything else to interact with the group. However, if you need to manage it from code, you'll need a get/set function to control it:


function myGroupVal( val ) {

if ( typeof(val) != 'undefined' ) {

$('#mygroup')
.find('input')
.removeProp('checked')
.filter('[value="'+val+'"]')
.prop('checked', true)
.end()
.end()
.buttonset('refresh');

} else {
return $('#mygroup').find(':checked').val();
}
}


When setting the value, the Buttonset requires a refresh so it updates the state of the buttons that represent the underlying radio group. I like to set the value attribute on the individual radio inputs and use that as the actual value of the group.

Here's a jsFiddle demo that uses an input box and some buttons to get/set the Buttonset.

Buttonset can also be used with check boxes such that more than one item can be selected at a time. The above example assumes only one item can be selected and ensures everything else is unchecked before checking the new item based on the passed value. For check boxes, you could work with an array of values to get/set such that more than one item could be checked at a time. Otherwise, as it stands now, the code will simply mimic a radio group even though the inputs are check boxes (try it).

Wednesday, April 17, 2013

Patterns for Filtered Collections in Backbone

[caption id="attachment_842" align="alignleft" width="186">Master Collection and Filtered Subsets Master Collection and Filtered Subsets[/caption]

Most likely, you'll have a need to filter the data in a collection. Although there are built-in filtering functions in the Backbone Collection object, they don't affect the actual collection instance - just return an array of models. If you're trying to filter the collection to cause a view to render the filtered set, you'll need to put that array of models in another collection. It doesn't make sense to reset the collection you filtered, you might want to clear the filter and go back to the original data set.

I've been playing with both Backbone Query and Backbone Subset to figure out what approach might work best for building a consistent, repeatable pattern for this problem.

Subset provides an all-in-one solution with the ability to create a Subset object from a source collection, add filters, and bind a view to the filtered collection available in the Subset model. Since the filters are a collection as well, you can bind them into a view and build a UI around them so your list of filtered items will update automatically.

Query doesn't provide the same framework but does offer a more powerful filtering engine. Combining Subset and Query together would create a fairly powerful tool for extracting subsets of data from a primary source collection.

However, one size doesn't always fit all. I had a case where the filters I wanted to allow entered from the UI did not map nicely on to the collection in Subset and the filtering logic needed to be implemented as a custom function passed to the Backbone Collection filter() function.

I do think the overall pattern defined by the Subset solution makes sense:


  1. Start with a container model, add a source collection, filter collection, and results collection

  2. Instance the container model with the source collection

  3. Bind the container to a view that will manage the filter and list views

  4. Have the container view instance and render the filter/list views binding in the corresponding collections from the container model

  5. Add appropriate handler logic to listen to filter changes, apply transforms, and filter the source to reset the result collection



The actual querying/filtering implementation depends on you needs. The setup is basically the same. Now you maintain one instance of all your models but can render multiple views of those models based on the application requirements.

Tuesday, April 9, 2013

Mixing jQuery UI Draggable, Droppable, and Sortable Interaction Widgets

jqueryui-drag-drop-sort

The jQuery UI library provides quite a few useful mouse interaction widgets which make building solutions that need drag/drop functionality significantly easier. Most of them have the ability to integrate cleanly with each other which reduces the amount of "glue" code required to make these interaction work together. I've been working with the Sortable widget for some time now to enable my users to rearrange items in a list. One of the challenges I faced was how to design a consistent method of adding/removing items to/from the list via drag/drop interactions and provide meaningful feedback to the user. There are several pieces to making all the interactions work together harmoniously. The setup I'm presenting here takes into account not only the required functionality but also creating consistent styles and visual cues to guide the user's interaction. There are probably other ways to handle it but this is the solution I've found that provides the most flexibility for my intended uses.

My basic approach is to have item instances available in a draggable widget. These can be move into a droppable widget or a sortable widget. Items from the sortable widget can be moved around as usual within the widget but also dragged out onto the droppable widget. When in sorting mode, I want the item to be constrained to the list, however, that would make it impossible to move the item out of the list to the droppable area. My solution was to create an area around the sortable item that is a separate drag area that would enable removing the item from the sortable area. This may not be necessary if you don't care about constraining the item to the parent list. The item could just be dragged to the droppable area. However, I feel like having the two distinct modes of operation provides more flexibility in the design and an opportunity to provide better feedback to the user about the activity they are performing. The biggest advantage to using a separate draggable is that you can configure it completely different than the sortable's drag configuration. Because of that flexibility, I went through the effort to find a way to combine the two together. The trick is to do it and not have one interfere with the operation of the other.

To achieve the solution outlined above, I started with the following basic item HTML:



<div class="item-wrapper">
<div class="drag-handle"></div>
<div class="item-container">Item 1</div>
</div>



Even though the .drag-handle is not a parent of the .item-container, it will be styled to appear that way:

jqueryui-drag-drop-sort-item-html

Technically, this arrangement is not necessary for differentiating the draggable/sortable behaviors. That can easily be achieved with the cancel option in the draggable configuration. However, if you want to toggle class styles when hovering over the different elements, you'll have the most flexibility with the two elements being siblings instead of parent/child. Both the jQuery.hover() and hoverIntent use mouseenter and mouseleave events to trigger their handlers. These two events differ from the behavior of mouseover/out. Read the discussion in the jQuery Docs for more information. Given that I might use either hovering method to create cues for the user, using the sibling arrangement of the drag handle and item container works best.

The .item-wrapper will be the actual target for all drag operations. The children will be configured to act as handles to initiate the actual dragging. The styling is important to properly position the .drag-handle and .item-container:



.item-wrapper {
position: relative;
padding: 3px;
}

.drag-handle {
position: absolute;
border: 3px solid transparent;
left: 0;
right: 0;
top: 0;
bottom: 0;
}

.item-container {
position: relative;
text-align: center;
}



The .item-wrapper has a 3px padding that matches the 3px border of the .drag-handle to create an area around the .item-container that will allow activating the draggable instead of the sortable operation. It will also have hovering events attached appropriately to enable styling visual cues for the user.

The next step is to create containers for each widget where the items can be held and moved around:



<div class="wrapper">

<h5>Draggable</h5>
<div class="drag-container ui-corner-all">
...
</div>

<h5>Droppable</h5>
<div class="drop-container ui-corner-all">
...
</div>

<h5>Sortable</h5>
<div class="sort-container ui-corner-all">
...
</div>

</div>



At any given time, Draggable and Droppable will only have one item and Sortable can have N items. At page load, I setup four items - 3 in the Sortable container (labeled Item 1, Item 2, and Item 3), one in the Draggable container (labeled Item 4), and one in the Droppable container (labeled Empty). When you drag Item 4 from the Draggable and drop it in a valid container (either the Droppable or the Sortable), the item number will increase (so the Sortable/Droppable will now have Item 4 and the Draggable will show Item 5). Playing with the demo might make more sense and help clarify the remaining discussion.

First, we'll configure the Draggable widget:



$('.drag-container')
.find('.item-wrapper').draggable({

cursor: 'move',
zIndex: 200,
opacity: 0.75,
scroll: false,
containment: 'window',
appendTo: document.body,
helper: 'clone',
connectToSortable: '.sort-container',
start: fixHelper

})




The most important part of this setup is the helper, connectToSortable, and start callback configuration items. The connectToSortable feature allows simple integration with a Sortable widget. You don't need to do anything other than specify that argument and ensure the helper is equal to "clone". I discuss the fixHelper() function later with the Sortable setup. I also skipped the hovering cues which I'll cover later as well.

The droppable is pretty straight forward and just needs to handle the drop event:



$('.drop-container').droppable({

hoverClass: 'mx-content-hover',
drop: function ( e, ui ) {

$(this).find('.item-container').html( ui.draggable.children('.item-container').html() );

if ( ui.draggable.parent().is('.drag-container') )
$('.drag-container .item-container').html('Item ' + (++items));
else
// Defer removing the item after exiting the current call stack
// so the Draggable widget can complete the drag processing
setTimeout(function () { ui.draggable.remove(); }, 0);

}
});



In this case, it copies the HTML from the source item into the droppable's item's HTML. Additionally, it has to determine which container was the source of the drag. If its the Draggable, then the item count needs to be updated. If its the Sortable, then we want to remove the item from the list.

The Sortable configuration is a little more complex since it not only configures the Sortable interaction but also has to attach Draggable widgets to each item in the Sortable container. It has to configure the items both at page initialization and each time a new item is dragged into the list:



$('.sort-container')
.sortable({

containment: 'parent',
handle: '.item-container',
tolerance: 'pointer',
helper: 'clone',
start: fixHelper,
update: /* see below */

}).find('.item-wrapper')
.draggable({
cursor: 'move',
zIndex: 200,
opacity: 0.75,
handle: '.drag-handle',
scroll: false,
containment: 'window',
appendTo: document.body,
helper: 'clone',
start: fixHelper
});



There are several things to note when looking at the configuration of the Sortable and the Draggables. First, both are targeting the same DOM elements - .item-container. Even though the Sortable is attached to the parent container, the drag operations are attached to its children (.item-container). Since this will cause issues if both the Sortable and Draggable try to respond to drag events on the same element, some of the other configuration options need to be used to restrict the scope of which part of the element each widget will respond to. This is achieved with the handle option in the Sortable and Draggable widgets:



$('.sort-container')
.sortable({
...
handle: '.item-container',
...
}).find('.item-wrapper')
.draggable({
...
handle: '.drag-handle',
...
});



Effectively, this limits the Sortable to responding to drag events on the .item-container element and Draggable will only listen to events on the .drag-handle. The final point of note on the setup is the helper configuration. It is set to "clone" and the start callback is used to normalize the cloned element across all the draggable widgets:



function fixHelper( e, ui ) {

var $ctr = $(this);

ui.helper
.addClass('mx-state-moving ui-corner-all')
.outerWidth($ctr.outerWidth())
.find('.mx-content-hover')
.removeClass('mx-content-hover')
.end();
}



This ensures the cloned element doesn't have any of the classes related to hover cues assigned, matches the width of the item as it appeared in the container, and adds a class to style the helper while moving:



.item-wrapper.mx-state-moving {
background-color: #fcefa1;
color: #000;
margin: 0;
}



The final problem to manage in the Sortable is handling a new item from the Draggable. The Sortable will automatically add an item from the Draggable container due to the connectToSortable option in the Draggable configuration. However, if you need to perform additional handling on the new item, you'll need to work with one of the callbacks to attach that functionality. This turned out to be a little more difficult than I expected. I was hoping to be able to detect that a drag operation was initiated by the Draggable (similar to how I did it in the Droppable) and also determine where the item was dropped in the Sortable. However, none of the callbacks provided all this information at the same time. In one case, the placeholder is available but the sender is not. The sender also seems to only work when the item is coming from another Sortable. To work around these issues, I decided not to include the .drag-handle element in the Draggable item. When its dropped on the Sortable, I can inspect the ui.item element in the Sortable update() callback to determine if its missing that element. If it is missing, then I know it came from the Draggable and I need to run the code to handle it. If it is not missing, then I know its a normal sort operation in the Sortable widget and no further processing is required:


$('.sort-container')
.sortable({

...

update: function ( e, ui ) {

/* Check if the drag handle is missing */
if ( ui.item.find('.drag-handle').length == 0 ) {

/* It is so increment the item counter */
$('.drag-container .item-container').html('Item ' + (++items));

/*
And setup the item so it has a drag handle and
responds to drag events
*/
ui.item
.find('.item-container')
.before( $('<div class="drag-handle">') )
.parent()
.draggable({

cursor: 'move',
zIndex: 200,
opacity: 0.75,
handle: '.drag-handle',
scroll: false,
containment: 'window',
appendTo: document.body,
helper: 'clone',
start: fixHelper

});

/*
Reset the containment. Somehow it breaks when adding
the external item
*/
$(this).sortable('option', 'containment', 'parent');
}

}

...

});


That takes care of the dragging functionality. Now we need to add some visual cues when the user hovers over various areas that are draggable. I simply toggle a class on any target I'd like to enable hovering. This handler can be used in either the jQuery hover() or the hoverIntent plugin:


function toggleHover( e ) {

if ( e.type == 'mouseenter' )
$(this).addClass( 'mx-content-hover' );
else
$(this).removeClass( 'mx-content-hover' );

}


Next, we need to define styles for each hover target:



.drag-container .item-wrapper.mx-content-hover {
background-color: #cce0ff;
color: #000;
}

.drop-container.mx-content-hover {
background-color: #ccff99;
}

.sort-container .drag-handle.mx-content-hover {
border-color: #cce0ff;
}

.sort-container .item-wrapper.mx-content-hover {
color: #000;
}



Finally, the jQuery hover handler (or hoverIntent depending on if you'd like a delayed response) needs to be setup on all the hover targets so their classes can be toggled:



$('.drag-container')
.find('.item-wrapper')
.draggable(...)
.hover( toggleHover );

$('.sort-container')
.sortable(...)
.find('.item-wrapper')
.draggable(...)
.hover( toggleHover )
.find('.drag-handle')
.hover( toggleHover );



That covers my basic approach to assembling several jQuery interaction widgets together and leveraging their built-in functionality while still being able to enhance the functionality where needed. While its certainly not a one-size-fits-all solution, I've found that it serves as a nice pattern that can be used as a starting point for more elaborate designs.

Monday, April 1, 2013

Reviewing CSS Layout Techniques for Controling Content Size to Fit the Page

Sometimes going back to basics is not a bad thing. Generally, I learn something new every time I start over. In this case, I decided to spend some time looking at how to improve/simplify setting up the basic layout I might use to build an application. When I think about the problems I need to consider when creating my layout, I generally can break them down into these basic issues:


  • Creating various "regions" to hold content that are either fixed or resize to fit the remaining space

  • Keeping all the content inside the viewable browser window bounds. Any scrolling will be dealt with inside the different content "regions"

  • Ensuring the desired layout works well with widget libraries like jQuery UI



I used the term "region" to be as generic as possible. These can mean columns, headers, rows, panels - anything that holds some functional part of the application. That can translate into menus, tables, toolboxes, or various other widgets. For this discussion, I chose a basic 3-column layout where the middle column expands to fill the remaining space not used by the two fixed outside columns. The whole layout should also fill the entire available space on the page (both height and width). I'm going to start by solving the columns layout, move to the height issue, and then review how adding several widgets might require additional tweaking.

Creating a 3-Column Layout




I've found several approaches for creating multi-column layouts with both fixed and fluid columns. For a 3-column setup, the most common are two fixed column (left and right) with a fluid middle column.

css-layout-3-columns-fixed-fluid-fixed

Most references use floating elements but I also saw solutions for absolute positioned elements. In the end, I chose to use absolute positioning. Others may be more comfortable with floats, however, I prefer avoiding them. The setup for the columns is fairly simple - set the left and right to the fixed amount and then set the left/right margin of the middle element to match. Then use absolute positioning to line each element up across the page. When you resize the page, the left/right stay constant and the middle will automatically fill the remaining space.


<div class="col-wrapper">

<div class="col-left">
<div class="col-content">
</div>
</div>

<div class="col-mid">
<div class="col-content">
</div>
</div>

<div class="col-right">
<div class="col-content">
</div>
</div>
</div>



.col-wrapper {
position: relative;
width: 100%;
font-size: 1.2em;
}

.col-left, .col-mid, .col-right {
position: absolute;
}

.col-left {
left: 0;
width: 300px;
background-color: yellow;
}

.col-mid {
left: 0;
margin-left: 300px;
margin-right: 300px;
background-color: lightblue;
}

.col-right {
right: 0;
width: 300px;
background-color: pink;
}

.col-content {
margin: 0.8em;
}




Notice that .col-mid defines a margin the same size as the right/left width. The result looks like this page. That pattern will work well for 2 and 3-column layouts. Moving to four or more might pose a bigger challenge.

Filling the Available Height




So far, the content in the columns will flow down the page and the browser will scroll the whole view. In an application, this probably is not desirable since one region may contain a menu you want to remain in view when the user is scrolling through a list of data. This means you need to ensure you contain the content to the height and width of the browser. The width is generally easy to manage, block elements naturally fill the width of their parent - its the height that always causes problems. As much as I'd like to keep this a pure CSS solution, I've found that using a tiny bit of Javascript is really the best way to deal with the height. Not only can you set it when the page finishes loading, you can also reset it every time the browser window is resized:


$(function() {
$(window).on('resize', function () {
$('.col-wrapper').outerHeight($(window).outerHeight());
}).trigger('resize');
});


This takes care of the outer wrapper for the columns, however, the individual columns are not sized to fill that height. The simple solution seems to just make the height equal 100%. Which will work until you add any margin or padding on the element that you set this way. Since the columns are just going to define the location of other elements and not anything more, I can safely use height: 100% in my style. However, the content inside those columns also needs to fill the space and have margin to create some white space. If I apply the 100% height rule to those element, I get this resulting layout. The goal is to get something that looks like this:

css-layout-full-height-columns

I stumbled upon a site with various HTML/CSS layout patterns. The one I found that solved the problem is named "Stretched" and used absolutely positioned elements with the left/right/top/bottom styles set to force the element to fill in the available space with the margin included in the calculation. This approach avoids the problem with the inner element overrunning the bounds of the parent element because the height/width is 100% and the margin/padding causes the element to push outside the parent. Applying that concept to my layout example fixes the problem and the content fits perfectly with the desired margins.

Adding Widgets to the Design




Now that we have the two main pieces to our layout, let's try building something a little more useful. I took the same 3-column layout and added jQuery UI widgets to the different elements. One of the things to consider when doing this is that most of the widgets will add certain styles to the target elements (or even wrap them). These styles need to be accounted for in the layout so everything looks lined up and uncluttered (no extra borders, etc).

css-layout-full-height-columns-jqueryui

Before diving into specific issues related to the widgets, I factored out the important parts of the layout so it was reusable across anything I needed to use it with. The columns where in pretty good shape already, but the stretching styles can be separated into one class that can mixed with other class styles as needed:



/* Setup container to fill column area and not overflow the bounds */
.fill-box {
position: absolute;
height: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
}



As long as there is a relative parent, this will cause the target element to completely fill the available space created by the parent element. Other styles can define any margin/padding/border and not leave the bounding box.

Next we can add specific styles for each column. The left column will hold an Accordion widget which will have Draggable elements in each grouping. The middle will hold a Sortable widget that will also accept the Draggable elements, and the right column will hold some additional content to fill the space.



/* Define specific styles for each column container */
.toolbox {
margin: 5px;
margin-top: 3px;
padding-bottom: 1px;
}

.sortable {
margin: 5px;
border: 1px solid #aaa;
overflow-y: auto;
}

.content {
margin: 5px;
border: 1px solid #aaa;
padding: 5px;
overflow-y: auto;
}



The markup has the normal column elements plus a container for the widgets. The sortable and content area do not need any other wrappers, however, the accordion does to ensure it sizes correctly. Without the extra DIV, it was cut off at the bottom.



<div class="column-wrapper">

<div class="column-left">
<div class="fill-box toolbox">
<div>
/* Need to wrap the accordion so it sizes correctly */
<h3>Stuff A</h3>
<div>
/* DIVs for the Draggable widget */
</div>
<h3>Stuff B</h3>
<div>
/* DIVs for the Draggable widget */
</div>
<h3>Stuff C</h3>
<div>
/* DIVs for the Draggable widget */
</div>
<div>
</div>
</div>

<div class="column-middle">
<div class="fill-box sortable ui-corner-all">
/* DIVs for the Sortable widget */
</div>
</div>

<div class="column-right">
<div class="fill-box content ui-corner-all">
/* Paragraphs of text */
</div>
</div>

</div>



Now, the code to initialize each widget may need to be modified to handle the sizing we would like. The Sortable doesn't need any special consideration, however, both the Draggable and Accordion do require a few adjustments to account for the width/height we are targeting.

First, the accordion widget has a heightStyle which can be set to "fill" so the widget will match the height of its parent. This calculation is only done one upon initialization of the widget so any subsequent screen resizes will not cause the widget to fill the height properly. Since there is already a handler in place for the window resize, the accordion can be refreshed in that handler to resize it as well.


/* Track the window height and set the outer column wrapper to match */
$(window).on('resize', function () {
$('.column-wrapper').outerHeight($(window).outerHeight());

/* Make the accordion recalc the height */
$('.toolbox > div').filter('.ui-accordion').accordion( 'refresh' );
}).trigger('resize');

$('.toolbox')
.accordion({ heightStyle: 'fill' });


Next, the draggable needs some tweaking to move correctly from the accordion widget to the sortable widget. Whenever the source element is cloned to be dragged to the sortable, it will fill the width of the parent it will be appended to (it needs to be in the column-wrapper so it doesn't get lost from the overflow). Since the helper can take a function, we can clone the element and set its width to match so it retains its size during the drag:


$('.draggable')
.draggable({
connectToSortable: '.sortable',
helper: function() {
return $(this).clone().outerWidth($(this).outerWidth());
},
appendTo: '.column-wrapper'
}).disableSelection();


This approach provides a reusable pattern for crafting sections of a page that require filling specific dimensions. Its repeatable from the top of the page all the way down to images and buttons. If you've been struggling trying to keep portions of your page fluid without having to manage it with code or hard-coded sizing rules, then some of the ideas here might make things a little easier.