(function($){
'use strict';v0.1
http://chadillac.github.io/MoshPit.js
Copyright (c)2014 Chad Seaman. Distributed under MIT license
(function($){
'use strict';Declare sizes globally, we’ll import laterMoshPitSizes
We declare these here so they can be customized outside of the application code by you. We don’t assign/use them internally until page DOM ready.
window.MoshPitSizes = {
'mobile':{min:0,max:480},
'tablet':{min:481,max:1024},
'desktop':{min:1025,max:1820},
'desktop-wide':{min:1821,max:99999999}
};cache jQuery objs$html, $body
Cache jQuery objects of <html> and <body> DOM nodes to prevent
unneed lookups later when adding, removing, and testing for classes.
note: we must wait on ready event for caching the values (which is done below), but we’ll
get their variables in place now.
var $html,$body;css class configurationcss
Configuration object for CSS classes that will be generated when we
build our -shown and state- class chains in their respective util methods.
var css = {
shown:{
prefix:"",
postfix:"-shown"
},
state:{
prefix:"state-",
postfix:""
}
};screen sizes lookup/configsizes
A config object for declaring an application state based on screen width measurements. The property names map directly to the CSS class name that will be used for that range.
.mobile, .tablet, .desktop, .desktop_wide
var sizes = {};setTimeout ID storage/mappingtimers
Store ID’s of our internal timeouts by name to allow us to clear them if/when we need to (e.g. resize throttling)
var timers = {}; var util = {get a view namespaceutil.get_namespace
We accept various things and each has a standard namespace
that we expect. Process a view and return it’s namespace
in the format we expect.
util.get_namespace(MyApp.ExampleRegion.el);
> 'example'
util.get_namespace($example);
> 'example'
get_namespace: function(view) {
var namespace = false;
if (view instanceof jQuery) {
namespace = view.attr('id');
} else if (typeof view == 'string') {
if (view.indexOf('#') == 0) {
view = view.substr(1);
}
namespace = view;
}
return namespace;
},CSV to a jQuery class chainutil.csv_to_class
Convert a CSV list into a class chain that can be used with
jQuery’s addClass, removeClass, and hasClass methods.
It also allows customization via pre and post which will
be added to each item in the list as it’s built.
util.csv_to_class('list,of,things','foo_','_bar')
> "foo_list_bar foo_of_bar foo_things_bar"
csv_to_class: function(csv,pre,post) {
var pre = (typeof pre != 'undefined' && pre.length) ? " "+pre : "";
var post = (typeof post != 'undefined' && post.length) ? post+" " : "";
csv = csv.replace(/\s/g,'');
csv = csv.split(',').join('[post],').split(',').join('[pre]');
csv = csv.replace('[post]',post).replace('[pre]',pre);
csv = pre + csv + post;
return $.trim(csv);
},exec a view func for each item of a CSVutil.exec_on_view_csv
Process a CSV list, execute our action using
each view from the list as the passed in param
// <body>
util.exec_on_view_csv('list,of,things', handlers.show);
// <body class="list-shown of-shown things-shown">
exec_on_view_csv: function(csv,action) {
var csv = csv.replace(/\s/g,'').split(',');
for (var i=0,len=csv.length; i<len; i++) {
var item = csv[i];
var view = $('#'+util.get_namespace(item));
action.call(moshpit,view);
}
},exec a state func for each item of a CSVutil.exec_on_state_csv
Process a CSV list, execute our action using
each state from the list as the passed in param
// <html>
util.exec_on_state_csv('list,of,things', handlers.add_state);
// <html class="state_list state_of state_things">
exec_on_state_csv: function(csv,action) {
var csv = csv.replace(/\s/g,'').split(',');
for (var i=0,len=csv.length; i<len; i++) {
var item = csv[i];
action.call(moshpit,item);
}
},generate shown classesutil.shown_class
Generate CSS class names for shown elements.
util.shown_class('list,of,things');
> 'list-shown of-shown things-shown'
shown_class: function(csv_list) {
csv_list = csv_list.replace(/#/g,'');
return util.csv_to_class(csv_list,css.shown.prefix,css.shown.postfix);
},generate state classesutil.state_chain
Generate CSS class names for user defined states.
util.state_chain('list,of,things');
> 'state_list state_of state_things'
state_chain: function(csv_list) {
csv_list = csv_list.replace(/#/g,'');
return util.csv_to_class(csv_list,css.state.prefix,css.state.postfix);
},generate size classesutil.get_sizes_chain
Generate CSS class chain for various screen sizes based on
the structure of sizes config object.
util.get_sizes_chain();
> 'mobile table desktop desktop_wide'
get_sizes_chain: function() {
if (this.sizes_chain) {
return this.sizes_chain;
}
var sizes_chain = '';
for (var size in sizes) {
if (sizes.hasOwnProperty(size)) {
sizes_chain += size+" ";
}
}
this.sizes_chain = $.trim(sizes_chain);
return this.sizes_chain;
},get the current display sizeutil.get_size
Test the current window size and return the name
of the size that matches based on sizes.min and
sizes.max configuration settings.
//window.width is 800px
util.get_size();
> 'tablet'
get_size: function() {
var win_w = $(window).width();
for (var size in sizes) {
var min = sizes[size].min;
var max = sizes[size].max;
if (sizes.hasOwnProperty(size)) {
if ( win_w >= min && win_w <= max ) {
return size;
}
}
}
},test if element is shownutil.is_shown
Helper method to see if a view is currently displayed/shown.
// #example_thing shown
util.is_shown('example_thing');
> true
// #example_thing hidden
util.is_shown('example_thing');
> false
is_shown: function(namespace) {
return $body.hasClass(util.shown_class(namespace));
},test if a state is currently setutil.has_state
Helper method to see if an application state is currently set.
// example_state is set
util.has_state('example');
> true
// example_state is not set
util.has_state('example');
> false
has_state: function(state) {
return $html.hasClass(css.state.prefix + state + css.state.postfix);
},Store vanilla jQuery methods we’ll replaceutil.jquery_funcs
We’ll need to store functions for $.show, $.hide
and $.toggle that we can use in our own functions
we’ll replace in jQuery.
jquery_funcs:false,copy jQuery default methods, inject our modified versionsutil.jquery_inject
We’ll need to listen for jQuery methods .show,.hide, and .toggle
to ensure calls to them still work with MoshPit. We’ll need to
modify them slightly to do this. To ensure they continue to function
normally we’ll copy the origionals locally, overwrite them with our own
and failover to the default method if it’s not applicible to ourselves.
note: although we call this multiple times from moshpit.add_jquery the
core copying and overwriting only happens on the inital call. It’s also
worth noting this only happens if using auto/tracking functionality via MoshPit.join
jquery_inject: function() {
if (util.jquery_funcs === false) {
util.jquery_funcs = {};
util.jquery_funcs.show = $.fn.show;
$.fn.show = function() {
var namespace = util.get_namespace($(this));
if (moshpit.views[namespace]) {
handlers.show(namespace);
return this;
} else {
return util.jquery_funcs.show.apply(this,arguments);
}
};
util.jquery_funcs.hide = $.fn.hide;
$.fn.hide = function() {
var namespace = util.get_namespace($(this));
if (moshpit.views[namespace]) {
handlers.hide(namespace);
return this;
} else {
return util.jquery_funcs.show.apply(this,arguments);
}
};
util.jquery_funcs.toggle = $.fn.toggle;
$.fn.toggle = function() {
var namespace = util.get_namespace($(this));
if (moshpit.views[namespace]) {
handlers.toggle(namespace);
return this;
} else {
return util.jquery_funcs.toggle.apply(this,arguments);
}
}
}
}
}; var handlers = {hide a viewhandlers.hide
Hide a view by removing it’s class from <body>.
// <body class="example-shown">
handlers.hide('example');
// <body class="">
hide: function(namespace) {
$body.removeClass(util.shown_class(util.get_namespace(namespace)));
},show a viewhandlers.show
Show a view by adding it’s class to <body>.
// <body>
handlers.show('example');
// <body class="example-shown">
show: function(namespace) {
$body.addClass(util.shown_class(util.get_namespace(namespace)));
},toggle a viewhandlers.toggle
Flip a view‘s shown state based on it’s current state.
//<body>
handlers.toggle('example');
//<body class="example-shown">
handlers.toggle('example');
//<body class="">
toggle: function(namespace) {
namespace = util.get_namespace(namespace);
if (util.is_shown(namespace)) {
handlers.hide(namespace);
} else {
handlers.show(namespace);
}
},hide a Marionette componenthandlers.marionette_hide
Deal with a Marionette component being closed/hidden.
marionette_hide: function() {
handlers.hide(util.get_namespace(this.el));
}, marionette_show: function() {
handlers.show(util.get_namespace(this.el));
},add a state classhandlers.add_state
Add a user defined state to the <html> element.
// <html>
handlers.add_state('example');
// <html class="state-example">
add_state: function(state) {
$html.addClass(css.state.prefix+state+css.state.postfix);
},delete a state classhandlers.del_state
Remove a user defined state from the <html> element.
// <html class="state_example">
handlers.del_state('example');
// <html class="">
del_state: function(state) {
$html.removeClass(css.state.prefix+state+css.state.postfix);
},toggle a state classhandlers.toggle_state
Add or remove a user defined state from <html> element
depending on it currently being set.
// <html>
handlers.toggle_state('example');
// <html class="state_example">
handlers.toggle_state('example');
// <html class="">
toggle_state: function(state) {
if (util.has_state(state)) {
handlers.del_state(state);
} else {
handlers.add_state(state);
}
},window.resize handler/throttlinghandlers.resize
Handle resize events from window with a short timeout
to ensure we don’t spam them when they fire in rapid succession.
Also handle the adding and removing of size classes to the <html>
element.
resize: function(evnt,stable) {
clearTimeout(timers.resize);
if (stable) {
$html.removeClass(util.get_sizes_chain()).addClass(util.get_size());
return;
}
timers.resize = setTimeout(handlers.resize,100,evnt,true);
},moshpit click handlerhandlers.click
Handle click events from document to see if we need to handle them
for any MoshPit related activity.
note: we target [data-moshpit] when we assign the handler so we’re only catching the events we already know we care about.
click: function(evnt) {
var $clicked = $(evnt.currentTarget);
var action = $clicked.data('moshpit');
var view = $clicked.data('view');
var state = $clicked.data('state');
if (view || state) {
if (typeof handlers[action] == 'function') {
if (state) {
util.exec_on_state_csv(state, handlers[action]);
} else {
util.exec_on_view_csv(view, handlers[action]);
}
evnt.preventDefault();
}
}
}
};moshpit
Collection of setup and teardown procedures for
automated tracking of elements that use MoshPit.join
and MoshPit.leave.
var moshpit = {Tracked views/namespaces lookupmoshpit.views
A lookup object we’ll use to test events from jQuery to see if it’s related to a view we’re currently tracking.
views:{},Setup a tracked Marionette componentmoshpit.add_marionette
Marionette components have their own event system via Marionette.Wreqr for
tracking .hide,.show,.close events. We’ll utilize them
to ease integration before setting up our jQuery tracking
for the view el as well.
add_marionette: function(view) {
view.on('show', handlers.marionette_show);
view.on('close', handlers.marionette_hide);
moshpit.add_jquery.call(moshpit,$(view.el));
},Remove tracking of Marionette componentmoshpit.del_marionette
When removing tracking of a Marionette component we’ll need
to remove the Wreqr events we’d subscribed to during setup
as well as remove it’s jQuery tracking.
del_marionette: function(view) {
view.off('show', handlers.marionette_show);
view.off('close', handlers.marionette_hide);
moshpit.del_jquery.call(moshpit,$(view.el));
},Add a tracked viewmoshpit.add_jquery
We’ll rely on jQuery for tracking view changes that come in
via .show, .hide, and .toggle.
add_jquery: function($view) {
util.jquery_inject(); // ensure we intergrate ourselves into jquery
var namespace = util.get_namespace($view);
moshpit.views[namespace] = true;
}, del_jquery: function($view) {
var namespace = util.get_namespace($view);
moshpit.views[namespace] = false;
handlers.hide(namespace);
}
};MoshPit
We’ll expose MoshPit on the window object with
a handful of externally usable features we want others
to have access to.
window.MoshPit = {enable tracking of a viewMoshPit.join
Will be called externally when a view wants to
be tracked by MoshPit,
// <body>
$('#example').show();
// <body>
// <body>
MoshPit.join('example');
$('#example').show();
// <body class="example_shown">
join: function(view) {
if (typeof view == 'string') {
util.exec_on_view_csv(view, moshpit.add_jquery);
} else if (typeof view == 'object') {
if (view instanceof jQuery) {
moshpit.add_jquery.call(moshpit, view);
} else if (view instanceof Marionette.Region
|| view instanceof Marionette.View
|| view instanceof Marionette.ItemView
|| view instanceof Marionette.Layout) {
moshpit.add_marionette.call(moshpit, view);
}
}
return MoshPit;
},disable tracking of a viewMoshPit.leave
Will be called externally when a view no longer
should be tracked by MoshPit.
// <body>
MoshPit.join('example');
$('#example').show();
// <body class="example_shown">
MoshPit.leave('example');
// <body class="">
$('#example').show();
// <body class="">
leave: function(view) {
if (typeof view == 'string') {
util.exec_on_view_csv(view, moshpit.del_jquery);
} else if (typeof view == 'object') {
if (view instanceof jQuery) {
moshpit.del_jquery.call(moshpit, view);
} else if (view instanceof Marionette.Region
|| view instanceof Marionette.View
|| view instanceof Marionette.ItemView
|| view instanceof Marionette.Layout) {
moshpit.del_marionette.call(moshpit, view);
}
}
return MoshPit;
},manually show a viewMoshPit.show
Let’s an external resource show a view regardless
of it’s tracked status.
// <body>
if (show_the_example) {
MoshPit.show('example');
}
// <body class="example_shown">
show: function(view) {
util.exec_on_view_csv(view, handlers.show);
return MoshPit;
},manually hide a viewMoshPit.hide
Let’s an external resource hide a view regardless
of it’s tracked status.
// <body class="example_shown">
if (hide_the_example) {
MoshPit.hide('example');
}
// <body class="">
hide: function(view) {
util.exec_on_view_csv(view, handlers.hide);
return MoshPit;
},manually toggle a viewMoshPit.toggle
Let’s an external resource toggle a view regardless
of it’s tracked status.
MoshPit.toggle('example');
// <body class="example_shown">
MoshPit.toggle('example');
// <body class="">
toggle: function(view) {
util.exec_on_view_csv(view, handlers.toggle);
return MoshPit;
},check if a view is shownMoshPit.is_shown
Externally accessible means to find out if a view is currently being shown.
// <body class="example_shown"
MoshPit.is_shown('example');
> true
MoshPit.hide('example');
// <body class=""
MoshPit.is_shown('example');
> false
is_shown: function(view) {
return util.is_shown(util.get_namespace(view));
},add a view stateMoshPit.add_state
Applies a view state to the entire page.
// <html>
MoshPit.add_state('highlight_all_examples');
// <html class="state_highlight_all_examples">
note: states are useful for modifying multiple views and allows for simplier LESS structures and less (as in LoC) generated CSS
add_state: function(state) {
util.exec_on_state_csv(state, handlers.add_state);
return MoshPit;
},remove a view stateMoshPit.remove_state
Removes a view state from the entire page.
// <html class="state_highlight_all_examples">
MoshPit.remove_state('highlight_all_examples");
// <html class="">
remove_state: function(state) {
util.exec_on_state_csv(state, handlers.del_state);
return MoshPit;
},toggle a view stateMoshPit.toggle_state
Toggles a view state on the entire page.
// <html>
MoshPit.toggle_state('highlight_all_examples");
// <html class="state_highlight_all_examples">
MoshPit.toggle_state('highlight_all_examples");
// <html class="">
toggle_state: function(state) {
util.exec_on_state_csv(state, handlers.toggle_state);
return MoshPit;
},check if a view state is enabledMoshPit.has_state
Externally accessible means to find out if a view state is currently active.
// <html class="state_highlight_all_examples">
MoshPit.has_state("highlight_all_examples");
> true
// <html class="">
MoshPit.has_state("highlight_all_examples");
> false
has_state: util.has_state,get current display sizeMoshPit.get_size
Externally accessible means to find out the current size of the display.
// <html class="desktop">
MoshPit.get_size();
> desktop
get_size: util.get_size };init
Steps we’ll need to take on domready.
Hook init into jQuery’s $.ready event.
var init = function() {
sizes = window.MoshPitSizes;
handlers.resize();
$html = $('html');
$body = $('body');
$(window).on('resize', handlers.resize);
$(document).on('click','[data-moshpit]', handlers.click);
};
$(init);})(jQuery);