<commit_before>var Mobify = window.Mobify = window.Mobify || {};
Mobify.$ = Mobify.$ || window.Zepto || window.jQuery;
Mobify.UI = Mobify.UI || {};

(function($, document) {
    $.support = $.support || {};

    $.extend($.support, {
        'touch': 'ontouchend' in document
    });

})($, document);

/**
    @module Holds common functions relating to UI.
*/
Mobify.UI.Utils = (function($) {
    var exports = {}
        , has = $.support;
    /**
        Events (either touch or mouse)
    */
    exports.events = (has.touch)
        ? {down: 'touchstart', move: 'touchmove', up: 'touchend'}
        : {down: 'mousedown', move: 'mousemove', up: 'mouseup'};

    /**
        Returns the position of a mouse or touch event in (x, y)
        @function
        @param {Event} touch or mouse event
        @returns {Object} X and Y coordinates
    */
    exports.getCursorPosition = (has.touch)
        ? function(e) {e = e.originalEvent || e; return {x: e.touches[0].clientX, y: e.touches[0].clientY}}
        : function(e) {return {x: e.clientX, y: e.clientY}};

    /**
        Returns prefix property for current browser.
        @param {String} CSS Property Name
        @return {String} Detected CSS Property Name
    */
    exports.getProperty = function(name) {
        var prefixes = ['Webkit', 'Moz', 'O', 'ms', '']
          , testStyle = document.createElement('div').style;

        for (var i = 0; i < prefixes.length; ++i) {
            if (testStyle[prefixes[i] + name] !== undefined) {
                return prefixes[i] + name;
            }
        }

        // Not Supported
        return;
    };

    // determine which transition event to use
    function whichTransitionEvent(){
        // http://stackoverflow.com/questions/5023514/how-do-i-normalize-css3-transition-functions-across-browsers
        // hack for ios 3.1.* because of poor transition support.
        if (/iPhone\ OS\ 3_1/.test(navigator.userAgent)) {
            return undefined;
        }

        var el = document.createElement('fakeelement');
        var transitions = {
            'transition':'transitionEnd',
            'OTransition':'oTransitionEnd',
            'MSTransition':'msTransitionEnd',
            'MozTransition':'transitionend',
            'WebkitTransition':'webkitTransitionEnd'
        }

        var t;
        for(t in transitions){
            if( el.style[t] !== undefined ){
                return transitions[t];
            }
        }
        return;
    };

    $.extend(exports.events, {
        'transitionend': whichTransitionEvent()
     });

    return exports;

})(Mobify.$);

Mobify.UI.Accordion = (function($, Utils) {

    var has = $.support;

    // Constructor
    var Accordion = function(element) {
        this.element = element;
        this.$element = $(element);
        this.dragRadius = 10;
        this.bind();
    };

    Accordion.prototype.bind = function() {
        var $element = this.$element
            , xy
            , dxy
            , dragRadius = this.dragRadius;

        function endTransition(){
            // transition attached to .content elements, use parent to grab .item
            var $item = $(this).parent();

            // if the transition is ending
            if ($item.hasClass('m-closed')) $(this).parent().removeClass('m-active');

            // recalculate proper height
            var height = 0;
            $('.m-item', $element).each(function(index) {
                var $item = $(this);
                height += $item.height();
            });
            $element.css('min-height', height + 'px');
        };

        function close($item) {
            // toggle opened and closed classes
            $item.removeClass('m-opened');
            $item.addClass('m-closed');

            // toggle active class on close only if there is no transition support
            if(!Utils.events.transitionend) $item.removeClass('m-active');

            // set max-height to 0 upon close
            $item.find('.m-content').css('max-height', 0)
        };

        function open($item) {
            var $content = $item.find('.m-content');
            $item.addClass('m-active');
            $item.removeClass('m-closed');
            $item.addClass('m-opened')

            // determine which height function to use (outerHeight not supported by zepto)
            var contentChildren = $content.children();
            var contentHeight = ('outerHeight' in contentChildren) ? contentChildren['outerHeight']() : contentChildren['height']();
            $content.css('max-height', contentHeight * 1.5 +'px');

            // if transitions are supported, minimize browser reflow by adding the height
            // of the to-be expanded content element to the height of the entire accordion
            if (Utils.events.transitionend) {
                $element.css('min-height', $element.height() + contentHeight + 'px');
            }
        };

        function down(e) {
            // get initial position on mouse/touch start
            xy = Utils.getCursorPosition(e);
        };

        function move(e) {
            // update position upon move
            dxy = Utils.getCursorPosition(e);
        };

        function up(e) {
            // if there is dragging, do not close/open accordion
            if (dxy) {
                dx = xy.x - dxy.x;
                dy = xy.y - dxy.y;
                dxy = undefined;
                if ((dx*dx) + (dy*dy) > dragRadius*dragRadius) return;
            }

            // close or open item depending on active class
            var $item = $(this).parent();
            if ($item.hasClass('m-active')) {
                close($item);
            }
            else {
                open($item);
            }
        };

        function click(e) {
            e.preventDefault();
        };

        // Auto-open items that are hash linked
        var hash = location.hash;
        var $hashitem = $element.find('.m-header a[href="'+hash+'"]');

        if ($hashitem.length) {
            open($hashitem.parent());
        }

        // bind events
        $element.find('.m-header')
            .on(Utils.events.down, down)
            .on(Utils.events.move, move)
            .on(Utils.events.up, up)
            .on('click', click);
        if (Utils.events.transitionend) {
            $element.find('.m-content').on(Utils.events.transitionend, endTransition);
        }

    };

    Accordion.prototype.unbind = function() {
        this.$element.off();
    }

    Accordion.prototype.destroy = function() {
        this.unbind();
        this.$element.remove();
        this.$element = null;
    }

    return Accordion;

})(Mobify.$, Mobify.UI.Utils);

(function($) {
    $.fn.accordion = function(action) {
        this.each(function () {
            var $this = $(this)
              , accordion = $this._accordion

            if (!accordion) {
                accordion = new Mobify.UI.Accordion(this);
            }

            if (action) {
                accordion[action]();

                if (action === 'destroy') {
                    accordion = null;
                }
            }

            $this._accordion = accordion;
        })

        return this;
    };
})(Mobify.$);
<commit_msg>Fix according by binding all browser-specific transitionend events. Detection mechanism we use to determine which transitionend event to use is broken in Chrome dev build (26) and Firefox 18. Likely a bug to send to browser venders (TODO: Writeup minimal testcase on JSFiddle!)<commit_after>var Mobify = window.Mobify = window.Mobify || {};
Mobify.$ = Mobify.$ || window.Zepto || window.jQuery;
Mobify.UI = Mobify.UI || {};

(function($, document) {
    $.support = $.support || {};

    $.extend($.support, {
        'touch': 'ontouchend' in document
    });

})($, document);

/**
    @module Holds common functions relating to UI.
*/
Mobify.UI.Utils = (function($) {
    var exports = {}
        , has = $.support;
    /**
        Events (either touch or mouse)
    */
    exports.events = (has.touch)
        ? {down: 'touchstart', move: 'touchmove', up: 'touchend'}
        : {down: 'mousedown', move: 'mousemove', up: 'mouseup'};

    /**
        Returns the position of a mouse or touch event in (x, y)
        @function
        @param {Event} touch or mouse event
        @returns {Object} X and Y coordinates
    */
    exports.getCursorPosition = (has.touch)
        ? function(e) {e = e.originalEvent || e; return {x: e.touches[0].clientX, y: e.touches[0].clientY}}
        : function(e) {return {x: e.clientX, y: e.clientY}};

    /**
        Returns prefix property for current browser.
        @param {String} CSS Property Name
        @return {String} Detected CSS Property Name
    */
    exports.getProperty = function(name) {
        var prefixes = ['Webkit', 'Moz', 'O', 'ms', '']
          , testStyle = document.createElement('div').style;

        for (var i = 0; i < prefixes.length; ++i) {
            if (testStyle[prefixes[i] + name] !== undefined) {
                return prefixes[i] + name;
            }
        }

        // Not Supported
        return;
    };

    $.extend(exports.events, {
        'transitionend': [  "transitionEnd",
                            "oTransitionEnd",
                            "msTransitionEnd",
                            "transitionend",
                            "webkitTransitionEnd" ].join(" ")
     });

    return exports;

})(Mobify.$);

Mobify.UI.Accordion = (function($, Utils) {

    var has = $.support;

    // Constructor
    var Accordion = function(element) {
        this.element = element;
        this.$element = $(element);
        this.dragRadius = 10;
        this.bind();
    };

    Accordion.prototype.bind = function() {
        var $element = this.$element
            , xy
            , dxy
            , dragRadius = this.dragRadius;

        function endTransition(){
            // transition attached to .content elements, use parent to grab .item
            var $item = $(this).parent();

            // if the transition is ending
            if ($item.hasClass('m-closed')) $(this).parent().removeClass('m-active');

            // recalculate proper height
            var height = 0;
            $('.m-item', $element).each(function(index) {
                var $item = $(this);
                height += $item.height();
            });
            $element.css('min-height', height + 'px');
        };

        function close($item) {
            // toggle opened and closed classes
            $item.removeClass('m-opened');
            $item.addClass('m-closed');

            // toggle active class on close only if there is no transition support
            if(!Utils.events.transitionend) $item.removeClass('m-active');

            // set max-height to 0 upon close
            $item.find('.m-content').css('max-height', 0)
        };

        function open($item) {
            var $content = $item.find('.m-content');
            $item.addClass('m-active');
            $item.removeClass('m-closed');
            $item.addClass('m-opened')

            // determine which height function to use (outerHeight not supported by zepto)
            var contentChildren = $content.children();
            var contentHeight = ('outerHeight' in contentChildren) ? contentChildren['outerHeight']() : contentChildren['height']();
            $content.css('max-height', contentHeight * 1.5 +'px');

            // if transitions are supported, minimize browser reflow by adding the height
            // of the to-be expanded content element to the height of the entire accordion
            if (Utils.events.transitionend) {
                $element.css('min-height', $element.height() + contentHeight + 'px');
            }
        };

        function down(e) {
            // get initial position on mouse/touch start
            xy = Utils.getCursorPosition(e);
        };

        function move(e) {
            // update position upon move
            dxy = Utils.getCursorPosition(e);
        };

        function up(e) {
            // if there is dragging, do not close/open accordion
            if (dxy) {
                dx = xy.x - dxy.x;
                dy = xy.y - dxy.y;
                dxy = undefined;
                if ((dx*dx) + (dy*dy) > dragRadius*dragRadius) return;
            }

            // close or open item depending on active class
            var $item = $(this).parent();
            if ($item.hasClass('m-active')) {
                close($item);
            }
            else {
                open($item);
            }
        };

        function click(e) {
            e.preventDefault();
        };

        // Auto-open items that are hash linked
        var hash = location.hash;
        var $hashitem = $element.find('.m-header a[href="'+hash+'"]');

        if ($hashitem.length) {
            open($hashitem.parent());
        }

        // bind events
        $element.find('.m-header')
            .on(Utils.events.down, down)
            .on(Utils.events.move, move)
            .on(Utils.events.up, up)
            .on('click', click);
        if (Utils.events.transitionend) {
            $element.find('.m-content').on(Utils.events.transitionend, endTransition);
        }

    };

    Accordion.prototype.unbind = function() {
        this.$element.off();
    }

    Accordion.prototype.destroy = function() {
        this.unbind();
        this.$element.remove();
        this.$element = null;
    }

    return Accordion;

})(Mobify.$, Mobify.UI.Utils);

(function($) {
    $.fn.accordion = function(action) {
        this.each(function () {
            var $this = $(this)
              , accordion = $this._accordion

            if (!accordion) {
                accordion = new Mobify.UI.Accordion(this);
            }

            if (action) {
                accordion[action]();

                if (action === 'destroy') {
                    accordion = null;
                }
            }

            $this._accordion = accordion;
        })

        return this;
    };
})(Mobify.$);
<|endoftext|>


<commit_before>/* InstantClick 2.1 | (C) 2014 Alexandre Dieulot | http://instantclick.io/license.html */

var InstantClick = function(document, location) {
  // Internal variables
  var $ua = navigator.userAgent,
      $currentLocationWithoutHash,
      $urlToPreload,
      $preloadTimer,

  // Preloading-related variables
      $history = {},
      $xhr,
      $url = false,
      $title = false,
      $hasBody = true,
      $body = false,
      $timing = {},
      $isPreloading = false,
      $isWaitingForCompletion = false,

  // Variables defined by public functions
      $useWhitelist,
      $preloadOnMousedown,
      $delayBeforePreload,
      $eventsCallbacks = {
        change: [],
        click: []
      }

  ////////// HELPERS //////////

  function removeHash(url) {
    var index = url.indexOf('#')
    if (index < 0) {
      return url
    }
    return url.substr(0, index)
  }

  function getLinkTarget(target) {
    while (target.nodeName != 'A') {
      target = target.parentNode
    }
    return target
  }

  function triggerPageEvent(eventType, arg1) {
    for (var i = 0; i < $eventsCallbacks[eventType].length; i++) {
      $eventsCallbacks[eventType][i](arg1)
    }

    /* The `change` event takes one boolean argument: "isInitialLoad" */
  }

  function changePage(title, body, newUrl, scrollY) {
    var doc = document.implementation.createHTMLDocument('')
    doc.documentElement.innerHTML = body
    document.documentElement.replaceChild(doc.body, document.body)
    /* We cannot just use `document.body = doc.body` as it causes Safari 5.1, 6.0,
       and Mobile 7.0 to execute script tags directly.
    */

    var elem = document.createElement('i')
    elem.innerHTML = title
    document.title = elem.textContent

    if (newUrl) {
      history.pushState(null, null, newUrl)

      var hashIndex = newUrl.indexOf('#'),
          hashElem = hashIndex > -1
                     && document.getElementById(newUrl.substr(hashIndex + 1)),
          offset = 0

      if (hashElem) {
        while (hashElem.offsetParent) {
          offset += hashElem.offsetTop

          hashElem = hashElem.offsetParent
        }
      }
      scrollTo(0, offset)

      $currentLocationWithoutHash = removeHash(newUrl)
    }
    else {
      scrollTo(0, scrollY)
    }

    instantanize()

    triggerPageEvent('change', false)
  }

  function setPreloadingAsHalted() {
    $isPreloading = false
    $isWaitingForCompletion = false
  }

  ////////// EVENT HANDLERS //////////

  function mousedown(e) {
    preload(getLinkTarget(e.target).href)
  }

  function mouseover(e) {
    var a = getLinkTarget(e.target)
    a.addEventListener('mouseout', mouseout)

    if (!$delayBeforePreload) {
      preload(a.href)
    }
    else {
      $urlToPreload = a.href
      $preloadTimer = setTimeout(preload, $delayBeforePreload)
    }
  }

  function click(e) {
    if (e.which > 1 || e.metaKey || e.ctrlKey) { // Opening in new tab
      return
    }
    e.preventDefault()
    triggerPageEvent('click')
    display(getLinkTarget(e.target).href)
  }

  function mouseout() {
    if ($preloadTimer) {
      clearTimeout($preloadTimer)
      $preloadTimer = false
      return
    }

    if (!$isPreloading || $isWaitingForCompletion) {
      return
    }
    $xhr.abort()
    setPreloadingAsHalted()
  }

  function readystatechange() {
    if ($xhr.readyState < 4) {
      return
    }
    if ($xhr.status == 0) {
      /* Request aborted */
      return
    }

    $timing.ready = +new Date - $timing.start

    var text = $xhr.responseText

    $title = ''
    var titleIndex = text.indexOf('<title')
    if (titleIndex > -1) {
      $title = text.substr(text.indexOf('>', titleIndex) + 1)
      $title = $title.substr(0, $title.indexOf('</title'))
    }

    var bodyIndex = text.indexOf('<body')
    if (bodyIndex > -1) {
      $body = text.substr(bodyIndex)
      var closingIndex = $body.indexOf('</body')
      if (closingIndex > -1) {
        $body = $body.substr(0, closingIndex)
      }

      var urlWithoutHash = removeHash($url)
      $history[urlWithoutHash] = {
        body: $body,
        title: $title,
        scrollY: urlWithoutHash in $history ? $history[urlWithoutHash].scrollY : 0
      }
    }
    else {
      $hasBody = false
    }

    if ($isWaitingForCompletion) {
      $isWaitingForCompletion = false
      display($url)
    }
  }

  ////////// MAIN FUNCTIONS //////////

  function instantanize(isInitializing) {
    if(location.protocol == "file:") {
        return; // Don't start when using file:// urls. It just makes links glitch out, and local links are fast enough
    }

    var as = document.getElementsByTagName('a'),
        a,
        domain = location.protocol + '//' + location.host

    for (var i = as.length - 1; i >= 0; i--) {
      a = as[i]
      if (a.target // target="_blank" etc.
          || a.hasAttribute('download')
          || a.href.indexOf(domain + '/') != 0 // Another domain, or no href
                                               // attribute
          || (a.href.indexOf('#') > -1
              && removeHash(a.href) == $currentLocationWithoutHash) // Anchor
          || ($useWhitelist
              ? !a.hasAttribute('data-instant')
              : a.hasAttribute('data-no-instant'))
         ) {
        continue
      }
      if ($preloadOnMousedown) {
        a.addEventListener('mousedown', mousedown)
      }
      else {
        a.addEventListener('mouseover', mouseover)
      }
      a.addEventListener('click', click)
    }
    if (!isInitializing) {
      var scripts = document.getElementsByTagName('script'),
          script,
          copy,
          parentNode,
          nextSibling

      for (i = 0, j = scripts.length; i < j; i++) {
        script = scripts[i]
        if (script.hasAttribute('data-no-instant')) {
          continue
        }
        copy = document.createElement('script')
        if (script.src) {
          copy.src = script.src
        }
        if (script.innerHTML) {
          copy.innerHTML = script.innerHTML
        }
        parentNode = script.parentNode
        nextSibling = script.nextSibling
        parentNode.removeChild(script)
        parentNode.insertBefore(copy, nextSibling)
      }
    }
  }

  function preload(url) {
    if (!$preloadOnMousedown
        && 'display' in $timing
        && +new Date - ($timing.start + $timing.display) < 100) {
      /* After a page is displayed, if the user's cursor happens to be above
         a link a mouseover event will be in most browsers triggered
         automatically, and in other browsers it will be triggered when the
         user moves his mouse by 1px.

         Here are the behavior I noticed, all on Windows:
         - Safari 5.1: auto-triggers after 0 ms
         - IE 11: auto-triggers after 30-80 ms (depends on page's size?)
         - Firefox: auto-triggers after 10 ms
         - Opera 18: auto-triggers after 10 ms

         - Chrome: triggers when cursor moved
         - Opera 12.16: triggers when cursor moved

         To remedy to this, we do not start preloading if last display
         occurred less than 100 ms ago. If they happen to click on the link,
         they will be redirected.
      */

      return
    }
    if ($preloadTimer) {
      clearTimeout($preloadTimer)
      $preloadTimer = false
    }

    if (!url) {
      url = $urlToPreload
    }

    if ($isPreloading && (url == $url || $isWaitingForCompletion)) {
      return
    }
    $isPreloading = true
    $isWaitingForCompletion = false

    $url = url
    $body = false
    $hasBody = true
    $timing = {
      start: +new Date
    }
    $xhr.open('GET', url)
    $xhr.send()
  }

  function display(url) {
    if (!('display' in $timing)) {
      $timing.display = +new Date - $timing.start
    }
    if ($preloadTimer) {
      /* Happens when there’s a delay before preloading and that delay
         hasn't expired (preloading didn't kick in).
      */

      if ($url && $url != url) {
        /* Happens when the user clicks on a link before preloading
           kicks in while another link is already preloading.
        */

        location.href = url
        return
      }
      preload(url)
      $isWaitingForCompletion = true
      return
    }
    if (!$isPreloading || $isWaitingForCompletion) {
      /* If the page isn't preloaded, it likely means the user has focused
         on a link (with his Tab key) and then pressed Return, which
         triggered a click.
         Because very few people do this, it isn't worth handling this case
         and preloading on focus (also, focusing on a link doesn't mean it's
         likely that you'll "click" on it), so we just redirect them when
         they "click".
         It could also mean the user hovered over a link less than 100 ms
         after a page display, thus we didn't start the preload (see
         comments in `preload()` for the rationale behind this.)

         If the page is waiting for completion, the user clicked twice while
         the page was preloading.
         Two possibilities:
         1) He clicks on the same link again, either because it's slow to
            load (there's no browser loading indicator with InstantClick,
            so he might think his click hasn't registered if the page
            isn't loading fast enough) or because he has a habit of
            double clicking on the web
         2) He clicks on another link

         In the first case, we redirect him (send him to the page the old
         way) so that he can have the browser's loading indicator back.
         In the second case, we redirect him because we haven't preloaded
         that link, since we were already preloading the last one.

         Determining if it's a double click might be overkill as there is
         (hopefully) not that many people that double click on the web.
         Fighting against the perception that the page is stuck is
         interesting though, a seemingly good way to do that would be to
         later incorporate a progress bar.
      */

      location.href = url
      return
    }
    if (!$hasBody) {
      location.href = $url
      return
    }
    if (!$body) {
      $isWaitingForCompletion = true
      return
    }
    $history[$currentLocationWithoutHash].scrollY = pageYOffset
    setPreloadingAsHalted()
    changePage($title, $body, $url)
  }

  ////////// PUBLIC VARIABLE AND FUNCTIONS //////////

  var supported = !!(
    'pushState' in history && (
      !$ua.match('Android') ||
      $ua.match('Chrome/')
    )
  )
  /* The state of Android's AOSP browsers:

     2.3.7: pushState appears to work correctly, but
            `doc.documentElement.innerHTML = body` is buggy.
            See details here: http://stackoverflow.com/q/21918564

     3.0:   pushState appears to work correctly (though the URL bar is only
            updated on focus), but
            `document.documentElement.replaceChild(doc.body, document.body)`
        throws DOMException: WRONG_DOCUMENT_ERR.

     4.0.2: Doesn't support pushState.

     4.0.4,
     4.1.1,
     4.2,
     4.3:   pushState is here, but it doesn't update the URL bar.
            (Great logic there.)

     4.4:   Works correctly. Claims to be 'Chrome/30.0.0.0'.

     All androids tested with Android SDK's Emulator.
     Version numbers are from the browser's user agent.

     Because of this mess, the only whitelisted browser on Android is Chrome.
  */

  function init() {
    if ($currentLocationWithoutHash) {
      /* Already initialized */
      return
    }
    if (!supported) {
      triggerPageEvent('change', true)
      return
    }
    for (var i = arguments.length - 1; i >= 0; i--) {
      var arg = arguments[i]
      if (arg === true) {
        $useWhitelist = true
      }
      else if (arg == 'mousedown') {
        $preloadOnMousedown = true
      }
      else if (typeof arg == 'number') {
        $delayBeforePreload = arg
      }
    }
    $currentLocationWithoutHash = removeHash(location.href)
    $history[$currentLocationWithoutHash] = {
      body: document.body.outerHTML,
      title: document.title,
      scrollY: pageYOffset
    }
    $xhr = new XMLHttpRequest()
    $xhr.addEventListener('readystatechange', readystatechange)

    instantanize(true)

    triggerPageEvent('change', true)

    addEventListener('popstate', function() {
      var loc = removeHash(location.href)
      if (loc == $currentLocationWithoutHash) {
        return
      }

      if (!(loc in $history)) {
        location.href = location.href
        /* Reloads the page while using cache for scripts, styles and images,
           unlike `location.reload()` */
        return
      }

      $history[$currentLocationWithoutHash].scrollY = pageYOffset
      $currentLocationWithoutHash = loc
      changePage($history[loc].title, $history[loc].body, false, $history[loc].scrollY)
    })
  }

  function on(eventType, callback) {
    $eventsCallbacks[eventType].push(callback)
  }

  /* The debug function isn't included by default to reduce file size.
     To enable it, add a slash at the beginning of the comment englobing
     the debug function, and uncomment "debug: debug," in the return
     statement below the function. */

  /*
  function debug() {
    return {
      currentLocationWithoutHash: $currentLocationWithoutHash,
      history: $history,
      xhr: $xhr,
      url: $url,
      title: $title,
      hasBody: $hasBody,
      body: $body,
      timing: $timing,
      isPreloading: $isPreloading,
      isWaitingForCompletion: $isWaitingForCompletion
    }
  }
  //*/

  ////////////////////

  return {
    // debug: debug,
    supported: supported,
    init: init,
    on: on
  }

}(document, location);
<commit_msg>Moved location.protocol check to supported variable<commit_after>/* InstantClick 2.1 | (C) 2014 Alexandre Dieulot | http://instantclick.io/license.html */

var InstantClick = function(document, location) {
  // Internal variables
  var $ua = navigator.userAgent,
      $currentLocationWithoutHash,
      $urlToPreload,
      $preloadTimer,

  // Preloading-related variables
      $history = {},
      $xhr,
      $url = false,
      $title = false,
      $hasBody = true,
      $body = false,
      $timing = {},
      $isPreloading = false,
      $isWaitingForCompletion = false,

  // Variables defined by public functions
      $useWhitelist,
      $preloadOnMousedown,
      $delayBeforePreload,
      $eventsCallbacks = {
        change: [],
        click: []
      }

  ////////// HELPERS //////////

  function removeHash(url) {
    var index = url.indexOf('#')
    if (index < 0) {
      return url
    }
    return url.substr(0, index)
  }

  function getLinkTarget(target) {
    while (target.nodeName != 'A') {
      target = target.parentNode
    }
    return target
  }

  function triggerPageEvent(eventType, arg1) {
    for (var i = 0; i < $eventsCallbacks[eventType].length; i++) {
      $eventsCallbacks[eventType][i](arg1)
    }

    /* The `change` event takes one boolean argument: "isInitialLoad" */
  }

  function changePage(title, body, newUrl, scrollY) {
    var doc = document.implementation.createHTMLDocument('')
    doc.documentElement.innerHTML = body
    document.documentElement.replaceChild(doc.body, document.body)
    /* We cannot just use `document.body = doc.body` as it causes Safari 5.1, 6.0,
       and Mobile 7.0 to execute script tags directly.
    */

    var elem = document.createElement('i')
    elem.innerHTML = title
    document.title = elem.textContent

    if (newUrl) {
      history.pushState(null, null, newUrl)

      var hashIndex = newUrl.indexOf('#'),
          hashElem = hashIndex > -1
                     && document.getElementById(newUrl.substr(hashIndex + 1)),
          offset = 0

      if (hashElem) {
        while (hashElem.offsetParent) {
          offset += hashElem.offsetTop

          hashElem = hashElem.offsetParent
        }
      }
      scrollTo(0, offset)

      $currentLocationWithoutHash = removeHash(newUrl)
    }
    else {
      scrollTo(0, scrollY)
    }

    instantanize()

    triggerPageEvent('change', false)
  }

  function setPreloadingAsHalted() {
    $isPreloading = false
    $isWaitingForCompletion = false
  }

  ////////// EVENT HANDLERS //////////

  function mousedown(e) {
    preload(getLinkTarget(e.target).href)
  }

  function mouseover(e) {
    var a = getLinkTarget(e.target)
    a.addEventListener('mouseout', mouseout)

    if (!$delayBeforePreload) {
      preload(a.href)
    }
    else {
      $urlToPreload = a.href
      $preloadTimer = setTimeout(preload, $delayBeforePreload)
    }
  }

  function click(e) {
    if (e.which > 1 || e.metaKey || e.ctrlKey) { // Opening in new tab
      return
    }
    e.preventDefault()
    triggerPageEvent('click')
    display(getLinkTarget(e.target).href)
  }

  function mouseout() {
    if ($preloadTimer) {
      clearTimeout($preloadTimer)
      $preloadTimer = false
      return
    }

    if (!$isPreloading || $isWaitingForCompletion) {
      return
    }
    $xhr.abort()
    setPreloadingAsHalted()
  }

  function readystatechange() {
    if ($xhr.readyState < 4) {
      return
    }
    if ($xhr.status == 0) {
      /* Request aborted */
      return
    }

    $timing.ready = +new Date - $timing.start

    var text = $xhr.responseText

    $title = ''
    var titleIndex = text.indexOf('<title')
    if (titleIndex > -1) {
      $title = text.substr(text.indexOf('>', titleIndex) + 1)
      $title = $title.substr(0, $title.indexOf('</title'))
    }

    var bodyIndex = text.indexOf('<body')
    if (bodyIndex > -1) {
      $body = text.substr(bodyIndex)
      var closingIndex = $body.indexOf('</body')
      if (closingIndex > -1) {
        $body = $body.substr(0, closingIndex)
      }

      var urlWithoutHash = removeHash($url)
      $history[urlWithoutHash] = {
        body: $body,
        title: $title,
        scrollY: urlWithoutHash in $history ? $history[urlWithoutHash].scrollY : 0
      }
    }
    else {
      $hasBody = false
    }

    if ($isWaitingForCompletion) {
      $isWaitingForCompletion = false
      display($url)
    }
  }

  ////////// MAIN FUNCTIONS //////////

  function instantanize(isInitializing) {
    var as = document.getElementsByTagName('a'),
        a,
        domain = location.protocol + '//' + location.host

    for (var i = as.length - 1; i >= 0; i--) {
      a = as[i]
      if (a.target // target="_blank" etc.
          || a.hasAttribute('download')
          || a.href.indexOf(domain + '/') != 0 // Another domain, or no href
                                               // attribute
          || (a.href.indexOf('#') > -1
              && removeHash(a.href) == $currentLocationWithoutHash) // Anchor
          || ($useWhitelist
              ? !a.hasAttribute('data-instant')
              : a.hasAttribute('data-no-instant'))
         ) {
        continue
      }
      if ($preloadOnMousedown) {
        a.addEventListener('mousedown', mousedown)
      }
      else {
        a.addEventListener('mouseover', mouseover)
      }
      a.addEventListener('click', click)
    }
    if (!isInitializing) {
      var scripts = document.getElementsByTagName('script'),
          script,
          copy,
          parentNode,
          nextSibling

      for (i = 0, j = scripts.length; i < j; i++) {
        script = scripts[i]
        if (script.hasAttribute('data-no-instant')) {
          continue
        }
        copy = document.createElement('script')
        if (script.src) {
          copy.src = script.src
        }
        if (script.innerHTML) {
          copy.innerHTML = script.innerHTML
        }
        parentNode = script.parentNode
        nextSibling = script.nextSibling
        parentNode.removeChild(script)
        parentNode.insertBefore(copy, nextSibling)
      }
    }
  }

  function preload(url) {
    if (!$preloadOnMousedown
        && 'display' in $timing
        && +new Date - ($timing.start + $timing.display) < 100) {
      /* After a page is displayed, if the user's cursor happens to be above
         a link a mouseover event will be in most browsers triggered
         automatically, and in other browsers it will be triggered when the
         user moves his mouse by 1px.

         Here are the behavior I noticed, all on Windows:
         - Safari 5.1: auto-triggers after 0 ms
         - IE 11: auto-triggers after 30-80 ms (depends on page's size?)
         - Firefox: auto-triggers after 10 ms
         - Opera 18: auto-triggers after 10 ms

         - Chrome: triggers when cursor moved
         - Opera 12.16: triggers when cursor moved

         To remedy to this, we do not start preloading if last display
         occurred less than 100 ms ago. If they happen to click on the link,
         they will be redirected.
      */

      return
    }
    if ($preloadTimer) {
      clearTimeout($preloadTimer)
      $preloadTimer = false
    }

    if (!url) {
      url = $urlToPreload
    }

    if ($isPreloading && (url == $url || $isWaitingForCompletion)) {
      return
    }
    $isPreloading = true
    $isWaitingForCompletion = false

    $url = url
    $body = false
    $hasBody = true
    $timing = {
      start: +new Date
    }
    $xhr.open('GET', url)
    $xhr.send()
  }

  function display(url) {
    if (!('display' in $timing)) {
      $timing.display = +new Date - $timing.start
    }
    if ($preloadTimer) {
      /* Happens when there’s a delay before preloading and that delay
         hasn't expired (preloading didn't kick in).
      */

      if ($url && $url != url) {
        /* Happens when the user clicks on a link before preloading
           kicks in while another link is already preloading.
        */

        location.href = url
        return
      }
      preload(url)
      $isWaitingForCompletion = true
      return
    }
    if (!$isPreloading || $isWaitingForCompletion) {
      /* If the page isn't preloaded, it likely means the user has focused
         on a link (with his Tab key) and then pressed Return, which
         triggered a click.
         Because very few people do this, it isn't worth handling this case
         and preloading on focus (also, focusing on a link doesn't mean it's
         likely that you'll "click" on it), so we just redirect them when
         they "click".
         It could also mean the user hovered over a link less than 100 ms
         after a page display, thus we didn't start the preload (see
         comments in `preload()` for the rationale behind this.)

         If the page is waiting for completion, the user clicked twice while
         the page was preloading.
         Two possibilities:
         1) He clicks on the same link again, either because it's slow to
            load (there's no browser loading indicator with InstantClick,
            so he might think his click hasn't registered if the page
            isn't loading fast enough) or because he has a habit of
            double clicking on the web
         2) He clicks on another link

         In the first case, we redirect him (send him to the page the old
         way) so that he can have the browser's loading indicator back.
         In the second case, we redirect him because we haven't preloaded
         that link, since we were already preloading the last one.

         Determining if it's a double click might be overkill as there is
         (hopefully) not that many people that double click on the web.
         Fighting against the perception that the page is stuck is
         interesting though, a seemingly good way to do that would be to
         later incorporate a progress bar.
      */

      location.href = url
      return
    }
    if (!$hasBody) {
      location.href = $url
      return
    }
    if (!$body) {
      $isWaitingForCompletion = true
      return
    }
    $history[$currentLocationWithoutHash].scrollY = pageYOffset
    setPreloadingAsHalted()
    changePage($title, $body, $url)
  }

  ////////// PUBLIC VARIABLE AND FUNCTIONS //////////

  var supported = !!(
    'pushState' in history && (
      !$ua.match('Android') ||
      $ua.match('Chrome/')
    ) && (location.protocol != "file:") // Don't start when using file:// urls. It just makes links glitch out, and local links are fast enough
  )
  /* The state of Android's AOSP browsers:

     2.3.7: pushState appears to work correctly, but
            `doc.documentElement.innerHTML = body` is buggy.
            See details here: http://stackoverflow.com/q/21918564

     3.0:   pushState appears to work correctly (though the URL bar is only
            updated on focus), but
            `document.documentElement.replaceChild(doc.body, document.body)`
        throws DOMException: WRONG_DOCUMENT_ERR.

     4.0.2: Doesn't support pushState.

     4.0.4,
     4.1.1,
     4.2,
     4.3:   pushState is here, but it doesn't update the URL bar.
            (Great logic there.)

     4.4:   Works correctly. Claims to be 'Chrome/30.0.0.0'.

     All androids tested with Android SDK's Emulator.
     Version numbers are from the browser's user agent.

     Because of this mess, the only whitelisted browser on Android is Chrome.
  */

  function init() {
    if ($currentLocationWithoutHash) {
      /* Already initialized */
      return
    }
    if (!supported) {
      triggerPageEvent('change', true)
      return
    }
    for (var i = arguments.length - 1; i >= 0; i--) {
      var arg = arguments[i]
      if (arg === true) {
        $useWhitelist = true
      }
      else if (arg == 'mousedown') {
        $preloadOnMousedown = true
      }
      else if (typeof arg == 'number') {
        $delayBeforePreload = arg
      }
    }
    $currentLocationWithoutHash = removeHash(location.href)
    $history[$currentLocationWithoutHash] = {
      body: document.body.outerHTML,
      title: document.title,
      scrollY: pageYOffset
    }
    $xhr = new XMLHttpRequest()
    $xhr.addEventListener('readystatechange', readystatechange)

    instantanize(true)

    triggerPageEvent('change', true)

    addEventListener('popstate', function() {
      var loc = removeHash(location.href)
      if (loc == $currentLocationWithoutHash) {
        return
      }

      if (!(loc in $history)) {
        location.href = location.href
        /* Reloads the page while using cache for scripts, styles and images,
           unlike `location.reload()` */
        return
      }

      $history[$currentLocationWithoutHash].scrollY = pageYOffset
      $currentLocationWithoutHash = loc
      changePage($history[loc].title, $history[loc].body, false, $history[loc].scrollY)
    })
  }

  function on(eventType, callback) {
    $eventsCallbacks[eventType].push(callback)
  }

  /* The debug function isn't included by default to reduce file size.
     To enable it, add a slash at the beginning of the comment englobing
     the debug function, and uncomment "debug: debug," in the return
     statement below the function. */

  /*
  function debug() {
    return {
      currentLocationWithoutHash: $currentLocationWithoutHash,
      history: $history,
      xhr: $xhr,
      url: $url,
      title: $title,
      hasBody: $hasBody,
      body: $body,
      timing: $timing,
      isPreloading: $isPreloading,
      isWaitingForCompletion: $isWaitingForCompletion
    }
  }
  //*/

  ////////////////////

  return {
    // debug: debug,
    supported: supported,
    init: init,
    on: on
  }

}(document, location);
<|endoftext|>


<commit_before>// __BEGIN_LICENSE__
//Copyright (c) 2015, United States Government, as represented by the
//Administrator of the National Aeronautics and Space Administration.
//All rights reserved.
//
//The xGDS platform is licensed under the Apache License, Version 2.0
//(the "License"); you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//http://www.apache.org/licenses/LICENSE-2.0.
//
//Unless required by applicable law or agreed to in writing, software distributed
//under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
//CONDITIONS OF ANY KIND, either express or implied. See the License for the
//specific language governing permissions and limitations under the License.
// __END_LICENSE__

sse = {}; // namespace

$.extend(sse, {
	DEBUG: false,
	sources: {},
	initialize: function () {
		sse.heartbeat();
	},
	getSourcesKey: function (event_type, channel) {
		return event_type + "_" + channel;
	},
	heartbeat: function () {
		setInterval(sse.checkHeartbeat, 11000);
		sse.subscribe('heartbeat', sse.connectedCallback, 'sse');
	},
	checkHeartbeat: function () {
		var connected = false
		if (sse.lastHeartbeat != undefined) {
			var diff = moment.duration(moment().diff(sse.lastHeartbeat));
			if (diff.asSeconds() <= 10) {
				connected = true;
			}
		}
		if (!connected) {
			sse.disconnectedCallback();
		}
	},
	// connectedCallback will trigger on each SS event from the "heartbeat"
	// channel, it will modify a div with ID "connected_div" and another
	// div with ID "connected" to show that we are connected and recieving
	// SS events
	connectedCallback: function (event) {
		try {
			sse.lastHeartbeat = moment(JSON.parse(event.data).timestamp);
			var cdiv = $("#connected_div");
			if (cdiv.hasClass('alert-danger')) {
				cdiv.removeClass('alert-danger');
				cdiv.addClass('alert-success');
				var c = $("#connected");
				c.removeClass('fa-bolt');
				c.addClass('fa-plug');
			}
		} catch (err) {
			// in case there is no such page
			console.log("[SSE Error] failiure in the connectedCallback function");
			console.log("[SSE Error]", err);
		}
	},
	// disconnectedCallback will trigger when the last SSE heartbeat was recieved more than
	// 10 seconds ago, it will modify a div with ID "connected_div" and another
	// div with ID "connected" to show that we are disconnected
	disconnectedCallback: function (event) {
		try {
			var cdiv = $("#connected_div");
			if (cdiv.hasClass('alert-success')) {
				cdiv.removeClass('alert-success');
				cdiv.addClass('alert-danger');
				var c = $("#connected");
				c.addClass('fa-bolt');
				c.removeClass('fa-plug');
			}
		} catch (err) {
			// in case there is no such page
			console.log("[SSE Error] failiure in the disconnectedCallback function");
			console.log("[SSE Error]", err);
		}
	},
	// allChannels will loop over all our SSE channels, except the "sse" channel,
	// and call "theFunction" for each channel
	allChannels: function (theFunction, channels) {
		for (var i = 0; i < channels.length; i++) {
			var channel = channels[i];
			if (channel != 'sse') {
				theFunction(channel);
			}
		}
	},
	activeChannels: undefined,
	getChannelsUrl: '/xgds_core/sseActiveChannels',
	// getChannels function will return the activeChannels array; if it is undefined,
	// the function will request an array of channels from URL @ getChannelsUrl
	getChannels: function (url) {
		// get the active channels over AJAX
		if (sse.activeChannels === undefined) {
			$.ajax({
				url: sse.getChannelsUrl,
				dataType: 'json',
				async: false,
				success: $.proxy(function (data) {
					sse.activeChannels = data;
				}, this)
			});
		}
		return sse.activeChannels;
	},
	parseEventChannel: function (event) {
		if (sse.DEBUG) {
			console.log("[SSE Info] inside parse event channel function; event:", event);
		}
		return sse.parseChannel(event.target.url);
	},
	parseChannel: function (fullUrl) {
		if (sse.DEBUG) {
			console.log("[SSE Info] parsing a channel with full URL:", fullUrl);
		}
		var splits = fullUrl.split('=');
		if (splits.length > 1) {
			return splits[splits.length - 1];
		}
		return 'sse';
	},
	subscribe: function (event_type, callback, channels) {
		if (channels != undefined) {
			if (sse.DEBUG) {
				console.log("[SSE Info] inside subscribe function and channels have been defined");
			}
			if (!Array.isArray(channels)) {
				channels = [channels];
			}
			for (let channel of channels) {
				if (sse.DEBUG) {
					console.log("[SSE Info] creating a new Event Source for channel with name:", channel, "and event type:", event_type);
				}
				let sourceKey = sse.getSourcesKey(event_type, channel);
				if (sourceKey in sse.sources) continue;
				sse.sources[sourceKey] = new EventSource("/sse/stream?channel=" + channel);
				sse.sources[sourceKey].addEventListener(event_type, callback, false);
			}
		} else {
			if (sse.DEBUG) {
				console.log("[SSE Info] inside subscribe function and channels are undefined");
			}
			for (let channel of sse.getChannels()) {
				if (sse.DEBUG) {
					console.log("[SSE Info] creating a new Event Source for channel with name:", channel, "and event type:", event_type);
				}
				let sourceKey = sse.getSourcesKey(event_type, channel);
				if (sourceKey in sse.sources) continue;
				sse.sources[sourceKey] = new EventSource("/sse/stream?channel=" + channel);
				sse.sources[sourceKey].addEventListener(event_type, callback, false);
			}
		}
	},
	unsubscribe: function (event_type, channels) {
		if (!Array.isArray(channels)) {
			channels = [channels];
		}
		for (let channel of channels) {
			let sourceKey = sse.getSourcesKey(event_type, channel);
			sse.sources[sourceKey].close();
			sse.sources[sourceKey] = null;
			delete sse.sources[sourceKey];
			if (sse.DEBUG) {
				console.log("[SSE Info] deleted an existing Event Source for channel with name:", channel, "and event type:", event_type);
			}
		}
	}
});
<commit_msg>[sse] added extra checks to sse utils file to ensure we dont get errors<commit_after>// __BEGIN_LICENSE__
//Copyright (c) 2015, United States Government, as represented by the
//Administrator of the National Aeronautics and Space Administration.
//All rights reserved.
//
//The xGDS platform is licensed under the Apache License, Version 2.0
//(the "License"); you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//http://www.apache.org/licenses/LICENSE-2.0.
//
//Unless required by applicable law or agreed to in writing, software distributed
//under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
//CONDITIONS OF ANY KIND, either express or implied. See the License for the
//specific language governing permissions and limitations under the License.
// __END_LICENSE__

sse = {}; // namespace

$.extend(sse, {
	DEBUG: false,
	sources: {},
	initialize: function () {
		sse.heartbeat();
	},
	getSourcesKey: function (event_type, channel) {
		return event_type + "_" + channel;
	},
	heartbeat: function () {
		setInterval(sse.checkHeartbeat, 11000);
		sse.subscribe('heartbeat', sse.connectedCallback, 'sse');
	},
	checkHeartbeat: function () {
		var connected = false
		if (sse.lastHeartbeat != undefined) {
			var diff = moment.duration(moment().diff(sse.lastHeartbeat));
			if (diff.asSeconds() <= 10) {
				connected = true;
			}
		}
		if (!connected) {
			sse.disconnectedCallback();
		}
	},
	// connectedCallback will trigger on each SS event from the "heartbeat"
	// channel, it will modify a div with ID "connected_div" and another
	// div with ID "connected" to show that we are connected and recieving
	// SS events
	connectedCallback: function (event) {
		try {
			sse.lastHeartbeat = moment(JSON.parse(event.data).timestamp);
			var cdiv = $("#connected_div");
			if (cdiv.hasClass('alert-danger')) {
				cdiv.removeClass('alert-danger');
				cdiv.addClass('alert-success');
				var c = $("#connected");
				c.removeClass('fa-bolt');
				c.addClass('fa-plug');
			}
		} catch (err) {
			// in case there is no such page
			console.log("[SSE Error] failiure in the connectedCallback function");
			console.log("[SSE Error]", err);
		}
	},
	// disconnectedCallback will trigger when the last SSE heartbeat was recieved more than
	// 10 seconds ago, it will modify a div with ID "connected_div" and another
	// div with ID "connected" to show that we are disconnected
	disconnectedCallback: function (event) {
		try {
			var cdiv = $("#connected_div");
			if (cdiv.hasClass('alert-success')) {
				cdiv.removeClass('alert-success');
				cdiv.addClass('alert-danger');
				var c = $("#connected");
				c.addClass('fa-bolt');
				c.removeClass('fa-plug');
			}
		} catch (err) {
			// in case there is no such page
			console.log("[SSE Error] failiure in the disconnectedCallback function");
			console.log("[SSE Error]", err);
		}
	},
	// allChannels will loop over all our SSE channels, except the "sse" channel,
	// and call "theFunction" for each channel
	allChannels: function (theFunction, channels) {
		for (var i = 0; i < channels.length; i++) {
			var channel = channels[i];
			if (channel != 'sse') {
				theFunction(channel);
			}
		}
	},
	activeChannels: undefined,
	getChannelsUrl: '/xgds_core/sseActiveChannels',
	// getChannels function will return the activeChannels array; if it is undefined,
	// the function will request an array of channels from URL @ getChannelsUrl
	getChannels: function (url) {
		if (sse.DEBUG) {
			console.log("[SSE Info] inside getChannels function");
		}
		// get the active channels over AJAX
		if (sse.activeChannels === undefined) {
			$.ajax({
				url: sse.getChannelsUrl,
				dataType: 'json',
				async: false,
				success: $.proxy(function (data) {
					sse.activeChannels = data;
				}, this)
			});
		}
		return sse.activeChannels;
	},
	parseEventChannel: function (event) {
		if (sse.DEBUG) {
			console.log("[SSE Info] inside parse event channel function; event:", event);
		}
		return sse.parseChannel(event.target.url);
	},
	parseChannel: function (fullUrl) {
		if (sse.DEBUG) {
			console.log("[SSE Info] parsing a channel with full URL:", fullUrl);
		}
		var splits = fullUrl.split('=');
		if (splits.length > 1) {
			return splits[splits.length - 1];
		}
		return 'sse';
	},
	subscribe: function (event_type, callback, channels) {
		if (channels != undefined) {
			if (sse.DEBUG) {
				console.log("[SSE Info] inside subscribe function and channels have been defined");
			}
			if (!Array.isArray(channels)) {
				channels = [channels];
			}
			for (let channel of channels) {
				if (sse.DEBUG) {
					console.log("[SSE Info] creating a new Event Source for channel with name:", channel, "and event type:", event_type);
				}
				let sourceKey = sse.getSourcesKey(event_type, channel);
				if (sourceKey in sse.sources) continue;
				sse.sources[sourceKey] = new EventSource("/sse/stream?channel=" + channel);
				sse.sources[sourceKey].addEventListener(event_type, callback, false);
			}
		} else {
			if (sse.DEBUG) {
				console.log("[SSE Info] inside subscribe function and channels are undefined");
			}
			for (let channel of sse.getChannels()) {
				if (sse.DEBUG) {
					console.log("[SSE Info] creating a new Event Source for channel with name:", channel, "and event type:", event_type);
				}
				let sourceKey = sse.getSourcesKey(event_type, channel);
				if (sourceKey in sse.sources) continue;
				sse.sources[sourceKey] = new EventSource("/sse/stream?channel=" + channel);
				sse.sources[sourceKey].addEventListener(event_type, callback, false);
			}
		}
	},
	unsubscribe: function (event_type, channels) {
		if (!channels) {
			channels = this.getChannels();
		}
		if (!Array.isArray(channels)) {
			channels = [channels];
		}
		for (let channel of channels) {
			let sourceKey = sse.getSourcesKey(event_type, channel);
			if (sse.DEBUG) {
				console.log("[SSE Info] attempting to unsubscribe from channel:", channel, "and event type:", event_type);
			}
			if (sourceKey in sse.sources) {
				sse.sources[sourceKey].close();
				sse.sources[sourceKey] = null;
				delete sse.sources[sourceKey];
				if (sse.DEBUG) {
					console.log("[SSE Info] deleted an existing Event Source for channel with name:", channel, "and event type:", event_type);
				}
			}
		}
	}
});
<|endoftext|>


<commit_before>/**
 * Client tests
 */
/* global sinon */
/*eslint-disable max-nested-callbacks */

import $ from "cheerio";
import React from "react";
import { omit } from "lodash";
import { shallow, mount } from "enzyme";
import SvgTestHelper from "../../../../svg-test-helper";
import VictoryAxis from "src/components/victory-axis/victory-axis";
import AxisLine from "src/components/victory-axis/axis-line";
import Tick from "src/components/victory-axis/tick";

describe("components/victory-axis", () => {
  describe("default component rendering", () => {
    it("renders an svg with the correct width and height", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const svg = wrapper.find("svg");
      expect(svg.prop("style").width).to.equal("100%");
      expect(svg.prop("style").height).to.equal("auto");
    });

    it("renders an svg with the correct viewBox", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const svg = wrapper.find("svg");
      const viewBoxValue =
        `0 0 ${VictoryAxis.defaultProps.width} ${VictoryAxis.defaultProps.height}`;
      expect(svg.prop("viewBox")).to.equal(viewBoxValue);
    });

    it("renders 6 ticks", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const ticks = wrapper.find(Tick);
      expect(ticks.length).to.equal(6);
    });

    it("renders a line", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const line = wrapper.find(AxisLine);
      SvgTestHelper.expectIsALine(line);
    });
  });

  describe("dependentAxis prop", () => {
    it("renders an independent axis by default", () => {
      const props = {padding: 50, width: 300};
      const wrapper = shallow(
        <VictoryAxis {...props}/>
      );
      const line = wrapper.find(AxisLine);
      expect(SvgTestHelper.isIndependentAxis(line, props)).to.be.true;
    });

    it("renders a dependent axis if specified", () => {
      const props = {padding: 50, height: 300};
      const wrapper = shallow(
        <VictoryAxis dependentAxis {...props}/>
      );
      const line = wrapper.find(AxisLine);
      expect(SvgTestHelper.isDependentAxis(line, props)).to.be.true;
    });
  });

  describe("event handling", () => {
    it("attaches an event to the axis line", () => {
      const clickHandler = sinon.spy();
      const wrapper = mount(
        <VictoryAxis events={{axis: {onClick: clickHandler}}}/>
      );
      const Data = wrapper.find(AxisLine);
      Data.forEach((node, index) => {
        const initialProps = Data.at(index).props();
        node.simulate("click");
        expect(clickHandler.called).to.equal(true);
        // the first argument is the standard evt object
        expect(omit(clickHandler.args[index][1], ["events", "key"]))
          .to.eql(omit(initialProps, ["events", "key"]));
        expect(clickHandler.args[index][2]).to.eql(index);
      });
    });
  });
});
<commit_msg>Add test for tick lines<commit_after>/**
 * Client tests
 */
/* global sinon */
/*eslint-disable max-nested-callbacks */

import $ from "cheerio";
import React from "react";
import { omit } from "lodash";
import { shallow, mount } from "enzyme";
import SvgTestHelper from "../../../../svg-test-helper";
import VictoryAxis from "src/components/victory-axis/victory-axis";
import AxisLine from "src/components/victory-axis/axis-line";
import Tick from "src/components/victory-axis/tick";

describe("components/victory-axis", () => {
  describe("default component rendering", () => {
    it("renders an svg with the correct width and height", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const svg = wrapper.find("svg");
      expect(svg.prop("style").width).to.equal("100%");
      expect(svg.prop("style").height).to.equal("auto");
    });

    it("renders an svg with the correct viewBox", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const svg = wrapper.find("svg");
      const viewBoxValue =
        `0 0 ${VictoryAxis.defaultProps.width} ${VictoryAxis.defaultProps.height}`;
      expect(svg.prop("viewBox")).to.equal(viewBoxValue);
    });

    it("renders 6 ticks", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const ticks = wrapper.find(Tick);
      expect(ticks.length).to.equal(6);
    });

    it("renders ticks as lines", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const ticks = wrapper.find(Tick);
      ticks.forEach(SvgTestHelper.expectIsALine);
    });

    it("renders a line", () => {
      const wrapper = shallow(
        <VictoryAxis/>
      );
      const line = wrapper.find(AxisLine);
      SvgTestHelper.expectIsALine(line);
    });
  });

  describe("dependentAxis prop", () => {
    it("renders an independent axis by default", () => {
      const props = {padding: 50, width: 300};
      const wrapper = shallow(
        <VictoryAxis {...props}/>
      );
      const line = wrapper.find(AxisLine);
      expect(SvgTestHelper.isIndependentAxis(line, props)).to.be.true;
    });

    it("renders a dependent axis if specified", () => {
      const props = {padding: 50, height: 300};
      const wrapper = shallow(
        <VictoryAxis dependentAxis {...props}/>
      );
      const line = wrapper.find(AxisLine);
      expect(SvgTestHelper.isDependentAxis(line, props)).to.be.true;
    });
  });

  describe("event handling", () => {
    it("attaches an event to the axis line", () => {
      const clickHandler = sinon.spy();
      const wrapper = mount(
        <VictoryAxis events={{axis: {onClick: clickHandler}}}/>
      );
      const Data = wrapper.find(AxisLine);
      Data.forEach((node, index) => {
        const initialProps = Data.at(index).props();
        node.simulate("click");
        expect(clickHandler.called).to.equal(true);
        // the first argument is the standard evt object
        expect(omit(clickHandler.args[index][1], ["events", "key"]))
          .to.eql(omit(initialProps, ["events", "key"]));
        expect(clickHandler.args[index][2]).to.eql(index);
      });
    });
  });
});
<|endoftext|>


<commit_before>import { get, set } from '@ember/object';
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import { hash, hashSettled/* , all */ } from 'rsvp';
import { loadScript, loadStylesheet, proxifyUrl } from 'shared/utils/load-script';

export default Route.extend({
  access:              service(),
  catalog:             service(),
  settings:            service(),
  globalStore:         service(),
  intl:                service(),
  router:              service(),
  roleTemplateService: service('roleTemplate'),

  beforeModel(transition) {
    const allowed = [
      'import',
      'custom',
      'googlegke',
      'amazoneks',
      'azureaks',
    ];
    const provider = get(transition, 'to.queryParams.provider');

    if (!allowed.includes(provider) && !transition.from) {
      // we can't use transitionTo here as it does not seem to abort the current transition
      return this.transitionTo({ queryParams: { provider: 'googlegke' } } );
    }

    return;
  },

  model(params) {
    let gs = get(this, 'globalStore');

    let cluster = gs.createRecord({ type: 'cluster' });

    if (get(params, 'provider')) {
      set(this, 'provider', get(params, 'provider'));
    }

    return hash({
      cluster,
      clusterRoleTemplateBinding: gs.findAll('clusterRoleTemplateBinding'),
      cloudCredentials:           gs.findAll('cloudcredential'),
      kontainerDrivers:           gs.findAll('kontainerDriver'),
      me:                         get(this, 'access.principal'),
      nodeDrivers:                gs.findAll('nodeDriver'),
      nodeTemplates:              gs.findAll('nodeTemplate'),
      psps:                       gs.findAll('podSecurityPolicyTemplate'),
      roleTemplates:              get(this, 'roleTemplateService').fetchFilteredRoleTemplates(),
      users:                      gs.findAll('user'),
    });
  },

  afterModel(model) {
    // load the css/js url here, if the url loads fail we should error the driver out
    // show the driver in the ui, greyed out, and possibly add error text "can not load comonent from url [put url here]"

    let { kontainerDrivers } = model;
    let externalDrivers      = kontainerDrivers.filter( (d) => d.uiUrl !== '');
    let promises = {};

    externalDrivers.forEach( (d) => {
      if (get(d, 'hasUi')) {
        const jsUrl  = proxifyUrl(d.uiUrl, this.get('app.proxyEndpoint'));
        const cssUrl = proxifyUrl(d.uiUrl.replace(/\.js$/, '.css'), get(this, 'app.proxyEndpoint'));

        // skip setProperties cause of weird names
        set(promises, `${ d.name }Js`, loadScript(jsUrl, `driver-ui-js-${ d.name }`));
        set(promises, `${ d.name }Css`, loadStylesheet(cssUrl, `driver-ui-css-${ d.name }`));
      }
    });

    if (Object.keys(promises).length > 0) {
      return hashSettled(promises).then( (settled) => {
        let allkeys = Object.keys(settled);

        allkeys.forEach( (key) => {
          if (get(settled, `${ key }.state`) === 'rejected') {
            let tmp = key.indexOf('Js') > -1 ? key.replace(/\Js$/, '') : key.replace(/\Css$/, '');
            let match = kontainerDrivers.findBy('id', tmp);

            console.log('Error Loading External Component for: ', match);
            if (match && get(match, 'scriptError') !== true) {
              set(match, 'scriptError', get(this, 'intl').t('clusterNew.externalError'));
            }
          }
        });
      }).finally(() => {
        return model;
      });
    } else {
      return model;
    }
  },

  queryParams: { provider: { refreshModel: true } },

});
<commit_msg>only load external cluster driver scripts with active state<commit_after>import { get, set } from '@ember/object';
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import { hash, hashSettled/* , all */ } from 'rsvp';
import { loadScript, loadStylesheet, proxifyUrl } from 'shared/utils/load-script';

export default Route.extend({
  access:              service(),
  catalog:             service(),
  settings:            service(),
  globalStore:         service(),
  intl:                service(),
  router:              service(),
  roleTemplateService: service('roleTemplate'),

  beforeModel(transition) {
    const allowed = [
      'import',
      'custom',
      'googlegke',
      'amazoneks',
      'azureaks',
    ];
    const provider = get(transition, 'to.queryParams.provider');

    if (!allowed.includes(provider) && !transition.from) {
      // we can't use transitionTo here as it does not seem to abort the current transition
      return this.transitionTo({ queryParams: { provider: 'googlegke' } } );
    }

    return;
  },

  model(params) {
    let gs = get(this, 'globalStore');

    let cluster = gs.createRecord({ type: 'cluster' });

    if (get(params, 'provider')) {
      set(this, 'provider', get(params, 'provider'));
    }

    return hash({
      cluster,
      clusterRoleTemplateBinding: gs.findAll('clusterRoleTemplateBinding'),
      cloudCredentials:           gs.findAll('cloudcredential'),
      kontainerDrivers:           gs.findAll('kontainerDriver'),
      me:                         get(this, 'access.principal'),
      nodeDrivers:                gs.findAll('nodeDriver'),
      nodeTemplates:              gs.findAll('nodeTemplate'),
      psps:                       gs.findAll('podSecurityPolicyTemplate'),
      roleTemplates:              get(this, 'roleTemplateService').fetchFilteredRoleTemplates(),
      users:                      gs.findAll('user'),
    });
  },

  afterModel(model) {
    // load the css/js url here, if the url loads fail we should error the driver out
    // show the driver in the ui, greyed out, and possibly add error text "can not load comonent from url [put url here]"

    let { kontainerDrivers } = model;
    let externalDrivers      = kontainerDrivers.filter( (d) => d.uiUrl !== '' && d.state === 'active');
    let promises = {};

    externalDrivers.forEach( (d) => {
      if (get(d, 'hasUi')) {
        const jsUrl  = proxifyUrl(d.uiUrl, this.get('app.proxyEndpoint'));
        const cssUrl = proxifyUrl(d.uiUrl.replace(/\.js$/, '.css'), get(this, 'app.proxyEndpoint'));

        // skip setProperties cause of weird names
        set(promises, `${ d.name }Js`, loadScript(jsUrl, `driver-ui-js-${ d.name }`));
        set(promises, `${ d.name }Css`, loadStylesheet(cssUrl, `driver-ui-css-${ d.name }`));
      }
    });

    if (Object.keys(promises).length > 0) {
      return hashSettled(promises).then( (settled) => {
        let allkeys = Object.keys(settled);

        allkeys.forEach( (key) => {
          if (get(settled, `${ key }.state`) === 'rejected') {
            let tmp = key.indexOf('Js') > -1 ? key.replace(/\Js$/, '') : key.replace(/\Css$/, '');
            let match = kontainerDrivers.findBy('id', tmp);

            console.log('Error Loading External Component for: ', match);
            if (match && get(match, 'scriptError') !== true) {
              set(match, 'scriptError', get(this, 'intl').t('clusterNew.externalError'));
            }
          }
        });
      }).finally(() => {
        return model;
      });
    } else {
      return model;
    }
  },

  queryParams: { provider: { refreshModel: true } },

});
<|endoftext|>


<commit_before>;(function () {
    function initWatch (vm, watch) {
        vm._watchers || (vm._watchers = []);
        for (var key in watch) {
            var handler = watch[key];
            if (BI.isArray(handler)) {
                for (var i = 0; i < handler.length; i++) {
                    vm._watchers.push(createWatcher(vm, key, handler[i]));
                }
            } else {
                vm._watchers.push(createWatcher(vm, key, handler));
            }
        }
        BI.each(vm.$watchDelayCallbacks, function (i, watchDelayCallback) {
            var innerWatch = watchDelayCallback[0];
            var innerHandler = watchDelayCallback[1];
            if (BI.isKey(innerWatch)) {
                var key = innerWatch;
                innerWatch = {};
                innerWatch[key] = innerHandler;
            }
            for (var key in innerWatch) {
                var handler = innerWatch[key];
                if (BI.isArray(handler)) {
                    for (var i = 0; i < handler.length; i++) {
                        vm._watchers.push(createWatcher(vm, key, handler[i]));
                    }
                } else {
                    vm._watchers.push(createWatcher(vm, key, handler));
                }
            }
        });
    }

    function createWatcher (vm, keyOrFn, cb, options) {
        if (BI.isPlainObject(cb)) {
            options = cb;
            cb = cb.handler;
        }
        options = options || {};
        return Fix.watch(vm.model, keyOrFn, _.bind(cb, vm), BI.extend(options, {
            store: vm.store
        }));
    }

    var target = null;
    var targetStack = [];

    function pushTarget (_target) {
        if (target) targetStack.push(target);
        Fix.Model.target = target = _target;
    }

    function popTarget () {
        Fix.Model.target = target = targetStack.pop();
    }

    var oldWatch = Fix.watch;
    Fix.watch = function (model, expOrFn, cb, options) {
        if (BI.isPlainObject(cb)) {
            options = cb;
            cb = cb.handler;
        }
        if (typeof cb === "string") {
            cb = model[cb];
        }
        return oldWatch.call(this, model, expOrFn, function () {
            options && options.store && pushTarget(options.store);
            try {
                var res = cb.apply(this, arguments);
            } catch (e) {
                console.error(e);
            }
            options && options.store && popTarget();
            return res;
        }, options);
    };

    function findStore (widget) {
        if (target != null) {
            return target;
        }
        widget = widget || BI.Widget.context;
        var p = widget;
        while (p) {
            if (p instanceof Fix.Model || p.store || p.__cacheStore) {
                break;
            }
            p = p._parent || (p.options && p.options.element);
        }
        if (p) {
            if (p instanceof Fix.Model) {
                return widget.__cacheStore = p;
            }
            widget.__cacheStore = p.store || p.__cacheStore;
            return p.__cacheStore || p.store;
        }
    }

    // var _create = BI.createWidget;
    // BI.createWidget = function (item, options, context) {
    //     var pushed = false;
    //     if (BI.isWidget(options)) {
    //         pushContext(options);
    //         pushed = true;
    //     } else if (context != null) {
    //         pushContext(context);
    //         pushed = true;
    //     }
    //     var result = _create.apply(this, arguments);
    //     // try {
    //     //     var result = _create.apply(this, arguments);
    //     // } catch (e) {
    //     //     console.error(e);
    //     // }
    //     pushed && popContext();
    //     return result;
    // };

    _.each(["populate", "addItems", "prependItems"], function (name) {
        var old = BI.Loader.prototype[name];
        BI.Loader.prototype[name] = function () {
            BI.Widget.pushContext(this);
            try {
                var result = old.apply(this, arguments);
            } catch (e) {
                console.error(e);
            }
            BI.Widget.popContext();
            return result;
        };
    });

    function createStore () {
        var needPop = false;
        if (_global.Fix && this._store) {
            var store = findStore(this.options.context || this._parent || this.options.element);
            if (store) {
                pushTarget(store);
                needPop = true;
            }
            this.store = this._store();
            this.store && (this.store._widget = this);
            needPop && popTarget();
            needPop = false;
            pushTarget(this.store);
            if (this.store instanceof Fix.Model) {
                this.model = this.store.model;
            } else {
                this.model = this.store;
            }
            needPop = true;
        }
        return needPop;
    }

    var _init = BI.Widget.prototype._init;
    BI.Widget.prototype._init = function () {
        var self = this;
        var needPop = createStore.call(this);
        try {
            _init.apply(this, arguments);
        } catch (e) {
            console.error(e);
        }
        needPop && popTarget();
    };

    var _render = BI.Widget.prototype._render;
    BI.Widget.prototype._render = function () {
        var needPop = false;
        if (_global.Fix && this._store) {
            needPop = true;
            pushTarget(this.store);
            initWatch(this, this.watch);
        }
        _render.apply(this, arguments);
        // try {
        //     _render.apply(this, arguments);
        // } catch (e) {
        //     console.error(e);
        // }
        needPop && popTarget();
    };

    BI.Widget.prototype._initElement = function () {
        var self = this;
        var render = BI.isFunction(this.options.render) ? this.options.render : this.render;
        var els;
        if (this.options.updateMode === "auto") {
            // 自动更新模式
            var childComponents = {};
            var rendered = false;
            initWatch(this, {});
            this._watchers.push(Fix.watch(this.model, function () {
                if (rendered) {
                    var newEls = render && render.call(this);
                    BI.each(childComponents, function (i, childComponent) {
                        var nextProps = BI.get([newEls], childComponent.path);
                        if (nextProps) {
                            var shouldUpdate;
                            if (shouldUpdate = (childComponent.component.shouldUpdate && childComponent.component.shouldUpdate(nextProps))) {
                                childComponent.component._update(shouldUpdate === true ? nextProps : shouldUpdate);
                            } else {
                                childComponent.component._update(nextProps);
                            }
                            childComponent.props = BI.extend(childComponent.props, nextProps);
                        }
                    });
                } else {
                    els = render && render.call(this);

                    function traverse (parent, path) {
                        BI.each(parent, function (i, child) {
                            const childPath = path.concat(i);
                            if (BI.isArray(child)) {
                                traverse(child, childPath);
                            } else if (BI.isPlainObject(child)) {
                                if (child.type) {
                                    child.__ref = function (_ref) {
                                        if (_ref) {
                                            var comp = childComponents[this.getName()] = {};
                                            comp.component = _ref;
                                            comp.props = child;
                                            comp.path = childPath;
                                        } else {
                                            delete childComponents[this.getName()];
                                        }
                                    };
                                }
                                traverse(child, childPath);
                            }
                        });
                    }

                    traverse([els], []);
                    rendered = true;
                }
            }));
        } else {
            els = render && render.call(this);
        }
        if (BI.isPlainObject(els)) {
            els = [els];
        }
        if (BI.isArray(els)) {
            BI.each(els, function (i, el) {
                if (el) {
                    BI._lazyCreateWidget(el, {
                        element: self
                    });
                }
            });
        }
        // if (this._isRoot === true || !(this instanceof BI.Layout)) {
        this._mount();
    };

    var unMount = BI.Widget.prototype.__d;
    BI.Widget.prototype.__d = function () {
        try {
            unMount.apply(this, arguments);
        } catch (e) {
            console.error(e);
        }
        this.store && BI.isFunction(this.store.destroy) && this.store.destroy();
        BI.each(this._watchers, function (i, unwatches) {
            unwatches = BI.isArray(unwatches) ? unwatches : [unwatches];
            BI.each(unwatches, function (j, unwatch) {
                unwatch();
            });
        });
        this._watchers && (this._watchers = []);
        if (this.store) {
            this.store._parent && (this.store._parent = null);
            this.store._widget && (this.store._widget = null);
            this.store = null;
        }
        delete this.__cacheStore;
    };

    _.each(["_mount"], function (name) {
        var old = BI.Widget.prototype[name];
        old && (BI.Widget.prototype[name] = function () {
            this.store && pushTarget(this.store);
            try {
                var res = old.apply(this, arguments);
            } catch (e) {
                console.error(e);
            }
            this.store && popTarget();
            return res;
        });
    });

    if (BI.isIE9Below && BI.isIE9Below()) {
        _.each(["each", "map", "reduce", "reduceRight", "find", "filter", "reject", "every", "all", "some", "any", "max", "min",
            "sortBy", "groupBy", "indexBy", "countBy", "partition",
            "keys", "allKeys", "values", "pairs", "invert",
            "mapObject", "findKey", "pick", "omit", "tap"], function (name) {
            var old = BI[name];
            BI[name] = function (obj, fn, context) {
                return typeof fn === "function" ? old(obj, function (key, value) {
                    if (!(key in Fix.$$skipArray)) {
                        return fn.apply(this, arguments);
                    }
                }, context) : old.apply(this, arguments);
            };
        });
        BI.isEmpty = function (ob) {
            if (BI.isPlainObject(ob) && ob.__ob__) {
                return BI.keys(ob).length === 0;
            }
            return _.isEmpty(ob);
        };
        BI.keys = function (ob) {
            var keys = _.keys(ob);
            var nKeys = [];
            for (var i = 0; i < keys.length; i++) {
                if (!(keys[i] in Fix.$$skipArray)) {
                    nKeys.push(keys[i]);
                }
            }
            return nKeys;
        };
        BI.values = function (ob) {
            var keys = BI.keys(obj);
            var length = keys.length;
            var values = [];
            for (var i = 0; i < length; i++) {
                values[i] = obj[keys[i]];
            }
            return values;
        };
        BI.extend = function () {
            var args = Array.prototype.slice.call(arguments);
            if (args.length < 1) {
                return {};
            }
            var object = args[0];
            var i = 1;
            while (i < args.length) {
                BI.each(args[i], function (key, v) {
                    object[key] = v;
                });
                i++;
            }
            return object;
        };
        BI.size = function (ob) {
            if (BI.isPlainObject(ob) && ob.__ob__) {
                return BI.keys(ob).length;
            }
            return _.size(ob);
        };
        BI.isEmptyObject = function (ob) {
            return BI.size(ob) === 0;
        };
        BI.deepClone = function (ob) {
            return Fix.toJSON(ob);
        };
    }
}());
<commit_msg>update<commit_after>;(function () {
    function initWatch (vm, watch) {
        vm._watchers || (vm._watchers = []);
        for (var key in watch) {
            var handler = watch[key];
            if (BI.isArray(handler)) {
                for (var i = 0; i < handler.length; i++) {
                    vm._watchers.push(createWatcher(vm, key, handler[i]));
                }
            } else {
                vm._watchers.push(createWatcher(vm, key, handler));
            }
        }
        BI.each(vm.$watchDelayCallbacks, function (i, watchDelayCallback) {
            var innerWatch = watchDelayCallback[0];
            var innerHandler = watchDelayCallback[1];
            if (BI.isKey(innerWatch)) {
                var key = innerWatch;
                innerWatch = {};
                innerWatch[key] = innerHandler;
            }
            for (var key in innerWatch) {
                var handler = innerWatch[key];
                if (BI.isArray(handler)) {
                    for (var i = 0; i < handler.length; i++) {
                        vm._watchers.push(createWatcher(vm, key, handler[i]));
                    }
                } else {
                    vm._watchers.push(createWatcher(vm, key, handler));
                }
            }
        });
    }

    function createWatcher (vm, keyOrFn, cb, options) {
        if (BI.isPlainObject(cb)) {
            options = cb;
            cb = cb.handler;
        }
        options = options || {};
        return Fix.watch(vm.model, keyOrFn, _.bind(cb, vm), BI.extend(options, {
            store: vm.store
        }));
    }

    var target = null;
    var targetStack = [];

    function pushTarget (_target) {
        if (target) targetStack.push(target);
        Fix.Model.target = target = _target;
    }

    function popTarget () {
        Fix.Model.target = target = targetStack.pop();
    }

    var oldWatch = Fix.watch;
    Fix.watch = function (model, expOrFn, cb, options) {
        if (BI.isPlainObject(cb)) {
            options = cb;
            cb = cb.handler;
        }
        if (typeof cb === "string") {
            cb = model[cb];
        }
        return oldWatch.call(this, model, expOrFn, function () {
            options && options.store && pushTarget(options.store);
            try {
                var res = cb.apply(this, arguments);
            } catch (e) {
                console.error(e);
            }
            options && options.store && popTarget();
            return res;
        }, options);
    };

    function findStore (widget) {
        if (target != null) {
            return target;
        }
        widget = widget || BI.Widget.context;
        var p = widget;
        while (p) {
            if (p instanceof Fix.Model || p.store || p.__cacheStore) {
                break;
            }
            p = p._parent || (p.options && p.options.element);
        }
        if (p) {
            if (p instanceof Fix.Model) {
                return widget.__cacheStore = p;
            }
            widget.__cacheStore = p.store || p.__cacheStore;
            return p.__cacheStore || p.store;
        }
    }

    // var _create = BI.createWidget;
    // BI.createWidget = function (item, options, context) {
    //     var pushed = false;
    //     if (BI.isWidget(options)) {
    //         pushContext(options);
    //         pushed = true;
    //     } else if (context != null) {
    //         pushContext(context);
    //         pushed = true;
    //     }
    //     var result = _create.apply(this, arguments);
    //     // try {
    //     //     var result = _create.apply(this, arguments);
    //     // } catch (e) {
    //     //     console.error(e);
    //     // }
    //     pushed && popContext();
    //     return result;
    // };

    _.each(["populate", "addItems", "prependItems"], function (name) {
        var old = BI.Loader.prototype[name];
        BI.Loader.prototype[name] = function () {
            BI.Widget.pushContext(this);
            try {
                var result = old.apply(this, arguments);
            } catch (e) {
                console.error(e);
            }
            BI.Widget.popContext();
            return result;
        };
    });

    function createStore () {
        var needPop = false;
        if (_global.Fix && this._store) {
            var store = findStore(this.options.context || this._parent || this.options.element);
            if (store) {
                pushTarget(store);
                needPop = true;
            }
            this.store = this._store();
            this.store && (this.store._widget = this);
            needPop && popTarget();
            needPop = false;
            pushTarget(this.store);
            if (this.store instanceof Fix.Model) {
                this.model = this.store.model;
            } else {
                this.model = this.store;
            }
            needPop = true;
        }
        return needPop;
    }

    var _init = BI.Widget.prototype._init;
    BI.Widget.prototype._init = function () {
        var self = this;
        var needPop = createStore.call(this);
        try {
            _init.apply(this, arguments);
        } catch (e) {
            console.error(e);
        }
        needPop && popTarget();
    };

    var _render = BI.Widget.prototype._render;
    BI.Widget.prototype._render = function () {
        var needPop = false;
        if (_global.Fix && this._store) {
            needPop = true;
            pushTarget(this.store);
            initWatch(this, this.watch);
        }
        _render.apply(this, arguments);
        // try {
        //     _render.apply(this, arguments);
        // } catch (e) {
        //     console.error(e);
        // }
        needPop && popTarget();
    };

    BI.Widget.prototype._initElement = function () {
        var self = this;
        var render = BI.isFunction(this.options.render) ? this.options.render : this.render;
        var els;
        if (this.options.updateMode === "auto" && this._store) {
            // 自动更新模式
            var childComponents = {};
            var rendered = false;
            this._watchers.push(Fix.watch(this.model, function () {
                if (rendered) {
                    var newEls = render && render.call(this);
                    BI.each(childComponents, function (i, childComponent) {
                        var nextProps = BI.get([newEls], childComponent.path);
                        if (nextProps) {
                            var shouldUpdate;
                            if (shouldUpdate = (childComponent.component.shouldUpdate && childComponent.component.shouldUpdate(nextProps))) {
                                childComponent.component._update(shouldUpdate === true ? nextProps : shouldUpdate);
                            } else {
                                childComponent.component._update(nextProps);
                            }
                            childComponent.props = BI.extend(childComponent.props, nextProps);
                        }
                    });
                } else {
                    els = render && render.call(this);

                    function traverse (parent, path) {
                        BI.each(parent, function (i, child) {
                            const childPath = path.concat(i);
                            if (BI.isArray(child)) {
                                traverse(child, childPath);
                            } else if (BI.isPlainObject(child)) {
                                if (child.type) {
                                    child.__ref = function (_ref) {
                                        if (_ref) {
                                            var comp = childComponents[this.getName()] = {};
                                            comp.component = _ref;
                                            comp.props = child;
                                            comp.path = childPath;
                                        } else {
                                            delete childComponents[this.getName()];
                                        }
                                    };
                                }
                                traverse(child, childPath);
                            }
                        });
                    }

                    traverse([els], []);
                    rendered = true;
                }
            }));
        } else {
            els = render && render.call(this);
        }
        if (BI.isPlainObject(els)) {
            els = [els];
        }
        if (BI.isArray(els)) {
            BI.each(els, function (i, el) {
                if (el) {
                    BI._lazyCreateWidget(el, {
                        element: self
                    });
                }
            });
        }
        // if (this._isRoot === true || !(this instanceof BI.Layout)) {
        this._mount();
    };

    var unMount = BI.Widget.prototype.__d;
    BI.Widget.prototype.__d = function () {
        try {
            unMount.apply(this, arguments);
        } catch (e) {
            console.error(e);
        }
        this.store && BI.isFunction(this.store.destroy) && this.store.destroy();
        BI.each(this._watchers, function (i, unwatches) {
            unwatches = BI.isArray(unwatches) ? unwatches : [unwatches];
            BI.each(unwatches, function (j, unwatch) {
                unwatch();
            });
        });
        this._watchers && (this._watchers = []);
        if (this.store) {
            this.store._parent && (this.store._parent = null);
            this.store._widget && (this.store._widget = null);
            this.store = null;
        }
        delete this.__cacheStore;
    };

    _.each(["_mount"], function (name) {
        var old = BI.Widget.prototype[name];
        old && (BI.Widget.prototype[name] = function () {
            this.store && pushTarget(this.store);
            try {
                var res = old.apply(this, arguments);
            } catch (e) {
                console.error(e);
            }
            this.store && popTarget();
            return res;
        });
    });

    if (BI.isIE9Below && BI.isIE9Below()) {
        _.each(["each", "map", "reduce", "reduceRight", "find", "filter", "reject", "every", "all", "some", "any", "max", "min",
            "sortBy", "groupBy", "indexBy", "countBy", "partition",
            "keys", "allKeys", "values", "pairs", "invert",
            "mapObject", "findKey", "pick", "omit", "tap"], function (name) {
            var old = BI[name];
            BI[name] = function (obj, fn, context) {
                return typeof fn === "function" ? old(obj, function (key, value) {
                    if (!(key in Fix.$$skipArray)) {
                        return fn.apply(this, arguments);
                    }
                }, context) : old.apply(this, arguments);
            };
        });
        BI.isEmpty = function (ob) {
            if (BI.isPlainObject(ob) && ob.__ob__) {
                return BI.keys(ob).length === 0;
            }
            return _.isEmpty(ob);
        };
        BI.keys = function (ob) {
            var keys = _.keys(ob);
            var nKeys = [];
            for (var i = 0; i < keys.length; i++) {
                if (!(keys[i] in Fix.$$skipArray)) {
                    nKeys.push(keys[i]);
                }
            }
            return nKeys;
        };
        BI.values = function (ob) {
            var keys = BI.keys(obj);
            var length = keys.length;
            var values = [];
            for (var i = 0; i < length; i++) {
                values[i] = obj[keys[i]];
            }
            return values;
        };
        BI.extend = function () {
            var args = Array.prototype.slice.call(arguments);
            if (args.length < 1) {
                return {};
            }
            var object = args[0];
            var i = 1;
            while (i < args.length) {
                BI.each(args[i], function (key, v) {
                    object[key] = v;
                });
                i++;
            }
            return object;
        };
        BI.size = function (ob) {
            if (BI.isPlainObject(ob) && ob.__ob__) {
                return BI.keys(ob).length;
            }
            return _.size(ob);
        };
        BI.isEmptyObject = function (ob) {
            return BI.size(ob) === 0;
        };
        BI.deepClone = function (ob) {
            return Fix.toJSON(ob);
        };
    }
}());
<|endoftext|>