What It's all About Intention.js is a lightweight tool for responsive design developed at Dow Jones that manipulates the DOM via HTML attributes. The methods for manipulation are placed with the elements themselves, so flexible layouts don't have to be so abstract and messy. What should an element's classes be on mobile vs tablet? Where should advertising markup be placed when viewed on a desktop browser? Does the page require an alternate slideshow widget on touch-enabled devices? These are all scenarios that Intention.js can handle, altering the page based onusers' devices. Context.js creates a set of common page contexts for width thresholds, touch devices, high-res displays and a fallback. And you can easily add your own contexts on top of these, or create all your own custom threshold group.

Let's start with the basics, though. Thresholds, Axes, & Contexts Intention.js works by determining what are called “threshold groups”. These threshold groups define the context in which a user is viewing your site. Threshold groups work by measuring an axis, like the browser’s width or the device’s pixel density (for high resolution screens). A lot of these contexts are predictable, and some common patterns are included in a handy implementation of intention.js called context.js - context_name, value width - mobile, 0 - tablet, 510 - standard, 840 orientation - portrait, 0 - landscape, 90 touch - touch, true highres - highres, true Manipulations via HTML Attributes Intention.js has three basic manipulations: attribution manipulations, class manipulations, and placement manipulations. With these three, you can change the value of any attribute, add or remove an element’s class, and adjust the position of an element within the structure of the document.

Usage Basic Syntax Giving Intention.js instructions is as easy as flagging the element as "intentional" and giving it an intentional attribute.

Intentional attributes can be specific to: A change in contexts within an axis

A specific context passing true

A specific context within a specific axis passing true Intentional attributes can be specific to: For the purposes of documentation, in- will be used instead of the proper HTML-valid data-in- . <elm attr= "val" intent in-axisID: in-axisID:contextName-attr= "val" in-contextName-attr= "val" > </elm> <div intent in-width: ></div> <img intent in-orientation:landscape-src= "wide.png" /> <nav intent in-touch-prepend= "#content" /> Dependencies Intention.js requires jQuery and Underscore.js to work. You can download and link to them manually, or you can include them via require. <script data-main= "assets/js/context" src= "assets/js/require/require.js" > </script> Compatibility Intention.js is tested to work on all modern browsers, including Internet Explorer back to IE8! Woo-hoo!



Note: jQuery 2.x dropped support for IE8, so obviously using it in conjunction with Intention will not work in IE8. If you don't care about that, rest assured there is nothing Intention needs in jQuery 1.x that isn't available in jQuery 2.x. Intention Markup <header> <img src= "logo.png" intent in-highres-src= "retina.png" /> </header> <nav intent in-mobile-prepend= "#content" in-tablet-prepend= "#content" in-standard-after= "header" in-touch-class= "swipeDrawer" > <a id= "about" intent in-mobile-href= "about.html" in-tablet-href= "about.html" in-standard-href= "#about" > About </a> <a id= "projects" href= "/faq" intent in-mobile-before= "#about" > FAQ </a> </nav> <div id= "content" intent in-width: in-orientation: > This is all so easy! </div> On an iPhone 5 <header> <img src= "retina.png" /> </header> <div id= "content" class= "mobile portrait" > <nav class= "swipeDrawer" > <a href= "/faq" > FAQ </a> <a href= "about.html" > About </a> </nav> This is all so easy! </div> On Regular Tablets <header> <img src= "logo.png" /> </header> <div id= "content" class= "tablet portrait" > <nav class= "swipeDrawer" > <a href= "about.html" > About </a> <a href= "/faq" > FAQ </a> </nav> This is all so easy! </div> On Desktops <header> <img src= "logo.png" /> </header> <nav> <a href= "about.html" > About </a> <a href= "/faq" > FAQ </a> </nav> <div id= "content" class= "standard" > This is all so easy! </div>

<div id= "timeExample" intent in-time: > </div> var time = intent . responsive ({ ID : 'time' , contexts : [ { name : 'night' , min : 20 }, { name : 'evening' , min : 17 }, { name : 'sunset' , min : 16 }, { name : 'day' , min : 9 }, { name : 'morning' , min : 0 } ], matcher : function ( test , ctx ) { return test >= ctx . min ; }, measure : function ( arg ) { var time = new Date (); return time . getHours (); } }); time . respond (); //check the current context time.respond(' night evening sunset day morning '); Intention.js responds to a bunch of device contexts, but it’s open to any index. You can create custom contexts based on anything you can measure, and restructure your code in response! It’s not just changing a page based on the browser’s width: it’s noticing touch-capabilities, portrait/landscape orientation, high resolution contexts. Intention.js can be taught to restructure pages based on scroll depth, pageviews, time of day—basically anything!

Manipulations Class Manipulations The simplest intentional attribute is a class manipulation. This manipulation adds the current context as a class to the element. Adding in-axis_name: (note the trailing colon) to a flagged element is enough to get it working. <img intent in-orientation: src= "a.jpg" /> ↓ ↓ ↓ <!--In portrait orientation--> <img class= "portrait" src= "a.jpg" /> <!--In landscape orientation--> <img class= "landscape" src= "a.jpg" /> Intention does not touch attributes that are assigned outside of in- commands — so <div class= "foo" intent in-width: /> will keep its class foo regardless of what horizontal_axis context is passed. Attribute Manipulation Intention.js can also manipulate an element's attributes with more specificity than can be achieved via class manipulations. To start, set a base (default) attribute in case no contexts are met, then specify context-specific attribute values. <img intent in-base-src= "reg_img.png" in-highres-src= "big_img.png" /> ↓ ↓ ↓ <!--On regular devices--> <img src= "reg_img.png" /> <!--On retina displays--> <img src= "big_img.png" /> Attribute manipulation can used for more specific class manipulations, too. <section intent in-mobile-class= "narrow" in-tablet-class= "medium" in-standard-class= "wide" /> Placement Manipulation Intention.js can rearrange elements within a page layout based on the context. Suppose we want to demote the status of the navigation when the user is on smaller devices. The following specification on the navigation might do what we need: <header> <nav intent in-mobile-prepend= "footer" in-tablet-append= "section" in-standard-append= "header" > </nav> <section> ... </section> <footer> ... </footer> </header> When the device is 320px wide or less, the navigation will sit at the top of the footer. When the device is between 321px and 768px wide, it will appear right below the section . Obviously, on larger displays (wider than 769px) the navigation will be at the end of the header . Move functions Intention.js provides four basic functions for rearranging elements. They include: - prepend - append - before - after These function just like jQuery DOM manipulations. intent in-ctxName-moveFx="selector" Selectors can be general element selections like above ( ...prepend="footer" ), or specific ( ...prepend="#intro" ). Play with me A B C var mini = intent . responsive ({ ID : 'mini' , contexts : [ { name : "large" , min : 300 }, { name : "avg" , min : 75 } ], matcher : function ( test , context ) { return test >= context . min ; }, measure : function ( arg ) { return $ ( "#resizable" ). width (); } }); mini . respond (); //'resize' is a jQueryUI event $ ( '#resizable' ) . on ( 'resize' , mini . respond ); <div id= "resizable" > <div class= "orange" intent in-mini: > Play with me <div id= "orange1" > </div> <div id= "orange2" > </div> </div> <div class= "blue" intent in-mini: in-avg-before= "#orange2" in-large-after= ".orange" > </div> <div class= "green" intent in-mini: in-avg-after= ".orange" in-large-append= ".blue" > <div id= "green1" > <a intent in-avg-href= "#average" in-large-href= "#large" > A </a> </div> <div id= "green2" > B </div> <div id= "green3" > C </div> </div> </div>

Custom Axes & Contexts One of intention.js's most exciting features is its scalability: you can use it to respond to almost anything! Any type of data that is quantifiable can be used to manipulate the page. Of course context.js comes with the most common patterns of responsive design, but let's take a look at how to create our own. Markup Each axis is made up of four basic properties: the axis ID (optional), the context group, the matcher function, and the measure function,. As we know, an axis is a measurable object or set of information. This axis is optionally given an ID so it can be used in specific manipulations. A measure function finds the current measurement of that axis, and the matcher function finds where that measurement lies in a set of thresholds and breakpoints called contexts. Contexts are defined in an ordered array and help dictate when the DOM should be manipulated. var axis = intent . responsive ({ ID : 'axisID' , contexts : [ { name : 'ctx1' , val : '3' }, { name : 'ctx2' , val : '2' }, { name : 'ctx3' , val : '1' } ], matcher : function ( measure , context ){ return measure >= context . val ; }, measure : function (){ return someMeasurement ; } }); First things first If you are completely abandoning context.js, you must be sure to first create an Intention object. This is required for any axis creation or response. Context.js does this right out of the box, so if you are only extending the included axes, you need not create a new Intention object. Be sure to check out the next section Initialization to see what else context.js takes care of—things you'll have to do when starting from scratch. var intent = new Intention (); // axis creation // be sure the Intention object // is saved to a global variable // if you want to use it // across plugins window . intent = intent ; Contexts To work with whatever data we measure, we need to set up contexts that will act as thresholds. The contexts property is an ordered array of objects. Each context object represents a range of data. If a measurement falls in that range of data, the corresponding context passes true. Then that context is set as the current context. When a measurement is being matched against the contexts, the function will iterate through the array in order, so the breakpoints must be listed in an increasing or decreasing order. If a context passes true, the array is immediately exited. Each context object must have a name property. This is used to identify exactly what context is true. Be sure the context's name is a string! var axis = intent . responsive ({ // ... contexts : [ { name : 'ctx3' , val : '20' }, { name : 'ctx2' , val : '10' }, { name : 'ctx1' , val : '0' } ], // // Here we use a descending value order // and our matcher function will be // written accordingly // // ... }); Measure Function The measure function's task is simply to find data that will later be matched against the contexts. It can be a series of complex operations, but as long as it returns a value, it's doing its job. In most cases, this function is just a return that passes off a value to the matcher function. var axis = intent . responsive ({ // ... measure : function (){ return someMeasurement } }); Matcher Function The matcher function uses the measure function's returned value and the context array as parameters. It tests the measurement against each context's value property. When a measurement fits in a context's data range, the context passes true. This is done with a comparative statement. For example, if a measurement does not exceed the maximum value of a context, then the context will pass true. If it does, then the matcher function will see if exceeds the next context's maximum value (which will be a higher value). The comparative statement must agree with the order of the contexts. If context values are listed in descending order, the matcher function must test if the measurement is greater than or equal to the context minimum value. If it is, then we know it definitely is greater than all of the other contexts' minimum values. var axis = intent . responsive ({ // ... matcher : function ( measure , context ){ // for contexts arranged in // greatest-to-least order // (exits array when the measure // is greater than the minimum) return measure >= context . min ; // for contexts arrange in // least-to-greatest order // (exits array when the measure // is less than the maximum) return measure <= context . max ; // the default matcher function // looks for an exact match return measure === context . val ; // be sure to compare the measurement // to the context's value, not just // the context object }, // ... }); Axis IDs An axis ID property allows for context-aware restructuring. Although this property is optional, it is used in HTML attributes to command DOM manipulations. An axis ID lets you create manipulations for any change in contexts in a specified axis, or for any specific context within a specific axis. Without an assigned ID, the axis is randomly given a hash as an ID that changes on every page load—not very helpful for making specific changes to the layout. var axis = intent . responsive ({ ID : 'axisID' , contexts : [ { name : 'ctx3' , val : '20' }, { name : 'ctx2' , val : '10' }, { name : 'ctx1' , val : '0' } ], matcher : function ( measure , ctx . min ){ return measure >= ctx . min ; }, measure : function (){ return someMeasurement ; } }); <div intent in-axisID: ></div> <img intent in-axisID:ctx3-src= "..." /> Putting it all together Responding Every intent.responsive axis returns some useful properties, of which respond is probably the most important. This property contains a function that sets off the measure and matcher functions. Calling axis.respond() updates the current context within an axis. Note that the intent.responsive({...}) 's variable name is used to for responding, not the axis ID. Keep variable scope in mind! You can always use intent.axes.axisID.respond() if you are out of the axis variable's scope. var axis = intent . responsive ({ // ... }); axis . respond (); // respond once $ ( window ). on ( 'event' , axis . respond ); // respond on each instance of 'event' If your axis needs only respond once, you can make it do so right after you create the axis with a trailing respond command. The touch axis is such an axis: Intention needs only test touch capabilities at page load because they are unlikely to change mid-session. var axis = intent . responsive ({ // ... }). respond (); // note that you cannot have the // axis respond again after this // axisID.respond(); won't work Finding the Current Context Another useful property intent.responsive returns is current . Calling this property will return the name of the most recent context passed as a string. // assuming your Intention() object // is saved as "intent" intent . axes . axisID . current

Intialization If you're not using context.js, there's a few things you need to do to get Intention working. Context.js does this for you, but you may find yourself using intention.js for something other than standard responses. Finding Elements Once all the contexts have been written, Intention must search the DOM for elements that have been flagged "intentional". It will find each element and create a record of it and its manipulations in an array of objects intent.elms . Every specified manipulation will be saved in this array as instructions, so when an axis or context is passed the manipulation is more immediate. To perform this search, first construct all of your axes and threshold groups, then run this function at doc ready. $(function(){ intent.elements(document); }); Should you need to inject intentional HTML after the page load, you can always add elements to this registry using intent.add() . For demonstration purposes, here's an example of an object saved in the element array. [... ↓ Object → elm: section#output ↓ spec: Object __move__:"#input" __placement__:"after" ↓ width: Object standard: Object class:"standard" tablet: Object class:"tablet" mobile: Object class="mobile" ...] Extending Intention For Other Plugins Context.js returns the Intentional object right out of the box, so you can use it across plugins and files; but if you are just using intention.js and your own custom axes, you might want to also extend the Intentional object yourself. Allowing the Intentional object to be used in plugins and other files is as simple as saving it to a global variable. At the end of your document, simply create a variable for the window scope that matches the variable name for the Intention object you created in the very beginning. var intent = new Intention(); //... //all your stuff //... window.intent = intent; (function(){ var intent = new Intention(), axisName = intent.responsive({ ID: 'axisID', contexts: [ {name:'context1', min:1}, {name:'context2', min:0} ], matcher: function(measure, context){ return measure >= context.min; }, measure: function() { return someMeasurement; } }); $(window).on('event', axisName.respond); $(function() { intent.elements(document); }); //save it to a global variable window.intent = intent; });

Intent Events It's possible (and very easy) to set up event listeners for context changes. Intention.js supports event binding to scenarios such as: When a specific context passes true When a specific context in a specific axis passes true When a specific axis passes any context The syntax generally follows jQuery's syntax for event binding. Preface the .on() event handler with the namespace intent . The event itself is made up of two optional parts: an axis ID and a context name. The axis ID refers to the axis' ID property and should always be followed by a : . Using the axis ID on its own will fire every time a context in that axis is passed. Specifying a context name will narrow the event to fire only when that context within that axis is passed. Alternatively, an event handler can be created for contexts without a specified axis. Simply excluding the axis ID component (and its trailing : ) will make a more general event listener for the specified context name. In this scenario, be careful of naming conflicts. If any two contexts share the same name, both will fire this same event. Below is a list of reference IDs for the axes supplied in context.js horizontal_axis: width - standard - tablet - mobile orientation_axis: orientation - portrait - landscape touch: touch highres: highres 1. When a specific context passed true. intent . on ( 'contextName' , function () { // ... }); intent . on ( 'mobile' , function () { // ... }); 2. When a specific context in an specific axis passes true intent . on ( 'axisID:contextName' , function () { // ... }); intent . on ( 'width:mobile' , function () { // ... }); 3. When a specific axis passes any context intent . on ( 'axisName:' , function () { // where axisName is the axis' ID property // ... }); intent . on ( 'width:' , function () { // ... }); Note: currently, Intention does not support multiple event types per event handler. You will have to chain events instead. Event Handlers for Intention's First Response There are cases when you need to listen for Intention's first response on page load—maybe to insert specific content or run certain functions. There are obviously a number of ways to do this, but here are two simple methods. One-time Page Load Responses On certain occasions you may only care about the first context passed when the page first loads. After that first load, you may not want the event handler to keep firing. In this scenario, we can use jQuery's Deferred Objects. var init = function ( contexts , callback ){ var dfds = [], _ . each ( contexts , function ( ctx ){ var dfd = $ . Deferred (); dfds . push ( dfd ); if ( intent . is ( ctx )) { dfd . resolve (); } else { intent . on ( ctx , dfd . resolve ); } }); $ . when . apply ( this , dfds ). done ( callback ); }; init ([ 'mobile' ], function () { console . log ( 'mobile ctx is first passed' ); }); init ([ 'standard' , 'tablet' ], function () { console . log ( 'both standard and tablet have been passed' ); }); The above function init() accepts two parameters: an array of context names to be passed and a callback function that is triggered when the array is satisfied. With supplied each context, init() creates a deferred object and adds it to a master array. If the user is already in that context when the function is called, the deferred object will be resolved. If the user is not already in that specific context, init will create an event handler to wait for the context to pass true, when it will resolve the deferred object. When all of the deferred objects in the master array have been resolved, the callback function will fire once and only once. Repeating Event Handlers If you do not care about the first response on page load exclusively but want to create an event handler that still affects the first response, the easiest way is to edit context.js. We want to have all event handlers be written before the affected axes respond. Otherwise, the first manipulations will have already occurred before the event handler is even created. var intent = new Intention (); // Event handlers can be created // before the axis is even written intent . on ( 'standard' , function () { // ... }); var horizontal_axis = intent . responsive ({ ID : 'width' , contexts : [ { name : 'standard' , min : 840 }, { name : 'tablet' , min : 768 }, { name : 'mobile' , min : 0 } ], matcher : function ( measure , context ) { return measure >= context . min ; }, measure : function () { return $ ( window ). width (); } }); // Just as long as they are made // before the axis responds // and after the intent object intent . on ( 'width:' , function () { // ... }); horizontal_axis . respond (); $ ( window ) . on ( 'resize' , horizontal_axis . respond ); window . intent = intent ; $ ( function () { intent . elements ( document ); });