0
\$\begingroup\$

Before you read ahead: ES6 may not be used.

Consider a web-page showing a report of some data that is structured as follows:

<button id='sort-by-category'>Category</button>
<button id='sort-by-timestamp'>Timestamp</button>
<section id='report-body'>
    <section class='report-section' data-category-name='fruit' data-item-id='1' data-timestamp='2016-09-12T15:47:45.000Z'>
        ...
    </section>
    <section class='report-section' data-category-name='fruit' data-item-id='2' data-timestamp='2016-06-11T15:47:45.000Z'>
        ...
    </section>
    <section class='report-section' data-category-name='vegetable' data-item-id='1' data-timestamp='2016-09-10T15:47:45.000Z'>
        ...
    </section>
</section>

Such that each section in the report body needs to be sorted according to the custom data- attributes. The catch is that not all attributes are created equal; for example: sorting by timestamp is straightforward, but sorting by item-id isn't because we also want to group items of the same category together.

Here's the implementation of that in JS:

(function () {
    "use strict";

    var categoryOrder = {
        "fruit" : 0,
        "vegetable" : 1
    };


    function compareStrings(string1, string2) {
        if (string1 < string2) {
            return -1;
        } else if (string1 > string2) {
            return 1;
        }
        return 0;
    }


    function compareCategories(element1, element2) {
        var category1 = element1.getAttribute("data-category-name");
        var category2 = element2.getAttribute("data-category-name");

        if (category1 !== category2) {
            return categoryOrder[category1] - categoryOrder[category2];
        }

        category1 = +element1.getAttribute("data-item-id");
        category2 = +element2.getAttribute("data-item-id");

        return category1 - category2;
    }


    // dates are ISO-8601
    function compareTimestamps(element1, element2) {
        var timeStamp1 = element1.getAttribute("data-timestamp");
        var timeStamp2 = element2.getAttribute("data-timestamp");
        return compareStrings(timeStamp1, timeStamp2);
    }


    function getElementsByClassName(className) {
        var result = document.getElementsByClassName(className);
        return Array.prototype.slice.call(result);
    }


    function makeRearrangeCallback(comparator, reportBody) {
        return function() {
            var reportBlocks = getElementsByClassName("report-section");

            reportBlocks.sort(comparator);

            for (var i = 0; i < reportBlocks.length; ++i) {
                reportBody.appendChild(reportBlocks[i]);
            }
        };
    }


    function addRearrangeCallbackToElement(elementId, comparator, reportBody) {
        var element = document.getElementById(elementId);

        element.addEventListener(
            "click",
            makeRearrangeCallback(comparator, reportBody),
            false);
    }


    function main() {
        var reportBody = document.getElementById("report-body");
        addRearrangeCallbackToElement("sort-by-category", compareCategories, reportBody);
        addRearrangeCallbackToElement("sort-by-timestamp", compareTimestamps, reportBody);
    }


    window.addEventListener("load", main, false);
}());

Note that I'm not looking just for commentary on the JS part. If you believe that there could be a better way to store the information in the markup, feel free to mention it.

\$\endgroup\$
  • \$\begingroup\$ Any event listeners for the buttons? } else if (string1 > string2) { does not require the else. Markup looks fine, is it being build dynamically on the server? \$\endgroup\$ – Xotic750 Sep 19 '16 at 21:25
  • \$\begingroup\$ @Xotic750 the event listeners for the buttons are added in addRearrangeCallbackToElement \$\endgroup\$ – nasser-sh Sep 19 '16 at 21:28
  • \$\begingroup\$ @Xotic750 and this page is being built dynamically, yes. \$\endgroup\$ – nasser-sh Sep 19 '16 at 21:30
  • \$\begingroup\$ So they are. Nothing seemed to happen when I built a fiddle, so I assumed they were missing. No wrap needed to be switched on. :) \$\endgroup\$ – Xotic750 Sep 19 '16 at 21:32
  • \$\begingroup\$ @Xotic750 it's all good, I appreciate the feedback! \$\endgroup\$ – nasser-sh Sep 19 '16 at 21:42
1
\$\begingroup\$

If I were to make any changes then this would be the code, using ES5, and reduce the number of functions a little, and variable assignments.

(function() {
  'use strict';

  var slice = Function.prototype.call.bind(Array.prototype.slice);
  var categoryOrder = {
    fruit: 0,
    vegetable: 1
  };

  function compareCategories(element1, element2) {
    var category1 = element1.getAttribute('data-category-name');
    var category2 = element2.getAttribute('data-category-name');
    if (category1 !== category2) {
      return categoryOrder[category1] - categoryOrder[category2];
    }
    return element1.getAttribute('data-item-id') - element2.getAttribute('data-item-id');
  }

  // dates are ISO-8601
  function compareTimestamps(element1, element2) {
    return element1.getAttribute('data-timestamp')
      .localeCompare(element2.getAttribute('data-timestamp'));
  }

  function makeRearrangeCallback(comparator, reportBody) {
    return function() {
      slice(document.getElementsByClassName('report-section'))
        .sort(comparator)
        .forEach(function(reportBlock) {
          reportBody.appendChild(reportBlock);
        });
    };
  }

  function addRearrangeCallbackToElement(elementId, comparator, reportBody) {
    document.getElementById(elementId)
      .addEventListener('click', makeRearrangeCallback(comparator, reportBody), false);
  }

  function main() {
    var reportBody = document.getElementById('report-body');
    addRearrangeCallbackToElement('sort-by-category', compareCategories, reportBody);
    addRearrangeCallbackToElement('sort-by-timestamp', compareTimestamps, reportBody);
  }

  window.addEventListener('load', main, false);
}());
<button id='sort-by-category'>Category</button>
<button id='sort-by-timestamp'>Timestamp</button>
<section id='report-body'>
  <section class='report-section' data-category-name='fruit' data-item-id='1' data-timestamp='2016-09-12T15:47:45.000Z'>
    ..a
  </section>
  <section class='report-section' data-category-name='fruit' data-item-id='2' data-timestamp='2016-06-11T15:47:45.000Z'>
    ..b
  </section>
  <section class='report-section' data-category-name='vegetable' data-item-id='1' data-timestamp='2016-09-10T15:47:45.000Z'>
    ..c
  </section>
</section>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.