Wednesday, 30 November 2016

Transforming an object with arrays into an array of objects with Javascript

This question was a lot of fun and I spent a happy 10 minutes figuring out how to transform the data. This seems to do the job:

var obj = {
    "pharmacy": [
        "Walmart",
        "Safeway",
        "Kroger Pharmacy"
    ],
    "price": [
        58.14,
        65.45,
        66.76
    ]
}, data = [];

for(var i = 0; i < obj[Object.keys(obj)[0]].length; i++){
    var tempObject = {};
    for(var key in obj){
        tempObject[key] = obj[key][i];
    }
    data.push(tempObject)
}

console.log(data);

I'm only putting it up here so I don't forget how to do it again if I need to do so. It's also worthwhile noting that the initial arrays need to be the same length or things could go really quite wrong.

Saturday, 5 November 2016

DataTable Row Re-order

I got a little stumped by a Question on stackoverflow. The poster asked about swapping rows within a DataTable and I've never had a need to do that so I got to thinking about how it might be done. The poster also got into a wee bit of tizzy over the use of this so that was the first thing I corrected. Afterwards, it kept playing on my mind as an interesting challenge so this morning I decided to scratch that itch. I clocked that I needed an artificial index so added a hidden column to the table upon which to sort the rows:

<div class="container">
    <table cellpadding="0" cellspacing="0" border="0" class="table" id="example">
        <thead>
            <tr>
                <th>Index</th>
                <th>Rendering engine</th>
                <th>Browser</th>
                <th>Platform(s)</th>
                <th>Swap Down</th>
                <th>Swap Up</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <th>0</th>
                <td>Trident</td>
                <td>Internet Explorer 4.0</td>
                <td>Win 95+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>1</th>
                <td>Trident</td>
                <td>Internet Explorer 5.0</td>
                <td>Win 95+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>2</th>
                <td>Trident</td>
                <td>Internet Explorer 5.5</td>
                <td>Win 95+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>3</th>
                <td>Trident</td>
                <td>Internet Explorer 6</td>
                <td>Win 98+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>4</th>
                <td>Trident</td>
                <td>Internet Explorer 7</td>
                <td>Win XP SP2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>5</th>
                <td>Trident</td>
                <td>AOL browser (AOL desktop)</td>
                <td>Win XP</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>6</th>
                <td>Gecko</td>
                <td>Firefox 1.0</td>
                <td>Win 98+ / OSX.2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>7</th>
                <td>Gecko</td>
                <td>Firefox 1.5</td>
                <td>Win 98+ / OSX.2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>8</th>
                <td>Gecko</td>
                <td>Firefox 2.0</td>
                <td>Win 98+ / OSX.2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>9</th>
                <td>Gecko</td>
                <td>Firefox 3.0</td>
                <td>Win 2k+ / OSX.3+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>10</th>
                <td>Gecko</td>
                <td>Camino 1.0</td>
                <td>OSX.2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>11</th>
                <td>Gecko</td>
                <td>Camino 1.5</td>
                <td>OSX.3+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>12</th>
                <td>Gecko</td>
                <td>Netscape 7.2</td>
                <td>Win 95+ / Mac OS 8.6-9.2</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>13</th>
                <td>Gecko</td>
                <td>Netscape Browser 8</td>
                <td>Win 98SE+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>14</th>
                <td>Misc</td>
                <td>Lynx</td>
                <td>Text only</td>
                <td></td>
                <td></td>
            </tr>
        </tbody>
    </table>
</div>

This JS seems to do the trick in terms of swapping rows:

$(function() {
    var table = $("#example").DataTable({
        "order": [
            [0, 'asc']
        ],
        "paging": true,
        "columns": [{
            "visible": false
        }, {
            "orderable": false
        }, {
            "orderable": false
        }, {
            "orderable": false
        }, {
            "render": function(d) {
                return $("<i/>", {
                    "class": "fa fa-caret-down swapable swapDown"
                }).prop("outerHTML");
            },
            "orderable": false
        }, {
            "render": function(d) {
                return $("<i/>", {
                    "class": "fa fa-caret-up swapable swapUp"
                }).prop("outerHTML");
            },
            "orderable": false
        }]
    });
    $('#example tbody').on('click', 'td', function(event) {
        // We're only interested in cells with a class of swapable
        if ($(this).find(".swapable")) {
            // Helper variable
            var _this = $(this).find(".swapable");
            // Total number of rows (including hidden rows (zero based index))
            var tableRows = table.data().length - 1;
            // Index of current row
            var rowIndex = table.row(this).index();
            // Data of current row
            var rowData = table.row(this).data();
            // Index value of row (artifical because it's ours) - also tempval
            var artificalIndex = ~~rowData[0];
            /*
             * If we're on the bottom row of the table and the direction of travel is downwards  or if we're
             * on the top row and the direction of travel is upwards then we need to just swap the top and
             * bottom rows
             */
            if(
                    (_this.hasClass("swapDown") && artificalIndex === tableRows)
                    ||
                    (_this.hasClass("swapUp") && artificalIndex === 0)
            ){
                var topIndex, bottomIndex;
                table.rows().every(function(rowIdx, tableLoop, rowLoop) {
                    var data = this.data();
                    if(~~data[0] === 0){
                        topIndex = rowIdx;
                    }
                    if(~~data[0] === tableRows){
                        bottomIndex = rowIdx;
                    }
                });
                table.cell(topIndex, 0).data(tableRows);
                table.cell(bottomIndex, 0).data(0);
            }else{
                var movingIndex, tempData;
                table.rows().every(function(rowIdx, tableLoop, rowLoop) {
                    var data = this.data();
                    // Moving down
                    if (_this.hasClass("swapDown") && ~~data[0] === artificalIndex + 1) {
                        movingIndex = rowIdx;
                        tempData = data[0];
                    }
                    // Moving up
                    if (_this.hasClass("swapUp") && ~~data[0] === artificalIndex - 1) {
                        movingIndex = rowIdx;
                        tempData = data[0];
                    }
                });
                table.cell(rowIndex, 0).data(tempData);
                table.cell(movingIndex, 0).data(artificalIndex);
            }
            table.draw(false);
        }
    });
});

I'm happy that it works but it does seem a little inefficient and I'm interested in how it might be improved if anyone can come up with any ideas? The working JSFiddle is here so please use that as a base upon which to work.

One of my main issues was figuring out how to cope with people clicking the up icon on the top row or the down icon on the bottom row, then I clocked that it merely meant that the top and bottom rows needed to be swapped. I also ran into difficulty figuring out the index of rows as I'm pretty sure it isn't updated after a draw as I kept running into issues... this is the reason I introduced my own, artificial, index in the hidden first column.

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