Thursday 27 October 2016

JavaScript accordion sans jQuery

Doing some work where using jQuery was verboten this (Ridiculously simple accordion without the jQuery UI library) was found.

It still used jQuery though so rolled my own. This markup:

<div id="accordion">
    <h4 class="accordion-toggle">Accordion 1</h4>
    <div class="accordion-content default">
        <p>Cras malesuada ultrices augue molestie risus.</p>
    </div>
    <h4 class="accordion-toggle">Accordion 2</h4>
    <div class="accordion-content">
        <p>Lorem ipsum dolor sit amet mauris eu turpis.</p>
    </div>
    <h4 class="accordion-toggle">Accordion 3</h4>
    <div class="accordion-content">
        <p>Vivamus facilisisnibh scelerisque laoreet.</p>
    </div>
</div>

With this CSS:

.accordion-toggle {
    cursor: pointer;
}
.accordion-content {
    display: none;
}
.accordion-content.default {
    display: block;
}

And with this Javascript (Polyfill added):

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Polyfill
if (!Array.prototype.filter) {
    Array.prototype.filter = function(fun/*, thisArg*/) {
        'use strict';

        if (this === void 0 || this === null) {
            throw new TypeError();
        }

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== 'function') {
            throw new TypeError();
        }

        var res = [];
        var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
        for (var i = 0; i < len; i++) {
            if (i in t) {
                var val = t[i];

                // NOTE: Technically this should Object.defineProperty at
                //       the next index, as push can be affected by
                //       properties on Object.prototype and Array.prototype.
                //       But that method's new, and collisions should be
                //       rare, so use the more-compatible alternative.
                if (fun.call(thisArg, val, i, t)) {
                    res.push(val);
                }
            }
        }

        return res;
    };
}

(function() {
    "use strict";
    // Stolen from: https://toddmotto.com/ditch-the-array-foreach-call-nodelist-hack/
    var forEach = function(array, callback, scope) {
        for (var i = 0; i < array.length; i++) {
            callback.call(scope, i, array[i]);
        }
    },
        accordion = document.getElementById("accordion"),
        toggles = accordion.querySelectorAll(".accordion-toggle"),
        greedy = false;
    forEach(toggles, function(i, v) {
        v.addEventListener("click", function() {
            var content = this.nextElementSibling;
            var classes = content.className.split(" ");
            if (classes.indexOf("default") !== -1) {
                content.className = classes.filter(function(value){
                    return value !== "default"
                }).join(" ");
            } else {
                if (greedy) {
                    forEach(toggles, function(i, toggle) {
                        var ct = toggle.nextElementSibling;
                        var cs = ct.className.split(" ");
                        ct.className = cs.filter(function(value){
                            return value !== "default"
                        }).join(" ");
                    });
                }
                content.className += " default";
            }
        }, false);
    });
})();

Does all that I need except for the animation - but I'll get there yet!

Working example here.

Sunday 23 October 2016

document ready -vs- self-invoking anonymous function

I've had this article starred in my feedly for a few days, and I finally got around to reading it last night - and wondered at the discussion it evoked.

I'm a great fan of the shortcut code for document ready, but I'm also a huge fan of self-invoking anonymous function (SIAFs), most especially since I clocked that adding javascript to the bottom of the page was the way to go!

Anyway, I decided to create this wee test script to see which was fastest:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>zero or one</title>
        <script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
        <script>
            $(function(){
                var p = $("p").length;
                console.log("jQuery shortcut in the head", p);
            });
            $(document).ready(function(){
                var p = $("p").length;
                console.log("jQuery in the head", p);
            });
            (function(){
                var  p =document.querySelectorAll("p").length;
                console.log("SIAF in the head", p);
            })();
            (function($){
                var p = $("p").length;
                console.log("SIAF with jQuery in the head", p);
            })(jQuery);
        </script>
    </head>
    <body>
        <p>I'm the content of this website</p>
        <script>
            $(function(){
                var p = $("p").length;
                console.log("jQuery shortcut at the end of the body", p);
            });
            $(document).ready(function(){
                var p = $("p").length;
                console.log("jQuery at the end of the body", p);
            });
            (function(){
                var p = document.querySelectorAll("p").length;
                console.log("SIAF at the end of the body", p);
            })();
            (function($){
                var p = $("p").length;
                console.log("SIAF with jQuery at the end of the body", p);
            })(jQuery);
        </script>
        <code>
        <pre>
SIAF in the head <span style="color:blue;">0</span>                         index.html:15 
SIAF with jQuery in the head <span style="color:blue;">0</span>             index.html:18 
SIAF at the end of the body <span style="color:blue;">1</span>              index.html:32 
SIAF with jQuery at the end of the body <span style="color:blue;">1</span>  index.html:35 
jQuery shortcut in the head <span style="color:blue;">1</span>              index.html:9 
jQuery in the head <span style="color:blue;">1</span>                       index.html:12 
jQuery shortcut at the end of the body <span style="color:blue;">1</span>   index.html:26 
jQuery at the end of the body <span style="color:blue;">1</span>            index.html:29
        </pre>
        </code>
    </body>
</html>

The result is not overly exciting except that the SIAF in the head failed to see the paragraph. What was interesting, though, was that the SIAFs in the footer executed before the any of the scripts which relied upon jQuery, probably because they didn't have to wait for the jQuery library to load...


Legend Number of Paragraphs found
SIAF in the head 0
SIAF with jQuery in the head 0
SIAF at the end of the body 1
SIAF with jQuery at the end of the body 1
jQuery shortcut in the head 1
jQuery in the head 1
jQuery shortcut at the end of the body 1
jQuery at the end of the body 1

Tuesday 18 October 2016

Fun little challenge (Temperatures closest to 0)

/**
 * Auto-generated code below aims at helping you parse
 * the standard input according to the problem statement.
 **/

var n = parseInt(readline()); // the number of temperatures to analyse
var temps = readline(); // the n temperatures expressed as integers ranging from -273 to 5526
var b = function(a){
    return Math.abs(a);
}
var closest = temps.split(" ").reduce((p,c)=>(b(p)===b(c))?(p<c)?c:p:(b(c-0)<b(p-0)?c:p));


// Write an action using print()
// To debug: printErr('Debug messages...');

print((closest) ? closest : 0);

Thursday 13 October 2016

More than one way... and the joys of the reduce method.

I've been doing the odd bit of tutoring for people learning Javascript because it's always nice to learn something new by teaching it. I recently had a case when someone was tasked with refactoring some code from function declarations to function expressions. This has always interested me so I was more than happy to help. The example provided had a function called square which returned the one parameter multiplied by itself. Simple as, though I did wonder why we weren't using fat arrow syntax as that would've made it even cleaner:

var square = x => x * x;

More properly we had:

var square = function(x) {
    return x * x;
};

Anyway, that wasn't what was required; what was required was for a function which iterated over an array and returned the sum of all of the squares of each element in the array. This was the original function which required refactoring:

function sumSquares(numbers) {
    var total = 0;
    for (var i = 0; i < numbers.length; i++){
        total += numbers[i] * numbers[i]
    }
    return total;
}

console.log(sumSquares([1,2,3])); // 14

For some reason, the for loop needed to be replaced with a function called each which looks like this:

function each(array, func) {
    for (var i = 0; i < array.length; i++) {
        func(array[i]);
    }
}

Which is cool, but I quite like for loops, and while loops for that matter... ahh well.

So we were left with something like this:

function sumSquares(numbers) {
    var total = 0;
    each(numbers, function(element){
        total += square(element)
    });
    return total;
}

console.log(sumSquares([1,2,3])); // 14

Which works but I'm not overly happy with it. I can sort of see what the task is trying to get the student to understand but I'm not sure if it goes far enough, especially with the introduction of the array reduce method and most especially with its polyfill for when you're targeting an ancient browser:

// Production steps of ECMA-262, Edition 5, 15.4.4.21
// Reference: http://es5.github.io/#x15.4.4.21
if (!Array.prototype.reduce) {
  Array.prototype.reduce = function(callback /*, initialValue*/) {
    'use strict';
    if (this === null) {
      throw new TypeError('Array.prototype.reduce called on null or undefined');
    }
    if (typeof callback !== 'function') {
      throw new TypeError(callback + ' is not a function');
    }
    var t = Object(this), len = t.length >>> 0, k = 0, value;
    if (arguments.length == 2) {
      value = arguments[1];
    } else {
      while (k < len && !(k in t)) {
        k++; 
      }
      if (k >= len) {
        throw new TypeError('Reduce of empty array with no initial value');
      }
      value = t[k++];
    }
    for (; k < len; k++) {
      if (k in t) {
        value = callback(value, t[k], k, t);
      }
    }
    return value;
  };
}

Anyway, my preferred quick and dirty refactoring would be this:

console.log([1,2,3].reduce((p, c) => p + (c * c), 0));

It's always nice when you can replace a shed load of stuff with a single liner, I blame Empire of Code for getting me that way ;-)

We can test it easily enough using this:

console.log([1,2,3].reduce((p,c)=>p+(c*c),0) === (1*1)+(2*2)+(3*3)); // true

I guess that the beauty of Javascript is that there are so very many ways of doing any one thing. We could re-write it using typescript like this:

var sumSquares = (arr: number[]): number => arr.reduce((a: number, b: number) => a + (b * b), 0);
console.log(sumSquares([1, 2, 3])); // 14

Which would get compiled to this Javascript:

var sumSquares = function (arr) { return arr.reduce(function (a, b) { return a + (b * b); }, 0); };

console.log(sumSquares([1, 2, 3])); // 14

Anyway, that was fun and I learnt something - not least that there wee drop of madness where the function gets called within the each function - I'm going to have to ponder that bit some more as I originally just wanted to pass the square function as the second parameter but then got confused in that it didn't return anything and ended up re-writing it so that it did, adding it as a call within a function works a treat though.






Thursday 6 October 2016

Dreading Recursion

Yesterday I showed you how I processed a select to get a JSON object; I will now show you what I did with it.

Semantic UI is a CSS and JS framework which is somewhat opinionated about the way pages should be structured. I used to hate it, but after investing so much time working with it, I have grown rather fond of it. I needed to generate a set of nested accordions from the JSON object so I went all proper Compter Science and used a bit of recursion.

Recursion is something that I approach with dread, then enjoy implementing and end up feeling all proud of my success. In this case, I had the basic structure of accordions to play with (Semantic UI dictates that titles and content blocks be on the same level whereas Bootstrap dictates that titles and content be wrapped within a panel – I am not sure which I prefer in all honesty.

This is the structure of the JSON object:

var tree = {
  "One": {},
  "Two": {
    "Two point one": {},
    "Two point two": {}
  },
  "Three": {},
  "Four": {
    "Four point one": {},
    "Four point two": {}
  },
  "Five": {
    "Five point one": {},
    "Five point two": {}
  },
  "Six": {}
};

This is the original markup:

<div id="accordionHolder"></div>

This is how I populate the original markup with the accordion:

$(function() {
  $("#accordionHolder").append(buildAccordion(tree)).ready(function(){
    $(".accordion").accordion({
      "selector": {
        "trigger": ".title .icon"
      }
    });
  });
});

Finally, this is the recursive function to generate the content:

function buildAccordion(obj) {
  var accordion = $("<div></div>", {
    "class": "ui accordion"
  });
  for (var limb in obj) {
    if (obj.hasOwnProperty(limb)) {
      console.log(limb, Object.keys(obj[limb]).length);
      if (Object.keys(obj[limb]).length === 0) {
        accordion.append($("<div></div>", {
          "class": "title",
          "text": limb
        }));
      } else {
        accordion.append($("<div></div>", {
          "class": "title",
          "text": limb
        }).prepend($("<i></i>", {
          "class": "dropdown icon"
        })));
        accordion.append($("<div></div>", {
          "class": "content"
        }).append(buildAccordion(obj[limb])));
      }
    }
  }
  return accordion;
}

I also added this wee bit of CSS to indent the child accordions:

#accordionHolder .content .accordion {
  margin: 0em 0em 0em 2em !important;
}
#accordionHolder .title.active {
  font-weight: bold;
}

It is not a bad way to do it. I thought about refactoring it to do things the Bootstrap way but then started to feel that dread I mentioned before. Working example here.

Tuesday 4 October 2016

Eval really evil?

I've recently had a situation where I needed to parse the options within a select, and it's been causing me to do some little head-scratching… so I thought I'd solve my solutions. The options used non-breaking spaces to indicate their position within a hierarchy, so I needed a way of counting the number of non-breaking spaces a string had within it. This function worked a treat with this:

function howManyRepeated(str, search){
    try{ 
        return str.toLowerCase().split("").sort().join("").match(search).length; 
    }catch(e){ 
        return 0;
    }
}

That meant that I needed a way of generating a regex for non-breaking spaces, this did the job for me:

var nbsp = new RegExp(String.fromCharCode(160), "g");

I also needed a way of removing those non-breaking spaces, so this does that:

function replaceNbsps(str, search) {
    return str.replace(search, "");
}

The main meat of the program involved iterating over the options and generating an array of the names, I wanted each option to hold a further object, which could be empty if it wasn't empty it needed to know whereabouts in the hierarchy it was. After spending some little time adding more and more if statements I clocked that I was adding to a the selectObject and that I was just adding strings to it within square brackets. Eval was the obvious solution to this problem, so this is what I came up with:

var selectObject = {};
$(function() {
    var parent = [];
    var options = $("#select option:not(.default)");
    options.each(function(k, v) {
        var level = howManyRepeated($(v).text(), nbsp) / 2;
        if (level) {
            if (level === 1) {
                parent = [replaceNbsps($(v).text(), nbsp)];
                selectObject[parent[0]] = {};
            }
            if (level > 1) {
                while (parent.length > level - 1) {
                    parent.pop();
                }
                parent.push(replaceNbsps($(v).text(), nbsp));
                var evalString = "selectObject";
                for (var i = 0; i < parent.length; i++) {
                    evalString += '["' + parent[i] + '"]';
                }
                evalString += " = {};";
                eval(evalString);

            }
        }
    });
    console.log(selectObject);
});

It's mainly here so I don't forget how to do it and because I've not blogged in a while. Working JSFiddle is here.

This is what the HTML looked like:

<select id="select">
    <option class="default" selected="selected">All</option>
    <option>&nbsp;&nbsp;One</option>
    <option>&nbsp;&nbsp;Two</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Two point one</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Two point two</option>
    <option>&nbsp;&nbsp;Three</option>
    <option>&nbsp;&nbsp;Four</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Four point one</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Four point one point one</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Four point two</option>
    <option>&nbsp;&nbsp;Five</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Five point one</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Five point two</option>
    <option>&nbsp;&nbsp;Six</option>
</select>

And this is the resulting object:

{
    "One": {},
    "Two": {
        "Two point one": {},
        "Two point two": {}
    },
    "Three": {},
    "Four": {
        "Four point one": {
            "Four point one point one": {}
        },
        "Four point two": {}
    },
    "Five": {
        "Five point one": {},
        "Five point two": {}
    },
    "Six": {}
}