<commit_before>/**
 * 为常用的 button 组件添加点击效果，同时全局增加 tap 事件
 *
 * @author      mufeng  <smufeng@gmail.com>
 * @version     0.2     <2016-01-19>
 */

$.ready(function() {
    var $document = $(document), tap, fastmove, input;

    /* 修复path对象，无则自己模拟一个出来 */
    function fixPath(e) {
        var target = e.target, arr = [];

        while (target && target.parentNode) {
            arr.push(target);   // 当前存下来

            target = target.parentNode;
        }

        return arr;     // 返回创建的对象
    }

    function checkClass(item, test) {
        var find, cls = item.attr ? item.attr("class") :
                  item.getAttribute || item.getAttribute("class");

        /* 转换为数组，便于后面操作 */
        cls  = (cls || "").split(" ");
        find = (test || "button item tab-item").split(" ");

        for (var i=0; i<find.length; i++) {
            if (cls.indexOf(find[i]) >= 0) return true;
        }

        return false;
    }

    // 清除激活类
    function clearActive($target) {
        if (checkClass($target)) {
            $target.removeClass("active");
        }
    }
    $._ui_clearActive = clearActive;

    tap = {
        delay  : 200,
        animate: 240,
        checkMove : 2,
        doubleTime: 300,
        delayClass: 50,

        input: null,
        target: null,
        lastType: "",
        disClick: false,
        moveClear: false,
        delayStart: null,
        tapIsStart: false,

        startX: 0,
        startY: 0,
        startTime: 0,

        _focus: function(e) {
            this.input = e.target;
        },

        _isMove: function(e, move) {
            var mv = move ? move : this.checkMove,
                touch = e.changedTouches, cx, cy;

            touch = touch && touch[0] ? touch[0] : e;
            cx = Math.abs(touch.pageX - this.startX);
            cy = Math.abs(touch.pageY - this.startY);

            return !!(cx >= mv || cy >= mv);
        },

        // 用于检测两次的点击事件是否可能为点透现象
        _isDouble: function(e, time) {
            var mt, testMove, ct;

            testMove = !this._isMove(e);

            if (time !== false) {
                mt = parseInt(time);
                mt = isNaN(mt) ? this.doubleTime : mt;
                ct = $.getTime() - this.startTime;

                return (ct < mt) && testMove;
            } else {
                return testMove;
            }
        },

        _start: function(e) {
            var touch = e.touches ? e.touches[0] : e,
                tagName, path = fixPath(e), ltype;

            ltype = this.lastType;
            this.lastType = e.type;

            if ((e.touches && e.touches.length > 1) ||
                (e.type == "mousedown" && ltype != e.type
                    && this._isDouble(e)) ) {
                this.startX = null;      this.startY = null;

                return true;
            }

            var touch = e.touches ? e.touches[0] : e,
                tagName, path = fixPath(e);

            /* 记录此次点击事件的相关信息，用于方法判断 */
            this.startX = touch.pageX;
            this.startY = touch.pageY;
            this.startTime = $.getTime();
            this.target    = e.target;
            this.moveClear = false;
            this.tapIsStart= true;

            // 修复 BUTTON元素 出现点击两次的问题
            for (var i = 0; i<path.length; i++) {
                var $item = $(path[i]), type;
                tagName = $item[0].tagName.toUpperCase();
                type    = ($item.attr("type") || "").toUpperCase();

                if (tagName == "BUTTON" || (tagName == "INPUT" &&
                    "RADIO CHECKBOX".split(" ").indexOf(type) >= 0) ) {
                    e.preventDefault();
                    break;
                }
            }

            this.delayStart = setTimeout(function() {
                /* 给按钮类的组件添加点击样式 */
                for (var i = 0; i<path.length; i++) {
                    var $item = $(path[i]);
                    tagName = $item[0].tagName.toUpperCase();

                    if (checkClass($item)) {
                        $item.addClass("active");
                    }
                }
            }, this.delayClass);
        },

        _move: $.delayCall(function(e) {
            e.preventDefault(); // 修复微信下拉显示网页地址

            if (!this.moveClear && this._isMove(e, 1)) {

                // 清除 tap 自定义相关操作
                this.moveClear = true;
                clearTimeout(this.delayStart);

                // 清除事件中相关的元素的激活类
                var path = fixPath(e), $item;
                for(var i = 0; i<path.length; i++) {
                    $item = $(path[i]);
                    clearActive($item);
                }
            }
        }, 20),

        _end: function(e) {
            var ct, $target = $(e.target), tagName;

            ct = $.getTime() - this.startTime;
            this.tapIsStart = false;

            if (!this._isMove(e) && ct < this.delay) {
                $target.trigger("tap");

                /* 修复移动端input点击焦点不更新的问题 */
                tagName = e.target.tagName.toUpperCase();
                if (e.target != this.input) {
                    this.input && this.input.blur();        // 刷新焦点
                }

                if ("INPUT TEXTAREA".split(" ").indexOf(tagName) >= 0) {
                    this.input = e.target;                  // 更新绑定元素

                    var type = $target.attr("type");

                    type = type ? type.toUpperCase() : type;

                    if (type && "RADIO CHECKBOX".split(" ").indexOf(type) >= 0) {
                        if (this.input.checked == true) {
                            $target.removeClass("checked");
                        } else {
                            if (type == "CHECKBOX") {
                                $target.addClass("checked");
                            }
                        }
                    }
                }

                this.disClick = false;
                $target.trigger("click");
            }

            setTimeout(function() {
                var path = fixPath(e);
                for(var i = 0; i<path.length; i++) {
                    var $item = $(path[i]);
                    clearActive($item);
                }
            }, this.delayClass);
        },

        handleEvent: function (e) {
            switch ( e.type ) {
                case 'touchstart':
                case 'pointerdown':
                case 'MSPointerDown':
                case 'mousedown':
                    this._start(e);
                    break;
                case 'touchmove':
                case 'pointermove':
                case 'MSPointerMove':
                case 'mousemove':
                    this._move(e);
                    break;
                case 'touchend':
                case 'pointerup':
                case 'MSPointerUp':
                case 'mouseup':
                case 'touchcancel':
                case 'pointercancel':
                case 'MSPointerCancel':
                case 'mousecancel':
                    this._end(e);
                    break;
            }
        }
    };

    /**
     *
     * 事件初始化，同时修复移动端 点击穿透问题，原理如下:
     *
     * 移动端的click都是touch之后，才会模拟触发。触发的顺序如下:
     *
     * touchstart -> touchmove -> touchend ->
     * mousedown  -> mousemove -> mouseenter -> click
     *
     * 在重叠的区域里，被遮盖的元素绑定click，遮盖的元素绑定touch事件，
     * 且touch后遮盖的元素会隐藏的话，就会造成穿透，因为click是在touch
     * 之后延迟触发的，浏览器会误认为是在遮盖的元素上触发了click。
     *
     */
    ;(function() {
        var kt = window.ontouchstart !== undefined ? 0 : 1,
            doc = document, bind = "addEventListener";

        /* 修复input 焦点问题 */
        doc[bind]("focus", tap._focus, true)

        doc[bind]("mousedown", tap);
        doc[bind]("mousemove", tap);
        doc[bind]("mouseup", tap);
        doc[bind]("mousecancel", tap);

        doc[bind]("touchstart", tap);
        doc[bind]("touchmove", tap);
        doc[bind]("touchend", tap);
        doc[bind]("touchcancel", tap);

        doc[bind]("pointerdown", tap);
        doc[bind]("pointermove", tap);
        doc[bind]("pointerup", tap);
        doc[bind]("pointercancel", tap);

        doc[bind]("MSPointerDown", tap);
        doc[bind]("MSPointerMove", tap);
        doc[bind]("MSPointerUp", tap);
        doc[bind]("MSPointerCancel", tap);

        /* 修复鼠标点透问题 */
        if (kt === 0 /* 只有支持 touch 事件时才绑定 */) {
            $document.on("click", function(e) {
                if (!tap.disClick &&
                    (e.timeStamp - tap.startTime) < 300) {

                    /* 临时修复 自定义 click 事件无法触发 input 默认点击效果 BUG */
                    if (e.target.tagName.toUpperCase() == "INPUT") {
                        var tar  = e.target,
                            type = $(tar).attr("type");

                        type = type ? type.toUpperCase() : type;

                        if (type && "RADIO CHECKBOX".split(" ").indexOf(type) >= 0) {
                            if (tar.checked === false) {
                                tar.checked = true;
                            } else if (type == "CHECKBOX") {
                                tar.checked = false;
                            }
                        }
                    }

                    tap.disClick = true;
                } else {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                }
            }, true);
        }
    })(window, tap);
});<commit_msg>tap点击性能针对 crosswalk 环境优化<commit_after>/**
 * 为常用的 button 组件添加点击效果，同时全局增加 tap 事件
 *
 * @author      mufeng  <smufeng@gmail.com>
 * @version     0.2     <2016-01-19>
 */

$.ready(function() {
    var $document = $(document), tap, fastmove, input;

    /* 修复path对象，无则自己模拟一个出来 */
    function fixPath(e) {
        var target = e.target, arr = [];

        while (target && target.parentNode) {
            arr.push(target);   // 当前存下来

            target = target.parentNode;
        }

        return arr;     // 返回创建的对象
    }

    function checkClass(item, test) {
        var find, cls = item.attr ? item.attr("class") :
                  item.getAttribute || item.getAttribute("class");

        /* 转换为数组，便于后面操作 */
        cls  = (cls || "").split(" ");
        find = (test || "button item tab-item").split(" ");

        for (var i=0; i<find.length; i++) {
            if (cls.indexOf(find[i]) >= 0) return true;
        }

        return false;
    }

    // 清除激活类
    function clearActive($target) {
        if (checkClass($target)) {
            $target.removeClass("active");
        }
    }
    $._ui_clearActive = clearActive;

    tap = {
        delay  : 200,
        animate: 240,
        checkMove : 2,
        doubleTime: 300,
        delayClass: 50,

        input: null,
        target: null,
        lastType: "",
        disClick: false,
        moveClear: false,
        delayStart: null,
        tapIsStart: false,

        startX: 0,
        startY: 0,
        startTime: 0,

        _focus: function(e) {
            this.input = e.target;
        },

        _isMove: function(e, move) {
            var mv = move ? move : this.checkMove,
                touch = e.changedTouches, cx, cy;

            touch = touch && touch[0] ? touch[0] : e;
            cx = Math.abs(touch.pageX - this.startX);
            cy = Math.abs(touch.pageY - this.startY);

            return !!(cx >= mv || cy >= mv);
        },

        // 用于检测两次的点击事件是否可能为点透现象
        _isDouble: function(e, time) {
            var mt, testMove, ct;

            testMove = !this._isMove(e);

            if (time !== false) {
                mt = parseInt(time);
                mt = isNaN(mt) ? this.doubleTime : mt;
                ct = $.getTime() - this.startTime;

                return (ct < mt) && testMove;
            } else {
                return testMove;
            }
        },

        _start: function(e) {
            var touch = e.touches ? e.touches[0] : e,
                tagName, path = fixPath(e), ltype;

            ltype = this.lastType;
            this.lastType = e.type;

            if ((e.touches && e.touches.length > 1) ||
                (e.type == "mousedown" && ltype != e.type
                    && this._isDouble(e)) ) {
                this.startX = null;      this.startY = null;

                return true;
            }

            var touch = e.touches ? e.touches[0] : e,
                tagName, path = fixPath(e);

            /* 记录此次点击事件的相关信息，用于方法判断 */
            this.startX = touch.pageX;
            this.startY = touch.pageY;
            this.startTime = $.getTime();
            this.target    = e.target;
            this.moveClear = false;
            this.tapIsStart= true;

            // 修复 BUTTON元素 出现点击两次的问题
            for (var i = 0; i<path.length; i++) {
                var $item = $(path[i]), type;
                tagName = $item[0].tagName.toUpperCase();
                type    = ($item.attr("type") || "").toUpperCase();

                if (tagName == "BUTTON" || (tagName == "INPUT" &&
                    "RADIO CHECKBOX".split(" ").indexOf(type) >= 0) ) {
                    e.preventDefault();
                    break;
                }
            }

            // this.delayStart = setTimeout(function() {
            //     /* 给按钮类的组件添加点击样式 */
            //     for (var i = 0; i<path.length; i++) {
            //         var $item = $(path[i]);
            //         tagName = $item[0].tagName.toUpperCase();

            //         if (checkClass($item)) {
            //             $item.addClass("active");
            //         }
            //     }
            // }, this.delayClass);

            for (var i = 0; i<path.length; i++) {
                var $item = $(path[i]);
                tagName = $item[0].tagName.toUpperCase();

                if (checkClass($item)) {
                    $item.addClass("active");
                }
            }
        },

        _move: $.delayCall(function(e) {
            e.preventDefault(); // 修复微信下拉显示网页地址

            // if (!this.moveClear && this._isMove(e, 1)) {

            //     // 清除 tap 自定义相关操作
            //     this.moveClear = true;
            //     clearTimeout(this.delayStart);

            //     // 清除事件中相关的元素的激活类
            //     var path = fixPath(e), $item;
            //     for(var i = 0; i<path.length; i++) {
            //         $item = $(path[i]);
            //         clearActive($item);
            //     }
            // }
        }, 20),

        _end: function(e) {
            var ct, $target = $(e.target), tagName;

            ct = $.getTime() - this.startTime;
            this.tapIsStart = false;

            if (!this._isMove(e) && ct < this.delay) {
                $target.trigger("tap");

                /* 修复移动端input点击焦点不更新的问题 */
                tagName = e.target.tagName.toUpperCase();
                if (e.target != this.input) {
                    this.input && this.input.blur();        // 刷新焦点
                }

                if ("INPUT TEXTAREA".split(" ").indexOf(tagName) >= 0) {
                    this.input = e.target;                  // 更新绑定元素

                    var type = $target.attr("type");

                    type = type ? type.toUpperCase() : type;

                    if (type && "RADIO CHECKBOX".split(" ").indexOf(type) >= 0) {
                        if (this.input.checked == true) {
                            $target.removeClass("checked");
                        } else {
                            if (type == "CHECKBOX") {
                                $target.addClass("checked");
                            }
                        }
                    }
                }

                this.disClick = false;
                $target.trigger("click");
            }

            setTimeout(function() {
                var path = fixPath(e);
                for(var i = 0; i<path.length; i++) {
                    var $item = $(path[i]);
                    clearActive($item);
                }
            }, this.delayClass);
        },

        handleEvent: function (e) {
            switch ( e.type ) {
                case 'touchstart':
                case 'pointerdown':
                case 'MSPointerDown':
                case 'mousedown':
                    this._start(e);
                    break;
                case 'touchmove':
                case 'pointermove':
                case 'MSPointerMove':
                case 'mousemove':
                    this._move(e);
                    break;
                case 'touchend':
                case 'pointerup':
                case 'MSPointerUp':
                case 'mouseup':
                case 'touchcancel':
                case 'pointercancel':
                case 'MSPointerCancel':
                case 'mousecancel':
                    this._end(e);
                    break;
            }
        }
    };

    /**
     *
     * 事件初始化，同时修复移动端 点击穿透问题，原理如下:
     *
     * 移动端的click都是touch之后，才会模拟触发。触发的顺序如下:
     *
     * touchstart -> touchmove -> touchend ->
     * mousedown  -> mousemove -> mouseenter -> click
     *
     * 在重叠的区域里，被遮盖的元素绑定click，遮盖的元素绑定touch事件，
     * 且touch后遮盖的元素会隐藏的话，就会造成穿透，因为click是在touch
     * 之后延迟触发的，浏览器会误认为是在遮盖的元素上触发了click。
     *
     */
    ;(function() {
        var kt = window.ontouchstart !== undefined ? 0 : 1,
            doc = document, bind = "addEventListener";

        /* 修复input 焦点问题 */
        doc[bind]("focus", tap._focus, true)

        doc[bind]("mousedown", tap);
        doc[bind]("mousemove", tap);
        doc[bind]("mouseup", tap);
        doc[bind]("mousecancel", tap);

        doc[bind]("touchstart", tap);
        doc[bind]("touchmove", tap);
        doc[bind]("touchend", tap);
        doc[bind]("touchcancel", tap);

        doc[bind]("pointerdown", tap);
        doc[bind]("pointermove", tap);
        doc[bind]("pointerup", tap);
        doc[bind]("pointercancel", tap);

        doc[bind]("MSPointerDown", tap);
        doc[bind]("MSPointerMove", tap);
        doc[bind]("MSPointerUp", tap);
        doc[bind]("MSPointerCancel", tap);

        /* 修复鼠标点透问题 */
        if (kt === 0 /* 只有支持 touch 事件时才绑定 */) {
            $document.on("click", function(e) {
                if (!tap.disClick &&
                    (e.timeStamp - tap.startTime) < 300) {

                    /* 临时修复 自定义 click 事件无法触发 input 默认点击效果 BUG */
                    if (e.target.tagName.toUpperCase() == "INPUT") {
                        var tar  = e.target,
                            type = $(tar).attr("type");

                        type = type ? type.toUpperCase() : type;

                        if (type && "RADIO CHECKBOX".split(" ").indexOf(type) >= 0) {
                            if (tar.checked === false) {
                                tar.checked = true;
                            } else if (type == "CHECKBOX") {
                                tar.checked = false;
                            }
                        }
                    }

                    tap.disClick = true;
                } else {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                }
            }, true);
        }
    })(window, tap);
});<|endoftext|>


<commit_before>(function (window) {

  /**
   * Creates a balancer instance.
   *
   * Terminology:
   *
   *    master:
   *
   *       When the user begins to alter a slider, this slider is
   *       designated the master. A slider is only a master while the
   *       user is making a change.
   *
   *    subordinates:
   *
   *       When the user is altering a slider value, all of the other
   *       sliders, minus those which are disabled, are considered
   *       subordinates. The subordinates are the sliders whose values
   *       are changes to ensure that the group remains balanced.
   */
  function Balancer (options) {
    _.bindAll(this, 'onBegin', 'onChange', 'onCommit', 'onAbort');

    this.options   = _.clone(options || {});
    this.max       = this.__getMax();
    this.precision = 1;

    this.views      = [];
    this.quinns     = [];
    this.quinnOrder = [];

    this.masterId     = null;
    this.subordinates = null;
  }

  // Holds balancer instances so that InputElements may automatically
  // add themselves to the correct balancer when initialzed.
  Balancer.balancers = {};

  // Gets the Balancer with the given name. If one does not exist, it
  // will be created with the given options.
  Balancer.get = function (name, options) {
    if (! Balancer.balancers[name]) {
      Balancer.balancers[name] = new Balancer(options);
    }

    return Balancer.balancers[name];
  };

  // Balancer triggers Quinn-like begin, change, commit, and abort events when
  // the master slider is adjusted.
  _.extend(Balancer.prototype, Backbone.Events);

  /**
   * ### add
   *
   * Given an initialized InputElementView, adds the view to the group and
   * will balance the slider value when it, or other sliders, have their value
   * changed.
   *
   * Note that add expects an InputElementView, not an InputElement.
   */
  Balancer.prototype.add = function (inputView) {
    var quinn = inputView.quinn;

    quinn.balanceId = _.uniqueId('balance_');

    quinn.bind('begin',  this.onBegin);
    quinn.bind('change', this.onChange);
    quinn.bind('commit', this.onCommit);
    quinn.bind('abort',  this.onAbort);

    this.views.push(inputView);
    this.quinns.push(inputView.quinn);
    this.quinnOrder.push(quinn.balanceId);

    this.max       = this.__getMax();
    this.precision = this.__getPrecision();

    return this;
  };

  /**
   * Triggered when the user alteres a slider; performs balancing of the
   * subordinates.
   */
  Balancer.prototype.doBalance = function (newValue, quinn) {
    var iterations    = 20,
        amountChanged = newValue - quinn.value,

        originalValues, originalQuinns,

        flexPerSlider, flex, sliders, sLength, slider,
        previousFlex, nextIterationSliders, i;

    // Return quickly; if the amount changed is larger than max, then the
    // change is impossible
    if (amountChanged > this.max) {
      return false;
    }

    // Determine which sliders can be altered; of the subordinates, some may
    // already be at their maximum or minimum values, so there's no point
    // changing those.
    sliders = _.select(this.subordinates, function (sub) {
      if (amountChanged >= 0) {
        // Master slider is being increased.
        return (sub.value > sub.selectable[0]);
      } else {
        // Master slider is being reduced.
        return (sub.value < sub.selectable[1]);
      }
    });

    if ((sLength = sliders.length) === 0) {
      // All subordinates are at their min or max value; the change is not
      // possible.
      return false;
    }

    originalQuinns = sliders;
    originalValues = this.__originalValues(sliders);

    // Flex is the balance maximum value, minus the value of those sliders
    // which may be altered.
    // Flex is the amount of "value" which needs to be adjusted for. e.g.
    //
    //    max: 100
    //    slider 1: 0
    //    slider 2: 100
    //
    //  If slider 1 is moved to 25, the Flex is -25, since in order to balance
    //  the sliders we need to subtract 25 from the subordinate sliders.
    //
    flex = _.sum(_.pluck(sliders, 'value')) + newValue;
    flex = this.snapValue(this.max - flex);

    while (iterations--) {
      nextIterationSliders = [];

      for (i = 0; i < sLength; i++) {
        // The amount of flex to be given to each slider. Calculated each time
        // we balance a slider since the previous slider may have used up all
        // of the flex if it was less than the smallest slider step value (with
        // a step value of 0.1, in a three-slider group, a per-subordinate
        // change of 0.05 would round UP to 0.1, leaving no flex for the
        // second slider).
        flexPerSlider = this.snapValue(flex / (sLength - i));

        slider    = sliders[i];
        prevValue = slider.value;

        if (slider.__setValue(prevValue + flexPerSlider)) {
          this.__sliderUsed(slider);
        }

        // Reduce the flex by the amount by which the slider was changed, in
        // case more iterations are required.
        flex = this.snapValue(flex - (slider.value - prevValue));

        // Finally, if this slider still can be moved further as it isn't at
        // it's min or max selectable value, it may be used again in the next
        // iteration.
        if ((flex < 0 && slider.value !== slider.selectable[0]) ||
            (flex > 0 && slider.value !== slider.selectable[1])) {
          nextIterationSliders.push(slider);
        } else if (flex === 0) {
          break; // We're done!
        }

      }

      sliders = nextIterationSliders;
      sLength = sliders.length;

      // Can't go any further; either we used up all the flex successfully
      // balancing the sliders, or the flex was unchanged from the previous
      // iterations, in which case balancing isn't possible.
      if (this.snapValue(flex) === 0.0 || previousFlex === flex) {
        break;
      }

      previousFlex = flex;
    }

    if (this.snapValue(flex) !== 0.0) {
      this.__revert(originalQuinns, originalValues);
      return false;
    }
  };

  /**
   * Returns the sum of all of the slider values.
   */
  Balancer.prototype.getSum = function () {
    return _.reduce(this.quinns, function (sum, quinn) {
      return sum + quinn.value;
    });
  };

  /**
   * Given a float, "snaps" it to match the precision (i.e. if the smallest
   * step value of any of the balanced sliders is 0.1, it will round the
   * number to the nearest 0.1). Used heavily in doBalance to account for the
   * _vast_ number of floating point errors which occur when a group contains
   * three or more sliders.
   */
  Balancer.prototype.snapValue = function (value) {
    return parseFloat(value.toFixed(this.precision));
  };

  // ### Events.

  Balancer.prototype.onBegin = function (value, quinn) {
    // If no slider is already being adjusted, then this is when the
    // user begins moving a slider.
    if (this.masterId === null) {
      this.masterId     = quinn.balanceId;
      this.subordinates = this.__getSubordinates();

      this.__runOnSubordinates('__willChange');

      this.trigger('begin');
    }

    // Otherwise is is an onBegin event being fired when a subordinate
    // slider is being balanced; ignore it.
  };

  Balancer.prototype.onChange = function (value, quinn) {
    // Fired when a slider value is changed; we only want to track
    // changes to the master slider, as the onChange event for the
    // subordinates should be ignored.
    if (this.isMaster(quinn)) {
      if (this.doBalance(value, quinn) === false) {
        // Can't do the balance, so we prevent the slider movement.
        return false;
      }

      this.trigger('change');
    }
  };

  Balancer.prototype.onCommit = function (value, quinn) {
    if (this.isMaster(quinn)) {
      this.__finish('__hasChanged');
      this.trigger('commit');
    }
  };

  Balancer.prototype.onAbort = function (value, quinn) {
    if (this.isMaster(quinn)) {
      this.__finish('__abortChange');
      this.trigger('abort');
    }
  };

  // ### Pseudo-Private Methods.

  /**
   * Returns all the sliders except the one with which the user is
   * interacting, minus those which are disabled.
   */
  Balancer.prototype.__getSubordinates = function () {
    var self = this, subs;

    subs = _.select(this.quinns, function (quinn) {
      return (! self.isMaster(quinn) && ! quinn.isDisabled)
    });

    if (this.quinns.length <= 2) {
      return subs;
    }

    // The subs are now re-ordered so that the most recently used sliders are
    // at the end of the array. Otherwise doBalance is somewhat biased towards
    // the first slider.
    return _.sortBy(subs, function (quinn) {
      return _.indexOf(self.quinnOrder, quinn.balanceId);
    });
  };

  /**
   * Marks a slider as used, pushing it to the back of the quinnOrder array.
   */
  Balancer.prototype.__sliderUsed = function (quinn) {
    if (this.quinns.length <= 2) {
      return true;
    }

    var balanceId = quinn.balanceId;

    this.quinnOrder = _.without(this.quinnOrder, balanceId)
    this.quinnOrder.push(balanceId);
  };

  /**
   * #### _getMax
   *
   * Returns the maximum permitted cumulative value of the sliders which are
   * members of the group. This is cached as this.max.
   */
  Balancer.prototype.__getMax = function () {
    if (this.options.max) {
      return this.options.max;
    }

    // No explicit max, just use the total of all the sliders (no balancing).
    return _.reduce(this.views, function (sum, view) {
      return (sum + view.quinn.range[1]);
    }, 0) / this.views.length;
  };

  /**
   * Since many sliders use a fractional step value, and floating point
   * arithmetic is a nightmare, determine the number of decimal places used in
   * the smallest slider step value, so that we can do all balancing using
   * integers.
   */
  Balancer.prototype.__getPrecision = function () {
    var precision = _.max(_.map(this.quinns, function (quinn) {
      var strStep = quinn.options.step.toString().split('.');
      return parseInt((strStep[1] || '').length, 10);
    }));

    return precision || 0;
  };

  /**
   * Finishes editing slider values by resetting this.masterId and
   * this.subordinates, and calling 'method' on the subordinates.
   */
  Balancer.prototype.__finish = function (method) {
    this.__runOnSubordinates(method);

    this.masterId     = null;
    this.subordinates = null;
  };

  /**
   * Runs a method on all the subordinates.
   */
  Balancer.prototype.__runOnSubordinates = function (method) {
    var subLength = this.subordinates.length, i;

    for (i = 0; i < subLength; i++) {
      this.subordinates[i][method]();
    }
  };

  /**
   * Given an array of sliders, returns an object with their values keyed on
   * the balance ID assigned to the slider.
   */
  Balancer.prototype.__originalValues = function (quinns) {
    var values  = {},
        qLength = quinns.length,
        i;

    for (i = 0; i < qLength; i++) {
      values[quinns[i].balanceId] = quinns[i].value;
    }

    return values;
  };

  /**
   * Given an array of subordinates, and a hash of balance IDs and values,
   * restores each slider to it's value prior to being changed.
   *
   * Used when balancing fails.
   */
  Balancer.prototype.__revert = function (quinns, values) {
    var length = quinns.length, i;

    for (i = 0; i < length; i++) {
      quinns[i].__setValue(values[quinns[i].balanceId]);
    }
  };

  /**
   * Returns if the given Quinn instance is the current master slider.
   */
  Balancer.prototype.isMaster = function (quinn) {
    return (this.masterId !== null &&
            this.masterId === quinn.balanceId)
  };

  // ## Globals
  window.InputElement.Balancer = Balancer;

})(window);
<commit_msg>Fix some JSLint issues.<commit_after>(function (window) {
  'use strict';

  /**
   * Creates a balancer instance.
   *
   * Terminology:
   *
   *    master:
   *
   *       When the user begins to alter a slider, this slider is
   *       designated the master. A slider is only a master while the
   *       user is making a change.
   *
   *    subordinates:
   *
   *       When the user is altering a slider value, all of the other
   *       sliders, minus those which are disabled, are considered
   *       subordinates. The subordinates are the sliders whose values
   *       are changes to ensure that the group remains balanced.
   */
  function Balancer (options) {
    _.bindAll(this, 'onBegin', 'onChange', 'onCommit', 'onAbort');

    this.options   = _.clone(options || {});
    this.max       = this.__getMax();
    this.precision = 1;

    this.views      = [];
    this.quinns     = [];
    this.quinnOrder = [];

    this.masterId     = null;
    this.subordinates = null;
  }

  // Holds balancer instances so that InputElements may automatically
  // add themselves to the correct balancer when initialzed.
  Balancer.balancers = {};

  // Gets the Balancer with the given name. If one does not exist, it
  // will be created with the given options.
  Balancer.get = function (name, options) {
    if (! Balancer.balancers[name]) {
      Balancer.balancers[name] = new Balancer(options);
    }

    return Balancer.balancers[name];
  };

  // Balancer triggers Quinn-like begin, change, commit, and abort events when
  // the master slider is adjusted.
  _.extend(Balancer.prototype, Backbone.Events);

  /**
   * ### add
   *
   * Given an initialized InputElementView, adds the view to the group and
   * will balance the slider value when it, or other sliders, have their value
   * changed.
   *
   * Note that add expects an InputElementView, not an InputElement.
   */
  Balancer.prototype.add = function (inputView) {
    var quinn = inputView.quinn;

    quinn.balanceId = _.uniqueId('balance_');

    quinn.bind('begin',  this.onBegin);
    quinn.bind('change', this.onChange);
    quinn.bind('commit', this.onCommit);
    quinn.bind('abort',  this.onAbort);

    this.views.push(inputView);
    this.quinns.push(inputView.quinn);
    this.quinnOrder.push(quinn.balanceId);

    this.max       = this.__getMax();
    this.precision = this.__getPrecision();

    return this;
  };

  /**
   * Triggered when the user alteres a slider; performs balancing of the
   * subordinates.
   */
  Balancer.prototype.doBalance = function (newValue, quinn) {
    var iterations    = 20,
        amountChanged = newValue - quinn.value,

        originalValues, originalQuinns,

        flexPerSlider, flex, sliders, sLength, slider, prevValue,
        previousFlex, nextIterationSliders, i;

    // Return quickly; if the amount changed is larger than max, then the
    // change is impossible
    if (amountChanged > this.max) {
      return false;
    }

    // Determine which sliders can be altered; of the subordinates, some may
    // already be at their maximum or minimum values, so there's no point
    // changing those.
    sliders = _.select(this.subordinates, function (sub) {
      if (amountChanged >= 0) {
        // Master slider is being increased.
        return (sub.value > sub.selectable[0]);
      } else {
        // Master slider is being reduced.
        return (sub.value < sub.selectable[1]);
      }
    });

    if ((sLength = sliders.length) === 0) {
      // All subordinates are at their min or max value; the change is not
      // possible.
      return false;
    }

    originalQuinns = sliders;
    originalValues = this.__originalValues(sliders);

    // Flex is the balance maximum value, minus the value of those sliders
    // which may be altered.
    // Flex is the amount of "value" which needs to be adjusted for. e.g.
    //
    //    max: 100
    //    slider 1: 0
    //    slider 2: 100
    //
    //  If slider 1 is moved to 25, the Flex is -25, since in order to balance
    //  the sliders we need to subtract 25 from the subordinate sliders.
    //
    flex = _.sum(_.pluck(sliders, 'value')) + newValue;
    flex = this.snapValue(this.max - flex);

    while (iterations--) {
      nextIterationSliders = [];

      for (i = 0; i < sLength; i++) {
        // The amount of flex to be given to each slider. Calculated each time
        // we balance a slider since the previous slider may have used up all
        // of the flex if it was less than the smallest slider step value (with
        // a step value of 0.1, in a three-slider group, a per-subordinate
        // change of 0.05 would round UP to 0.1, leaving no flex for the
        // second slider).
        flexPerSlider = this.snapValue(flex / (sLength - i));

        slider    = sliders[i];
        prevValue = slider.value;

        if (slider.__setValue(prevValue + flexPerSlider)) {
          this.__sliderUsed(slider);
        }

        // Reduce the flex by the amount by which the slider was changed, in
        // case more iterations are required.
        flex = this.snapValue(flex - (slider.value - prevValue));

        // Finally, if this slider still can be moved further as it isn't at
        // it's min or max selectable value, it may be used again in the next
        // iteration.
        if ((flex < 0 && slider.value !== slider.selectable[0]) ||
            (flex > 0 && slider.value !== slider.selectable[1])) {
          nextIterationSliders.push(slider);
        } else if (flex === 0) {
          break; // We're done!
        }
      }

      sliders = nextIterationSliders;
      sLength = sliders.length;

      // Can't go any further; either we used up all the flex successfully
      // balancing the sliders, or the flex was unchanged from the previous
      // iterations, in which case balancing isn't possible.
      if (this.snapValue(flex) === 0.0 || previousFlex === flex) {
        break;
      }

      previousFlex = flex;
    }

    if (this.snapValue(flex) !== 0.0) {
      this.__revert(originalQuinns, originalValues);
      return false;
    }
  };

  /**
   * Given a float, "snaps" it to match the precision (i.e. if the smallest
   * step value of any of the balanced sliders is 0.1, it will round the
   * number to the nearest 0.1). Used heavily in doBalance to account for the
   * _vast_ number of floating point errors which occur when a group contains
   * three or more sliders.
   */
  Balancer.prototype.snapValue = function (value) {
    return parseFloat(value.toFixed(this.precision));
  };

  /**
   * Returns if the given Quinn instance is the current master slider.
   */
  Balancer.prototype.isMaster = function (quinn) {
    return (this.masterId !== null &&
            this.masterId === quinn.balanceId);
  };

  // ### Events.

  Balancer.prototype.onBegin = function (value, quinn) {
    // If no slider is already being adjusted, then this is when the
    // user begins moving a slider.
    if (this.masterId === null) {
      this.masterId     = quinn.balanceId;
      this.subordinates = this.__getSubordinates();

      this.__runOnSubordinates('__willChange');

      this.trigger('begin');
    }

    // Otherwise is is an onBegin event being fired when a subordinate
    // slider is being balanced; ignore it.
  };

  Balancer.prototype.onChange = function (value, quinn) {
    // Fired when a slider value is changed; we only want to track
    // changes to the master slider, as the onChange event for the
    // subordinates should be ignored.
    if (this.isMaster(quinn)) {
      if (this.doBalance(value, quinn) === false) {
        // Can't do the balance, so we prevent the slider movement.
        return false;
      }

      this.trigger('change');
    }
  };

  Balancer.prototype.onCommit = function (value, quinn) {
    if (this.isMaster(quinn)) {
      this.__finish('__hasChanged');
      this.trigger('commit');
    }
  };

  Balancer.prototype.onAbort = function (value, quinn) {
    if (this.isMaster(quinn)) {
      this.__finish('__abortChange');
      this.trigger('abort');
    }
  };

  // ### Pseudo-Private Methods.

  /**
   * Returns all the sliders except the one with which the user is
   * interacting, minus those which are disabled.
   */
  Balancer.prototype.__getSubordinates = function () {
    var self = this, subs;

    subs = _.select(this.quinns, function (quinn) {
      return (! self.isMaster(quinn) && ! quinn.isDisabled);
    });

    if (this.quinns.length <= 2) {
      return subs;
    }

    // The subs are now re-ordered so that the most recently used sliders are
    // at the end of the array. Otherwise doBalance is somewhat biased towards
    // the first slider.
    return _.sortBy(subs, function (quinn) {
      return _.indexOf(self.quinnOrder, quinn.balanceId);
    });
  };

  /**
   * Marks a slider as used, pushing it to the back of the quinnOrder array.
   */
  Balancer.prototype.__sliderUsed = function (quinn) {
    if (this.quinns.length <= 2) {
      return true;
    }

    var balanceId = quinn.balanceId;

    this.quinnOrder = _.without(this.quinnOrder, balanceId);
    this.quinnOrder.push(balanceId);
  };

  /**
   * Returns the maximum permitted cumulative value of the sliders which are
   * members of the group. This is cached as this.max.
   */
  Balancer.prototype.__getMax = function () {
    if (this.options.max) {
      return this.options.max;
    }

    // No explicit max, just use the total of all the sliders (no balancing).
    return _.reduce(this.quinns, function (sum, quinn) {
      return (sum + quinn.range[1]);
    }, 0) / this.quinns.length;
  };

  /**
   * Since many sliders use a fractional step value, and floating point
   * arithmetic is a nightmare, determine the number of decimal places used in
   * the smallest slider step value, so that we can do all balancing using
   * integers.
   */
  Balancer.prototype.__getPrecision = function () {
    var precision = _.max(_.map(this.quinns, function (quinn) {
      var strStep = quinn.options.step.toString().split('.');
      return parseInt((strStep[1] || '').length, 10);
    }));

    return precision || 0;
  };

  /**
   * Finishes editing slider values by resetting this.masterId and
   * this.subordinates, and calling 'method' on the subordinates.
   */
  Balancer.prototype.__finish = function (method) {
    this.__runOnSubordinates(method);

    this.masterId     = null;
    this.subordinates = null;
  };

  /**
   * Runs a method on all the subordinates.
   */
  Balancer.prototype.__runOnSubordinates = function (method) {
    var subLength = this.subordinates.length, i;

    for (i = 0; i < subLength; i++) {
      this.subordinates[i][method]();
    }
  };

  /**
   * Given an array of sliders, returns an object with their values keyed on
   * the balance ID assigned to the slider.
   */
  Balancer.prototype.__originalValues = function (quinns) {
    var values  = {},
        qLength = quinns.length,
        i;

    for (i = 0; i < qLength; i++) {
      values[quinns[i].balanceId] = quinns[i].value;
    }

    return values;
  };

  /**
   * Given an array of subordinates, and a hash of balance IDs and values,
   * restores each slider to it's value prior to being changed.
   *
   * Used when balancing fails.
   */
  Balancer.prototype.__revert = function (quinns, values) {
    var length = quinns.length, i;

    for (i = 0; i < length; i++) {
      quinns[i].__setValue(values[quinns[i].balanceId]);
    }
  };

  // ## Globals
  window.InputElement.Balancer = Balancer;

})(window);
<|endoftext|>


<commit_before>/*
 * hqModules provides a poor man's module system for js. It is not a module *loader*,
 * only a module *referencer*: "importing" a module doesn't automatically load it as
 * a script to the page; it must already have been loaded with an explict script tag.
 *
 * Modules MUST have a name, and SHOULD be given then name
 * of the javascript file in which they reside, and
 * SHOULD be themselves simple javascript objects.
 *
 * Modules are defined with hqDefine, and "imported" (referenced) with hqImport. Example:
 *
 * Define the module:
 *   // myapp/static/myapp/js/utils.js
 *   hqDefine('myapp/js/utils.js', function () {
 *      var module = {};
 *      module.util1 = function () {...};
 *      module.util2 = function () {...};
 *      return module;
 *   });
 *
 * Include the module source on your page:
 *   // myapp/templates/myapp/page.html
 *   ...
 *   <script src="myapp/js/utils.js"></script>
 *   ...
 *
 * Reference the module in other code
 * (either directly in the template or in another file/module):
 *
 *   var utils = hqImport('myapp/js/utils.js');
 *   ... utils.util1() ...
 *   ... utils.util2() ...
 *
 * You can also use the following idiom to effectively import
 * only one property or function:
 *
 *   var util1 = hqImport('myapp/js/utils.js').util1;
 */

var COMMCAREHQ_MODULES = {};

function hqDefine(path, moduleAccessor) {
    var parts = ['COMMCAREHQ_MODULES'].concat(path.split('/'));
    var i;
    var object = window;
    for (i = 0; i < parts.length - 1; i += 1) {
        if (typeof object[parts[i]] === 'undefined') {
            object[parts[i]] = {};
        }
        object = object[parts[i]];
    }
    if (typeof object[parts[i]] !== 'undefined') {
        throw new Error("The module '" + path + "' has already been defined elsewhere.");
    }
    object[parts[i]] = moduleAccessor();
}

function hqImport(path) {
    var parts = ['COMMCAREHQ_MODULES'].concat(path.split('/'));
    var i;
    var object = window;
    for (i = 0; i < parts.length; i += 1) {
        object = object[parts[i]];
    }
    return object;
}
<commit_msg>simplify hqModules code slightly<commit_after>/*
 * hqModules provides a poor man's module system for js. It is not a module *loader*,
 * only a module *referencer*: "importing" a module doesn't automatically load it as
 * a script to the page; it must already have been loaded with an explict script tag.
 *
 * Modules MUST have a name, and SHOULD be given then name
 * of the javascript file in which they reside, and
 * SHOULD be themselves simple javascript objects.
 *
 * Modules are defined with hqDefine, and "imported" (referenced) with hqImport. Example:
 *
 * Define the module:
 *   // myapp/static/myapp/js/utils.js
 *   hqDefine('myapp/js/utils.js', function () {
 *      var module = {};
 *      module.util1 = function () {...};
 *      module.util2 = function () {...};
 *      return module;
 *   });
 *
 * Include the module source on your page:
 *   // myapp/templates/myapp/page.html
 *   ...
 *   <script src="myapp/js/utils.js"></script>
 *   ...
 *
 * Reference the module in other code
 * (either directly in the template or in another file/module):
 *
 *   var utils = hqImport('myapp/js/utils.js');
 *   ... utils.util1() ...
 *   ... utils.util2() ...
 *
 * You can also use the following idiom to effectively import
 * only one property or function:
 *
 *   var util1 = hqImport('myapp/js/utils.js').util1;
 */

var COMMCAREHQ_MODULES = {};

function hqDefine(path, moduleAccessor) {
    var parts = path.split('/');
    var i;
    var object = COMMCAREHQ_MODULES;
    for (i = 0; i < parts.length - 1; i += 1) {
        if (typeof object[parts[i]] === 'undefined') {
            object[parts[i]] = {};
        }
        object = object[parts[i]];
    }
    if (typeof object[parts[i]] !== 'undefined') {
        throw new Error("The module '" + path + "' has already been defined elsewhere.");
    }
    object[parts[i]] = moduleAccessor();
}

function hqImport(path) {
    var parts = path.split('/');
    var i;
    var object = COMMCAREHQ_MODULES;
    for (i = 0; i < parts.length; i += 1) {
        object = object[parts[i]];
    }
    return object;
}
<|endoftext|>


<commit_before>if (typeof(OpenM_SSOConnectionProxy) === 'undefined') {
    var OpenM_SSOConnectionProxy = {
        url: "",
        session_mode: "",
        api_selected: "",
        alreadyHaveConnectionOK: false,
        alreadyHaveTryingReconnection: false,
        MODE_PARAMETER: "API_SESSION_MODE",
        API_SELECTION_PARAMETER: "API",
        MODE_API_SELECTION: "API_SELECTION",
        MODE_ALL_API: "ALL_API",
        MODE_WITHOUT_API: "WITHOUT_API",
        ACTION_PARAMETER: "ACTION",
        IS_CONNECTED_ACTION: "isConnected",
        RETURN_IS_CONNECTED_PARAMETER: "isConnected",
        LOGIN_ACTION: "login",
        RETURN_TO_PARAMETER: "proxy_return_to",
        DASH: "! !",
        connected: false,
        frame: undefined,
        waitingConnectionInterval: 1000,
        waitingConnectionTimeOut: 120,
        waitingReConnectionTimeOut: 30,
        waitingConnectionInProgress: false,
        waitingReConnectionInProgress: false,
        redirectionToLoginFormEnabled: false,
        login: function() {
            if (this.connected)
                return;
            var u = this.url + "?" + this.MODE_PARAMETER + "=" + this.session_mode + "&" + this.API_SELECTION_PARAMETER + "=" + this.api_selected + "&" + this.ACTION_PARAMETER + "=" + this.LOGIN_ACTION + "&" + this.RETURN_TO_PARAMETER + "=" + encodeURI(window.location.href.replace("#", this.DASH));
            window.location = u;
        },
        open: function() {
            if (this.redirectionToLoginFormEnabled)
                return this.login();
            if (this.connected)
                return;
            if (this.waitingConnectionInProgress && typeof(this.frame) !== 'undefined' && !this.frame.closed)
                return;
            var width = 450;
            var height = 450;
            var left = (screen.width - width) / 2;
            var top = (screen.height - height) / 2;
            this.frame = window.open(this.url + "?" + this.MODE_PARAMETER + "=" + this.session_mode + "&" + this.API_SELECTION_PARAMETER + "=" + this.api_selected, "popup", "toolbar=0, location=0, directories=0, status=0, scrollbars=0, resizable=0, copyhistory=0, height=" + height + ", width=" + width + ", top=" + top + ", left=" + left + "");
            if (this.waitingConnectionInProgress)
                return;
            var c = this;
            var t;
            c.waitingConnectionInProgress = true;
            var i = setInterval(function() {
                if (typeof(c.frame) !== 'undefined' && c.frame.closed) {
                    if (typeof(t) !== 'undefined')
                        clearTimeout(t);
                    c.waitingConnectionInProgress = false;
                    if (typeof(i) !== 'undefined')
                        clearInterval(i);
                }
                c.isConnected(function() {
                    if (c.connected) {
                        clearInterval(i);
                        if (typeof(t) !== 'undefined')
                            clearTimeout(t);
                        if (c.frame !== undefined && typeof(c.frame.close) === 'function')
                            c.frame.close();
                        c.waitingConnectionInProgress = false;
                        c.onReconnectionOK();
                    }
                });
            }, c.waitingConnectionInterval);
            t = setTimeout(function() {
                if (typeof(i) !== 'undefined')
                    clearInterval(i);
                if (c.frame !== undefined && typeof(c.frame.close) === 'function')
                    c.frame.close();
                c.waitingConnectionInProgress = false;
            }, c.waitingConnectionTimeOut * 1000);
        },
        reconnectframe: undefined,
        reconnect: function(loginIfNotConnected) {
            if (!this.alreadyHaveConnectionOK && loginIfNotConnected !== true)
                return;
            if (!this.alreadyHaveConnectionOK && loginIfNotConnected === true && this.alreadyHaveTryingReconnection === true)
                return this.open();
            if (this.waitingReConnectionInProgress)
                return;
            this.connected = false;
            this.alreadyHaveTryingReconnection = true;
            this.reconnectframe = $(document.createElement("iframe"));
            this.reconnectframe.attr("src", this.url + "?" + this.MODE_PARAMETER + "=" + this.session_mode + "&" + this.API_SELECTION_PARAMETER + "=" + this.api_selected);
            this.reconnectframe.attr("position", "absolute");
            this.reconnectframe.attr("right", 0);
            this.reconnectframe.attr("bottom", 0);
            this.reconnectframe.attr("width", 1);
            this.reconnectframe.attr("height", 1);
            this.reconnectframe.attr("border", 0);
            this.reconnectframe.attr("background-color", "transparent");
            $("html body").append(this.reconnectframe);
            var c = this;
            var t;
            c.waitingReConnectionInProgress = true;
            var i = setInterval(function() {
                c.isConnected(function() {
                    if (c.connected) {
                        clearInterval(i);
                        if (typeof(t) !== 'undefined')
                            clearTimeout(t);
                        if (c.reconnectframe !== undefined)
                            c.reconnectframe.remove();
                        c.waitingReConnectionInProgress = false;
                        c.onReconnectionOK();
                    }
                });
            }, c.waitingConnectionInterval);
            t = setTimeout(function() {
                if (typeof(i) !== 'undefined')
                    clearInterval(i);
                if (c.reconnectframe !== undefined && typeof(c.reconnectframe.close) === 'function')
                    c.reconnectframe.close();
                c.waitingReConnectionInProgress = false;
            }, c.waitingReConnectionTimeOut * 1000);
        },
        isConnected: function(callBack, synchro) {
            var c = this;
            if (synchro === undefined)
                synchro = false;
            var cb = callBack;
            var a = {
                async: !synchro,
                type: 'POST',
                data: {},
                url: this.url + "?" + this.MODE_PARAMETER + "=" + this.session_mode + "&" + this.API_SELECTION_PARAMETER + "=" + this.api_selected,
                dataType: 'json',
                success: function(data) {
                    if (data[c.RETURN_IS_CONNECTED_PARAMETER] === 1) {
                        c.connected = true;
                        c.alreadyHaveConnectionOK = true;
                    }
                    if (cb !== undefined)
                        cb();
                }
            };
            a.data[this.ACTION_PARAMETER] = this.IS_CONNECTED_ACTION;
            $.ajax(a);
            return this.connected;
        },
        reconnectionOkListeners: new Array(),
        addReconnectionOKListener: function(listener) {
            this.reconnectionOkListeners.push(listener);
        },
        onReconnectionOK: function() {
            $.each(this.reconnectionOkListeners, function(key, value) {
                if (typeof(value) === 'function')
                    value();
            });
        }
    };
}<commit_msg>bugfix to unique reconnection frame opened<commit_after>if (typeof(OpenM_SSOConnectionProxy) === 'undefined') {
    var OpenM_SSOConnectionProxy = {
        url: "",
        session_mode: "",
        api_selected: "",
        alreadyHaveConnectionOK: false,
        alreadyHaveTryingReconnection: false,
        MODE_PARAMETER: "API_SESSION_MODE",
        API_SELECTION_PARAMETER: "API",
        MODE_API_SELECTION: "API_SELECTION",
        MODE_ALL_API: "ALL_API",
        MODE_WITHOUT_API: "WITHOUT_API",
        ACTION_PARAMETER: "ACTION",
        IS_CONNECTED_ACTION: "isConnected",
        RETURN_IS_CONNECTED_PARAMETER: "isConnected",
        LOGIN_ACTION: "login",
        RETURN_TO_PARAMETER: "proxy_return_to",
        DASH: "! !",
        connected: false,
        frame: undefined,
        waitingConnectionInterval: 1000,
        waitingConnectionTimeOut: 120,
        waitingReConnectionTimeOut: 30,
        waitingConnectionInProgress: false,
        waitingReConnectionInProgress: false,
        redirectionToLoginFormEnabled: false,
        login: function() {
            if (this.connected)
                return;
            var u = this.url + "?" + this.MODE_PARAMETER + "=" + this.session_mode + "&" + this.API_SELECTION_PARAMETER + "=" + this.api_selected + "&" + this.ACTION_PARAMETER + "=" + this.LOGIN_ACTION + "&" + this.RETURN_TO_PARAMETER + "=" + encodeURI(window.location.href.replace("#", this.DASH));
            window.location = u;
        },
        open: function() {
            if (this.redirectionToLoginFormEnabled)
                return this.login();
            if (this.connected)
                return;
            if (this.waitingConnectionInProgress && typeof(this.frame) !== 'undefined' && !this.frame.closed)
                return;
            var width = 450;
            var height = 450;
            var left = (screen.width - width) / 2;
            var top = (screen.height - height) / 2;
            this.frame = window.open(this.url + "?" + this.MODE_PARAMETER + "=" + this.session_mode + "&" + this.API_SELECTION_PARAMETER + "=" + this.api_selected, "popup", "toolbar=0, location=0, directories=0, status=0, scrollbars=0, resizable=0, copyhistory=0, height=" + height + ", width=" + width + ", top=" + top + ", left=" + left + "");
            if (this.waitingConnectionInProgress)
                return;
            var c = this;
            var t;
            c.waitingConnectionInProgress = true;
            var i = setInterval(function() {
                if (typeof(c.frame) !== 'undefined' && c.frame.closed) {
                    if (typeof(t) !== 'undefined')
                        clearTimeout(t);
                    c.waitingConnectionInProgress = false;
                    if (typeof(i) !== 'undefined')
                        clearInterval(i);
                }
                c.isConnected(function() {
                    if (c.connected) {
                        clearInterval(i);
                        if (typeof(t) !== 'undefined')
                            clearTimeout(t);
                        if (c.frame !== undefined && typeof(c.frame.close) === 'function')
                            c.frame.close();
                        c.waitingConnectionInProgress = false;
                        c.onReconnectionOK();
                    }
                });
            }, c.waitingConnectionInterval);
            t = setTimeout(function() {
                if (typeof(i) !== 'undefined')
                    clearInterval(i);
                if (c.frame !== undefined && typeof(c.frame.close) === 'function')
                    c.frame.close();
                c.waitingConnectionInProgress = false;
            }, c.waitingConnectionTimeOut * 1000);
        },
        reconnectframe: undefined,
        reconnectionCheckInterval: undefined,
        reconnectionTimeOut: undefined,
        reconnect: function(loginIfNotConnected) {
            if (!this.alreadyHaveConnectionOK && loginIfNotConnected !== true)
                return;
            if (!this.alreadyHaveConnectionOK && loginIfNotConnected === true && this.alreadyHaveTryingReconnection === true)
                return this.open();
            if (this.waitingReConnectionInProgress)
                return;
            this.connected = false;
            this.alreadyHaveTryingReconnection = true;
            clearInterval(this.reconnectionCheckInterval);
            clearTimeout(this.reconnectionTimeOut);
            if (this.reconnectframe !== undefined)
                this.reconnectframe.remove();
            this.reconnectframe = $(document.createElement("iframe"));
            this.reconnectframe.attr("src", this.url + "?" + this.MODE_PARAMETER + "=" + this.session_mode + "&" + this.API_SELECTION_PARAMETER + "=" + this.api_selected);
            this.reconnectframe.attr("position", "absolute");
            this.reconnectframe.attr("right", 0);
            this.reconnectframe.attr("bottom", 0);
            this.reconnectframe.attr("width", 1);
            this.reconnectframe.attr("height", 1);
            this.reconnectframe.attr("border", 0);
            this.reconnectframe.attr("background-color", "transparent");
            $("html body").append(this.reconnectframe);
            var c = this;
            var t = c.reconnectionTimeOut;
            c.waitingReConnectionInProgress = true;
            var i = c.reconnectionCheckInterval;
            i = setInterval(function() {
                c.isConnected(function() {
                    if (c.connected) {
                        clearInterval(i);
                        if (typeof(t) !== 'undefined')
                            clearTimeout(t);
                        if (c.reconnectframe !== undefined)
                            c.reconnectframe.remove();
                        c.waitingReConnectionInProgress = false;
                        c.onReconnectionOK();
                    }
                });
            }, c.waitingConnectionInterval);
            t = setTimeout(function() {
                if (typeof(i) !== 'undefined')
                    clearInterval(i);
                if (c.reconnectframe !== undefined && typeof(c.reconnectframe.close) === 'function')
                    c.reconnectframe.close();
                c.waitingReConnectionInProgress = false;
            }, c.waitingReConnectionTimeOut * 1000);
        },
        isConnected: function(callBack, synchro) {
            var c = this;
            if (synchro === undefined)
                synchro = false;
            var cb = callBack;
            var a = {
                async: !synchro,
                type: 'POST',
                data: {},
                url: this.url + "?" + this.MODE_PARAMETER + "=" + this.session_mode + "&" + this.API_SELECTION_PARAMETER + "=" + this.api_selected,
                dataType: 'json',
                success: function(data) {
                    if (data[c.RETURN_IS_CONNECTED_PARAMETER] === 1) {
                        c.connected = true;
                        c.alreadyHaveConnectionOK = true;
                    }
                    if (cb !== undefined)
                        cb();
                }
            };
            a.data[this.ACTION_PARAMETER] = this.IS_CONNECTED_ACTION;
            $.ajax(a);
            return this.connected;
        },
        reconnectionOkListeners: new Array(),
        addReconnectionOKListener: function(listener) {
            this.reconnectionOkListeners.push(listener);
        },
        onReconnectionOK: function() {
            $.each(this.reconnectionOkListeners, function(key, value) {
                if (typeof(value) === 'function')
                    value();
            });
        }
    };
}<|endoftext|>


<commit_before>/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/**
 * Handlers for commands related to file handling (opening, saving, etc.)
 */
var FileCommandHandlers = (function() {
    // TODO: remove this and use the real exports variable when we switch to modules.
    var exports = {};

    var _editor, _title, _currentFilePath, _currentTitlePath,
        _isDirty = false,
        _savedUndoPosition = 0;

    exports.init = function init(editor, title) {
        _editor = editor;
        _title = title;

        _editor.setOption("onChange", function() {
            updateDirty();
        });

        // Register global commands
        CommandManager.register(Commands.FILE_OPEN, handleFileOpen);
        CommandManager.register(Commands.FILE_SAVE, handleFileSave);
        CommandManager.register(Commands.FILE_CLOSE, handleFileClose);
    };

    function updateDirty() {
        // If we've undone past the undo position at the last save, and there is no redo stack,
        // then we can never get back to a non-dirty state.
        var historySize = _editor.historySize();
        if (historySize.undo < _savedUndoPosition && historySize.redo == 0) {
            _savedUndoPosition = -1;
        }
        var newIsDirty = (_editor.historySize().undo != _savedUndoPosition);
        if (_isDirty != newIsDirty) {
            _isDirty = newIsDirty;
            updateTitle();
        }
    }

    function updateTitle() {
        _title.text(
            _currentTitlePath
                ? (_currentTitlePath + (_isDirty ? " \u2022" : ""))
                : "Untitled"
        );
    }

    function handleFileOpen(fullPath) {
        // TODO: In the future, when we implement multiple open files, we won't close the previous file when opening
        // a new one. However, for now, since we only support a single open document, I'm pretending as if we're
        // closing the existing file first. This is so that I can put the code that checks for an unsaved file and
        // prompts the user to save it in the close command, where it belongs. When we implement multiple open files,
        // we can remove this here.
        var result;
        if (_currentFilePath) {
            result = new $.Deferred();
            CommandManager
                .execute(Commands.FILE_CLOSE)
                .done(function() {
                    doOpenWithOptionalPath(fullPath)
                        .done(function() {
                            result.resolve();
                        })
                        .fail(function() {
                            result.reject();
                        });
                })
                .fail(function() {
                    result.reject();
                });
        }
        else {
            result = doOpenWithOptionalPath(fullPath);
        }
        result.always(function() {
            _editor.focus();
        });
        return result;
    }

    function doOpenWithOptionalPath(fullPath) {
        var result;
        if (!fullPath) {
            // Prompt the user with a dialog
            // TODO: we're relying on this to not be asynchronous--is that safe?
            NativeFileSystem.showOpenDialog(false, false, "Open File", ProjectManager.getProjectRoot().fullPath,
                ["htm", "html", "js", "css"], function(files) {
                    if (files.length > 0) {
                        result = doOpen(files[0]);
                        return;
                    }
                });
        }
        else {
            result = doOpen(fullPath);
        }
        if (!result)
            result = new $.Deferred();
        return result;
    }

    function doOpen(fullPath) {
        var result = new $.Deferred();
        if (fullPath) {
            var reader = new NativeFileSystem.FileReader();

            // TODO: we should implement something like NativeFileSystem.resolveNativeFileSystemURL() (similar
            // to what's in the standard file API) to get a FileEntry, rather than manually constructing it
            var fileEntry = new NativeFileSystem.FileEntry(fullPath);

            // TODO: it's weird to have to construct a FileEntry just to get a File.
            fileEntry.file(function(file) {
                reader.onload = function(event) {
                    _currentFilePath = _currentTitlePath = fullPath;

                    // In the main toolbar, show the project-relative path (if the file is inside the current project)
                    // or the full absolute path (if it's not in the project).
                    var projectRootPath = ProjectManager.getProjectRoot().fullPath;
                    if (projectRootPath.length > 0 && projectRootPath.charAt(projectRootPath.length - 1) != "/") {
                        projectRootPath += "/";
                    }
                    if (fullPath.indexOf(projectRootPath) == 0) {
                        _currentTitlePath = fullPath.slice(projectRootPath.length);
                        if (_currentTitlePath.charAt(0) == '/') {
                            _currentTitlePath = _currentTitlePath.slice(1);
                        }
                    }

                    // TODO: have a real controller object for the editor
                    _editor.setValue(event.target.result);

                    // Make sure we can't undo back to the previous content.
                    _editor.clearHistory();

                    // This should be 0, but just to be safe...
                    _savedUndoPosition = _editor.historySize().undo;
                    updateDirty();

                    result.resolve();
                };

                reader.onerror = function(event) {
                    showFileOpenError(event.target.error.code, fullPath);
                    result.reject();
                }

                reader.readAsText(file, "utf8");
            },
            function (error) {
                showFileOpenError(event.target.error.code, fullPath);
                result.reject();
            });
        }
        return result;
    }

    function handleFileSave() {
        var result = new $.Deferred();
        if (_currentFilePath && _isDirty) {
            // TODO: we should implement something like NativeFileSystem.resolveNativeFileSystemURL() (similar
            // to what's in the standard file API) to get a FileEntry, rather than manually constructing it
            var fileEntry = new NativeFileSystem.FileEntry(_currentFilePath);

            fileEntry.createWriter(
                function(writer) {
                    writer.onwrite = function() {
                        _savedUndoPosition = _editor.historySize().undo;
                        updateDirty();
                        result.resolve();
                    }
                    writer.onerror = function(event) {
                        showSaveFileError(event.target.error.code, _currentFilePath);
                        result.reject();
                    }
                    writer.write(_editor.getValue());
                },
                function(event) {
                    showSaveFileError(event.target.error.code, _currentFilePath);
                    result.reject();
                }
            );
        }
        else {
            result.resolve();
        }
        result.always(function() {
            _editor.focus();
        });
        return result;
    }

    function handleFileClose() {
        var result = new $.Deferred();
        if (_currentFilePath && _isDirty) {
            brackets.showModalDialog(
                  brackets.DIALOG_ID_SAVE_CLOSE
                , brackets.strings.SAVE_CLOSE_TITLE
                , brackets.strings.format(brackets.strings.SAVE_CLOSE_MESSAGE, _currentTitlePath)
            ).done(function(id) {
                if (id === brackets.DIALOG_BTN_CANCEL) {
                    result.reject();
                }
                else {
                    if (id === brackets.DIALOG_BTN_OK) {
                        CommandManager
                            .execute(Commands.FILE_SAVE)
                            .done(function() {
                                doClose();
                                result.resolve();
                            })
                            .fail(function() {
                                result.reject();
                            });
                    }
                    else {
                        // This is the "Don't Save" case--we can just go ahead and close the file.
                        doClose();
                        result.resolve();
                    }
                }
            });
            result.always(function() {
                _editor.focus();
            });
        }
        else {
            doClose();
            _editor.focus();
            result.resolve();
        }
        return result;
    }

    function doClose() {
        // TODO: When we implement multiple files being open, this will probably change to just
        // dispose of the editor for the current file (and will later change again if we choose to
        // limit the number of open editors).
        _editor.setValue("");
        _editor.clearHistory();
        _currentFilePath = _currentTitlePath = null;
        _savedUndoPosition = 0;
        _isDirty = false;
        updateTitle();
        _editor.focus();
    }

    function showFileOpenError(code, path) {
        brackets.showModalDialog(
              brackets.DIALOG_ID_ERROR
            , brackets.strings.ERROR_OPENING_FILE_TITLE
            , brackets.strings.format(
                    brackets.strings.ERROR_OPENING_FILE
                  , path
                  , getErrorString(code))
        );
    }

    function showSaveFileError(code, path) {
        brackets.showModalDialog(
              brackets.DIALOG_ID_ERROR
            , brackets.strings.ERROR_SAVING_FILE_TITLE
            , brackets.strings.format(
                    brackets.strings.ERROR_SAVING_FILE
                  , path
                  , getErrorString(code))
        );
    }

    function getErrorString(code) {
        // There are a few error codes that we have specific error messages for. The rest are
        // displayed with a generic "(error N)" message.
        var result;

        if (code == FileError.NOT_FOUND_ERR)
            result = brackets.strings.NOT_FOUND_ERR;
        else if (code == FileError.NOT_READABLE_ERR)
            result = brackets.strings.NOT_READABLE_ERR;
        else if (code == FileError.NO_MODIFICATION_ALLOWED_ERR)
            result = brackets.strings.NO_MODIFICATION_ALLOWED_ERR;
        else
            result = brackets.strings.format(brackets.strings.GENERIC_ERROR, code);

        return result;
    }

    return exports;
})();

<commit_msg>Fix deferred bug in doOpenWithOptionalPath()<commit_after>/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/**
 * Handlers for commands related to file handling (opening, saving, etc.)
 */
var FileCommandHandlers = (function() {
    // TODO: remove this and use the real exports variable when we switch to modules.
    var exports = {};

    var _editor, _title, _currentFilePath, _currentTitlePath,
        _isDirty = false,
        _savedUndoPosition = 0;

    exports.init = function init(editor, title) {
        _editor = editor;
        _title = title;

        _editor.setOption("onChange", function() {
            updateDirty();
        });

        // Register global commands
        CommandManager.register(Commands.FILE_OPEN, handleFileOpen);
        CommandManager.register(Commands.FILE_SAVE, handleFileSave);
        CommandManager.register(Commands.FILE_CLOSE, handleFileClose);
    };

    function updateDirty() {
        // If we've undone past the undo position at the last save, and there is no redo stack,
        // then we can never get back to a non-dirty state.
        var historySize = _editor.historySize();
        if (historySize.undo < _savedUndoPosition && historySize.redo == 0) {
            _savedUndoPosition = -1;
        }
        var newIsDirty = (_editor.historySize().undo != _savedUndoPosition);
        if (_isDirty != newIsDirty) {
            _isDirty = newIsDirty;
            updateTitle();
        }
    }

    function updateTitle() {
        _title.text(
            _currentTitlePath
                ? (_currentTitlePath + (_isDirty ? " \u2022" : ""))
                : "Untitled"
        );
    }

    function handleFileOpen(fullPath) {
        // TODO: In the future, when we implement multiple open files, we won't close the previous file when opening
        // a new one. However, for now, since we only support a single open document, I'm pretending as if we're
        // closing the existing file first. This is so that I can put the code that checks for an unsaved file and
        // prompts the user to save it in the close command, where it belongs. When we implement multiple open files,
        // we can remove this here.
        var result;
        if (_currentFilePath) {
            result = new $.Deferred();
            CommandManager
                .execute(Commands.FILE_CLOSE)
                .done(function() {
                    doOpenWithOptionalPath(fullPath)
                        .done(function() {
                            result.resolve();
                        })
                        .fail(function() {
                            result.reject();
                        });
                })
                .fail(function() {
                    result.reject();
                });
        }
        else {
            result = doOpenWithOptionalPath(fullPath);
        }
        result.always(function() {
            _editor.focus();
        });
        return result;
    }

    function doOpenWithOptionalPath(fullPath) {
        var result;
        if (!fullPath) {
            // Prompt the user with a dialog
            // TODO: we're relying on this to not be asynchronous--is that safe?
            NativeFileSystem.showOpenDialog(false, false, "Open File", ProjectManager.getProjectRoot().fullPath,
                ["htm", "html", "js", "css"], function(files) {
                    if (files.length > 0) {
                        result = doOpen(files[0]);
                        return;
                    }
                });
        }
        else {
            result = doOpen(fullPath);
        }
        if (!result)
            result = (new $.Deferred()).reject();
        return result;
    }

    function doOpen(fullPath) {
        var result = new $.Deferred();
        if (fullPath) {
            var reader = new NativeFileSystem.FileReader();

            // TODO: we should implement something like NativeFileSystem.resolveNativeFileSystemURL() (similar
            // to what's in the standard file API) to get a FileEntry, rather than manually constructing it
            var fileEntry = new NativeFileSystem.FileEntry(fullPath);

            // TODO: it's weird to have to construct a FileEntry just to get a File.
            fileEntry.file(function(file) {
                reader.onload = function(event) {
                    _currentFilePath = _currentTitlePath = fullPath;

                    // In the main toolbar, show the project-relative path (if the file is inside the current project)
                    // or the full absolute path (if it's not in the project).
                    var projectRootPath = ProjectManager.getProjectRoot().fullPath;
                    if (projectRootPath.length > 0 && projectRootPath.charAt(projectRootPath.length - 1) != "/") {
                        projectRootPath += "/";
                    }
                    if (fullPath.indexOf(projectRootPath) == 0) {
                        _currentTitlePath = fullPath.slice(projectRootPath.length);
                        if (_currentTitlePath.charAt(0) == '/') {
                            _currentTitlePath = _currentTitlePath.slice(1);
                        }
                    }

                    // TODO: have a real controller object for the editor
                    _editor.setValue(event.target.result);

                    // Make sure we can't undo back to the previous content.
                    _editor.clearHistory();

                    // This should be 0, but just to be safe...
                    _savedUndoPosition = _editor.historySize().undo;
                    updateDirty();

                    result.resolve();
                };

                reader.onerror = function(event) {
                    showFileOpenError(event.target.error.code, fullPath);
                    result.reject();
                }

                reader.readAsText(file, "utf8");
            },
            function (error) {
                showFileOpenError(event.target.error.code, fullPath);
                result.reject();
            });
        }
        return result;
    }

    function handleFileSave() {
        var result = new $.Deferred();
        if (_currentFilePath && _isDirty) {
            // TODO: we should implement something like NativeFileSystem.resolveNativeFileSystemURL() (similar
            // to what's in the standard file API) to get a FileEntry, rather than manually constructing it
            var fileEntry = new NativeFileSystem.FileEntry(_currentFilePath);

            fileEntry.createWriter(
                function(writer) {
                    writer.onwrite = function() {
                        _savedUndoPosition = _editor.historySize().undo;
                        updateDirty();
                        result.resolve();
                    }
                    writer.onerror = function(event) {
                        showSaveFileError(event.target.error.code, _currentFilePath);
                        result.reject();
                    }
                    writer.write(_editor.getValue());
                },
                function(event) {
                    showSaveFileError(event.target.error.code, _currentFilePath);
                    result.reject();
                }
            );
        }
        else {
            result.resolve();
        }
        result.always(function() {
            _editor.focus();
        });
        return result;
    }

    function handleFileClose() {
        var result = new $.Deferred();
        if (_currentFilePath && _isDirty) {
            brackets.showModalDialog(
                  brackets.DIALOG_ID_SAVE_CLOSE
                , brackets.strings.SAVE_CLOSE_TITLE
                , brackets.strings.format(brackets.strings.SAVE_CLOSE_MESSAGE, _currentTitlePath)
            ).done(function(id) {
                if (id === brackets.DIALOG_BTN_CANCEL) {
                    result.reject();
                }
                else {
                    if (id === brackets.DIALOG_BTN_OK) {
                        CommandManager
                            .execute(Commands.FILE_SAVE)
                            .done(function() {
                                doClose();
                                result.resolve();
                            })
                            .fail(function() {
                                result.reject();
                            });
                    }
                    else {
                        // This is the "Don't Save" case--we can just go ahead and close the file.
                        doClose();
                        result.resolve();
                    }
                }
            });
            result.always(function() {
                _editor.focus();
            });
        }
        else {
            doClose();
            _editor.focus();
            result.resolve();
        }
        return result;
    }

    function doClose() {
        // TODO: When we implement multiple files being open, this will probably change to just
        // dispose of the editor for the current file (and will later change again if we choose to
        // limit the number of open editors).
        _editor.setValue("");
        _editor.clearHistory();
        _currentFilePath = _currentTitlePath = null;
        _savedUndoPosition = 0;
        _isDirty = false;
        updateTitle();
        _editor.focus();
    }

    function showFileOpenError(code, path) {
        brackets.showModalDialog(
              brackets.DIALOG_ID_ERROR
            , brackets.strings.ERROR_OPENING_FILE_TITLE
            , brackets.strings.format(
                    brackets.strings.ERROR_OPENING_FILE
                  , path
                  , getErrorString(code))
        );
    }

    function showSaveFileError(code, path) {
        brackets.showModalDialog(
              brackets.DIALOG_ID_ERROR
            , brackets.strings.ERROR_SAVING_FILE_TITLE
            , brackets.strings.format(
                    brackets.strings.ERROR_SAVING_FILE
                  , path
                  , getErrorString(code))
        );
    }

    function getErrorString(code) {
        // There are a few error codes that we have specific error messages for. The rest are
        // displayed with a generic "(error N)" message.
        var result;

        if (code == FileError.NOT_FOUND_ERR)
            result = brackets.strings.NOT_FOUND_ERR;
        else if (code == FileError.NOT_READABLE_ERR)
            result = brackets.strings.NOT_READABLE_ERR;
        else if (code == FileError.NO_MODIFICATION_ALLOWED_ERR)
            result = brackets.strings.NO_MODIFICATION_ALLOWED_ERR;
        else
            result = brackets.strings.format(brackets.strings.GENERIC_ERROR, code);

        return result;
    }

    return exports;
})();

<|endoftext|>


<commit_before>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: 2010 M-Way Solutions GmbH. All rights reserved.
// Creator:   Dominik
// Date:      26.10.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('model.js');

/**
 * @class
 *
 * The root of all views.
 *
 */
M.View = M.Object.extend({

    /**
     * The type of this object.
     *
     * @property {String}
     */
    type: 'M.View',

    /**
     * Generic attribute for views. Some subclasses have a property that is more readable to its context than 'value'.
     * Internally these properties are mapped to 'value'.
     * e.g. in LabelView: value:this.text
     *
     * @property {Object, String}
     */
    value: null,

    /**
     * The path to a content that is bind to the view's value.
     *
     * @property {String, Object}
     */
    contentBinding: null,

    /**
     * An array specifying the view's children.
     *
     * @property {Object}
     */
    childViews: null,

    /**
     * Determines whether this view renders directly to the DOM or just returns its HTML representation.
     *
     * @property {Boolean}
     */
    renderToDOM: YES,

    /**
     * Indicates whether this view currently has the focus or not.
     *
     * @property {Boolean}
     */
    hasFocus: NO,

    /**
     * The id of the View used for the HTML attribute ID.
     */
    id: null,

    /**
     * Indicates whether the view should be displayed inline or not.
     */
    isInline: NO,

    /**
     * This method encapsulates the 'extend' method of M.Object for better reading of code syntax.
     * It triggers the content binding for this view,
     * gets an ID from and registers itself at the ViewManager.
     *
     * @param {Object} obj The mixed in object for the extend call.
     */
    design: function(obj) {
        var extendedView = this.extend(obj);
        if(extendedView.contentBinding) {
            extendedView.attachToObservable(extendedView.contentBinding);
        }
        extendedView.id = M.ViewManager.getNextId();
        M.ViewManager.register(extendedView);
        return extendedView;
    },

     /**
     * Interface method.
     * Renders itself to HTML.
     * Render implementation is done by the special child obj of View (LabelView etc.).
     */
    render: function() {

    },

    /**
     * Interface method.
     * Updates itself in DOM.
     * Render implementation is done by the special child obj of View (LabelView etc.).
     */
    renderUpdate: function() {

    },

    /**
     * Triggers render() on all children.
     */
    renderChildViews: function() {
        if(this.childViews) {
            var arr = this.childViews[0].split(' ');
            for(var i in arr) {
                this[arr[i]].render();
            }
        }
    },

    /**
     * Interface method.
     * Triggers rendering engine to style this view/apply a theme.
     */
    applyTheme: function() {

    },

    /**
     * contentDidChange is called by the observable when observable's state did change.
     * It updates the view's value property.
     */
    contentDidChange: function(){
        var bindingPath = this.contentBinding.split('.');
        if(bindingPath && bindingPath.length === 3 && !(this.hasFocus && this.type === 'M.TextFieldView')) {
            this.value = eval(bindingPath[0])[bindingPath[1]][bindingPath[2]];
            this.renderUpdate();
            this.delegateValueUpdate();
        } else {
            M.Logger.log('bindingPath not valid', M.WARN);
        }
    },

    /**
     * This method attaches itself to an observable.
     *
     * @param {String} contentBinding The path to the observable property.
     */
    attachToObservable: function(contentBinding) {
        var bindingPath = contentBinding.split('.');
        if(bindingPath && bindingPath.length === 3 && eval(bindingPath[0]) && eval(bindingPath[0])[bindingPath[1]]) {
            eval(bindingPath[0])[bindingPath[1]].observable.attach(this, bindingPath[2]);
            this.isObserver = YES;
        } else {
            M.Logger.log('bindingPath not valid', M.WARN);
        }
    },

    /**
     * Interface method.
     * This method sets its value to the value it has in its DOM representation.
     */
    setValueFromDOM: function() {

    },

    /**
     * This method delegates any value changes to a controller, if the 'contentBindingReverse'
     * property is specified.
     */
    delegateValueUpdate: function() {
        /* delegate value updates to a binded controller,
           but only if the view currently is the master */
        if(this.contentBindingReverse && this.hasFocus) {
            var params = this.contentBindingReverse.split('.');
            var controller = eval(params[0]);
            controller[params[1]].set(params[2], this.value);
        }
    },

    /**
     * Interface method.
     * Method to append css styles and other style specific inline attributes to the rendered view.
     */
    style: function() {

    },

    /**
     * This method is called whenever the view gets the focus.
     */
    gotFocus: function() {
        this.hasFocus = YES;
    },

    /**
     * This method is called whenever the view lost the focus.
     */
    lostFocus: function() {
        this.hasFocus = NO;
    }

});<commit_msg>Added some basic properties like 'isEnabled'<commit_after>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: 2010 M-Way Solutions GmbH. All rights reserved.
// Creator:   Dominik
// Date:      26.10.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('model.js');

/**
 * @class
 *
 * The root of all views.
 *
 */
M.View = M.Object.extend({

    /**
     * The type of this object.
     *
     * @property {String}
     */
    type: 'M.View',

    /**
     * Generic attribute for views. Some subclasses have a property that is more readable to its context than 'value'.
     * Internally these properties are mapped to 'value'.
     * e.g. in LabelView: value:this.text
     *
     * @property {Object, String}
     */
    value: null,

    /**
     * The path to a content that is bind to the view's value.
     *
     * @property {String, Object}
     */
    contentBinding: null,

    /**
     * An array specifying the view's children.
     *
     * @property {Object}
     */
    childViews: null,

    /**
     * Determines whether this view renders directly to the DOM or just returns its HTML representation.
     *
     * @property {Boolean}
     */
    renderToDOM: YES,

    /**
     * Indicates whether this view currently has the focus or not.
     *
     * @property {Boolean}
     */
    hasFocus: NO,

    /**
     * The id of the View used for the HTML attribute ID.
     */
    id: null,

    /**
     * Indicates whether the view should be displayed inline or not.
     */
    isInline: NO,

    /**
     * Indicates whether the view is enabled or not.
     */
    isEnabled: YES,

    modelId: null,

    /**
     * This method encapsulates the 'extend' method of M.Object for better reading of code syntax.
     * It triggers the content binding for this view,
     * gets an ID from and registers itself at the ViewManager.
     *
     * @param {Object} obj The mixed in object for the extend call.
     */
    design: function(obj) {
        var extendedView = this.extend(obj);
        if(extendedView.contentBinding) {
            extendedView.attachToObservable(extendedView.contentBinding);
        }
        extendedView.id = M.ViewManager.getNextId();
        M.ViewManager.register(extendedView);
        return extendedView;
    },

     /**
     * Interface method.
     * Renders itself to HTML.
     * Render implementation is done by the special child obj of View (LabelView etc.).
     */
    render: function() {

    },

    /**
     * Interface method.
     * Updates itself in DOM.
     * Render implementation is done by the special child obj of View (LabelView etc.).
     */
    renderUpdate: function() {

    },

    /**
     * Triggers render() on all children.
     */
    renderChildViews: function() {
        if(this.childViews) {
            var arr = this.childViews[0].split(' ');
            for(var i in arr) {
                this[arr[i]].render();
            }
        }
    },

    /**
     * Interface method.
     * Triggers rendering engine to style this view/apply a theme.
     */
    applyTheme: function() {

    },

    /**
     * contentDidChange is called by the observable when observable's state did change.
     * It updates the view's value property.
     */
    contentDidChange: function(){
        var bindingPath = this.contentBinding.split('.');
        if(bindingPath && bindingPath.length === 3 && !(this.hasFocus && this.type === 'M.TextFieldView')) {
            this.value = eval(bindingPath[0])[bindingPath[1]][bindingPath[2]];
            this.renderUpdate();
            this.delegateValueUpdate();
        } else {
            M.Logger.log('bindingPath not valid', M.WARN);
        }
    },

    /**
     * This method attaches itself to an observable.
     *
     * @param {String} contentBinding The path to the observable property.
     */
    attachToObservable: function(contentBinding) {
        var bindingPath = contentBinding.split('.');
        if(bindingPath && bindingPath.length === 3 && eval(bindingPath[0]) && eval(bindingPath[0])[bindingPath[1]]) {
            eval(bindingPath[0])[bindingPath[1]].observable.attach(this, bindingPath[2]);
            this.isObserver = YES;
        } else {
            M.Logger.log('bindingPath not valid', M.WARN);
        }
    },

    /**
     * Interface method.
     * This method sets its value to the value it has in its DOM representation.
     */
    setValueFromDOM: function() {

    },

    /**
     * This method delegates any value changes to a controller, if the 'contentBindingReverse'
     * property is specified.
     */
    delegateValueUpdate: function() {
        /* delegate value updates to a binded controller,
           but only if the view currently is the master */
        if(this.contentBindingReverse && this.hasFocus) {
            var params = this.contentBindingReverse.split('.');
            var controller = eval(params[0]);
            controller[params[1]].set(params[2], this.value);
        }
    },

    /**
     * Interface method.
     * Method to append css styles and other style specific inline attributes to the rendered view.
     */
    style: function() {

    },

    /**
     * This method is called whenever the view gets the focus.
     */
    gotFocus: function() {
        this.hasFocus = YES;
    },

    /**
     * This method is called whenever the view lost the focus.
     */
    lostFocus: function() {
        this.hasFocus = NO;
    }

});<|endoftext|>