Category Archives: JavaScript

iScroll 4 Hacks

29 Nov 2012

Here are some hacks that I’ve discovered to get the most out of iScroll. The basic customization parameters are documented at www.cubiq.org’s website but you have to look at the cleanly laid out source to find the gems.

The current list of tips:

  • Improved Lock Direction
  • Faster Snap
  • Snap to a new element and only that element
  • Controlling Scroll Bar Position
  • Enabling SELECT, INPUT and TEXTAREA focus
  • Enabling hidden iScroll divs (where the wrapper div has display:none)
  • The bounce of death – non-scrolling content
  • Disappearing Lists
  • iScroll scrolls up when a scrolled element is clicked

Improved Lock Direction

There will come a time when you have two iScrolls on a page – one vertical and one horizontal. You will find that even if you enable “lockDirection” that iScroll will still be touchy. Try to scroll up and the slightest movement left or right will be caught by the horizontal iScroll. Try to swipe left or right and the slightest up or down movement will be caught by the vertical iScroll. Lock direction provides some improvement so be sure to enable it:

...
var myScroll = myScroll = new iScroll('yourWrapper', {lockDirection:true });
..

But to get even better “lockDirection performance” try the following hacks. Locate lines 470 and 473 within iscroll.js. We’ll want to change a number from 5 to 0.

Line 470 before:

...
if (that.absDistX > that.absDistY - 5) {
...

And line 470 after:

...
if (that.absDistX > that.absDistY - 0) { // yes, you can just remove the math too
...

Do the same with line 473. You should now notice that lockDirection with overlapping iScrolls works better than before.

Faster Snap

To enjoy faster element snapping open iscroll.js and scroll to line 437 where you will find this:

...
that.lastScale = scale / this.scale;
..

Subtract 10 from the lastScale value as seen below:

...
that.lastScale = scale / this.scale - 10;
..

Next, go to line 780 and find this line:

...
var deceleration = 0.0006,
...

Change the deceleration value to 0.002 per the next example:

...
var deceleration = 0.002, // 0.0006
...

Done, your snapping should be much “snappier”.

Snap to a new element and only that element

This tip is pretty cool – and simple. You can prevent “over swiping” in a snap-enabled iScroll by disabling momentum in one of two ways. The first approach is the easiest and most simple – and that is to disable momentum when you initialize iScroll, for example:

...
var myScroll = myScroll = new iScroll('introSliderWrapper', {snapThreshold:.5, momentum:false });
..

Note the “momentum:false” in the above configuration object. Momentum will be turned off.

Ok, so this next way of doing it is a **complete** hack and is really only of geeky interest – but if you look at the iScroll source you will see that there is some old code present that is very similar to the code that precedes it.

The new and old “duplicate” blocks are at lines 589 (new, momentum) and 608 (old, no momentum). Each block of code is surrounded by “if” statements that contain the exact same conditional. The “return” statement at the end of the first “if” block prevents the second “if” from running. It is necessary to comment the entire “if” block that starts at line 590 all the way to and including the return statement at line 605. Be aware that if you do this you are in effect creating a “global” change that will impact all iScrolls that you may employ. As a result, its best to stick with the perfectly functional momentum:false flag.

Anyway, to continue, locate the following code at line 589:

...
    // Do we need to snap?
    if (that.options.snap) {
      distX = newPosX - that.absStartX;
      distY = newPosY - that.absStartY;
      if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); }
      else {
            snap = that._snap(newPosX, newPosY);
            newPosX = snap.x;
            newPosY = snap.y;
            newDuration = m.max(snap.time, newDuration);
      }
    }

    that.scrollTo(m.round(newPosX), m.round(newPosY), newDuration);

    if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
    return;
  }
...

And with it commented out:

...
    // Do we need to snap?
    /*if (that.options.snap) {
      distX = newPosX - that.absStartX;
      distY = newPosY - that.absStartY;
      if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); }
      else {
            snap = that._snap(newPosX, newPosY);
            newPosX = snap.x;
            newPosY = snap.y;
            newDuration = m.max(snap.time, newDuration);
      }
    }

    that.scrollTo(m.round(newPosX), m.round(newPosY), newDuration);

    if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
    return; */
  }
...

Done.... when swiping you will no longer swipe past the the adjacent snap-to element. As mentioned, this is done easier by adding the momentum attribute to the iScroll configuration object.

Controlling Scroll Bar Position

Enabling scrollbars in iScroll is easy - making sure they are positioned where you want - easy as well as long as you position your div's appropriately. Even if you run into problems, you can hack the scroll bar into position.

To make scrollbars appear just pass a properly formatted configuration object to the iScroll, like so:

...
new iScroll('scrollWrapper',{
   vScrollbar: true, // the vertical scroll bar has been enabled
   momentum:true
});
...

This will make the vertical scrollbar appear - but in some cases the scrollbar may not be aligned with your scrolling content but aligned along the right-edge of the window. Sure, it may work but if you have a multi-column layout then chances are that you may not want the scroll bar "removed" from the scrolling area. The fix here is simple and achieved via CSS - make sure that your "scrollWrapper" div has its position style set to one of the following:

  1. absolute
  2. relative
  3. fixed
  4. inherit (as long as the inherited value is one of the preceeding three values)

Once the above is done then iScroll will properly place the scrollbar along side your content.

What if you can't change your css or don't want to? Then you can attack it with a little jQuery.

iScroll will place the scrollbar divs within the scroll wrapper but without an id. Since the wrapper does have an id we can use jQuery to get the wrapper's children of which the scrollbar is a member. The last child will always be the scroll bar. For example:

...
window.addEventListener('load',function(){
   new iScroll('scrollWrapper',{ // the scrollbar will be the last child within the wrapper
      vScrollbar: true,
      momentum:true
   });

   // lets get the wrapper's children
   var children = $('#scrollWrapper').children();
   // the children array has a length of two, the last one is always the scrollbar
   // position the scroll bar as desired
   children[1].style.height = 350 + 'px';
   children[1].style.top = 0 + 'px';
   children[1].style.left = 190 + 'px';
},false);
...

As you can see from the above its a simple matter to attack the iScroll scroll bar. If you like you could obviously change this:

children[1].style.height = 350 + 'px';

To this:

children[children.length-1].style.height = 350 + 'px';

So that you **always** get the last child as who knows how you're structuring your content.

In addition to this you may not always have a scroll bar present depending on the length of your content. Therefore, we have to check for its existence in some way. In a typical setup the wrapper will only have a single child if the scrollbar is not there. The following check is based on this assumption:

...
window.addEventListener('load',function(){
   new iScroll('scrollWrapper',{ // the scrollbar will be the last child within the wrapper
      vScrollbar: true,
      momentum:true
   });

   // lets get the wrapper's children
   var children = $('#scrollWrapper').children();
   if (children.length > 1){ // if there is more than one child, the scrollbar must be present
      children[1].style.height = 350 + 'px';
      children[1].style.top = 0 + 'px';
      children[1].style.left = 190 + 'px';
   }
},false);
...

Enabling SELECT, INPUT and TEXTAREA focus

iScroll will disable the ability to give focus to certain form elements preferring instead to treat all interactions with the screen as something that must go through iScroll itself. You can get around this by adding the following function to the onBeforeScrollStart event - shown here as a configuration object while instantiating iScroll. You could also just add it to the iScroll source - view source, scroll down until you see the onBeforeStartEvent and add the function there.

...
var scroll = new iScroll('content',
   {scrollbarClass: 'myScrollbar',
    handleClick: false,
    useTransform:true,
    onBeforeScrollStart: function (e) {
        var target = e.target;
        while (target.nodeType != 1) target = target.parentNode;
        if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA')
            e.preventDefault();
	}
    });
...

Enabling hidden iScroll divs

This isn't a hack per se as iScroll has a method to deal with the issue. In cases where you instantiate an iscroll on a hidden wrapper div and then reveal the wrapper by changing its display style to something other than "none" you will find that the iScroll-ed div will just bounce in its wrapper, refusing to scroll.

The fix is to do the following **after** you have revealed the wrapper - the example uses the refresh and scrollTo methods to enable scrolling:

...
// instantiate a new iScroll
var myScroll = new iScroll('myDiv');

function reveal_iScroll_div(){
   document.getElementById('myDiv').style.display = 'block';
   myScroll.refresh();
   myScroll.scrollTo(0,0,0);
}
...

...

The bounce of death - non-scrolling content

Sometimes your content will just bounce in its container and not scroll at all. There are a couple things that cause this - 1 of them is the section preceding this one, the other is likely to be a simple line of CSS. Make sure that the div that is wrapping your content has a css display value of "block".

Disappearing Lists

Sometimes you'll have an iScroll whose contents are updated according to whatever the user has selected. If you have a long list and quickly scroll through them - and then try to update the contents of the scroll wrapper while your list items are flying by you may find that the list may update with new content but not be visible. That is, if you inspect the DOM you plainly see that your elements are there correctly positioned but they do not display (observed in Android 4.1.2).

It appears that we may need to stop the scrolling before updating the list. Something like this:

...
if (_myScroll){
    _myScroll.scrollTo(0,0,0);
}
// now update the list
...
// _myScroll.refresh() might not work here, the "scrollTo" before the update seems to help
...

iScroll scrolls up when a scrolled element is clicked

(7/15/2016) I just had this particular issue that took me quite a while to figure out - I had inited an iScroll with content that appeared to function correctly, but no matter what buttons I clicked in my scrolled content the result was a) what I programmed the button to do, and b) the iScroll would scroll back to the top (interestingly the scroll bar would NOT scroll to the top).

Turned out that I had instantiated the iScroll twice in quick succession on the same variable - maybe creating some sort of race condition? You would think that one would overwrite the other... but fixing the code so that I created an iScroll instance only once resolved the issue.

Convert XML to JSON

27 Nov 2012

Having a library of scripts in your back pocket comes in handy – this one is a gem IMO, it is an XML to JSON converter written in JavaScript. I spent the better part of my day trying the wide variety of scripts that a simple Google search yielded. Some didn’t work, others did albeit with quirks (i.e., output that was “technically” JSON while having a less than desired structure).

After much searching and testing I’ve found one that is “perfect”; inserting null values where needed, arrays appearing where they should, extra objects weren’t inserted, etc, etc…. in addition the author shared his test scripts to show the robustness of his work. Download it from here:

http://michael.hinnerup.net

The library claims to also convert JSON to XML – I’ve not tried it but if the quality of the XML to JSON portion is any indication then I expect it should work just as well.

Update 1/2/2013

Michael appears to have updated his blog as all of the content has vanished – and so as a service to others (and myself) I’m providing a link to the js. Whenever Michael gets his blog backup I’ll revert to linking to him, in the meantime you can download the library here:

xml_to_json.js

Also, as his documentation has also disappeared I’ve added a quick example below showing how to request an XML resource via JQuery and turn it into JSON once its been received by the client:

...
function getXML(){
  $.ajax({
    url:      hostURL,
    type:     'GET',
    dataType: 'xml',
    error:function(xhr,status,err){
      // handle errors here
    },
    success:function(responseData,status,xhr){
      if (responseData.childNodes.length > 0){
        var obj = xmlJsonClass.xml2json(responseData); // xml to json conversion happens here
        console.log(obj);
      } else {
        console.log('Response did not appear to contain any children.');
      }
    }
  });
}
...

Tip

I had XML that contained CDATA sections – what the XML-to-JSON script did was create a needless object for the CDATA – a structure that looked like this:

...
{test:
   {test1:'something',
    test2:'something else',
    test3:{#cdata:'another something'}
    }
}
...

Note the value for “test3” – instead of putting the string “another something” as the value the library put in another object. To avoid that, open the xml_2_json.js file and go to line 186 and note the for loop:

...
for (n = xml.firstChild; n; n = n.nextSibling) {
   o["#cdata"] = this.escape(n.nodeValue);
 }
...

That block of code is where the extra object gets inserted – simply change it to this:

...
for (n = xml.firstChild; n; n = n.nextSibling) {
   //o["#cdata"] = this.escape(n.nodeValue); // << original line
   o = this.escape(n.nodeValue); // << new line
 }
...

And now the sample output above appears in a manner that makes more sense:

...
{test:
   {test1:'something',
    test2:'something else',
    test3:'another something'
    }
}
...

Internet Explorer Browser Detect Script

24 Nov 2012

Someone asked a question about detecting different versions of IE the other day – this is my take at it – it is not a universal browser detect script rather it only cares about MSIE.

The original question had to do with dealing with old versions of MSIE which didn’t render some website properly and needing to direct users to some other web page as a result – which I took to mean – “how do I detect the browser type and perform some desired action?”

The navigator object’s userAgent property contains all the information needed to determine the type of browser. A Google search will reveal many scripts to do browser detection – but I’ll provide a quick script of my own written specifically for this question.

In addition to the userAgent you may also discover some details about the user’s client via two additional navigator properties which are navigator.appName and navigator.appVersion. You will find the appVersion property to be less then ideal (for example, MSIE9 will report as version 5) and the appName will be the full name of the browser – which is ok. For me I find it more convenient to deal with the userAgent property since everything that I’ll be looking for is contained in the resulting string.

First, add a script tag to your page and while we are at it lets assign the userAgent’s value to a variable so that we can easily reference it:

...
<script type="text/javascript">
   var ua = navigator.userAgent;
</script>
...

Next, lets come up with a simple regular expression for detecting MSIE.

...
<script type="text/javascript">
   var ua = navigator.userAgent;
   var msieTest = /MSIE/gi;
</script>
...

Regular expressions are pretty powerful tools for finding patterns in strings. The navigator.userAgent value is a string and the regular expression (/MSIE/gi) will let us discover if “MSIE” is contained within it. If it is, then we know that we’re dealing with Internet Explorer.

To use the regular expression we will use the regular expression “test” method. See below:

...
<script type="text/javascript">
   var ua = navigator.userAgent;
   var msieTest = /MSIE/gi;
   var isMSIE = msieTest.test(ua);
</script>
...

Looking at the above, you can see that I’ve created a variable called “isMSIE” and set it equal to the result of the “test”. The “test” method will return true if “MSIE” is found and “false” otherwise.

Knowing that, we just need to compare against the value of “isMSIE” and then take whatever action we deem necessary as seen below.

...
<script type="text/javascript">
   var ua = navigator.userAgent;
   var msieTest = /MSIE/gi;
   var isMSIE = msieTest.test(ua);
   if (isMSIE){
     alert('You are using Internet Explorer');
  } else {
    alert('You are not using Internet Explorer');
  }
</script>
...

We now have a pretty basic yet effective browser sniffer that is looking specifically for Internet Explorer. It would be more useful though to know the browser version as well. For that, lets use another regular expression to find where the version number is in the userAgent string. We know that the version comes right after the “MSIE” characters within the userAgent value so we write an expression that looks for it:

...
/MSIE \d.\d/gi
...

The regular expression “match” method will return to us an array of all matching values based on the above expression – in this case we know that there will always only be a single answer so we will look at the first index of the array to retrieve it. The next step will be to extract the numerical value – see the following:

...
var msieVersionStr = ua.match(/MSIE \d.\d/gi);
var msieVersionNum = parseFloat(msieVersionStr[0].substr(5,msieVersionStr[0].length));
...

“msieVersionNum” will hold the browser version for Internet Explorer. As it will **only** work for Internet Explorer we need to put it inside of the if statement that checks for the existence of Internet Explorer. That would look like this:

...
<script type="text/javascript">
  var ua = navigator.userAgent;
  console.log(ua);
   var ua = navigator.userAgent;
   var msieTest = /MSIE/gi;
   var isMSIE = msieTest.test(ua);
  if (isMSIE){
    var msieVersionStr = ua.match(/MSIE \d.\d/gi);
    var msieVersionNum = parseFloat(msieVersionStr[0].substr(5,msieVersionStr[0].length));
  } else {
    alert('You are not using Internet Explorer');
  }
</script>
...

Finally, to wrap this all up, we add some version checks in the form of additional “if” statements:

...
<script type="text/javascript">
  var ua = navigator.userAgent;
  console.log(ua);
   var ua = navigator.userAgent;
   var msieTest = /MSIE/gi;
   var isMSIE = msieTest.test(ua);
  if (isMSIE){
    var msieVersionStr = ua.match(/MSIE \d.\d/gi);
    var msieVersionNum = parseFloat(msieVersionStr[0].substr(5,msieVersionStr[0].length));
    if (msieVersionNum > 9){
       alert('You are using MSIE ' + msieVersionNum);
    } else if (msieVersionNum == 9){
       alert('You are using MSIE 9');
    } else if (msieVersionNum == 8){
       alert('You are using MSIE 8');
    } else if (msieVersionNum == 7){
       alert('You are using MSIE 7');
    } else if (msieVersionNum > 6){
       alert('You are using MSIE 6 or earlier');
    }
  } else {
    alert('You are not using Internet Explorer');
  }
</script>
...

And thats it, this script will check for Internet Explorer 6 and up.

Namespacing an existing JavaScript code base

23 Nov 2012

I read a question over on O’reilly’s website today that was posted way back in 2009 in the “Answers” section. The answer to the question IMO was not the best approach. The question was:

“I have to join a few home-made Javascript-libraries together. It’s so easy to use global variable-names, but how can I stay in my own namespace like “mycompany.strings”? “

The original reply was to do namespacing by writing a huge object literal. Going into the existing codebase and modifying code so as to lock up functions and variables within an object.

While doable, thats a heavy handed approach IMO. Seems to me that a javascript pattern called a “closure” is more appropriate. I went ahead and provided my thoughts to the 3-year old question figuring another approach wouldn’t hurt. Here’s the text of my response:

This post is a few years old, but I wanted to put forth another idea as to how to approach namespacing some existing code. It appears to me that the closure pattern would fit this need quite nicely. For example, see this typical bit of code, note the variable is in the global namespace as is the function:

...
var vehicle = {color:'red',type:'truck'}

function changeVehicleColor(str){
  vehicle.color = str;
}
...

With the closure pattern all that you would need to do is to wrap the above code within the closure construct, like so:

...
var myCompany = (function(){

  var vehicle = {color:'red',type:'truck'}

  function changeVehicleColor(str){
    vehicle.color = str;
  }

})();
...

Now the “vehicle” variable and the “changeVehicleColor” function are wrapped within the “myCompany” object. At this point they are private – they’re not accessible, so you have to expose a way of accessing them. You can do this with a return statement, see below:

...
var myCompany = (function(){

  var vehicle = {color:'red',type:'truck'}

  function changeVehicleColor(str){
    vehicle.color = str;
  }

  return {
    changeVehicleColor:changeVehicleColor
  }

})();
...

The “changeVehicleColor” method is now exposed for use while the “vehicle ” variable is still private. To change the vehicle color you would do the following:

...
myCompany.changeVehicleColor('yellow');
...

All done. The nice thing here is that you’re just wrapping the existing code within the closure construct. A lot easier than the original example, IMO, where you are creating an object literal and modifying the original code to do so. Not ideal for what the op claims are “a couple of libraries”. You can avoid that effort with a closure. The bonus is the notion of private and public bits of the existing code. The original sample, while it does work, leaves all variables open for manipulation outside of the namespace (see the “var1” example).

In addition, you can easily build upon the myCompany object. For example, if you have some utility methods, you could wrap them separately but within the same namespace like so – note that the “myCompany” namespace has already been created, so I am not using the var keyword (btw, this would also be true of the first answer):

...
myCompany.utils = (function(){
  // your code here
  return{
    // your methods, getters/setters here
  }
})();
...

Thus, you can break the library into different physical files if you like as long as the one that creates the namespace is the first file to load.

JQuery Mobile Checkboxes

31 Oct 2012

JQuery Mobile has some nice form element replacements that are more mobile device/touch-friendly than the way browsers currently present form elements. One of them is the visual replacement for a checkbox. To be clear the ugly checkbox is still there, its now layered underneath the JQM checkbox art.

The end results looks like this:

Click here to see a working example.

The docs IMO don’t quite tell you one critical thing – that certain tag attributes need to be identical in order for the framework to enhance the checkbox. If one of these is out of place you won’t get what you’re looking for.

Here’s how I implemented a checkbox today after trial and error:

...
  <style type="text/css">
    .checkboxWidth{
      width:53px;
    }
  </style>
...
  <fieldset data-role="controlgroup" class="checkboxWidth">
    <input type="checkbox" name="checkbox_value_0" id="checkbox_value_0" data-iconpos="notext" />
    <label for="checkbox_value_0" data="test"></label>
  </fieldset>
...

According to the docs the only thing to pay attention to is to ensure that the LABEL tag’s for attribute value is the same as the INPUT tag’s name value so that they are semantically linked together and displayed appropriately by the framework. There is one critical piece of information and that’s the INPUT tag’s id – it too must be the same value as the previous two attribute values. Omit that last bit and you don’t get any visual enhancement of the CHECKBOX element.

As far as getting the label removed all that is needed (as can be seen in the code sample above) is to add data-iconpos=”notext” to the input tag.

Lastly, I wanted to set the width so I added a class to the FIELDSET element and that was it, my check box was created and ready to go.

A handy tip is the use of the jQuery trigger method when inserting checkboxes into your document after it has loaded. Doing so ensures that you get the visual enhancement.

For example:

...
var newcbox = '<input type="checkbox" name="checkbox_value_0" id="checkbox_value_0" data-iconpos="notext" />';
    newcbox = '<label for="checkbox_value_0" data="test"></label>';

// add html to DOM and "trigger" jQuery visual enhancement
$(newcbox).appendTo('#contentWrapper').trigger('create');
...