Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

IntentionJS and RequireJS - How monkeys do it (the sfw edit)

Parent Feed: 

Prerequisites:

  • RequireJS (and some knowledge of how to use it)
  • IntentionJS
  • A brain
  • Bananas

There are plenty of reasons you shouldn’t HAVE to use IntentionJS, but it’s just so good when you NEED your fix, of DOM manipulation.

Normally a responsive website should be designed so that when you expand or collapse the viewport (effective screen), the DOM elements flow naturally from left to right, and top to bottom. The order is preserved, and it was designed so that those elements follow that flow in terms of importance and usability.

Admittedly, this does limit us at times, and we need elements to be in completely different placements in the DOM, depending on the device used. Sure we can mess with duplicated content by hiding and showing the right elements based on the screen size [please don’t, it’s bad], or doing some fancy schmancy css floating and absolute positioning, but then you start to get other fun issues, that go beyond the scope of this banana log.

So, we’re left with manually moving the elements around. You could start using the append() and after() functions say, from jQuery, but that also gets complicated.. setting screen widths, window resize, or using Modernizr.mq (media query), etc. All messy in some form or another.

We like clean, even though we like poo, we still like clean.

Our Hero, IntentionJS

From the mouths of the intentionJS magicians: (http://intentionjs.com/)

“Intention.js offers a light-weight and clear way to dynamically restructure HTML in a responsive manner.”

Good right? riiight.

Okay, so we all have our own methods for including JS, and writing libraries and code. Here’s what we monkeys do.

Starting with RequireJS

We like using requireJS. A lot. In fact I get a banana every time I do. So in the main.js file loaded by require we have:

requirejs.config({
paths: {
// vendor
'intention': 'vendor/intention',
'underscore': 'vendor/underscore-min',
'viewportsize': 'vendor/viewportSize-min',
// custom
'jquery': 'modules/jquery-global',
'intentcontext': 'modules/intentcontext',
'homepage': 'modules/homepage',
'initintent': 'modules/initintent'
},
});

Here we are just telling require where to find all our ‘required’ files when we ‘require’ them. Yeah.. there is probably a better way of saying that. *puts on deal with it glasses*

Intention requires underscore, so we’re including that. Also, we’re using a little library called ‘viewportsize’. Why? well because different browsers report different viewport sizes, based on whether or not it’s showing the scroll bar. That’s a problem, this fixes that problem. (https://github.com/tysonmatanich/viewportSize)

Then we include jQuery, cause we need it. Then comes some magical code with unicorns.. and monkeys.

require([
'jquery',
'underscore',
'intentcontext',
], function ($, _, IntentContext) {
'use strict';
// DOM ready
$(function() {
// js loaded only on homepage
if ($('body').hasClass('front')) {
require(['homepage']);
}
if ($('html').length > 0) {
require(['initintent']);
}
}); // DOM ready
});

So here, we’re just including the needed libraries for the site in general, and then checking if we’re on the homepage, if so, include the homepage module. Then the very last that we include in the initialization of the intent. Think of it, as intentions’s big red “go” button. We’ll get to these a bit later. For now, just know, that we are making sure that the initinent file is included last, since we’re doing all ‘intention’ setup first. Since require loads these in ORDER, of the code inclusion, we’re able to do all the setup first, then lastly initialize it.

IntentContext.js

This is where we setup our ‘contexts’. A context is basically a ‘switch point’ - a point a which stuff is supposed to happen. Each ‘context’ is associated to a screen size (these values should match your CSS media queries for major layout changes)..

IntentContext.bp_desktop = 1025;
IntentContext.bp_tabletlandscape = 769;
IntentContext.bp_tablet = 641;
IntentContext.bp_mobilelandscape = 321;
IntentContext.bp_mobile = 0;

These are the breakpoints were major layout changes happen (for the purpose of this blog). Yours would match that of your CSS breakpoint values.

Next up, making our contexts. As you will see, each context has a name, and I’m setting the “min” value to the breakpoint value that I set in the above code. So basically, the ‘desktop’ context will get triggered every time the browser hits the “1025 pixel” viewport width or above. (It won’t keep re-triggering events though, as you increase viewport width above that, which is nice.) All the other ‘contexts’ will get triggered at their respective screen width values.

IntentContext.horizontal_axis = IntentContext.intent.responsive({
ID: 'width',
contexts: [{
name: 'desktop',
min: IntentContext.bp_desktop
}, {
name: 'tabletlandscape',
min: IntentContext.bp_tabletlandscape
}, {
name: 'tablet',
min: IntentContext.bp_tablet
}, {
name: 'mobilelandscape',
min: IntentContext.bp_mobilelandscape
}, {
name: 'mobile',
min: IntentContext.bp_mobile
}],
matcher: function (measure, context) {
return measure >= context.min;
},
measure: function () {
IntentContext.v_width = viewportSize.getWidth();
return IntentContext.v_width;
}
});

So, there is a thing. It’s thing you may need. Normally intention won’t activate the context on first page load, which you may need. We will get to that. (This is what that initintent.js file is for).

Homepage.js

Now we need to tell which elements where to be placed in the dom, according to whatever ‘context’ is triggered. You can either go directly in the HTML and add all the special intention attributes to the elements, or do it via JS. I like doing it in the JS, i find it cleaner.

So in our Homepage.prototype.intent = function () function:

var footer = $('.l-footer');
footer.attr('intent', '');
footer.attr('in-desktop-after', '.l-header');
footer.attr('in-tabletlandscape-after', '.l-main');
footer.attr('in-tablet-after', '.l-main');
footer.attr('in-mobilelandscape-after', '.l-header');
footer.attr('in-mobile-after', '.l-header');
IntentContext.intent.on('desktop', function() {
footer.attr('style', 'border: 4px solid red;');
});
IntentContext.intent.on('tabletlandscape', function() {
});
IntentContext.intent.on('tablet', function() {
});
IntentContext.intent.on('mobilelandscape', function() {
footer.attr('style', 'border: 4px solid white;');
});
IntentContext.intent.on('mobile', function() {
footer.attr('style', 'border: 4px solid blue;');
});

First line we just get the element we want to target. The next lines are key.

I’m now manually adding all the required contexts on that elements, so for each ‘breakpoint’ context, we know where to place the footer. The syntax is as follows

footer.attr(‘in-[your-breakpoint-name]-[move-function], ‘[dom element]’)’

‘your-breakpoint-name’ is just the name you associated to the breakpoint, up in the IntentContext.js file.

‘move-function’ is the method in which you want to place that element. They work just like jQuery’s manipulation functions [append(), before(), after(), prepend()]

‘dom-element’ is just the element you are specifying to be “moved to”.

So in this case, when the browser hits the ‘desktop’ layout screen width, we are putting the ‘.l-footer’ element just after the ‘.l-header’ element in the DOM. The next lines all work the same and specify where the element needs to go, for whichever context (screen size).

Then, we have some more magical code.

IntentContext.intent.on('desktop', function() {
footer.attr('style', 'border: 4px solid red;');
});
IntentContext.intent.on('tabletlandscape', function() {
footer.attr('style', '');
});

So, for each context, we can run some custom code of any kind. In this case, every time we hit the ‘desktop’ context, we are going to add a border to the footer element. Everytime we hit the ‘tabletlandscape’ context, we make sure remove any lingering styles. Etc..

I normally like to use these methods to ‘reset’ certain things that may have been triggered on an alternate layout.

Lastly, the Initilization of Intent. This will allow us to use those .on() [in above code] functions on page load as well.

Know that all this will only happen on the homepage though. If you need this to happen on all pages, you can create a separate module that can handle site wide context changes, and just include it in the main.js require section.

InitIntent.js

IntentContext.horizontal_axis.respond();
IntentContext.intent.elements(document);
$(window).on('resize', IntentContext.horizontal_axis.respond);

So these 3 lines just get everything going. Check out the intention.js website for further detail, but suffice to know, they get intention up and running.

That should be it to have it all working nicely and being able to do some sexy DOM manipulation without to much pain.

Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web