<commit_before>/*!
 * jquery.inlinesvg
 * https://github.com/createlogic/InlineSVG
 * Copyright (c) 2010 - 2015 Bilal Niaz Awan
 * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
 * Version: 1.0.0
 */

(function($){
    $.fn.inlineSVG = function(options){

        options = $.extend({
            eachAfter: null,
            allAfter: null
        }, (options || {}));

        var $list = this;
        var counter = 0;

        return $list.each(function(){

            var $img = jQuery(this);
            var imgID = $img.attr('id');
            var imgClass = $img.attr('class');
            var imgURL = $img.attr('src');

            jQuery.get(imgURL, function(data) {
                // Get the SVG tag, ignore the rest
                var $svg = jQuery(data).find('svg');

                // Add replaced image's ID to the new SVG
                if(typeof imgID !== 'undefined') {
                    $svg = $svg.attr('id', imgID);
                }
                // Add replaced image's classes to the new SVG
                if(typeof imgClass !== 'undefined') {
                    $svg = $svg.attr('class', imgClass+' replaced-svg');
                }

                // Remove any invalid XML tags as per http://validator.w3.org
                $svg = $svg.removeAttr('xmlns:a');

                // Replace image with new SVG
                $img.replaceWith($svg);

                // Callback for each element
                options.eachAfter && options.eachAfter.call($svg.get(0));

                // Check for all is completed
                if (++counter === $list.length) {
                    options.allAfter && options.allAfter.call(null);
                }

            }, 'xml');

        });
    };
})(jQuery);<commit_msg>removed tabs<commit_after>/*!
 * jquery.inlinesvg
 * https://github.com/createlogic/InlineSVG
 * Copyright (c) 2010 - 2015 Bilal Niaz Awan
 * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
 * Version: 1.0.0
 */

(function($){
    $.fn.inlineSVG = function(options){

        options = $.extend({
            eachAfter: null,
            allAfter: null
        }, (options || {}));

        var $list = this;
        var counter = 0;

        return $list.each(function(){

            var $img = jQuery(this);
            var imgID = $img.attr('id');
            var imgClass = $img.attr('class');
            var imgURL = $img.attr('src');

            jQuery.get(imgURL, function(data) {
                // Get the SVG tag, ignore the rest
                var $svg = jQuery(data).find('svg');

                // Add replaced image's ID to the new SVG
                if(typeof imgID !== 'undefined') {
                    $svg = $svg.attr('id', imgID);
                }
                // Add replaced image's classes to the new SVG
                if(typeof imgClass !== 'undefined') {
                    $svg = $svg.attr('class', imgClass+' replaced-svg');
                }

                // Remove any invalid XML tags as per http://validator.w3.org
                $svg = $svg.removeAttr('xmlns:a');

                // Replace image with new SVG
                $img.replaceWith($svg);

                // Callback for each element
                options.eachAfter && options.eachAfter.call($svg.get(0));

                // Check for all is completed
                if (++counter === $list.length) {
                    options.allAfter && options.allAfter.call(null);
                }

            }, 'xml');

        });
    };
})(jQuery);<|endoftext|>


<commit_before>/*
 * jQuery Chaos Modal
 * By Matthew Sigley
 * Based on concept work by Kevin Liew - http://www.queness.com/post/77/simple-jquery-modal-window-tutorial
 * Version 1.3.3
 */
(function(a){function m(a){var b=!1;a.data.alwaysAtTop&&(b=a.data.alwaysAtTop);l.resizeModal(b)}function d(a){a.preventDefault();l.closeModal()}function f(a){a.preventDefault();l.printModal()}function g(c){27==c.which&&(a(this).off("keyup",g),d(c))}var l,k;a.chaosModalIndex=0;a.fn.openModal=function(c){c=a.extend({},{maskClose:!0,maxWidth:960,closeLink:!0,printLink:!1,alwaysAtTop:!1},c);var b=this.clone(),n=a("body");a("html");var h=a(document),e=a(window),r=c.maskClose,s=c.closeLink,t=c.printLink, q=c.alwaysAtTop;k=c.maxWidth;l=b;b.attr({id:"chaos-current-modal"});var p=a('

');p.css({position:"absolute","z-index":"9000","background-color":"#000",display:"none",top:"0",left:"0"});p.appendTo(n);0==b.find(".print-link").length&&t&&(b.prepend('Print'),b.children(".print-link").css({"float":"right",margin:"5px"}));0==b.find(".close-link").length&&s&&(b.prepend('Close'),b.children(".close-link").css({"float":"right", margin:"5px"}));b.css({display:"block",position:"absolute",background:"#fff","z-index":"9001",left:"-10000px",margin:"0",padding:"0"});b.appendTo(n);c=function(){b.width()>k?b.width(k):b.width(b.width());b.hide();b.resizeModal(q);p.fadeIn(1E3);p.fadeTo("slow",.8);b.fadeIn(2E3);e.bind("resize",{alwaysAtTop:q},m);0<b.find(".print-link").length&&b.find(".print-link").bind("click",f);0<b.find(".close-link").length&&b.find(".close-link").bind("click",d);r&&(p.on("click",d),h.on("keyup",g))};n=b.find("img").last(); if(n.length)n.on("load",c);else c();b.show();return this};a.fn.closeModal=function(){l=null;a(window).unbind("resize",m);0<this.find(".close-link").length&&this.find(".close-link").unbind("click");this.hide().remove();a("#chaos-modal-mask").hide().remove();return this};a.fn.resizeModal=function(c){var b=a(document),d=a(window),h=a("html"),e=b.height(),f=h.width(),g=d.height(),h=d.width();e<this.height()&&(e=this.height());f<h&&(f=h);a("#chaos-modal-mask").css({width:f,height:e});e=g/2-this.height()/ 2;h=h/2-this.width()/2;0>e&&(e=0);0>h&&(h=0);c?d.scrollTop(0):(e+=d.scrollTop(),c=b.height()-(e+this.height()),0>c&&0<=e+c&&(e+=c));d.scrollLeft(0);this.css({top:e,left:h});return this};a.fn.printModal=function(){window.print()}})(jQuery); jQuery(document).ready(function(a){function m(d){var f=a(this);d.preventDefault();f.off("click",m);var g=f.parent().find(".chaos-modal-box").first();d=!1;var l=/\.(jpeg|jpg|gif|png|bmp|wbmp)$/i,k=!1;g.length?(d=a("

").css({padding:"20px"}),g.clone().appendTo(d),d=a("

").css({background:"#fff"}).append(d)):(l.test(f.attr("href"))?k=f.attr("href"):(g=f.find("img"),1==g.length&&l.test(g.attr("src"))&&(k=g.attr("src"))),k&&(testRequest=a.ajax(k,{type:"HEAD",async:!1}),200==testRequest.status&& (d=a("

").css({padding:"20px"}),a('').css({display:"block"}).appendTo(d),d=a("

").css({background:"#fff"}).append(d))));d&&(f.attr({href:"#",id:"chaos-modal-link-"+a.chaosModalIndex}),d.attr({id:"chaos-modal-box-"+a.chaosModalIndex}),d.hide(),d.appendTo("body"),f.click(function(){var c=a(this).attr("id").match(/chaos-modal-link-(\d+)/i);c[1]&&(c=a("#chaos-modal-box-"+c[1]),1==c.length&&c.openModal());return!1}),f.click(),a.chaosModalIndex++);return!1}a(".chaos-modal-link").one("click", m)});<commit_msg>Updated to reflect changes in 1.3.3.<commit_after>/*
 * jQuery Chaos Modal
 * By Matthew Sigley
 * Based on concept work by Kevin Liew - http://www.queness.com/post/77/simple-jquery-modal-window-tutorial
 * Version 1.3.3
 */
(function(a){function l(a){var c=!1;a.data.alwaysAtTop&&(c=a.data.alwaysAtTop);k.resizeModal(c)}function b(a){a.preventDefault();k.closeModal()}function d(a){a.preventDefault();k.printModal()}function g(e){27==e.which&&(a(this).off("keyup",g),b(e))}var k,h;a.chaosModalIndex=0;a.fn.openModal=function(e){e=a.extend({},{maskClose:!0,maxWidth:960,closeLink:!0,printLink:!1,alwaysAtTop:!1},e);var c=this.clone(),m=a("body");a("html");var q=a(document),f=a(window),r=e.maskClose,s=e.closeLink,t=e.printLink, p=e.alwaysAtTop;h=e.maxWidth;k=c;c.attr({id:"chaos-current-modal"});var n=a('

');n.css({position:"absolute","z-index":"9000","background-color":"#000",display:"none",top:"0",left:"0"});n.appendTo(m);0==c.find(".print-link").length&&t&&(c.prepend('Print'),c.children(".print-link").css({"float":"right",margin:"5px"}));0==c.find(".close-link").length&&s&&(c.prepend('Close'),c.children(".close-link").css({"float":"right", margin:"5px"}));c.css({display:"block",position:"absolute",background:"#fff","z-index":"9001",left:"-10000px",margin:"0",padding:"0"});c.appendTo(m);e=function(){c.width()>h?c.width(h):c.width(c.width());c.hide();c.resizeModal(p);n.fadeIn(1E3);n.fadeTo("slow",.8);c.fadeIn(2E3);f.bind("resize",{alwaysAtTop:p},l);0<c.find(".print-link").length&&c.find(".print-link").bind("click",d);0<c.find(".close-link").length&&c.find(".close-link").bind("click",b);r&&(n.on("click",b),q.on("keyup",g))};m=c.find("img").last(); if(m.length)m.on("load",e);else e();c.show();return this};a.fn.closeModal=function(){k=null;a(window).unbind("resize",l);0<this.find(".close-link").length&&this.find(".close-link").unbind("click");this.hide().remove();a("#chaos-modal-mask").hide().remove();return this};a.fn.resizeModal=function(e){var c=a(document),b=a(window),d=a("html"),f=c.height(),g=d.width(),h=b.height(),d=b.width();f<this.height()&&(f=this.height());g<d&&(g=d);a("#chaos-modal-mask").css({width:g,height:f});f=h/2-this.height()/ 2;d=d/2-this.width()/2;0>f&&(f=0);0>d&&(d=0);e?b.scrollTop(0):(f+=b.scrollTop(),e=c.height()-(f+this.height()),0>e&&0<=f+e&&(f+=e));b.scrollLeft(0);this.css({top:f,left:d});return this};a.fn.printModal=function(){window.print()}})(jQuery); jQuery(document).ready(function(a){function l(b){var d=a(this);b.preventDefault();d.off("click",l);var g=d.parent().find(".chaos-modal-box").first();b=!1;var k=/\.(jpeg|jpg|gif|png|bmp|wbmp)$/i,h=!1;g.length?(b=a("

").css({padding:"20px"}),g.clone().appendTo(b),b=a("

").css({background:"#fff"}).append(b)):(k.test(d.attr("href"))?h=d.attr("href"):(g=d.find("img"),1==g.length&&k.test(g.attr("src"))&&(h=g.attr("src"))),h&&(testRequest=a.ajax(h,{type:"HEAD",async:!1}),200==testRequest.status&& (b=a("

").css({padding:"20px"}),a('').css({display:"block"}).appendTo(b),b=a("

").css({background:"#fff"}).append(b))));b&&(d.attr({href:"#",id:"chaos-modal-link-"+a.chaosModalIndex}),b.attr({id:"chaos-modal-box-"+a.chaosModalIndex}),b.hide(),b.appendTo("body"),d.click(function(){var b=a(this).attr("id").match(/chaos-modal-link-(\d+)/i);b[1]&&(b=a("#chaos-modal-box-"+b[1]),1==b.length&&b.openModal());return!1}),d.click(),a.chaosModalIndex++);return!1}a(".chaos-modal-link").not("a").each(function(){var b= a(this),d=b.children();b.removeClass("chaos-modal-link");d.not("a").length||1==d.length&&d.addClass("chaos-modal-link")});a("a.chaos-modal-link").one("click",l)});<|endoftext|>


<commit_before>/*!
 * jQuery Scrollbox
 * (c) 2009-2013 Hunter Wu <hunter.wu@gmail.com>
 * MIT Licensed.
 *
 * http://github.com/wmh/jquery-scrollbox
 */

(function($) {

$.fn.scrollbox = function(config) {
  //default config
  var defConfig = {
    linear: false,          // Scroll method
    startDelay: 2,          // Start delay (in seconds)
    delay: 3,               // Delay after each scroll event (in seconds)
    step: 5,                // Distance of each single step (in pixels)
    speed: 32,              // Delay after each single step (in milliseconds)
    switchItems: 1,         // Items to switch after each scroll event
    direction: 'vertical',
    distance: 'auto',
    autoPlay: true,
    onMouseOverPause: true,
    paused: false,
    queue: null
  };
  config = $.extend(defConfig, config);
  config.scrollOffset = config.direction === 'vertical' ? 'scrollTop' : 'scrollLeft';
  if (config.queue) {
    config.queue = $('#' + config.queue);
  }

  return this.each(function() {
    var container = $(this),
        containerUL,
        scrollingId = null,
        nextScrollId = null,
        paused = false,
        backward,
        forward,
        resetClock,
        scrollForward,
        scrollBackward;

    if (config.onMouseOverPause) {
      container.bind('mouseover', function() { paused = true; });
      container.bind('mouseout', function() { paused = false; });
    }
    containerUL = container.children('ul:first-child');

    scrollForward = function() {
      if (paused) {
        return;
      }
      var curLi,
          i,
          newScrollOffset,
          scrollDistance,
          theStep;

      curLi = containerUL.children('li:first-child');

      scrollDistance = config.distance !== 'auto' ? config.distance :
        config.direction === 'vertical' ? curLi.height() : curLi.width();

      // offset
      if (!config.linear) {
        theStep = Math.max(3, parseInt((scrollDistance - container[0][config.scrollOffset]) * 0.3, 10));
        newScrollOffset = Math.min(container[0][config.scrollOffset] + theStep, scrollDistance);
      } else {
        newScrollOffset = Math.min(container[0][config.scrollOffset] + config.step, scrollDistance);
      }
      container[0][config.scrollOffset] = newScrollOffset;

      if (newScrollOffset >= scrollDistance) {
        for (i = 0; i < config.switchItems; i++) {
          if (config.queue && config.queue.hasChildNodes() && config.queue.getElementsByTagName('li').length > 0) {
            containerUL.append(config.queue.getElementsByTagName('li')[0]);
            containerUL.remove(containerUL.children('li:first-child'));
          } else {
            containerUL.append(containerUL.children('li:first-child'));
          }
        }
        container[0][config.scrollOffset] = 0;
        clearInterval(scrollingId);
        if (config.autoPlay) {
          nextScrollId = setTimeout(forward, config.delay * 1000);
        }
      }
    };

    // Backward
    // 1. If forwarding, then reverse
    // 2. If stoping, then backward once
    scrollBackward = function() {
      if (paused) {
        return;
      }
      var curLi,
          i,
          liLen,
          newScrollOffset,
          scrollDistance,
          theStep;

      // init
      if (container[0][config.scrollOffset] === 0) {
        liLen = containerUL.children('li').length;
        for (i = 0; i < config.switchItems; i++) {
          containerUL.children('li:last-child').insertBefore(containerUL.children('li:first-child'));
        }

        curLi = container.children('li:first-child');
        scrollDistance = config.distance !== 'auto' ?
            config.distance :
            config.direction === 'vertical' ? curLi.height() : curLi.width();
        container[0][config.scrollOffset] = scrollDistance;
      }

      // new offset
      if (!config.linear) {
        theStep = Math.max(3, parseInt(container[0][config.scrollOffset] * 0.3, 10));
        newScrollOffset = Math.max(container[0][config.scrollOffset] - theStep, 0);
      } else {
        newScrollOffset = Math.max(container[0][config.scrollOffset] - config.step, 0);
      }
      container[0][config.scrollOffset] = newScrollOffset;

      if (newScrollOffset === 0) {
        clearInterval(scrollingId);
        if (config.autoPlay) {
          nextScrollId = setTimeout(forward, config.delay * 1000);
        }
      }
    };

    forward = function() {
      clearInterval(scrollingId);
      scrollingId = setInterval(scrollForward, config.speed);
    };

    backward = function() {
      clearInterval(scrollingId);
      scrollingId = setInterval(scrollBackward, config.speed);
    };

    resetClock = function(delay) {
      config.delay = delay || config.delay;
      clearTimeout(nextScrollId);
      if (config.autoPlay) {
        nextScrollId = setTimeout(forward, config.delay * 1000);
      }
    };

    if (config.autoPlay) {
      nextScrollId = setTimeout(forward, config.startDelay * 1000);
    }

    // bind events for container
    container.bind('resetClock', function(delay) { resetClock(delay); });
    container.bind('forward', function() { clearTimeout(nextScrollId); forward(); });
    container.bind('backward', function() { clearTimeout(nextScrollId); backward(); });
    container.bind('speedUp', function(speed) {
      if (typeof speed === 'undefined') {
        speed = Math.max(1, parseInt(config.speed / 2, 10));
      }
      config.speed = speed;
    });
    container.bind('speedDown', function(speed) {
      if (typeof speed === 'undefined') {
        speed = config.speed * 2;
      }
      config.speed = speed;
    });
  });
};

}(jQuery));<commit_msg>Fix DOM manipulation of queue container<commit_after>/*!
 * jQuery Scrollbox
 * (c) 2009-2013 Hunter Wu <hunter.wu@gmail.com>
 * MIT Licensed.
 *
 * http://github.com/wmh/jquery-scrollbox
 */

(function($) {

$.fn.scrollbox = function(config) {
  //default config
  var defConfig = {
    linear: false,          // Scroll method
    startDelay: 2,          // Start delay (in seconds)
    delay: 3,               // Delay after each scroll event (in seconds)
    step: 5,                // Distance of each single step (in pixels)
    speed: 32,              // Delay after each single step (in milliseconds)
    switchItems: 1,         // Items to switch after each scroll event
    direction: 'vertical',
    distance: 'auto',
    autoPlay: true,
    onMouseOverPause: true,
    paused: false,
    queue: null
  };
  config = $.extend(defConfig, config);
  config.scrollOffset = config.direction === 'vertical' ? 'scrollTop' : 'scrollLeft';
  if (config.queue) {
    config.queue = $('#' + config.queue);
  }

  return this.each(function() {
    var container = $(this),
        containerUL,
        scrollingId = null,
        nextScrollId = null,
        paused = false,
        backward,
        forward,
        resetClock,
        scrollForward,
        scrollBackward;

    if (config.onMouseOverPause) {
      container.bind('mouseover', function() { paused = true; });
      container.bind('mouseout', function() { paused = false; });
    }
    containerUL = container.children('ul:first-child');

    scrollForward = function() {
      if (paused) {
        return;
      }
      var curLi,
          i,
          newScrollOffset,
          scrollDistance,
          theStep;

      curLi = containerUL.children('li:first-child');

      scrollDistance = config.distance !== 'auto' ? config.distance :
        config.direction === 'vertical' ? curLi.height() : curLi.width();

      // offset
      if (!config.linear) {
        theStep = Math.max(3, parseInt((scrollDistance - container[0][config.scrollOffset]) * 0.3, 10));
        newScrollOffset = Math.min(container[0][config.scrollOffset] + theStep, scrollDistance);
      } else {
        newScrollOffset = Math.min(container[0][config.scrollOffset] + config.step, scrollDistance);
      }
      container[0][config.scrollOffset] = newScrollOffset;

      if (newScrollOffset >= scrollDistance) {
        for (i = 0; i < config.switchItems; i++) {
          if (config.queue && config.queue.find('li').length > 0) {
            containerUL.append(config.queue.find('li')[0]);
            containerUL.children('li:first-child').remove();
          } else {
            containerUL.append(containerUL.children('li:first-child'));
          }
        }
        container[0][config.scrollOffset] = 0;
        clearInterval(scrollingId);
        if (config.autoPlay) {
          nextScrollId = setTimeout(forward, config.delay * 1000);
        }
      }
    };

    // Backward
    // 1. If forwarding, then reverse
    // 2. If stoping, then backward once
    scrollBackward = function() {
      if (paused) {
        return;
      }
      var curLi,
          i,
          liLen,
          newScrollOffset,
          scrollDistance,
          theStep;

      // init
      if (container[0][config.scrollOffset] === 0) {
        liLen = containerUL.children('li').length;
        for (i = 0; i < config.switchItems; i++) {
          containerUL.children('li:last-child').insertBefore(containerUL.children('li:first-child'));
        }

        curLi = container.children('li:first-child');
        scrollDistance = config.distance !== 'auto' ?
            config.distance :
            config.direction === 'vertical' ? curLi.height() : curLi.width();
        container[0][config.scrollOffset] = scrollDistance;
      }

      // new offset
      if (!config.linear) {
        theStep = Math.max(3, parseInt(container[0][config.scrollOffset] * 0.3, 10));
        newScrollOffset = Math.max(container[0][config.scrollOffset] - theStep, 0);
      } else {
        newScrollOffset = Math.max(container[0][config.scrollOffset] - config.step, 0);
      }
      container[0][config.scrollOffset] = newScrollOffset;

      if (newScrollOffset === 0) {
        clearInterval(scrollingId);
        if (config.autoPlay) {
          nextScrollId = setTimeout(forward, config.delay * 1000);
        }
      }
    };

    forward = function() {
      clearInterval(scrollingId);
      scrollingId = setInterval(scrollForward, config.speed);
    };

    backward = function() {
      clearInterval(scrollingId);
      scrollingId = setInterval(scrollBackward, config.speed);
    };

    resetClock = function(delay) {
      config.delay = delay || config.delay;
      clearTimeout(nextScrollId);
      if (config.autoPlay) {
        nextScrollId = setTimeout(forward, config.delay * 1000);
      }
    };

    if (config.autoPlay) {
      nextScrollId = setTimeout(forward, config.startDelay * 1000);
    }

    // bind events for container
    container.bind('resetClock', function(delay) { resetClock(delay); });
    container.bind('forward', function() { clearTimeout(nextScrollId); forward(); });
    container.bind('backward', function() { clearTimeout(nextScrollId); backward(); });
    container.bind('speedUp', function(speed) {
      if (typeof speed === 'undefined') {
        speed = Math.max(1, parseInt(config.speed / 2, 10));
      }
      config.speed = speed;
    });
    container.bind('speedDown', function(speed) {
      if (typeof speed === 'undefined') {
        speed = config.speed * 2;
      }
      config.speed = speed;
    });
  });
};

}(jQuery));<|endoftext|>


<commit_before>/*
* SVG Plugin
* Copyright 2013, CDCabrera, menotyou.com
* licensed under MIT license, http://opensource.org/licenses/mit-license.php
*
* Utilized Keith Wood's SVG for jQuery v1.4.5 plugin as a reference for what class methods to include
* http://keith-wood.name/svg.html
* Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
* MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
*/

(function(window, $, undefined){

    //-- SVG Plugin methods
    window.simplesvg = function()
    {
        function trimmedobj() //-- take what we need from jquery, shortcut for implmenting svg specific methods
        {
            var o = $.apply(this, arguments),
            tempobj = { selector:o.selector, length:o.length, context:o.context, each:o.each, get:o.get, eq:o.eq, splice:[].splice };
            o.each(function(i,v){ tempobj[i] = this;});
            return tempobj;
        }

        return $.extend(trimmedobj.apply(this,arguments),
        {
            addClass : function( value )
            {
                return this.each(function()
                {
                    if( !simplesvg(this).hasClass(value) )
                    {
                        this.setAttribute('class', (this.getAttribute('class')||'') + ' ' + value);
                    }
                });
            },

            removeClass : function( value )
            {
                return this.each(function()
                {
                    if( simplesvg(this).hasClass(value) )
                    {
                        this.setAttribute('class', (this.getAttribute('class')||'').replace(name, ' ').replace(/\s+/gi, '') );
                    }
                });
            },

            hasClass : function( value )
            {
                var ret = false;
                this.each(function()
                {
                    var classes = (this.getAttribute('class')||'').replace(/[\t\r\n]/g, ' ').split(/\s+/g);
                    if(classes.indexOf(value) > -1)
                    {
                        ret = true;
                        return false;
                    }
                    return true;
                });
                return ret;
            },

            above : function(isbelow)
            {
                return this.each(function()
                {
                    var parent      = $(this.parentNode),
                        parents     = parent.parentsUntil('svg'),
                        siblings    = parent.children().not(this);

                    if(isbelow)
                    {
                        parent.append( siblings );
                    }
                    else
                    {
                        parent.prepend( siblings );
                    }

                    parents.each(function()
                    {
                        var parent      = $(this.parentNode),
                            siblings    = parent.children().not(this);

                        if(isbelow)
                        {
                            parent.append( siblings );
                        }
                        else
                        {
                            parent.prepend( siblings );
                        }
                    });
                });
            },

            below : function()
            {
                return this.above(true);
            },

            centerOrigin : function( container )
            {
                var style = this.transformOrigin();

                if(style)
                {
                    style = style.split(/\s/);
                    return {
                        x: (style[0] || null),
                        y: (style[1] || null)
                    };
                }

                var coords      = this.getInfo( container ),
                    centerx     = coords.x + (coords.width / 2) + 'px',
                    centery     = coords.y + (coords.height / 2) + 'px';

                this.transformOrigin(centerx+' '+centery);

                return {x:centerx, y:centery};
            },

            transformOrigin : function( xy )
            {
                var self            = this[0],
                    vendororigin    = ['transformOrigin','webkitTransformOrigin','MozTransformOrigin','OTransformOrigin','msTransformOrigin'],
                    temparray       = vendororigin,
                    returnorigin    = null;

                if(xy)
                {
                    return this.each(function()
                    {
                        var self = this;

                        while(temparray.length)
                        {
                            var p = temparray.shift();
                            self.style[p] = xy;
                        }
                    });
                }
                else
                {
                    while(temparray.length)
                    {
                        var p = temparray.shift();

                        if(p in self.style)
                        {
                            returnorigin = self.style[p];
                            break;
                        }
                    }
                    return returnorigin;
                }
            },

            getInfo : function( container )
            {
                var self    = this[0],
                    jself   = $(this[0]),
                    bbox    = null,
                    offset  = jself.offset(),
                    rect    = self.getBoundingClientRect(),
                    obj     = {},
                    cx      = self.getAttribute('cx'),
                    cy      = self.getAttribute('cy'),
                    altpos,
                    containerpos;

                try
                {
                    obj = self.getBBox();
                }
                catch(e)
                {
                    altpos = {left: self.offsetLeft, top: self.offsetTop};
                    container = (!container)? jself.closest('svg').get(0) : simplesvg(container).get(0);

                    if(container)
                    {
                        containerpos = {left:container.offsetLeft, top:container.offsetTop};
                        obj =
                        {
                            x       : (altpos.left - containerpos.left),
                            y       : (altpos.top - containerpos.top),
                            width   : self.offsetWidth,               //-- avoided using "getBoundingClientRect" on purpose... trying to match how "getBBox" would return coords
                            height  : self.offsetHeight
                        };
                    }
                }

                $.extend(obj,{
                    cx:             ((cx && cx.length)? parseFloat(cx) : obj.x + (obj.width/2)),
                    cy:             ((cy && cy.length)? parseFloat(cy) : obj.y + (obj.height/2)),
                    node:           self,
                    currentwidth:   rect.width,
                    currentheight:  rect.height,
                    currentx:       offset.left,                        //-- use jQuery's conversion, accounts for "scroll" page height
                    currenty:       offset.top,
                    currentcx:      (offset.left + (rect.width/2)),
                    currentcy:      (offset.top + (rect.height/2))
                });

                return obj;
            },

            addSVG : function( value )
            {
                return this.each(function()
                {
                    $(this).append(simplesvg.makeSVG(value));
                });
            }
        });
    };

    //-- namespaced
    $.extend(window.simplesvg,
    {
        //-- SVG namespaced
        //-- http://stackoverflow.com/questions/9723422/is-there-some-innerhtml-replacement-in-svg-xml
        makeSVG: function( value )
        {
            var svg = '<svg xmlns="http://www.w3.org/2000/svg">{0}</svg>';
            return $(svg.replace('{0}',value)).children();
        },

        supported: ('SVGSVGElement' in window), //-- ('createElementNS' in window.document)

        //-- allowance for use of class selectors instead of attribute selectors when using SVG
        makeSel: function( value )
        {
            return value.replace(/\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*\s*)/g,"[class*=$1]");
        }
    });

    //-- SVG engine
    $.simplesvg = function(_settings)
    {
        _settings = $.extend(true,
        {
            parent          :   null,
            file            :   null,                                           //-- string: svg file path reference
            supported       :   simplesvg.supported,                            //-- boolean: is svg supported?
            cssplacement    :   $(document).find('head > title,head > link').last(), //-- DOM element, jQuery element, CSS selector string or function returning the aforementioned: where the SVG css is inserted if it contains any - helps IE
            events          :   {},                                             //-- object: object of events applied to the svg as a whole, use the form of { 'cssselector' : {click:function(){}, mouseover:function(){}} }
            autoshow        :   true,                                           //-- boolean: plugin automatically appends and shows content, otherwise user appends content using the "complete" callback
            complete        :   null,                                           //-- function: callback for plugin loaded
            unsupported     :   null,                                           //-- DOM element, jQuery element, CSS selector string, html string or function that returns all of the aforementioned: displays alternate content for unsupported browsers. This creates a deep clone and detaches the original element if it exists.
            error           :   null                                            //-- function: svg data loading error
        }, _settings);

        Go();

        //-- start everything
        function Go()
        {
            var htmlobjs = $(_settings.parent);

            if( !htmlobjs.length || !_settings.file )
            {
                return;
            }

            $.when(GetSVGData()).then(function(data)
            {
                SetupContent(htmlobjs, data);
            }, Error);
        }

        //-- get svg data
        function GetSVGData()
        {
            var def     = new $.Deferred(),
                file    = _settings.file;

            $.ajax(
            {
                url         :   file,
                dataType    :   'text', //-- bypass malformed "xml"
                cache       :   false,
                error       :   function()
                                {
                                    def.resolve(null);
                                },
                success     :   function(data)
                                {
                                    def.resolve( $('<div/>').html(data).children() );
                                }
            });

            return def.promise();
        }

        //-- error callback
        function Error()
        {
            var error       = _settings.error,
                supported   = _settings.supported;

            if( $.isFunction(error) )
            {
                error.call(this,{data:null, supported:supported});
            }
        }

        //-- setup display content
        function SetupContent( htmlobjs, svgdata )
        {
            if( !svgdata )
            {
                Error();
                return;
            }

            var complete    = ($.isFunction(_settings.complete))? _settings.complete : function(){},
                autoshow    = _settings.autoshow,
                supported   = _settings.supported,
                unsupported = _settings.unsupported,
                tempfunc    = ($.isFunction(unsupported))? unsupported : function(){return $(unsupported);};

            htmlobjs.each(function()
            {
                var self            = this,
                    svgclone        = svgdata.clone(true),
                    nodes           = GetSVGNodes.call(svgclone.get(0)),
                    dataobj         = {
                                        parent  : self,
                                        display : null,
                                        svg     : svgclone.get(0),
                                        alt     : null
                                      };

                //-- call unsupported pass back svg information
                $.when(tempfunc.call(this, {data:dataobj, nodes:nodes, supported:supported})).then(function(data)
                {
                    var altDisplayData  = $(data).clone(true),
                        newDisplayData  = ( supported )? svgclone : altDisplayData,
                        nodes           = GetSVGNodes.call(svgclone.get(0));

                    dataobj.display = (newDisplayData.length)? newDisplayData.get(0) : null;
                    dataobj.alt     = (altDisplayData.length)? altDisplayData.get(0) : null;

                    SetEvents( dataobj.parent, dataobj );

                    if(supported)
                    {
                        SetCSS( newDisplayData );
                    }

                    if(!autoshow)
                    {
                        newDisplayData.hide();
                    }

                    $(self).html( newDisplayData );

                    complete.call(self,
                    {
                        data        : dataobj,
                        nodes       : nodes,
                        supported   : supported
                    });

                }, Error);
            });
        }

        //-- pass nodes to unsupported content
        function GetSVGNodes()
        {
            var svg     = $(this),
                events  = (_settings.events||{}),
                nodes   = [];

            if($.isEmptyObject(events))
            {
                nodes = $.makeArray(svg.find('*'));
            }
            else
            {
                $.each(events,function(key,value)
                {
                    var selector= simplesvg.makeSel(key);
                    nodes = nodes.concat( $.makeArray( svg.find(selector) ) );
                });
            }

            return  nodes;
        }

        //-- attach user defined event object
        function SetEvents( displaydata, dataobj )
        {
            var parent      = $(displaydata),
                events      = (_settings.events||{}),
                supported   = _settings.supported;

            $.each(events,function(key,value)
            {
                var selector= simplesvg.makeSel(key);

                $.each(value, function(subkey,subvalue)
                {
                    parent.on(subkey, selector, function()
                    {
                        var self        = this,
                            args        = [].concat(Array.prototype.slice.call(arguments));

                        args.push({
                            data:       dataobj,
                            selector:   selector,
                            nodes:      $.makeArray( $(dataobj.svg).find(selector) ),
                            supported:  supported,
                            element:    (supported)? simplesvg(self).getInfo() : self
                        });

                        subvalue.apply(self, args);
                    });
                });
            });
        }

        //-- reorganize css, since we do it for IE do it for everybody...
        function SetCSS( element )
        {
            var cssplacement = $.isFunction(_settings.cssplacement) ? _settings.cssplacement : function(){return $(_settings.cssplacement);},
                svgstyle     = $(element).find('style'),
                tempprevious = null;

            cssplacement = cssplacement.call(this);

            svgstyle.each(function(i,v)
            {
                var newstyle = $('<style type="text/css"/>'),
                    contents = $(this).contents();

                if(i>0)
                {
                    $(tempprevious).after(newstyle);
                }
                else
                {
                    $(cssplacement[0]).after(newstyle);
                }

                if( 'styleSheet' in newstyle[0] ) //-- ie you fantastic browser you...
                {
                    newstyle[0].styleSheet.cssText = contents.text();
                }
                else
                {
                    newstyle.html(contents);
                }

                tempprevious = newstyle;
            });
        }
    };

})(this, jQuery);
<commit_msg>Update<commit_after>/*
* SVG Plugin
* Copyright 2013, CDCabrera, menotyou.com
* licensed under MIT license, http://opensource.org/licenses/mit-license.php
*
* Utilized Keith Wood's SVG for jQuery v1.4.5 plugin as a reference for what class methods to include
* http://keith-wood.name/svg.html
* Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
* MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
*/

(function(window, $, undefined){

    //-- SVG Plugin methods
    window.simplesvg = function()
    {
        function trimmedobj() //-- take what we need from jquery, shortcut for implmenting svg specific methods
        {
            var o = $.apply(this, arguments),
            tempobj = { selector:o.selector, length:o.length, context:o.context, each:o.each, get:o.get, eq:o.eq, splice:[].splice };
            o.each(function(i,v){ tempobj[i] = this;});
            return tempobj;
        }

        return $.extend(trimmedobj.apply(this,arguments),
        {
            addClass : function( value )
            {
                return this.each(function()
                {
                    if( !simplesvg(this).hasClass(value) )
                    {
                        this.setAttribute('class', (this.getAttribute('class')||'') + ' ' + value);
                    }
                });
            },

            removeClass : function( value )
            {
                return this.each(function()
                {
                    if( simplesvg(this).hasClass(value) )
                    {
                        this.setAttribute('class', (this.getAttribute('class')||'').replace(name, ' ').replace(/\s+/gi, '') );
                    }
                });
            },

            hasClass : function( value )
            {
                var ret = false;
                this.each(function()
                {
                    var classes = (this.getAttribute('class')||'').replace(/[\t\r\n]/g, ' ').split(/\s+/g);
                    if(classes.indexOf(value) > -1)
                    {
                        ret = true;
                        return false;
                    }
                    return true;
                });
                return ret;
            },

            above : function(isbelow)
            {
                return this.each(function()
                {
                    var parent      = $(this.parentNode),
                        parents     = parent.parentsUntil('svg'),
                        siblings    = parent.children().not(this);

                    if(isbelow)
                    {
                        parent.append( siblings );
                    }
                    else
                    {
                        parent.prepend( siblings );
                    }

                    parents.each(function()
                    {
                        var parent      = $(this.parentNode),
                            siblings    = parent.children().not(this);

                        if(isbelow)
                        {
                            parent.append( siblings );
                        }
                        else
                        {
                            parent.prepend( siblings );
                        }
                    });
                });
            },

            below : function()
            {
                return this.above(true);
            },

            centerOrigin : function( container )
            {
                var style = this.transformOrigin();

                if(style)
                {
                    style = style.split(/\s/);
                    return {
                        x: (style[0] || null),
                        y: (style[1] || null)
                    };
                }

                var coords      = this.getInfo( container ),
                    centerx     = coords.x + (coords.width / 2) + 'px',
                    centery     = coords.y + (coords.height / 2) + 'px';

                this.transformOrigin(centerx+' '+centery);

                return {x:centerx, y:centery};
            },

            transformOrigin : function( xy )
            {
                var self            = this[0],
                    vendororigin    = ['transformOrigin','webkitTransformOrigin','MozTransformOrigin','OTransformOrigin','msTransformOrigin'],
                    temparray       = vendororigin,
                    returnorigin    = null;

                if(xy)
                {
                    return this.each(function()
                    {
                        var self = this;

                        while(temparray.length)
                        {
                            var p = temparray.shift();
                            self.style[p] = xy;
                        }
                    });
                }
                else
                {
                    while(temparray.length)
                    {
                        var p = temparray.shift();

                        if(p in self.style)
                        {
                            returnorigin = self.style[p];
                            break;
                        }
                    }
                    return returnorigin;
                }
            },

            getInfo : function( container )
            {
                var self    = this[0],
                    jself   = $(this[0]),
                    bbox    = null,
                    offset  = jself.offset(),
                    rect    = self.getBoundingClientRect(),
                    obj     = {},
                    cx      = self.getAttribute('cx'),
                    cy      = self.getAttribute('cy'),
                    altpos,
                    containerpos;

                try
                {
                    obj = self.getBBox();
                }
                catch(e)
                {
                    altpos = {left: self.offsetLeft, top: self.offsetTop};
                    container = (!container)? jself.closest('svg').get(0) : simplesvg(container).get(0);

                    if(container)
                    {
                        containerpos = {left:container.offsetLeft, top:container.offsetTop};
                        obj =
                        {
                            x       : (altpos.left - containerpos.left),
                            y       : (altpos.top - containerpos.top),
                            width   : self.offsetWidth,               //-- avoided using "getBoundingClientRect" on purpose... trying to match how "getBBox" would return coords
                            height  : self.offsetHeight
                        };
                    }
                }

                $.extend(obj,{
                    cx:             ((cx && cx.length)? parseFloat(cx) : obj.x + (obj.width/2)),
                    cy:             ((cy && cy.length)? parseFloat(cy) : obj.y + (obj.height/2)),
                    node:           self,
                    currentwidth:   rect.width,
                    currentheight:  rect.height,
                    currentx:       offset.left,                        //-- use jQuery's conversion, accounts for "scroll" page height
                    currenty:       offset.top,
                    currentcx:      (offset.left + (rect.width/2)),
                    currentcy:      (offset.top + (rect.height/2))
                });

                return obj;
            },

            addSVG : function( value )
            {
                return this.each(function()
                {
                    $(this).append(simplesvg.makeSVG(value));
                });
            }
        });
    };

    //-- namespaced
    $.extend(window.simplesvg,
    {
        //-- SVG namespaced
        //-- http://stackoverflow.com/questions/9723422/is-there-some-innerhtml-replacement-in-svg-xml
        makeSVG: function( value )
        {
            var svg = '<svg xmlns="http://www.w3.org/2000/svg">{0}</svg>';
            return $(svg.replace('{0}',value)).children();
        },

        supported: ('SVGSVGElement' in window), //-- ('createElementNS' in window.document)

        //-- allowance for use of class selectors instead of attribute selectors when using SVG
        makeSel: function( value )
        {
            return value.replace(/\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*\s*)/g,"[class*=$1]");
        }
    });

    //-- SVG engine
    $.simplesvg = function(_settings)
    {
        _settings = $.extend(true,
        {
            parent          :   null,
            file            :   null,                                           //-- string: svg file path reference
            supported       :   simplesvg.supported,                            //-- boolean: is svg supported?
            cssplacement    :   $(document).find('head > title,head > link').last(), //-- DOM element, jQuery element, CSS selector string or function returning the aforementioned: where the SVG css is inserted if it contains any - helps IE
            events          :   {},                                             //-- object: object of events applied to the svg as a whole, use the form of { 'cssselector' : {click:function(){}, mouseover:function(){}} }
            autoshow        :   true,                                           //-- boolean: plugin automatically appends and shows content, otherwise user appends content using the "complete" callback
            cache           :   false,                                          //-- boolean: cache svg/xml
            complete        :   null,                                           //-- function: callback for plugin loaded
            unsupported     :   null,                                           //-- DOM element, jQuery element, CSS selector string, html string or function that returns all of the aforementioned: displays alternate content for unsupported browsers. This creates a deep clone and detaches the original element if it exists.
            error           :   null                                            //-- function: svg data loading error
        }, _settings);

        Go();

        //-- start everything
        function Go()
        {
            var htmlobjs = $(_settings.parent);

            if( !htmlobjs.length || !_settings.file )
            {
                return;
            }

            $.when(GetSVGData()).then(function(data)
            {
                SetupContent(htmlobjs, data);
            }, Error);
        }

        //-- get svg data
        function GetSVGData()
        {
            var def     = new $.Deferred(),
                file    = _settings.file,
                cache   = _settings.cache;

            $.ajax(
            {
                url         :   file,
                dataType    :   'text', //-- bypass malformed "xml"
                cache       :   cache,
                error       :   function()
                                {
                                    def.resolve(null);
                                },
                success     :   function(data)
                                {
                                    def.resolve( $('<div/>').html(data).children() );
                                }
            });

            return def.promise();
        }

        //-- error callback
        function Error()
        {
            var error       = _settings.error,
                supported   = _settings.supported;

            if( $.isFunction(error) )
            {
                error.call(this,{data:null, supported:supported});
            }
        }

        //-- setup display content
        function SetupContent( htmlobjs, svgdata )
        {
            if( !svgdata )
            {
                Error();
                return;
            }

            var complete    = ($.isFunction(_settings.complete))? _settings.complete : function(){},
                autoshow    = _settings.autoshow,
                supported   = _settings.supported,
                unsupported = _settings.unsupported,
                tempfunc    = ($.isFunction(unsupported))? unsupported : function(){return $(unsupported);};

            htmlobjs.each(function()
            {
                var self            = this,
                    svgclone        = svgdata.clone(true),
                    nodes           = GetSVGNodes.call(svgclone.get(0)),
                    dataobj         = {
                                        parent  : self,
                                        display : null,
                                        svg     : svgclone.get(0),
                                        alt     : null
                                      };

                //-- call unsupported pass back svg information
                $.when(tempfunc.call(this, {data:dataobj, nodes:nodes, supported:supported})).then(function(data)
                {
                    var altDisplayData  = $(data).clone(true),
                        newDisplayData  = ( supported )? svgclone : altDisplayData,
                        nodes           = GetSVGNodes.call(svgclone.get(0));

                    dataobj.display = (newDisplayData.length)? newDisplayData.get(0) : null;
                    dataobj.alt     = (altDisplayData.length)? altDisplayData.get(0) : null;

                    SetEvents( dataobj.parent, dataobj );

                    if(supported)
                    {
                        SetCSS( newDisplayData );
                    }

                    if(!autoshow)
                    {
                        newDisplayData.hide();
                    }

                    $(self).html( newDisplayData );

                    complete.call(self,
                    {
                        data        : dataobj,
                        nodes       : nodes,
                        supported   : supported
                    });

                }, Error);
            });
        }

        //-- pass nodes to unsupported content
        function GetSVGNodes()
        {
            var svg     = $(this),
                events  = (_settings.events||{}),
                nodes   = [];

            if($.isEmptyObject(events))
            {
                nodes = $.makeArray(svg.find('*'));
            }
            else
            {
                $.each(events,function(key,value)
                {
                    var selector= simplesvg.makeSel(key);
                    nodes = nodes.concat( $.makeArray( svg.find(selector) ) );
                });
            }

            return  nodes;
        }

        //-- attach user defined event object
        function SetEvents( displaydata, dataobj )
        {
            var parent      = $(displaydata),
                events      = (_settings.events||{}),
                supported   = _settings.supported;

            $.each(events,function(key,value)
            {
                var selector= simplesvg.makeSel(key);

                $.each(value, function(subkey,subvalue)
                {
                    parent.on(subkey, selector, function()
                    {
                        var self        = this,
                            args        = [].concat(Array.prototype.slice.call(arguments));

                        args.push({
                            data:       dataobj,
                            selector:   selector,
                            nodes:      $.makeArray( $(dataobj.svg).find(selector) ),
                            supported:  supported,
                            element:    (supported)? simplesvg(self).getInfo() : self
                        });

                        subvalue.apply(self, args);
                    });
                });
            });
        }

        //-- reorganize css, since we do it for IE do it for everybody...
        function SetCSS( element )
        {
            var cssplacement = $.isFunction(_settings.cssplacement) ? _settings.cssplacement : function(){return $(_settings.cssplacement);},
                svgstyle     = $(element).find('style'),
                tempprevious = null;

            cssplacement = cssplacement.call(this);

            svgstyle.each(function(i,v)
            {
                var newstyle = $('<style type="text/css"/>'),
                    contents = $(this).contents();

                if(i>0)
                {
                    $(tempprevious).after(newstyle);
                }
                else
                {
                    $(cssplacement[0]).after(newstyle);
                }

                if( 'styleSheet' in newstyle[0] ) //-- ie you fantastic browser you...
                {
                    newstyle[0].styleSheet.cssText = contents.text();
                }
                else
                {
                    newstyle.html(contents);
                }

                tempprevious = newstyle;
            });
        }
    };

})(this, jQuery);
<|endoftext|>


<commit_before>/**
 * jQuery DataTables sort plugin
 * Sorting Strings respecting locale.
 * Performance optimized for large static data sets.
 * Requires DataTables 1.10 or newer.
 *
 * In non english languages Javascript Array.sort using String.compare() gives false order,
 * e.g in German correct sort order is: Arzt, Ärzte, Ast, Baum, Zeder
 * in contrast to Arzt, Ast, Baum, Zeder, Ärzte as in English/ASCII string sort
 *
 * Since String.localeCompare has extremely bad performance this uses the following approach:
 * Sort ONCE the assigend columns using String.localeCompare (or String compare when sufficient), then cache a map of each cell to the position in the ordered list.
 * Later DataTables.api().sort() just uses the position index and can then take advantage of much faster integer comparison.
 * For large data sets this can be more than 100 times faster than the naive localeCompare approach.
 * This should even speed up sorting when there is only ASCII data.
 *
 * Please note that the cache must be invalidated and rebuild whenever the underlying
 * data changes (e.g. Rows added/removed). The plugin does not check this, user has to invalidate here.
 * Thus this will only be fast for relatively static data.
 *
 *
 * usage:
 * drop in DT_localesort.js, include script in your html.
 * In initialisation options set `"type": "string-locale-mapped-int"` and `"orderDataType":"string-locale-mapped-int"`
 * for each column that shall benefit from  sorted with this plugin.
 *
 * @summary Sort respecting Locale, caches order, uses integer compare for sorting
 *
 * @author [Andreas Furtner](https://github.com/AFurtner/)
 *
 * @example
 * ´´´
 * $('#example').dataTable( {
 *				 "columnDefs": [ {
 *								 'searchable':true,
 *								 "type": "string-locale-mapped-int",
 *								 "orderDataType":"string-locale-mapped-int",
 *								 "targets": [ 0, 1 ]
 *				 } ],
 *				 "stringLocaleMapped": { "caseInsensitive": false }, // optional, default is to order case insensitive, set to false if sort shall respect case
 * });
 *
 * // after adding/ modifing rows which must be reflected in a different sort order:
 * $('#example').api().invalidateStringLocaleSortCache() // will recalc caches on next sort
 * // or
 * $('#example').api().recalcStringLocaleSortCache() // will recalc caches immediately
 *
 * ´´´
 */
(function(){
  var factory = function( $, DataTable ) {
    "use strict";

    // invalidate lookup maps for all registered columns, will recalculate caches on next sort (normally executed on next draw())
    $.fn.dataTable.Api.register( 'invalidateStringLocaleMappedCache()', function () {
      this.iterator( 'table', function ( context ) {
        context.stringLocaleMapped.cache = [];
      } );

      return this;
    } );

    // invalidate lookup maps for all registered columns
    $.fn.dataTable.Api.register( 'recalcStringLocaleMappedCache()', function () {
      this.iterator( 'table', function ( context ) {
        context.stringLocaleMapped.cache = [];
        context.aoColumns.forEach( function(col, colIdx) {
          if (col.bSortable && col.sSortDataType == "string-locale-mapped-int") {
            buildStringLocaleMappedIntColumn(context, colIdx, context.oInstance.api().column(colIdx, {order: 'index'}).data())
          }
        });
      } );

      return this;
    } );

    function init(settings) {
      // this serves as a container for our internal stuff
      settings.stringLocaleMapped = {};

      var myOptions = settings.oInit.stringLocaleMapped;
      var haveOptions = myOptions != undefined;
      // always set this option
      settings.stringLocaleMapped.caseInsensitive = haveOptions && myOptions.caseInsensitive === true;
      if (haveOptions && myOptions.locale != undefined && typeof myOptions.locale === "string") {
        settings.stringLocaleMapped.locale = myOptions.locale;
      } else {
        settings.stringLocaleMapped.locale = (navigator.language || navigator.browserLanguage).split('-')[0];
      }
      settings.stringLocaleMapped.cache = [];
    }

    function getSortColumnData(tableInstance, settings, colIdx) {
      if (! settings.stringLocaleMapped) {
        init(settings);
      }
      if (! settings.stringLocaleMapped.cache[colIdx] ) {

        return buildStringLocaleMappedIntColumn(settings, colIdx, tableInstance.api().column(colIdx, {order: 'index'}).data());
      }
      return settings.stringLocaleMapped.cache[colIdx];
    }

    /*
     * fast table sorting relies on this cache sorter which maps
     * column cell indexes to (locale respecting) sort positions once!
     * So the expensive localeCompare is not done on each dataTable sort
     * which uses much faster integer comparison afterwards, as long as caches are not invalidated
     */
    function buildStringLocaleMappedIntColumn(context, col, colData){
      function onlyAsciiChars(elem) {
        for (var i = 0; i < elem.length; ++i) {
          // true when 7Bit characters only,  not sure this works for all code points?!
          if ((0xFF80 & elem.charCodeAt(i)) != 0) { return false; }
        }
        return true;
      }

      var tmpMap = colData.map(function (elem, i) {
        var asciiOnly = onlyAsciiChars(elem);
        return {
          "index":i,
          "data": context.stringLocaleMapped.caseInsensitive ? elem.toLowerCase() : elem,
          "isAscii" : asciiOnly
        }
      });

      // note: at least in tested browsers Intl.Collator('de').compare always puts "aalähnlich" before "Aalangel" even with its caseFirst option is set to "upper"
      var coll = Intl != undefined && Intl.Collator != undefined ?
            new Intl.Collator(context.stringLocaleMapped.locale)
          : { compare: function(x,y) { x.localeCompare(y); } };

      // EXPENSIVE sort, we want to use the locale compare only once per column, and not
      // in per DataTable.sort() (which internally uses Array.sort(): O(n*log(n)) for merge/quick sort)
      tmpMap.sort(function (x, y) {

        if (x.isAscii && y.isAscii) {
          return x.data < y.data ? -1 : x.data > y.data ? 1 : 0;
        } else {
          return coll.compare(x.data, y.data);
        }
      });

      context.stringLocaleMapped.cache[col]= new Array(tmpMap.length);
      var colToOrderPos = context.stringLocaleMapped.cache[col];

      colToOrderPos[ tmpMap[0].index ] = 0; // factored out 1st entry to take array index i-1 check out of the loop
      var idxForSameContent = 0;

      for (var i = 1; i< tmpMap.length; i++) {
        if (tmpMap[i].data == tmpMap[i-1].data) {
          // if elems equal, keep sort index equal to make multi column sort possible later
          colToOrderPos[ tmpMap[i].index ] = idxForSameContent;
        } else {
          // default
          colToOrderPos[ tmpMap[i].index ] = i;
          idxForSameContent = i;
        }
      }

      return colToOrderPos;
    }

    $.extend( DataTable.ext.order, {
      "string-locale-mapped-int": function (settings, colIdx) {
        return getSortColumnData(this, settings, colIdx);
      }
    });

    $.extend( DataTable.ext.type.order, {

      /*
       * we need this 'formatter', so that DataTable does not try to use the fallback string-asc|desc compare functions one of the following locale compares
       * for each sorting but instead uses integer comparison on the pre-computed numbers from above
       */
      "string-locale-mapped-int-pre": function (a) { return a; }
      //,
      /*
       * this is would be the naive approach for locale sorting: the two functions would be called for every compare in the inner sort loop in dataTable._fnSort()!
       * --> really slow!
       */
      //"string-locale-asc": function (x, y) { return x.localeCompare(y); },
      //"string-locale-desc": function (x, y) { return y.localeCompare(x); }
    });

  }; // /factory

// Define as an AMD module if possible
  if ( typeof define === 'function' && define.amd ) {
    define( ['jquery', 'datatables'], factory );
  }
  else if ( typeof exports === 'object' ) {
    // Node/CommonJS
    factory( require('jquery'), require('datatables') );
  }
  else if ( jQuery ) {
    // Otherwise simply initialise as normal, stopping multiple evaluation
    factory( jQuery, jQuery.fn.dataTable );
  }

})();
<commit_msg>FIX DT api registration returns the iterator now, returned this before making chaining of api functions impossible<commit_after>/**
 * jQuery DataTables sort plugin
 * Sorting Strings respecting locale.
 * Performance optimized for large static data sets.
 * Requires DataTables 1.10 or newer.
 *
 * In non english languages Javascript Array.sort using String.compare() gives false order,
 * e.g in German correct sort order is: Arzt, Ärzte, Ast, Baum, Zeder
 * in contrast to Arzt, Ast, Baum, Zeder, Ärzte as in English/ASCII string sort
 *
 * Since String.localeCompare has extremely bad performance this uses the following approach:
 * Sort ONCE the assigend columns using String.localeCompare (or String compare when sufficient), then cache a map of each cell to the position in the ordered list.
 * Later DataTables.api().sort() just uses the position index and can then take advantage of much faster integer comparison.
 * For large data sets this can be more than 100 times faster than the naive localeCompare approach.
 * This should even speed up sorting when there is only ASCII data.
 *
 * Please note that the cache must be invalidated and rebuild whenever the underlying
 * data changes (e.g. Rows added/removed). The plugin does not check this, user has to invalidate here.
 * Thus this will only be fast for relatively static data.
 *
 *
 * usage:
 * drop in DT_localesort.js, include script in your html.
 * In initialisation options set `"type": "string-locale-mapped-int"` and `"orderDataType":"string-locale-mapped-int"`
 * for each column that shall benefit from  sorted with this plugin.
 *
 * @summary Sort respecting Locale, caches order, uses integer compare for sorting
 *
 * @author [Andreas Furtner](https://github.com/AFurtner/)
 *
 * @example
 * ´´´
 * $('#example').dataTable( {
 *				 "columnDefs": [ {
 *								 'searchable':true,
 *								 "type": "string-locale-mapped-int",
 *								 "orderDataType":"string-locale-mapped-int",
 *								 "targets": [ 0, 1 ]
 *				 } ],
 *				 "stringLocaleMapped": { "caseInsensitive": false }, // optional, default is to order case insensitive, set to false if sort shall respect case
 * });
 *
 * // after adding/ modifing rows which must be reflected in a different sort order:
 * $('#example').api().invalidateStringLocaleSortCache() // will recalc caches on next sort
 * // or
 * $('#example').api().recalcStringLocaleSortCache() // will recalc caches immediately
 *
 * ´´´
 */
(function(){
  var factory = function( $, DataTable ) {
    "use strict";

    // invalidate lookup maps for all registered columns, will recalculate caches on next sort (normally executed on next draw())
    $.fn.dataTable.Api.register( 'invalidateStringLocaleMappedCache()', function () {
      return this.iterator( 'table', function ( context ) {
        context.stringLocaleMapped.cache = [];
      } );
    } );

    // invalidate lookup maps for all registered columns
    $.fn.dataTable.Api.register( 'recalcStringLocaleMappedCache()', function () {
      return this.iterator( 'table', function ( context ) {
        context.stringLocaleMapped.cache = [];
        context.aoColumns.forEach( function(col, colIdx) {
          if (col.bSortable && col.sSortDataType == "string-locale-mapped-int") {
            buildStringLocaleMappedIntColumn(context, colIdx, context.oInstance.api().column(colIdx, {order: 'index'}).data())
          }
        });
      } );
    } );

    function init(settings) {
      // this serves as a container for our internal stuff
      settings.stringLocaleMapped = {};

      var myOptions = settings.oInit.stringLocaleMapped;
      var haveOptions = myOptions != undefined;
      // always set this option
      settings.stringLocaleMapped.caseInsensitive = haveOptions && myOptions.caseInsensitive === true;
      if (haveOptions && myOptions.locale != undefined && typeof myOptions.locale === "string") {
        settings.stringLocaleMapped.locale = myOptions.locale;
      } else {
        settings.stringLocaleMapped.locale = (navigator.language || navigator.browserLanguage).split('-')[0];
      }
      settings.stringLocaleMapped.cache = [];
    }

    function getSortColumnData(tableInstance, settings, colIdx) {
      if (! settings.stringLocaleMapped) {
        init(settings);
      }
      if (! settings.stringLocaleMapped.cache[colIdx] ) {

        return buildStringLocaleMappedIntColumn(settings, colIdx, tableInstance.api().column(colIdx, {order: 'index'}).data());
      }
      return settings.stringLocaleMapped.cache[colIdx];
    }

    /*
     * fast table sorting relies on this cache sorter which maps
     * column cell indexes to (locale respecting) sort positions once!
     * So the expensive localeCompare is not done on each dataTable sort
     * which uses much faster integer comparison afterwards, as long as caches are not invalidated
     */
    function buildStringLocaleMappedIntColumn(context, col, colData){
      function onlyAsciiChars(elem) {
        for (var i = 0; i < elem.length; ++i) {
          // true when 7Bit characters only,  not sure this works for all code points?!
          if ((0xFF80 & elem.charCodeAt(i)) != 0) { return false; }
        }
        return true;
      }

      var tmpMap = colData.map(function (elem, i) {
        var asciiOnly = onlyAsciiChars(elem);
        return {
          "index":i,
          "data": context.stringLocaleMapped.caseInsensitive ? elem.toLowerCase() : elem,
          "isAscii" : asciiOnly
        }
      });

      // note: at least in tested browsers Intl.Collator('de').compare always puts "aalähnlich" before "Aalangel" even with its caseFirst option is set to "upper"
      var coll = Intl != undefined && Intl.Collator != undefined ?
            new Intl.Collator(context.stringLocaleMapped.locale)
          : { compare: function(x,y) { x.localeCompare(y); } };

      // EXPENSIVE sort, we want to use the locale compare only once per column, and not
      // in per DataTable.sort() (which internally uses Array.sort(): O(n*log(n)) for merge/quick sort)
      tmpMap.sort(function (x, y) {

        if (x.isAscii && y.isAscii) {
          return x.data < y.data ? -1 : x.data > y.data ? 1 : 0;
        } else {
          return coll.compare(x.data, y.data);
        }
      });

      context.stringLocaleMapped.cache[col]= new Array(tmpMap.length);
      var colToOrderPos = context.stringLocaleMapped.cache[col];

      colToOrderPos[ tmpMap[0].index ] = 0; // factored out 1st entry to take array index i-1 check out of the loop
      var idxForSameContent = 0;

      for (var i = 1; i< tmpMap.length; i++) {
        if (tmpMap[i].data == tmpMap[i-1].data) {
          // if elems equal, keep sort index equal to make multi column sort possible later
          colToOrderPos[ tmpMap[i].index ] = idxForSameContent;
        } else {
          // default
          colToOrderPos[ tmpMap[i].index ] = i;
          idxForSameContent = i;
        }
      }

      return colToOrderPos;
    }

    $.extend( DataTable.ext.order, {
      "string-locale-mapped-int": function (settings, colIdx) {
        return getSortColumnData(this, settings, colIdx);
      }
    });

    $.extend( DataTable.ext.type.order, {

      /*
       * we need this 'formatter', so that DataTable does not try to use the fallback string-asc|desc compare functions one of the following locale compares
       * for each sorting but instead uses integer comparison on the pre-computed numbers from above
       */
      "string-locale-mapped-int-pre": function (a) { return a; }
      //,
      /*
       * this is would be the naive approach for locale sorting: the two functions would be called for every compare in the inner sort loop in dataTable._fnSort()!
       * --> really slow!
       */
      //"string-locale-asc": function (x, y) { return x.localeCompare(y); },
      //"string-locale-desc": function (x, y) { return y.localeCompare(x); }
    });

  }; // /factory

// Define as an AMD module if possible
  if ( typeof define === 'function' && define.amd ) {
    define( ['jquery', 'datatables'], factory );
  }
  else if ( typeof exports === 'object' ) {
    // Node/CommonJS
    factory( require('jquery'), require('datatables') );
  }
  else if ( jQuery ) {
    // Otherwise simply initialise as normal, stopping multiple evaluation
    factory( jQuery, jQuery.fn.dataTable );
  }

})();
<|endoftext|>


<commit_before>// Copyright 2016-2017, University of Colorado Boulder

/**
 * Spinner for numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Andrea Lin (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Input = require( 'SCENERY/input/Input' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );

  // possible values for options.arrowsPosition
  var ARROWS_POSITION_VALUES = [
    'leftRight', // arrow buttons on left and right of value
    'topBottom', // arrow buttons on top and bottom of value
    'bothRight', // both arrow buttons to the right of the value
    'bothBottom' // both arrow buttons below the value
  ];

  // possible values for options.valueAlign
  var VALUE_ALIGN_VALUES = [ 'center', 'left', 'right' ];

  /**
   * @param {Property.<number>} numberProperty value, must be an integer
   * @param {Property.<Range>} rangeProperty - Dynamic range of values, min and max must be integers
   * @param {Object} [options]
   * @constructor
   */
  function NumberSpinner( numberProperty, rangeProperty, options ) {

    assert && assert( rangeProperty.value.contains( numberProperty.get() ),
      'value ' + numberProperty.get() + ' is out of range ' + rangeProperty.value.toString() );

    options = _.extend( {
      enabledProperty: new Property( true ),

      // {string} where to place the arrow buttons, see ARROWS_POSITION_VALUES
      arrowsPosition: 'bothRight',

      // {number|null} By default, arrows are scaled to fit dimensions of value background. This is an additional scale factor.
      arrowsScale: null,
      arrowButtonFill: 'white',
      arrowButtonStroke: 'black',
      arrowButtonLineWidth: 1,

      valuePattern: '{0}',
      decimalPlaces: 0,
      deltaValue: 1,
      font: new PhetFont( 28 ),

      // {string} alignment for value, see VALUE_ALIGN_VALUES
      valueAlign: 'center',
      xSpacing: 5,
      ySpacing: 3,

      // background node
      xMargin: 5,
      yMargin: 3,
      cornerRadius: 5,
      backgroundMinWidth: 0,
      backgroundFill: 'white',
      backgroundStroke: 'black',
      backgroundLineWidth: 1,

      // arrow button pointer areas
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0,

      // a11y
      tagName: 'input',
      inputType: 'number',
      inputValue: numberProperty.get()
    }, options );

    // validate options
    assert && assert( _.includes( ARROWS_POSITION_VALUES, options.arrowsPosition ), 'invalid arrowsPosition: ' + options.arrowsPosition );
    assert && assert( _.includes( VALUE_ALIGN_VALUES, options.valueAlign ), 'invalid valueAlign: ' + options.valueAlign );
    Tandem.indicateUninstrumentedCode();

    var self = this;

    var valueOptions = {
      font: options.font,
      fill: 'black'
    };

    // compute max width of the value that's going to be displayed
    var minString = StringUtils.format( options.valuePattern, Util.toFixed( rangeProperty.value.min, options.decimalPlaces ) );
    var maxString = StringUtils.format( options.valuePattern, Util.toFixed( rangeProperty.value.max, options.decimalPlaces ) );
    var maxWidth = Math.max(
      new Text( minString, valueOptions ).width,
      new Text( maxString, valueOptions ).width
    );

    // number
    var numberNode = new Text( numberProperty.get(), valueOptions );

    // compute the size of the background
    var backgroundWidth = Math.max( maxWidth + 2 * options.xMargin, options.backgroundMinWidth );
    var backgroundHeight = numberNode.height + ( 2 * options.yMargin );

    // background for displaying the value
    var backgroundNode = new Rectangle( 0, 0, backgroundWidth, backgroundHeight,
      options.cornerRadius, options.cornerRadius, {
        fill: options.backgroundFill,
        stroke: options.backgroundStroke,
        lineWidth: options.backgroundLineWidth
      } );
    numberNode.center = backgroundNode.center;
    var valueParent = new Node( { children: [ backgroundNode, numberNode ] } );

    // buttons
    var arrowButtonOptions = {
      baseColor: options.arrowButtonFill,
      stroke: options.arrowButtonStroke,
      lineWidth: options.arrowButtonLineWidth
    };

    // increment button
    var incrementFunction = function() {
      numberProperty.set( numberProperty.get() + options.deltaValue );
    };
    var incrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'up' : 'right';
    var incrementButton = new ArrowButton( incrementDirection, incrementFunction, arrowButtonOptions );
    incrementButton.focusable = false;

    // decrement button
    var decrementFunction = function() {
      numberProperty.set( numberProperty.get() - options.deltaValue );
    };
    var decrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'down' : 'left';
    var decrementButton = new ArrowButton( decrementDirection, decrementFunction, arrowButtonOptions );
    decrementButton.focusable = false;

    // arrow button scaling
    var arrowsScale;
    if ( !arrowsScale ) {
      if ( options.arrowsPosition === 'leftRight' ) {
        arrowsScale = valueParent.height / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        arrowsScale = valueParent.width / incrementButton.width;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        arrowsScale = ( ( valueParent.height / 2 ) - ( options.ySpacing / 2 ) ) / incrementButton.height;
      }
      else { // 'bothBottom'
        arrowsScale = ( ( valueParent.width / 2 ) - ( options.xSpacing / 2 ) ) / incrementButton.width;
      }
    }
    if ( options.arrowsScale ) {
      arrowsScale = arrowsScale * options.arrowsScale;
    }
    incrementButton.setScaleMagnitude( arrowsScale );
    decrementButton.setScaleMagnitude( arrowsScale );

    // layout
    if ( options.arrowsPosition === 'leftRight' ) {
      incrementButton.left = valueParent.right + options.xSpacing;
      decrementButton.right = valueParent.left - options.xSpacing;
      incrementButton.centerY = decrementButton.centerY = valueParent.centerY;
    }
    else if ( options.arrowsPosition === 'topBottom' ) {
      incrementButton.centerX = decrementButton.centerX = valueParent.centerX;
      incrementButton.bottom = valueParent.top - options.ySpacing;
      decrementButton.top = valueParent.bottom + options.ySpacing;
    }
    else if ( options.arrowsPosition === 'bothRight' ) {
      incrementButton.left = decrementButton.left = valueParent.right + options.xSpacing;
      incrementButton.bottom = valueParent.centerY - ( options.ySpacing / 2 );
      decrementButton.top = valueParent.centerY + ( options.ySpacing / 2 );
    }
    else { // 'bothBottom'
      incrementButton.left = valueParent.centerX + ( options.xSpacing / 2 );
      decrementButton.right = valueParent.centerX - ( options.xSpacing / 2 );
      incrementButton.top = decrementButton.top = valueParent.bottom + options.ySpacing;
    }

    // touch areas
    if ( options.touchAreaXDilation || options.touchAreaYDilation ) {

      incrementButton.touchArea = incrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );
      decrementButton.touchArea = decrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedY( -options.touchAreaYDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedY( options.touchAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedX( options.touchAreaXDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedX( -options.touchAreaXDilation );
      }
    }

    // mouse areas
    if ( options.mouseAreaXDilation || options.mouseAreaYDilation ) {

      incrementButton.mouseArea = incrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );
      decrementButton.mouseArea = decrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedY( -options.mouseAreaYDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedY( options.mouseAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedX( options.mouseAreaXDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedX( -options.mouseAreaXDilation );
      }
    }

    assert && assert( !options.children, 'decoration not supported' );
    options.children = [ valueParent, incrementButton, decrementButton ];

    Node.call( this, options );

    var updateEnabled = function() {
      // enable/disable arrow buttons
      incrementButton.enabled = ( ( numberProperty.value + options.deltaValue ) <= rangeProperty.value.max );
      decrementButton.enabled = ( ( numberProperty.value - options.deltaValue ) >= rangeProperty.value.min );
    };

    // synchronize with number value
    var numberPropertyObserver = function( value ) {
      assert && assert( rangeProperty.value.contains( value ), 'value out of range: ' + value );

      // update the number and align it
      numberNode.text = StringUtils.format( options.valuePattern, Util.toFixed( value, options.decimalPlaces ) );
      switch( options.valueAlign ) {
        case 'center':
          numberNode.center = backgroundNode.center;
          break;
        case 'left':
          numberNode.left = backgroundNode.left + options.xMargin;
          break;
        case 'right':
          numberNode.right = backgroundNode.right - options.xMargin;
          break;
        default:
          throw new Error( 'invalid valueAlign: ' + options.valueAlign );
      }

      updateEnabled();
    };
    numberProperty.link( numberPropertyObserver ); // must be unlinked in dispose

    // Dynamic range changes, see https://github.com/phetsims/scenery-phet/issues/305
    var rangeObserver = function( range ) {
      // If our value is outside our new range, adjust it to be within the range.
      numberProperty.value = range.constrainValue( numberProperty.value );

      // Range changes may change whether the buttons are enabled
      updateEnabled();
    };
    rangeProperty.link( rangeObserver );

    // enable or disable this component
    this.enabledProperty = options.enabledProperty; // @public
    var enabledPropertyObserver = function( enabled ) {
      self.pickable = enabled;
      self.opacity = enabled ? 1 : 0.5;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledPropertyObserver );

    // a11y input listener for keyboard nav
    this.addAccessibleInputListener( {
      keydown: function( event ) {

        // prevent user from changing value with number or the space keys
        if ( Input.isNumberKey( event.keyCode ) || event.keyCode === Input.KEY_SPACE ) {
          event.preventDefault();
        }
      },
      input: function( event ) {

        // if input value is empty, inputValue was cleared by the browser after focus, don't update the Property value
        if ( self.inputValue && self.enabledProperty.get() ) {
          if ( self.inputValue > numberProperty.get() && self.inputValue <= rangeProperty.get().max ) {

            // user intended to increment
            incrementFunction();
          }
          else if ( self.inputValue < numberProperty.get() && self.inputValue >= rangeProperty.get().min ) {

            // user intended to decrement
            decrementFunction();
          }
        }
        self.inputValue = numberProperty.get();
      }
    } );

    // @private
    this.disposeNumberSpinner = function() {
      numberProperty.unlink( numberPropertyObserver );
      rangeProperty.unlink( rangeObserver );
      self.enabledProperty.unlink( enabledPropertyObserver );
    };
  }

  sun.register( 'NumberSpinner', NumberSpinner );

  return inherit( Node, NumberSpinner, {

    // @public Ensures that this node is eligible for GC.
    dispose: function() {
      this.disposeNumberSpinner();
      Node.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }
  } );
} );
<commit_msg>use options not setters, https://github.com/phetsims/sun/issues/325<commit_after>// Copyright 2016-2017, University of Colorado Boulder

/**
 * Spinner for numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Andrea Lin (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Input = require( 'SCENERY/input/Input' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );

  // possible values for options.arrowsPosition
  var ARROWS_POSITION_VALUES = [
    'leftRight', // arrow buttons on left and right of value
    'topBottom', // arrow buttons on top and bottom of value
    'bothRight', // both arrow buttons to the right of the value
    'bothBottom' // both arrow buttons below the value
  ];

  // possible values for options.valueAlign
  var VALUE_ALIGN_VALUES = [ 'center', 'left', 'right' ];

  /**
   * @param {Property.<number>} numberProperty value, must be an integer
   * @param {Property.<Range>} rangeProperty - Dynamic range of values, min and max must be integers
   * @param {Object} [options]
   * @constructor
   */
  function NumberSpinner( numberProperty, rangeProperty, options ) {

    assert && assert( rangeProperty.value.contains( numberProperty.get() ),
      'value ' + numberProperty.get() + ' is out of range ' + rangeProperty.value.toString() );

    options = _.extend( {
      enabledProperty: new Property( true ),

      // {string} where to place the arrow buttons, see ARROWS_POSITION_VALUES
      arrowsPosition: 'bothRight',

      // {number|null} By default, arrows are scaled to fit dimensions of value background. This is an additional scale factor.
      arrowsScale: null,
      arrowButtonFill: 'white',
      arrowButtonStroke: 'black',
      arrowButtonLineWidth: 1,

      valuePattern: '{0}',
      decimalPlaces: 0,
      deltaValue: 1,
      font: new PhetFont( 28 ),

      // {string} alignment for value, see VALUE_ALIGN_VALUES
      valueAlign: 'center',
      xSpacing: 5,
      ySpacing: 3,

      // background node
      xMargin: 5,
      yMargin: 3,
      cornerRadius: 5,
      backgroundMinWidth: 0,
      backgroundFill: 'white',
      backgroundStroke: 'black',
      backgroundLineWidth: 1,

      // arrow button pointer areas
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0,

      // a11y
      tagName: 'input',
      inputType: 'number',
      inputValue: numberProperty.get()
    }, options );

    // validate options
    assert && assert( _.includes( ARROWS_POSITION_VALUES, options.arrowsPosition ), 'invalid arrowsPosition: ' + options.arrowsPosition );
    assert && assert( _.includes( VALUE_ALIGN_VALUES, options.valueAlign ), 'invalid valueAlign: ' + options.valueAlign );
    Tandem.indicateUninstrumentedCode();

    var self = this;

    var valueOptions = {
      font: options.font,
      fill: 'black'
    };

    // compute max width of the value that's going to be displayed
    var minString = StringUtils.format( options.valuePattern, Util.toFixed( rangeProperty.value.min, options.decimalPlaces ) );
    var maxString = StringUtils.format( options.valuePattern, Util.toFixed( rangeProperty.value.max, options.decimalPlaces ) );
    var maxWidth = Math.max(
      new Text( minString, valueOptions ).width,
      new Text( maxString, valueOptions ).width
    );

    // number
    var numberNode = new Text( numberProperty.get(), valueOptions );

    // compute the size of the background
    var backgroundWidth = Math.max( maxWidth + 2 * options.xMargin, options.backgroundMinWidth );
    var backgroundHeight = numberNode.height + ( 2 * options.yMargin );

    // background for displaying the value
    var backgroundNode = new Rectangle( 0, 0, backgroundWidth, backgroundHeight,
      options.cornerRadius, options.cornerRadius, {
        fill: options.backgroundFill,
        stroke: options.backgroundStroke,
        lineWidth: options.backgroundLineWidth
      } );
    numberNode.center = backgroundNode.center;
    var valueParent = new Node( { children: [ backgroundNode, numberNode ] } );

    // buttons
    var arrowButtonOptions = {
      baseColor: options.arrowButtonFill,
      stroke: options.arrowButtonStroke,
      lineWidth: options.arrowButtonLineWidth,
      focusable: false
    };

    // increment button
    var incrementFunction = function() {
      numberProperty.set( numberProperty.get() + options.deltaValue );
    };
    var incrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'up' : 'right';
    var incrementButton = new ArrowButton( incrementDirection, incrementFunction, arrowButtonOptions );

    // decrement button
    var decrementFunction = function() {
      numberProperty.set( numberProperty.get() - options.deltaValue );
    };
    var decrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'down' : 'left';
    var decrementButton = new ArrowButton( decrementDirection, decrementFunction, arrowButtonOptions );

    // arrow button scaling
    var arrowsScale;
    if ( !arrowsScale ) {
      if ( options.arrowsPosition === 'leftRight' ) {
        arrowsScale = valueParent.height / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        arrowsScale = valueParent.width / incrementButton.width;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        arrowsScale = ( ( valueParent.height / 2 ) - ( options.ySpacing / 2 ) ) / incrementButton.height;
      }
      else { // 'bothBottom'
        arrowsScale = ( ( valueParent.width / 2 ) - ( options.xSpacing / 2 ) ) / incrementButton.width;
      }
    }
    if ( options.arrowsScale ) {
      arrowsScale = arrowsScale * options.arrowsScale;
    }
    incrementButton.setScaleMagnitude( arrowsScale );
    decrementButton.setScaleMagnitude( arrowsScale );

    // layout
    if ( options.arrowsPosition === 'leftRight' ) {
      incrementButton.left = valueParent.right + options.xSpacing;
      decrementButton.right = valueParent.left - options.xSpacing;
      incrementButton.centerY = decrementButton.centerY = valueParent.centerY;
    }
    else if ( options.arrowsPosition === 'topBottom' ) {
      incrementButton.centerX = decrementButton.centerX = valueParent.centerX;
      incrementButton.bottom = valueParent.top - options.ySpacing;
      decrementButton.top = valueParent.bottom + options.ySpacing;
    }
    else if ( options.arrowsPosition === 'bothRight' ) {
      incrementButton.left = decrementButton.left = valueParent.right + options.xSpacing;
      incrementButton.bottom = valueParent.centerY - ( options.ySpacing / 2 );
      decrementButton.top = valueParent.centerY + ( options.ySpacing / 2 );
    }
    else { // 'bothBottom'
      incrementButton.left = valueParent.centerX + ( options.xSpacing / 2 );
      decrementButton.right = valueParent.centerX - ( options.xSpacing / 2 );
      incrementButton.top = decrementButton.top = valueParent.bottom + options.ySpacing;
    }

    // touch areas
    if ( options.touchAreaXDilation || options.touchAreaYDilation ) {

      incrementButton.touchArea = incrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );
      decrementButton.touchArea = decrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedY( -options.touchAreaYDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedY( options.touchAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedX( options.touchAreaXDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedX( -options.touchAreaXDilation );
      }
    }

    // mouse areas
    if ( options.mouseAreaXDilation || options.mouseAreaYDilation ) {

      incrementButton.mouseArea = incrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );
      decrementButton.mouseArea = decrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedY( -options.mouseAreaYDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedY( options.mouseAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedX( options.mouseAreaXDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedX( -options.mouseAreaXDilation );
      }
    }

    assert && assert( !options.children, 'decoration not supported' );
    options.children = [ valueParent, incrementButton, decrementButton ];

    Node.call( this, options );

    var updateEnabled = function() {
      // enable/disable arrow buttons
      incrementButton.enabled = ( ( numberProperty.value + options.deltaValue ) <= rangeProperty.value.max );
      decrementButton.enabled = ( ( numberProperty.value - options.deltaValue ) >= rangeProperty.value.min );
    };

    // synchronize with number value
    var numberPropertyObserver = function( value ) {
      assert && assert( rangeProperty.value.contains( value ), 'value out of range: ' + value );

      // update the number and align it
      numberNode.text = StringUtils.format( options.valuePattern, Util.toFixed( value, options.decimalPlaces ) );
      switch( options.valueAlign ) {
        case 'center':
          numberNode.center = backgroundNode.center;
          break;
        case 'left':
          numberNode.left = backgroundNode.left + options.xMargin;
          break;
        case 'right':
          numberNode.right = backgroundNode.right - options.xMargin;
          break;
        default:
          throw new Error( 'invalid valueAlign: ' + options.valueAlign );
      }

      updateEnabled();
    };
    numberProperty.link( numberPropertyObserver ); // must be unlinked in dispose

    // Dynamic range changes, see https://github.com/phetsims/scenery-phet/issues/305
    var rangeObserver = function( range ) {
      // If our value is outside our new range, adjust it to be within the range.
      numberProperty.value = range.constrainValue( numberProperty.value );

      // Range changes may change whether the buttons are enabled
      updateEnabled();
    };
    rangeProperty.link( rangeObserver );

    // enable or disable this component
    this.enabledProperty = options.enabledProperty; // @public
    var enabledPropertyObserver = function( enabled ) {
      self.pickable = enabled;
      self.opacity = enabled ? 1 : 0.5;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledPropertyObserver );

    // a11y input listener for keyboard nav
    this.addAccessibleInputListener( {
      keydown: function( event ) {

        // prevent user from changing value with number or the space keys
        if ( Input.isNumberKey( event.keyCode ) || event.keyCode === Input.KEY_SPACE ) {
          event.preventDefault();
        }
      },
      input: function( event ) {

        // if input value is empty, inputValue was cleared by the browser after focus, don't update the Property value
        if ( self.inputValue && self.enabledProperty.get() ) {
          if ( self.inputValue > numberProperty.get() && self.inputValue <= rangeProperty.get().max ) {

            // user intended to increment
            incrementFunction();
          }
          else if ( self.inputValue < numberProperty.get() && self.inputValue >= rangeProperty.get().min ) {

            // user intended to decrement
            decrementFunction();
          }
        }
        self.inputValue = numberProperty.get();
      }
    } );

    // @private
    this.disposeNumberSpinner = function() {
      numberProperty.unlink( numberPropertyObserver );
      rangeProperty.unlink( rangeObserver );
      self.enabledProperty.unlink( enabledPropertyObserver );
    };
  }

  sun.register( 'NumberSpinner', NumberSpinner );

  return inherit( Node, NumberSpinner, {

    // @public Ensures that this node is eligible for GC.
    dispose: function() {
      this.disposeNumberSpinner();
      Node.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }
  } );
} );
<|endoftext|>