<commit_before>;(function($) {

    $.stream = {
        iterations: 0,
        maxValue: null,
        stream: null
    };

    $.fn.extend({
        Progress: function(options, arg) {
            if (options && typeof(options) == 'object') {
                options = $.extend({}, $.Progress.defaults, options);
            }

            this.each(function() {
                new $.Progress(this, options, arg );
            });

            return;
        }
    });

    $.Progress = function(elem, option, arg) {
        var options  = option || $.Progress.defaults
            , isInit = $.Progress.init(options)
            , close;

        if(isInit === true) {
            $.stream.stream = $.Progress.connect(options);

            $.stream.stream.onmessage = function(e) {

                if($.stream.iterations == $.stream.maxValue) {
                    $.Progress.grow(elem, options, e.data, function() {
                        $.Progress.finish(elem, options);
                        close = $.Progress.close();
                    });
                } else {
                    $.Progress.grow(elem, options, e.data);
                }

                $.stream.iterations++;
            };
        } else {
            console.error(isInit);
        }

        return;
    };

    /**
     *
     * @param options
     * @returns {boolean|string}
     */
    $.Progress.init = function(options) {
        if(!options.url || typeof(options.url) != 'string')
            return 'jQuery.Progress Error: you must provide a string named "url" as an option that contains the URL to fetch.';

        return true;
    };

    /**
     *
     * @param options
     * @returns {*}
     */
    $.Progress.connect = function(options) {
        if(typeof(EventSource) === 'undefined') {
            console.error('jQuery.Progress Error: SSE is not supported.');
            return false;
        }

        return new EventSource(options.url);
    };

    /**
     *
     * @param elem
     * @param options
     * @param data
     * @param callback
     */
    $.Progress.grow = function(elem, options, data, callback) {
        if($.stream.iterations == 0) {
            $.stream.maxValue = data;
        } else {
            var percent = Math.round(data / $.stream.maxValue * 100) + '%';
            if(options.percentSelector && typeof(options.percentSelector) == 'string') {
                $(options.percentSelector).text(percent);
            }
            $(elem).animate({
                width: percent
            }, options.animationDuration, callback);
        }
    };

    /**
     *
     * @returns {boolean}
     */
    $.Progress.close = function() {
        if($.stream.stream) {
            $.stream.stream.close();
            return true
        }

        return false;
    };

    /**
     *
     * @param elem
     * @param options
     */
    $.Progress.finish = function(elem, options) {
        $(elem).addClass(options.classes.success);
    };

    /**
     *
     * @type {{animationDuration: number, classes: {success: string, error: string, pending: string}}}
     */
    $.Progress.defaults = {
        animationDuration: 1000,
        classes: {
            success: 'progress-bar-success',
            error: 'progress-bar-error',
            pending: 'progress-bar-pending'
        }
    };

})(jQuery);
<commit_msg>Added minor modifications (debug option)<commit_after>;(function($) {

    $.fn.extend({
        Progress: function(options) {
            if (options && typeof(options) == 'object') {
                options = $.extend({}, $.Progress.defaults, options);
            }

            this.each(function() {
                new $.Progress(this, options);
            });

            return;
        }
    });

    $.stream = {
        iterations: 0,
        maxValue: null,
        stream: null
    };

    $.messages = {
        couldNotEstablishConnection: '[jQuery.Progress] Error: could not establish the connection.',
        urlIsMissing: '[jQuery.Progress] Error: you must provide a string named "url" as an option that contains the URL to fetch.',
        sseNotSupported: '[jQuery.Progress] Error: SSE is not supported.',
        notAnInteger: function(data) {
            return '[jQuery.Progress] Error: the server must send back any positive integer (we received: "' + data + '"). Closing the connection.';
        },
        noDataReceived: '[jQuery.Progress] Error: no data was received from the server. Are you sure the server-side output starts with "data:"?',
    };

    $.Progress = function(elem, option) {

        var options  = option || $.Progress.defaults
            , isInit = $.Progress.init(options)
            , close;

        if(options.debug) console.log($(elem));

        if(isInit === true) {
            $.stream.stream = $.Progress.connect(options);

            if(typeof($.stream.stream) === 'object') {
                $.stream.stream.onopen = function(e) {
                    if(options.debug) console.log(e);
                };
                if(options.debug) console.log('$.stream.stream is an object');
                if(options.debug) console.log(typeof($.stream.stream.onmessage));

                $.stream.stream.onmessage = function(e) {

                    if(options.debug) console.log(e);
                    if(e.data === null) {
                        console.error($.messages.noDataReceived);
                    }

                    if(options.debug) console.log($.stream.stream);

                    if(options.debug) console.log('iterations ------> '+ $.stream.iterations);
                    if(options.debug) console.log('data: ------>'+ e.data);

                    if($.stream.iterations == $.stream.maxValue) {
                        $.Progress.grow(elem, options, e.data, function() {
                            $.Progress.finish(elem, options);
                            close = $.Progress.close();
                        });
                    } else {
                        $.Progress.grow(elem, options, e.data);
                    }

                    $.stream.iterations++;

                };
            } else {
                console.error($.messages.couldNotEstablishConnection)
            }

        } else {
            console.error(isInit);
        }

        return;
    };

    /**
     *
     * @param options
     * @returns {boolean|string}
     */
    $.Progress.init = function(options) {
        if(options.debug) console.log('init() called');
        if(!options.url || typeof(options.url) != 'string')
            return $.messages.urlIsMissing;

        return true;
    };

    /**
     *
     * @param options
     * @returns {*}
     */
    $.Progress.connect = function(options) {
        if(typeof(EventSource) === 'undefined') {
            console.error($.messages.sseNotSupported);
            return false;
        }
        return new EventSource(options.url);
    };

    /**
     *
     * @param elem
     * @param options
     * @param data
     * @param callback
     */
    $.Progress.grow = function(elem, options, data, callback) {
        if(parseInt(data) != data) {
            console.error($.messages.notAnInteger(data));
            $.Progress.close();
            return;
        }

        if($.stream.iterations == 0) {
            $.stream.maxValue = data;
        } else {
            var percent = Math.round(data / $.stream.maxValue * 100) + '%';
            if(options.percentSelector && typeof(options.percentSelector) == 'string') {
                $(options.percentSelector).text(percent);
            }
            $(elem).animate({
                width: percent
            }, options.animationDuration, callback);
        }
    };

    /**
     *
     * @returns {boolean}
     */
    $.Progress.close = function() {
        if($.stream.stream) {
            $.stream.stream.close();
            return true
        }

        return false;
    };

    /**
     *
     * @param elem
     * @param options
     */
    $.Progress.finish = function(elem, options) {
        if(options.debug) console.log('class added');
        $(elem).addClass(options.classes.success);
    };

    /**
     *
     * @type {{animationDuration: number, classes: {success: string, error: string, pending: string}}}
     */
    $.Progress.defaults = {
        animationDuration: 1000,
        debug: false,
        classes: {
            success: 'progress-bar-success',
            error: 'progress-bar-error',
            pending: 'progress-bar-pending'
        }
    };

})(jQuery);
<|endoftext|>


<commit_before>'use strict'
var app
var Element
var keyevents = require('../event/key')

function emit (type, event) {
  var eligable = findit(type, app, [])
  for (var i = eligable.length - 1; i >= 0; i--) {
    // fire function for deepest target, if return truthy try next
    if (!eligable[i].focused[type](eligable, event)) {
      return
    }
  }
}

function findit (type, obj, arr) {
  obj.each(function (prop, key) {
    if (prop.focused) {
      if (prop.focused.val === true) {
        if (prop.focused[type]) {
          arr.push(prop)
        }
        findit(type, prop, arr)
      }
    }
  }, function (prop) {
    return prop instanceof Element
  })
  return arr
}

// horrible temp fix
setTimeout(function () {
  Element = require('../element')
  app = require('../app')

  var create = function (i) {
    return function (d, event) {
      emit(i, event)
    }
  }
  var events = {}
  for (var i in keyevents.on.properties) {
    events[i] = create(i)
  }

  app.set({
    keyevents: {
      type: 'group',
      inject: keyevents,
      on: events
    }
  })
})

var Prop = require('./')

exports.define = {
  // focusItem (unfocus, event) {
  //   if (unfocus) {
  //     this.unfocusItem(unfocus, event)
  //   }
  //   // quick fix
  //   if (!app) {
  //     app = require('../app')
  //   }

  //   if (!app.focused) {
  //     app.setKey('focused', {}, event)
  //   }

  //   app.focused._input = this
  //   app.focused.emit('data', this, event)

  //   this.setKey('css', {add: 'focus'})
  //   this.emit('focus', unfocus, event)
  // },
  // unfocusItem (unfocus, event) {
  //   if (!unfocus) {
  //     unfocus = this
  //   }
  //   if (unfocus.constructor === Array) {
  //     for (var i = unfocus.length - 1; i >= 0; i--) {
  //       unfocus[i].setKey('css', {remove: 'focus'})
  //       unfocus[i].emit('blur', void 0, event)
  //     }
  //   } else {
  //     unfocus.setKey('css', {remove: 'focus'})
  //     unfocus.emit('blur', void 0, event)
  //   }
  // },

  findLeft () {
    var left = this.getNode().offsetLeft
    var closestLeft
    var closest
    this.parent.each((prop, key) => {
      if (prop !== this && prop.focused) {
        var propLeft = prop.getNode().offsetLeft
        if (propLeft < left) {
          if (closestLeft === void 0 || closestLeft < propLeft) {
            closestLeft = propLeft
            closest = prop
          }
        }
      }
    }, function (prop) {
      return prop instanceof Element
    })
    return closest
  },

  findRight () {
    var left = this.getNode().offsetLeft
    var closestLeft
    var closest
    this.parent.each((prop, key) => {
      if (prop !== this && prop.focused) {
        var propLeft = prop.getNode().offsetLeft
        if (propLeft > left) {
          if (closestLeft === void 0 || closestLeft > propLeft) {
            closestLeft = propLeft
            closest = prop
          }
        }
      }
    }, function (prop) {
      return prop instanceof Element
    })
    return closest
  },

  focusLeft (event) {
    var closest = this.findLeft()
    if (closest) {
      this.focused.set(false, event)
      closest.focused.set(true, event)
    } else {
      return true
    }
  },

  focusRight (event) {
    var closest = this.findRight()
    if (closest) {
      this.focused.set(false, event)
      closest.focused.set(true, event)
    } else {
      return true
    }
  },

  focusUp (event) {
    var top = this.getNode().offsetTop
    var closestTop
    var closest
    this.parent.each((prop, key) => {
      if (prop !== this && prop.focused) {
        var propTop = prop.getNode().offsetTop
        if (propTop < top) {
          if (closestTop === void 0 || closestTop < propTop) {
            closestTop = propTop
            closest = prop
          }
        }
      }
    }, function (prop) {
      return prop instanceof Element
    })
    if (closest) {
      this.focused.set(false, event)
      closest.focused.set(true, event)
    } else {
      return true
    }
  },

  focusDown (event) {
    var top = this.getNode().offsetTop
    var closestTop
    var closest
    this.parent.each((prop, key) => {
      if (prop !== this && prop.focused) {
        var propTop = prop.getNode().offsetTop
        if (propTop > top) {
          if (closestTop === void 0 || closestTop > propTop) {
            closestTop = propTop
            closest = prop
          }
        }
      }
    }, function (prop) {
      return prop instanceof Element
    })
    if (closest) {
      this.focused.set(false, event)
      closest.focused.set(true, event)
    } else {
      return true
    }
  }
}

var focused = new Prop({
  val: false,
  render (node, event, element) {
    if (this.val === true) {
      element.setKey('css', {add: 'focused'})
      // node.className += ' focused'
    } else {
      element.setKey('css', {remove: 'focused'})
      // node.className = node.className.replace(/focused/g, '')
    }
  },
  define: {
    arrowup (d, event) {
      var elem = this.parent
      return elem.focusUp()
    },
    arrowdown (d, event) {
      var elem = this.parent
      return elem.focusDown(event)
    },
    arrowleft (d, event) {
      var elem = this.parent
      return elem.focusLeft(event)
    },
    arrowright (d, event) {
      var elem = this.parent
      return elem.focusRight(event)
    }
  }
})

var create = function (i) {
  return function (val) {
    var define = {}
    define[i] = val
    this.define(define)
  }
}

var events = {}
for (var i in keyevents.on.properties) {
  events[i] = create(i)
}

focused.set({
  properties: events
})

exports.properties = {

  focused: focused,

  focus: new Prop({
    properties: {
      prevKey: true
    },
    prevKey: false,
    render (node, event, parent) {
      var val = this.parseValue()
      var type = typeof val
      var prevKey = this.prevKey
      var key

      if (type === 'string' || type === 'number') {
        key = val
      } else {
        key = val.key
      }
      if (key !== prevKey) {
        if (parent[key]) {
          parent[key].setKey('focused', true, event)
          if (parent[prevKey]) {
            parent[prevKey].setKey('focused', false, event)
          }
          this.prevKey = key
        }
      }
    }
  })

  // focus: new Prop({
  //   properties: {
  //     prevKey: true
  //   },
  //   prevKey: false,
  //   render (node, event, parent) {
  //     var val = this.parseValue()
  //     var type = typeof val
  //     var prevKey = this.prevKey
  //     var key

  //     if (type === 'string' || type === 'number') {
  //       key = val
  //     } else {
  //       key = val.key
  //     }

  //     if (key !== prevKey || app && app.focused._input && app.focused._input._input === null) {
  //       if (parent[key]) {
  //         parent[key].focusItem(prevKey && parent[prevKey], event)
  //         this.prevKey = key
  //       }
  //     }
  //   }
  // })
}
<commit_msg>focus now checks for visibility<commit_after>'use strict'
var app
var Element
var keyevents = require('../event/key')

function emit (type, event) {
  var eligable = findit(type, app, [])
  for (var i = eligable.length - 1; i >= 0; i--) {
    // fire function for deepest target, if return truthy try next
    if (!eligable[i].focused[type](eligable, event)) {
      return
    }
  }
}

function findit (type, obj, arr) {
  obj.each(function (prop, key) {
    if (prop.focused) {
      if (prop.focused.val === true) {
        if (prop.focused[type]) {
          arr.push(prop)
        }
        findit(type, prop, arr)
      }
    }
  }, function (prop) {
    return prop instanceof Element
  })
  return arr
}

// horrible temp fix
setTimeout(function () {
  Element = require('../element')
  app = require('../app')

  var create = function (i) {
    return function (d, event) {
      emit(i, event)
    }
  }
  var events = {}
  for (var i in keyevents.on.properties) {
    events[i] = create(i)
  }

  app.set({
    keyevents: {
      type: 'group',
      inject: keyevents,
      on: events
    }
  })
})

var Prop = require('./')

exports.define = {
  findLeft () {
    var left = this.getNode().offsetLeft
    var closestLeft
    var closest
    this.parent.each((prop, key) => {
      if (prop !== this && prop.focused) {
        var node = prop.getNode()
        if (node.offsetParent !== null) {
          var propLeft = node.offsetLeft
          if (propLeft < left) {
            if (closestLeft === void 0 || closestLeft < propLeft) {
              closestLeft = propLeft
              closest = prop
            }
          }
        }
      }
    }, function (prop) {
      return prop instanceof Element
    })
    return closest
  },

  findRight () {
    var left = this.getNode().offsetLeft
    var closestLeft
    var closest
    this.parent.each((prop, key) => {
      if (prop !== this && prop.focused) {
        var node = prop.getNode()
        if (node.offsetParent !== null) {
          var propLeft = node.offsetLeft
          if (propLeft > left) {
            if (closestLeft === void 0 || closestLeft > propLeft) {
              closestLeft = propLeft
              closest = prop
            }
          }
        }
      }
    }, function (prop) {
      return prop instanceof Element
    })
    return closest
  },

  focusLeft (event) {
    var closest = this.findLeft()
    if (closest) {
      this.focused.set(false, event)
      closest.focused.set(true, event)
    } else {
      return true
    }
  },

  focusRight (event) {
    var closest = this.findRight()
    if (closest) {
      this.focused.set(false, event)
      closest.focused.set(true, event)
    } else {
      return true
    }
  },

  focusUp (event) {
    var top = this.getNode().offsetTop
    var closestTop
    var closest
    this.parent.each((prop, key) => {
      if (prop !== this && prop.focused) {
        var node = prop.getNode()
        if (node.offsetParent !== null) {
          var propTop = node.offsetTop
          if (propTop < top) {
            if (closestTop === void 0 || closestTop < propTop) {
              closestTop = propTop
              closest = prop
            }
          }
        }
      }
    }, function (prop) {
      return prop instanceof Element
    })
    if (closest) {
      this.focused.set(false, event)
      closest.focused.set(true, event)
    } else {
      return true
    }
  },

  focusDown (event) {
    var top = this.getNode().offsetTop
    var closestTop
    var closest
    this.parent.each((prop, key) => {
      if (prop !== this && prop.focused) {
        var node = prop.getNode()
        if (node.offsetParent !== null) {
          var propTop = node.offsetTop
          if (propTop > top) {
            if (closestTop === void 0 || closestTop > propTop) {
              closestTop = propTop
              closest = prop
            }
          }
        }
      }
    }, function (prop) {
      return prop instanceof Element
    })
    if (closest) {
      this.focused.set(false, event)
      closest.focused.set(true, event)
    } else {
      return true
    }
  }
}

var focused = new Prop({
  val: false,
  render (node, event, element) {
    if (this.val === true) {
      element.setKey('css', {add: 'focused'})
    } else {
      element.setKey('css', {remove: 'focused'})
    }
  },
  define: {
    arrowup (d, event) {
      var elem = this.parent
      return elem.focusUp()
    },
    arrowdown (d, event) {
      var elem = this.parent
      return elem.focusDown(event)
    },
    arrowleft (d, event) {
      var elem = this.parent
      return elem.focusLeft(event)
    },
    arrowright (d, event) {
      var elem = this.parent
      return elem.focusRight(event)
    }
  }
})

var create = function (i) {
  return function (val) {
    var define = {}
    define[i] = val
    this.define(define)
  }
}

var events = {}
for (var i in keyevents.on.properties) {
  events[i] = create(i)
}

focused.set({
  properties: events
})

exports.properties = {

  focused: focused,

  focus: new Prop({
    properties: {
      prevKey: true
    },
    prevKey: false,
    render (node, event, parent) {
      var val = this.parseValue()
      var type = typeof val
      var prevKey = this.prevKey
      var key

      if (type === 'string' || type === 'number') {
        key = val
      } else {
        key = val.key
      }
      if (key !== prevKey) {
        if (parent[key]) {
          parent[key].setKey('focused', true, event)
          if (parent[prevKey]) {
            parent[prevKey].setKey('focused', false, event)
          }
          this.prevKey = key
        }
      }
    }
  })
}
<|endoftext|>


<commit_before>/*
 treeMenu - jQuery plugin
 version: 0.6

 Copyright 2014 Stepan Krapivin

*/
(function($) {
    $.fn.treemenu = function(options) {
        options = options || {};
        options.delay = options.delay || 0;
        options.openActive = options.openActive || false;
        options.closeOther = options.closeOther || false;
        options.activeSelector = options.activeSelector || ".active";

        this.addClass("treemenu");

        if (!options.nonroot) {
            this.addClass("treemenu-root");
        }

        options.nonroot = true;

        this.find("> li").each(function() {
            e = $(this);
            var subtree = e.find('> ul');
            var button = e.find('.toggler').eq(0);

            if (button.length == 0) {
                // create toggler
                var button = $('<span>');
                button.addClass('toggler');
                e.prepend(button);
            }

            if (subtree.length > 0) {
                subtree.hide();
                e.addClass('tree-closed');

                e.find(button).click(function() {
                    var li = $(this).parent('li');

                    if (options.closeOther && li.hasClass('tree-closed')) {
                        var siblings = li.parent('ul').find("li:not(.tree-empty)");
                        siblings.removeClass("tree-opened");
                        siblings.addClass("tree-closed");
                        siblings.removeClass(options.activeSelector);
                        siblings.find('> ul').slideUp(options.delay);
                    }

                    li.find('> ul').slideToggle(options.delay);
                    li.toggleClass('tree-opened');
                    li.toggleClass('tree-closed');
                    li.toggleClass(options.activeSelector);
                });

                $(this).find('> ul').treemenu(options);
            } else {
                $(this).addClass('tree-empty');
            }
        });

        if (options.openActive) {
            var cls = this.attr("class");

            this.find(options.activeSelector).each(function(){
                var el = $(this).parent();

                while (el.attr("class") !== cls) {
                    el.find('> ul').show();
                    if(el.prop("tagName") === 'UL') {
                        el.show();
                    } else if (el.prop("tagName") === 'LI') {
                        el.removeClass('tree-closed');
                        el.addClass("tree-opened");
                        el.show();
                    }

                    el = el.parent();
                }
            });
        }

        return this;
    }
})(jQuery);
<commit_msg>fixed typo<commit_after>/*
 treeMenu - jQuery plugin
 version: 0.6

 Copyright 2014 Stepan Krapivin

*/
(function($) {
    $.fn.treemenu = function(options) {
        options = options || {};
        options.delay = options.delay || 0;
        options.openActive = options.openActive || false;
        options.closeOther = options.closeOther || false;
        options.activeSelector = options.activeSelector || "active";

        this.addClass("treemenu");

        if (!options.nonroot) {
            this.addClass("treemenu-root");
        }

        options.nonroot = true;

        this.find("> li").each(function() {
            e = $(this);
            var subtree = e.find('> ul');
            var button = e.find('.toggler').eq(0);

            if (button.length == 0) {
                // create toggler
                var button = $('<span>');
                button.addClass('toggler');
                e.prepend(button);
            }

            if (subtree.length > 0) {
                subtree.hide();
                e.addClass('tree-closed');

                e.find(button).click(function() {
                    var li = $(this).parent('li');

                    if (options.closeOther && li.hasClass('tree-closed')) {
                        var siblings = li.parent('ul').find("li:not(.tree-empty)");
                        siblings.removeClass("tree-opened");
                        siblings.addClass("tree-closed");
                        siblings.removeClass(options.activeSelector);
                        siblings.find('> ul').slideUp(options.delay);
                    }

                    li.find('> ul').slideToggle(options.delay);
                    li.toggleClass('tree-opened');
                    li.toggleClass('tree-closed');
                    li.toggleClass(options.activeSelector);
                });

                $(this).find('> ul').treemenu(options);
            } else {
                $(this).addClass('tree-empty');
            }
        });

        if (options.openActive) {
            var cls = this.attr("class");

            this.find(options.activeSelector).each(function(){
                var el = $(this).parent();

                while (el.attr("class") !== cls) {
                    el.find('> ul').show();
                    if(el.prop("tagName") === 'UL') {
                        el.show();
                    } else if (el.prop("tagName") === 'LI') {
                        el.removeClass('tree-closed');
                        el.addClass("tree-opened");
                        el.show();
                    }

                    el = el.parent();
                }
            });
        }

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


<commit_before>'use strict'
var Observable = require('vigour-js/lib/observable')
var ua = require('vigour-ua')
var isNode = require('vigour-js/lib/util/is/node')

module.exports = new Observable({
  useVal: true,
  properties: {
    Connection: true,
    connections: true,
    internal: true
  },
  connected: {},
  on: {
    value: {
      upstream (data, event) {
        if (!this.hasOwnProperty('client')) {
          let upstream = this.val
          if (!upstream) {
            this.emit('error', 'no upstream')
            return
          }

          let hub = this.parent.parent
          let adapter = this.parent
          let id = this.lookUp('id')
          let set = { val: id }
          let connection
          let protocol = this

          if (isNode) {
            set.platform = 'node'
          } else {
            ua(window.navigator.userAgent, set)
          }

          hub.set({ clients: { [id]: set } }, false)
          let client = hub.clients[id]
          this.set({ client: client }, false)

          // console.log('ok!', upstream)
          // multiple in a bit
          // that means multiple clients per single connetion! hard thing todo!
          // so thats different then this -- we want to store a conneciton map where we store the upstream!
          // not the id thats not relevant it pure the val of the current upstream
          if (!this.connections) {
            // so this should be ok now!
            this.connections = {}
          }

          if (!this.connections[upstream]) {
            this.connections[upstream] = connection = new this.Connection({ upstream: this })
          } else {
            // should not have this connection totaly wrong!
            console.log('i got the connection!'.rainbow, upstream.blue, this.path.join('.').blue)
            connection = this.connections[upstream]
          }

          client.set({ connection: connection }, false)

          connection
            .on('message', function (data) {
              adapter.receive(data, 'upstream')
            })
            .on('close', function (data, event) {
              protocol.connected.val = false
              protocol.emit('close', data, event)
            })
            .on('reconnect', function (data, event) {
              protocol.emit('reconnect', data, event)
            })
            .on('connect', function (data, event) {
              // set protocol .connected
              protocol.connected.val = true
              let payload = {
                client: client.serialize(),
                stamp: event.stamp
              }
              if (typeof adapter.scope.val === 'string') { //add number as well!
                payload.scope = adapter.scope.val
              }
              this.send(payload)
              protocol.emit('connect', data, event)
            })
        } else {
          console.warn('allready have client this is only a switch', this.path)
        }
      }
    }
  }
}).Constructor
<commit_msg>removed logs<commit_after>'use strict'
var Observable = require('vigour-js/lib/observable')
var ua = require('vigour-ua')
var isNode = require('vigour-js/lib/util/is/node')

module.exports = new Observable({
  useVal: true,
  properties: {
    Connection: true,
    connections: true,
    internal: true
  },
  connected: {},
  on: {
    value: {
      upstream (data, event) {
        if (!this.hasOwnProperty('client')) {
          let upstream = this.val
          if (!upstream) {
            this.emit('error', 'no upstream')
            return
          }

          let hub = this.parent.parent
          let adapter = this.parent
          let id = this.lookUp('id')
          let set = { val: id }
          let connection
          let protocol = this

          if (isNode) {
            set.platform = 'node'
          } else {
            ua(window.navigator.userAgent, set)
          }

          hub.set({ clients: { [id]: set } }, false)
          let client = hub.clients[id]
          this.set({ client: client }, false)
          if (!this.connections) {
            this.connections = {}
          }

          if (!this.connections[upstream]) {
            this.connections[upstream] = connection = new this.Connection({ upstream: this })
          } else {
            connection = this.connections[upstream]
          }

          client.set({ connection: connection }, false)

          connection
            .on('message', function (data) {
              adapter.receive(data, 'upstream')
            })
            .on('close', function (data, event) {
              protocol.connected.val = false
              protocol.emit('close', data, event)
            })
            .on('reconnect', function (data, event) {
              protocol.emit('reconnect', data, event)
            })
            .on('connect', function (data, event) {
              // set protocol .connected
              protocol.connected.val = true
              let payload = {
                client: client.serialize(),
                stamp: event.stamp
              }
              if (typeof adapter.scope.val === 'string') { //add number as well!
                payload.scope = adapter.scope.val
              }
              this.send(payload)
              protocol.emit('connect', data, event)
            })
        } else {
          console.warn('allready have client this is only a switch', this.path)
        }
      }
    }
  }
}).Constructor
<|endoftext|>


<commit_before>var form_auto_save = true;

function form_form(app, type, id) {
  var self = this;
  self.app = app;
  self.type = type;
  self.toString = function(){ return type; };

  var _meta = null;
  var _modules = null;
  var _related = null;
  var _id = id;
  var _created = null;
  var _modified = null;
  var _state = null;
  var _idx = {};
  var _dirty = false;
  var _logs = [];

  form_make_listener(self);

  var $el = $$('form');
  self.$el = function () {
    return $el;
  };

  var $controls = $$('form-controls');
  self.$controls = function () {
    update_controls();
    return $controls;
  };

  var $info = $$('form-info');
  self.$info = function(){
    update_info();
    return $info;
  };

  var $form = $$('form', {parent: $el});

  function update_form() {
    $form.empty();
    var $t = $form;
    var s = [];
    for (var i = 0; i < _meta.length; i++) {
      var d = _meta[i];
      if (d.begin) {
        s.push($t);
        var $x = $("

");
        if (d.options && d.options.className)
          $x.addClass(d.options.className);
        if ($t.data('float'))
          $x.css({'float':'left'});
        if (d.begin == 'row')
          $x.data('float',true);
        $t.append($x);
        $t = $x;
      }
      else if (d.end) {
        $t = s.pop();
      }
      else {
       var f = create_field(d);
        var $fel = f.$el();
        if ($t.data('float'))
          $fel.css({'float':'left'});
        $t.append($fel);
        _idx[d.name] = f;
      }
    }
    $t.append("

");
  }

  function create_field(d) {
    var f = new indicated_field(d);
    f.field.form = self;
    if (f.field.init)
      f.field.init();
    f.add_listener('change', function () {
      _dirty = true;
      self.$save.prop('disabled', false);
      self.emit('change');
      if (form_auto_save)
        save(4000);
    });
    // for reference fields
    f.add_listener('add', function (f) {
      if (d.options && d.options.type)
        self.emit('create', {type: d.options.type, field: f.field});
    });
    f.add_listener('browse', function (f) {
      if (d.options && d.options.type)
        self.emit('browse', {type: d.options.type, field: f.field})
    });
    f.add_listener('select', function(f, o){
      if (d.options && d.options.type)
        self.emit('select', {type: d.options.type, id: o._id, field: f.field});
    })
    return f;
  }

  function update_controls()
  {
    $controls.empty();
    //  var $title = $$('title', {el: 'span', parent: $controls}).text(type);
    //    var $cancel = $$('btn btn-primary', {el: 'button', parent: $controls}).text('CLOSE');
    //    $cancel.click(function(){
    //      self.emit('close', self.data);
    //    });
    self.$save = $$('save', {el: 'button', parent: $controls}).text('SAVE');
    if (!_dirty)
      self.$save.prop('disabled', true);
    self.$time = $$('time', {el: 'span', parent: $controls});
    self.$save.click(function () {
      save(0);
    });
    if (_modified)
      self.$time.text(' Last modified ' + timeSince(new Date(_modified)) + '.');
    else
      self.$time.text(' New record.');
  }

  var save_delayed = -1;
  function save(delay) {

    function do_save() {
      if (!_dirty) return;
      console.log(Date.now(), "save", self.type);
      $.ajax({
        url: self.url(),
        data: { val: JSON.stringify(self.data) },
        method: 'post',
        success: function (o) {
          if (o.name && o.name.indexOf("Error") != -1)
            self.error(o);
          else {
            self.update(o);
            history.pushState(self.url(), self.toString(), self.url());
            self.emit('save', o);
          }
        },
        error: function (o) {
          console.error(o);
        }
      });
    }

    if (delay == 0)
      return do_save();
    if (save_delayed != -1)
      clearTimeout(save_delayed);
    save_delayed = setTimeout(function () {
      save_delayed = -1;
      do_save();
    }, delay);
  }
  self.save = save;

  Object.defineProperty(self, "data", {
    get: function () {
      var d = {_id: _id, created: _created, modified: _modified, state: _state};
      for (var p in _idx)
        d[p] = _idx[p].data;
      return d;
    },
    set: function (n) {
      if (n == null)
        return;
      _id = n._id;
      _created = n.created;
      _modified = n.modified;
      _state = n.state;
      for (var p in _idx) {
        if (_idx[p])
          _idx[p].data = get_deep_value(n, p); //n[p];
      }
      update_ui();
    }
  });

  Object.defineProperty(self, "state", {
    get: function () {
     return _state;
    }
  });

  Object.defineProperty(self, "related", {
    get: function () {
     return _related;
    },
    set: function (r) {
      _related = r;
    }
  });

  Object.defineProperty(self, "id", {
    get: function () {
     return _id;
    }
  });

  Object.defineProperty(self, "logs", {
    get: function () {
     return _logs;
    }
  });

  function update_ui()
  {
    update_controls();
    update_info();
  }

  function update_info() {
    $info.empty();

    if (_modules) {
      for (var i=0; i<_modules.length; i++) {
        var m = new form_modules[_modules[i].widget](self);
        //m.add_listener?
        $info.append(m.$el());
      }
    }

    if (_state)
    {
      var sm = new form_modules.state(self);
      $info.append(sm.$el());
      sm.add_listener('save', function () {
        $$ajax(self.app.base_url + sm.api + '/' + type + '/' + _id,
          JSON.stringify({state: sm.selected, reason: sm.text}), 'post').done(function (r) {
            _state = sm.selected;
            update_info();
            refresh_logs();
        });
      });
    }

    var rd = new form_modules.ref_delete(self);
    rd.add_listener('select', function () { self.emit('select'); });
    rd.add_listener('close', function () { self.emit('close'); });
    $info.append(rd.$el());

    var l = new form_modules.logs(self);
    $info.append(l.$el());
  }

  self.error = function (o) {
      self.$time.text(' ERROR - see fields for details');
    _idx[o.path].error = JSON.stringify(o);
  }

  self.update = function (o) {
    _dirty = false
    self.data = o;
    refresh_logs();
    self.$save.prop('disabled', true);
  }

  self.url = function()
  {
      var url = self.app.base_url;
      if (_id)
        url += '/update/' + type + '/' + _id;
      else
        url += '/create/' + type;
    return url;
  }

  self.refresh = function()
  {
    if (_dirty)
    {
      console.log('cant refresh yet... save record...');
      return;
    }
    var url = self.app.base_url + '/get/' + type;
    if (_id)
      url += '/' + _id;
    $$ajax(url).done(function (o) {
      _meta = o.form;
      _modules = o.modules;
      _related =  o.related;
      _idx = {};
      update_form();
      if (!_id)
        delete o.object._id;
      self.data = o.object;
      refresh_logs();
    });
  };

  function refresh_logs()
  {
    if (!_id)
      return;
    $$ajax(self.app.base_url + '/logs/'+type+'/'+_id).done(function(r){
      _logs = r;
      // TODO do this just for logs update_info();
    });
  }
}

function indicated_field(d)//, settings_callback)
{
  if (!form_fields[d.widget + "_field"])
    throw Error("no field for " + d.widget);

  var self = this;
  var name = d.name;
  var label = d.label ? d.label : d.name;
  var type = d.widget;
  var options = d.options;
  var $el = $$('control-group '+type+' '+label);
  var showLabel = true;
  var lastCols = null;
  var cols = null;

  self.$el = function () {
    return $el;
  }
  form_make_listener(self);

  var $label = $('').addClass('control-label').attr('for', name);
  var field = new form_fields[type + "_field"](d.options);
  form_make_listener(field);
  field.bubble_listener(self);
  self.field = field;
  $el.append($label, field.$el());
  label_update_ui();

  var $controls = null;
  if (field.$cel) {
    $controls = $$('controls '+type);
    $controls.append(field.$cel());
    $el.append($controls);
  }

  Object.defineProperty(this, "data", {
    get: function () {
      return field.data;
    },
    set: function (n) {
      field.data = n;
    }
  });

  function columns_update_ui() {
    $el.removeClass(lastCols);
    lastCols = 'col-1-' + cols;
    $el.addClass(lastCols);
  }

  Object.defineProperty(this, "columns", {
    get: function () {
      return  cols;
    },
    set: function (n) {
      cols = n;
      columns_update_ui();
    }
  });

  function label_update_ui() {
    if (options && options.collapsable) {
      $label.empty();
      var $c = $("x");
      function tgl(v){
        if (v) {
          $c.empty().append("");
          field.$el().hide();
        }
        else {
          $c.empty().append("");
          field.$el().show();
        }
      }
      var v = options.collapsed != null ? options.collapsed : true;
      tgl(v);
      $c.click(function(){
        v = !v;
        tgl(v);
      });
      $label.append($c, ""+label+"");
    } else {
      $label.text(label);
    }
    if (showLabel)
      $label.show();
    else
      $label.hide();
  }

  Object.defineProperty(this, "label", {
    get: function () {
      return  label;
    },
    set: function (n) {
      label = n;
      label_update_ui();
    }
  });

  Object.defineProperty(this, "field", {
    get: function () {
      return field;
    }
  });
  Object.defineProperty(this, "name", {
    get: function () {
      return name;
    }
  });
  Object.defineProperty(this, "error", {
    set: function (e) {
      field.$el().append('' + e + '');
      $el.addClass('error');
    }
  });

}

<commit_msg>"bubble" events<commit_after>var form_auto_save = true;

function form_form(app, type, id) {
  var self = this;
  self.app = app;
  self.type = type;
  self.toString = function(){ return type; };

  var _meta = null;
  var _modules = null;
  var _related = null;
  var _id = id;
  var _created = null;
  var _modified = null;
  var _state = null;
  var _idx = {};
  var _dirty = false;
  var _logs = [];

  form_make_listener(self);

  var $el = $$('form');
  self.$el = function () {
    return $el;
  };

  var $controls = $$('form-controls');
  self.$controls = function () {
    update_controls();
    return $controls;
  };

  var $info = $$('form-info');
  self.$info = function(){
    update_info();
    return $info;
  };

  var $form = $$('form', {parent: $el});

  function update_form() {
    $form.empty();
    var $t = $form;
    var s = [];
    for (var i = 0; i < _meta.length; i++) {
      var d = _meta[i];
      if (d.begin) {
        s.push($t);
        var $x = $("

");
        if (d.options && d.options.className)
          $x.addClass(d.options.className);
        if ($t.data('float'))
          $x.css({'float':'left'});
        if (d.begin == 'row')
          $x.data('float',true);
        $t.append($x);
        $t = $x;
      }
      else if (d.end) {
        $t = s.pop();
      }
      else {
       var f = create_field(d);
        var $fel = f.$el();
        if ($t.data('float'))
          $fel.css({'float':'left'});
        $t.append($fel);
        _idx[d.name] = f;
      }
    }
    $t.append("

");
  }

  function create_field(d) {
    var f = new indicated_field(d);
    f.field.form = self;
    if (f.field.init)
      f.field.init();
    f.add_listener('change', function () {
      _dirty = true;
      self.$save.prop('disabled', false);
      self.emit('change');
      if (form_auto_save)
        save(4000);
    });
    // for reference fields
    f.add_listener('add', function (f) {
      if (d.options && d.options.type)
        self.emit('create', {type: d.options.type, field: f.field});
    });
    f.add_listener('browse', function (f) {
      if (d.options && d.options.type)
        self.emit('browse', {type: d.options.type, field: f.field})
    });
    f.add_listener('select', function(f, o){
      if (d.options && d.options.type)
        self.emit('select', {type: d.options.type, id: o._id, field: f.field});
    })
    return f;
  }

  function update_controls()
  {
    $controls.empty();
    //  var $title = $$('title', {el: 'span', parent: $controls}).text(type);
    //    var $cancel = $$('btn btn-primary', {el: 'button', parent: $controls}).text('CLOSE');
    //    $cancel.click(function(){
    //      self.emit('close', self.data);
    //    });
    self.$save = $$('save', {el: 'button', parent: $controls}).text('SAVE');
    if (!_dirty)
      self.$save.prop('disabled', true);
    self.$time = $$('time', {el: 'span', parent: $controls});
    self.$save.click(function () {
      save(0);
    });
    if (_modified)
      self.$time.text(' Last modified ' + timeSince(new Date(_modified)) + '.');
    else
      self.$time.text(' New record.');
  }

  var save_delayed = -1;
  function save(delay) {

    function do_save() {
      if (!_dirty) return;
      console.log(Date.now(), "save", self.type);
      $.ajax({
        url: self.url(),
        data: { val: JSON.stringify(self.data) },
        method: 'post',
        success: function (o) {
          if (o.name && o.name.indexOf("Error") != -1)
            self.error(o);
          else {
            self.update(o);
            history.pushState(self.url(), self.toString(), self.url());
            self.emit('save', o);
          }
        },
        error: function (o) {
          console.error(o);
        }
      });
    }

    if (delay == 0)
      return do_save();
    if (save_delayed != -1)
      clearTimeout(save_delayed);
    save_delayed = setTimeout(function () {
      save_delayed = -1;
      do_save();
    }, delay);
  }
  self.save = save;

  Object.defineProperty(self, "data", {
    get: function () {
      var d = {_id: _id, created: _created, modified: _modified, state: _state};
      for (var p in _idx)
        d[p] = _idx[p].data;
      return d;
    },
    set: function (n) {
      if (n == null)
        return;
      _id = n._id;
      _created = n.created;
      _modified = n.modified;
      _state = n.state;
      for (var p in _idx) {
        if (_idx[p])
          _idx[p].data = get_deep_value(n, p); //n[p];
      }
      update_ui();
    }
  });

  Object.defineProperty(self, "state", {
    get: function () {
     return _state;
    }
  });

  Object.defineProperty(self, "related", {
    get: function () {
     return _related;
    },
    set: function (r) {
      _related = r;
    }
  });

  Object.defineProperty(self, "id", {
    get: function () {
     return _id;
    }
  });

  Object.defineProperty(self, "logs", {
    get: function () {
     return _logs;
    }
  });

  function update_ui()
  {
    update_controls();
    update_info();
  }

  function update_info() {
    $info.empty();

    if (_modules) {
      for (var i=0; i<_modules.length; i++) {
        var m = new form_modules[_modules[i].widget](self);
        //m.add_listener?
        $info.append(m.$el());
      }
    }

    if (_state)
    {
      var sm = new form_modules.state(self);
      $info.append(sm.$el());
      sm.add_listener('save', function () {
        $$ajax(self.app.base_url + sm.api + '/' + type + '/' + _id,
          JSON.stringify({state: sm.selected, reason: sm.text}), 'post').done(function (r) {
            _state = sm.selected;
            update_info();
            refresh_logs();
        });
      });
    }

    var rd = new form_modules.ref_delete(self);
    rd.bubble_listener(self);
    $info.append(rd.$el());

    var l = new form_modules.logs(self);
    $info.append(l.$el());
  }

  self.error = function (o) {
      self.$time.text(' ERROR - see fields for details');
    _idx[o.path].error = JSON.stringify(o);
  }

  self.update = function (o) {
    _dirty = false
    self.data = o;
    refresh_logs();
    self.$save.prop('disabled', true);
  }

  self.url = function()
  {
      var url = self.app.base_url;
      if (_id)
        url += '/update/' + type + '/' + _id;
      else
        url += '/create/' + type;
    return url;
  }

  self.refresh = function()
  {
    if (_dirty)
    {
      console.log('cant refresh yet... save record...');
      return;
    }
    var url = self.app.base_url + '/get/' + type;
    if (_id)
      url += '/' + _id;
    $$ajax(url).done(function (o) {
      _meta = o.form;
      _modules = o.modules;
      _related =  o.related;
      _idx = {};
      update_form();
      if (!_id)
        delete o.object._id;
      self.data = o.object;
      refresh_logs();
    });
  };

  function refresh_logs()
  {
    if (!_id)
      return;
    $$ajax(self.app.base_url + '/logs/'+type+'/'+_id).done(function(r){
      _logs = r;
      // TODO do this just for logs update_info();
    });
  }
}

function indicated_field(d)//, settings_callback)
{
  if (!form_fields[d.widget + "_field"])
    throw Error("no field for " + d.widget);

  var self = this;
  var name = d.name;
  var label = d.label ? d.label : d.name;
  var type = d.widget;
  var options = d.options;
  var $el = $$('control-group '+type+' '+label);
  var showLabel = true;
  var lastCols = null;
  var cols = null;

  self.$el = function () {
    return $el;
  }
  form_make_listener(self);

  var $label = $('').addClass('control-label').attr('for', name);
  var field = new form_fields[type + "_field"](d.options);
  form_make_listener(field);
  field.bubble_listener(self);
  self.field = field;
  $el.append($label, field.$el());
  label_update_ui();

  var $controls = null;
  if (field.$cel) {
    $controls = $$('controls '+type);
    $controls.append(field.$cel());
    $el.append($controls);
  }

  Object.defineProperty(this, "data", {
    get: function () {
      return field.data;
    },
    set: function (n) {
      field.data = n;
    }
  });

  function columns_update_ui() {
    $el.removeClass(lastCols);
    lastCols = 'col-1-' + cols;
    $el.addClass(lastCols);
  }

  Object.defineProperty(this, "columns", {
    get: function () {
      return  cols;
    },
    set: function (n) {
      cols = n;
      columns_update_ui();
    }
  });

  function label_update_ui() {
    if (options && options.collapsable) {
      $label.empty();
      var $c = $("x");
      function tgl(v){
        if (v) {
          $c.empty().append("");
          field.$el().hide();
        }
        else {
          $c.empty().append("");
          field.$el().show();
        }
      }
      var v = options.collapsed != null ? options.collapsed : true;
      tgl(v);
      $c.click(function(){
        v = !v;
        tgl(v);
      });
      $label.append($c, ""+label+"");
    } else {
      $label.text(label);
    }
    if (showLabel)
      $label.show();
    else
      $label.hide();
  }

  Object.defineProperty(this, "label", {
    get: function () {
      return  label;
    },
    set: function (n) {
      label = n;
      label_update_ui();
    }
  });

  Object.defineProperty(this, "field", {
    get: function () {
      return field;
    }
  });
  Object.defineProperty(this, "name", {
    get: function () {
      return name;
    }
  });
  Object.defineProperty(this, "error", {
    set: function (e) {
      field.$el().append('' + e + '');
      $el.addClass('error');
    }
  });

}

<|endoftext|>


<commit_before>// Copyright 2013-2015, University of Colorado Boulder

/**
 * Box that can be expanded/collapsed to show/hide contents.
 *
 * @author John Blanco (PhET Interactive Simulations)
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Emitter = require( 'AXON/Emitter' );
  var ExpandCollapseButton = require( 'SUN/ExpandCollapseButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );
  var Text = require( 'SCENERY/nodes/Text' );

  // phet-io modules
  var TBoolean = require( 'ifphetio!PHET_IO/types/TBoolean' );
  var TAccordionBox = require( 'SUN/TAccordionBox' );

  /**
   * @param {Node} contentNode that will be shown or hidden as the accordion box is expanded/collapsed.
   * @param {Object} [options] Various key-value pairs that control the appearance and behavior.  Some options are
   * specific to this class while some are passed to the superclass.  See the code where the options are set in the
   * early portion of the constructor for details.
   * @constructor
   */
  function AccordionBox( contentNode, options ) {

    var self = this;

    // set up tandem first so it can be used for other default options just below
    options.tandem = options.tandem || Tandem.tandemRequired();

    options = _.extend( {

      // applied to multiple parts of this UI component
      cursor: 'pointer', // {string} default cursor
      lineWidth: 1,
      cornerRadius: 3,

      // box
      stroke: 'black',
      fill: 'rgb( 238, 238, 238 )',
      minWidth: 0,

      // title
      titleNode: new Text( '', { tandem: options.tandem.createTandem( 'titleNode' ) } ), // a {Node} with well-defined bounds
      titleAlignX: 'center', // {string} horizontal alignment of the title, 'left'|'center'|'right'
      titleAlignY: 'center', // {string} vertical alignment of the title, relative to expand/collapse button 'top'|'center'
      titleXMargin: 10, // horizontal space between title and left|right edge of box
      titleYMargin: 2, // vertical space between title and top of box
      titleXSpacing: 5, // horizontal space between title and expand/collapse button
      showTitleWhenExpanded: true, // true = title is visible when expanded, false = title is hidden when expanded
      titleBarFill: null, // {Color|string} title bar fill
      titleBarStroke: null, // {Color|string} title bar stroke, used only for the expanded title bar
      titleBarExpandCollapse: true, // {boolean} clicking on the title bar expands/collapses the accordion box

      // expand/collapse button
      buttonLength: 16, // button is a square, this is the length of one side
      buttonAlign: 'left',  // {string} button alignment, 'left'|'right'
      buttonXMargin: 4, // horizontal space between button and left|right edge of box
      buttonYMargin: 2, // vertical space between button and top edge of box
      expandedProperty: new Property( true, {
        tandem: options.tandem.createTandem( 'expandedProperty' ),
        phetioValueType: TBoolean
      } ),
      buttonTouchAreaXDilation: 0,
      buttonTouchAreaYDilation: 0,
      buttonMouseAreaXDilation: 0,
      buttonMouseAreaYDilation: 0,

      // content
      contentAlign: 'center', // {string} horizontal alignment of the content, 'left'|'center'|'right'
      contentXMargin: 15, // horizontal space between content and left/right edges of box
      contentYMargin: 8,  // horizontal space between content and bottom edge of box
      contentXSpacing: 5, // horizontal space between content and button, ignored if showTitleWhenExpanded is true
      contentYSpacing: 8, // vertical space between content and title+button, ignored if showTitleWhenExpanded is false

      // phet-io support
      phetioType: TAccordionBox,

      // a11y options
      tagName: 'div',
      accessibleAccordionTitle: 'No Title Given',

    }, options );

    // verify string options
    assert && assert( options.buttonAlign === 'left' || options.buttonAlign === 'right' );
    assert && assert( options.contentAlign === 'left' || options.contentAlign === 'right' || options.contentAlign === 'center' );
    assert && assert( options.titleAlignX === 'left' || options.titleAlignX === 'right' || options.titleAlignX === 'center' );
    assert && assert( options.titleAlignY === 'top' || options.titleAlignY === 'center' );

    // emitters for the PhET-iO data stream
    this.startedCallbacksForExpandedTitleBarDownEmitter = new Emitter();
    this.endedCallbacksForExpandedTitleBarDownEmitter = new Emitter();
    this.startedCallbacksForCollapsedTitleBarDownEmitter = new Emitter();
    this.endedCallbacksForCollapsedTitleBarDownEmitter = new Emitter();

    Node.call( this );

    // @private - expand/collapse button, links to expandedProperty, must be disposed of
    this.expandCollapseButton = new ExpandCollapseButton( options.expandedProperty, {
      sideLength: options.buttonLength,
      cursor: options.cursor,
      tandem: options.tandem.createTandem( 'expandCollapseButton' )
    } );
    this.expandCollapseButton.touchArea = this.expandCollapseButton.localBounds.dilatedXY(
      options.buttonTouchAreaXDilation,
      options.buttonTouchAreaYDilation
    );
    this.expandCollapseButton.mouseArea = this.expandCollapseButton.localBounds.dilatedXY(
      options.buttonMouseAreaXDilation,
      options.buttonMouseAreaYDilation
    );

    // Compute box dimensions
    var collapsedBoxHeight = Math.max( this.expandCollapseButton.height + ( 2 * options.buttonYMargin ), options.titleNode.height + ( 2 * options.titleYMargin ) );
    assert && assert( options.cornerRadius < collapsedBoxHeight / 2 );
    var boxWidth = Math.max( options.minWidth, this.expandCollapseButton.width + options.titleNode.width + options.buttonXMargin + options.titleXMargin + options.titleXSpacing );
    var expandedBoxHeight;
    if ( options.showTitleWhenExpanded ) {
      // content is below button+title
      boxWidth = Math.max( boxWidth, contentNode.width + ( 2 * options.contentXMargin ) );
      expandedBoxHeight = collapsedBoxHeight + contentNode.height + options.contentYMargin + options.contentYSpacing;
    }
    else {
      // content is next to button
      boxWidth = Math.max( boxWidth, this.expandCollapseButton.width + contentNode.width + options.buttonXMargin + options.contentXMargin + options.contentXSpacing );
      expandedBoxHeight = Math.max( this.expandCollapseButton.height + ( 2 * options.buttonYMargin ), contentNode.height + ( 2 * options.contentYMargin ) );
    }

    // Expanded box
    var boxOptions = { fill: options.fill };
    var expandedBox = new Rectangle(
      0,
      0,
      boxWidth,
      expandedBoxHeight,
      options.cornerRadius,
      options.cornerRadius,
      _.extend( {
        tandem: options.tandem.createTandem( 'expandedBox' ),

        // a11y
        tagName: 'div'
      }, boxOptions )
    );
    this.addChild( expandedBox );

    // Collapsed box
    var collapsedBox = new Rectangle(
      0,
      0,
      boxWidth,
      collapsedBoxHeight,
      options.cornerRadius,
      options.cornerRadius,
      _.extend( { tandem: options.tandem.createTandem( 'collapsedBox' ) }, boxOptions )
    );
    this.addChild( collapsedBox );

    // Expanded title bar has (optional) rounded top corners, square bottom corners. Clicking it operates like expand/collapse button.
    var expandedTitleBarShape = Shape.roundedRectangleWithRadii( 0, 0, boxWidth, collapsedBoxHeight, {
      topLeft: options.cornerRadius,
      topRight: options.cornerRadius
    } );
    var expandedTitleBar = new Path( expandedTitleBarShape, {
      fill: options.titleBarFill,
      stroke: options.titleBarStroke,
      lineWidth: options.lineWidth, // use same lineWidth as box, for consistent look
      cursor: options.cursor,
      tandem: options.tandem.createTandem( 'expandedTitleBar' ),

      // a11y
      tagName: 'button',
      accessibleLabel: options.accessibleAccordionTitle,
      labelTagName: 'p'
    } );
    expandedBox.addChild( expandedTitleBar );

    // Collapsed title bar has corners that match the box. Clicking it operates like expand/collapse button.
    var collapsedTitleBar = new Rectangle( 0, 0, boxWidth, collapsedBoxHeight, options.cornerRadius, options.cornerRadius, {
      fill: options.titleBarFill,
      cursor: options.cursor,
      tandem: options.tandem.createTandem( 'collapsedTitleBar' ),

      // a11y
      tagName: 'button',
      accessibleLabel: options.accessibleAccordionTitle,
      labelTagName: 'p'
    } );
    collapsedBox.addChild( collapsedTitleBar );
    if ( options.titleBarExpandCollapse ) {
      collapsedTitleBar.addInputListener( {
        down: function() {
          self.startedCallbacksForExpandedTitleBarDownEmitter.emit();
          options.expandedProperty.value = true;
          self.endedCallbacksForExpandedTitleBarDownEmitter.emit();
        }
      } );
    }

    // a11y we always want accessible tab focus on the title, even when titleBarExpandeCollapse === false
    collapsedTitleBar.addAccessibleInputListener( {
      click: function() {
        self.startedCallbacksForExpandedTitleBarDownEmitter.emit();
        options.expandedProperty.value = true;
        self.endedCallbacksForExpandedTitleBarDownEmitter.emit();

        // a11y Set focus to expanded title bar
        expandedTitleBar.focus();
      }
    } );

    // Set the input listeners for the expandedTitleBar
    // a11y we need to focus on the collapsedTitleBar when the expandedTitleBar is clicked
    if ( options.showTitleWhenExpanded ) {
      if ( options.titleBarExpandCollapse ) {
        expandedTitleBar.addInputListener( {
          down: function() {
            self.startedCallbacksForCollapsedTitleBarDownEmitter.emit();
            options.expandedProperty.value = false;
            self.endedCallbacksForCollapsedTitleBarDownEmitter.emit();
          }
        } );
      }

      // a11y we always want accessible tab focus on the title
      expandedTitleBar.addAccessibleInputListener( {
        click: function() {
          self.startedCallbacksForCollapsedTitleBarDownEmitter.emit();
          options.expandedProperty.value = false;
          self.endedCallbacksForCollapsedTitleBarDownEmitter.emit();

          // a11y Set focus to expanded title bar
          collapsedTitleBar.focus();
        }
      } );
    }

    // TODO: a11y
    this.addChild( options.titleNode );
    this.addChild( this.expandCollapseButton );

    // box outline, on top of everything else
    var expandedBoxOutline;
    var collapsedBoxOutline;
    if ( options.stroke ) {
      var outlineOptions = { stroke: options.stroke, lineWidth: options.lineWidth };
      expandedBoxOutline = new Rectangle(
        0,
        0,
        boxWidth,
        expandedBoxHeight,
        options.cornerRadius,
        options.cornerRadius,
        _.extend( { tandem: options.tandem.createTandem( 'expandedBoxOutline' ) }, outlineOptions )
      );
      collapsedBoxOutline = new Rectangle(
        0,
        0,
        boxWidth,
        collapsedBoxHeight,
        options.cornerRadius,
        options.cornerRadius,
        _.extend( { tandem: options.tandem.createTandem( 'collapsedBoxOutline' ) }, outlineOptions )
      );
      expandedBox.addChild( expandedBoxOutline );
      collapsedBox.addChild( collapsedBoxOutline );
    }

    // content layout
    contentNode.bottom = expandedBox.bottom - options.contentYMargin;
    var contentSpanLeft = expandedBox.left + options.contentXMargin;
    var contentSpanRight = expandedBox.right - options.contentXMargin;
    if ( !options.showTitleWhenExpanded ) {
      // content will be placed next to button
      if ( options.buttonAlign === 'left' ) {
        contentSpanLeft += this.expandCollapseButton.width + options.contentXSpacing;
      }
      else { // right on right
        contentSpanRight -= this.expandCollapseButton.width + options.contentXSpacing;
      }
    }
    if ( options.contentAlign === 'left' ) {
      contentNode.left = contentSpanLeft;
    }
    else if ( options.contentAlign === 'right' ) {
      contentNode.right = contentSpanRight;
    }
    else { // center
      contentNode.centerX = ( contentSpanLeft + contentSpanRight ) / 2;
    }
    expandedBox.addChild( contentNode ); // do this after layout computations, see sun#280

    // button horizontal layout
    var titleLeftSpan = expandedBox.left + options.titleXMargin;
    var titleRightSpan = expandedBox.right - options.titleXMargin;
    if ( options.buttonAlign === 'left' ) {
      this.expandCollapseButton.left = expandedBox.left + options.buttonXMargin;
      titleLeftSpan = this.expandCollapseButton.right + options.titleXSpacing;
    }
    else {
      this.expandCollapseButton.right = expandedBox.right - options.buttonXMargin;
      titleRightSpan = this.expandCollapseButton.left - options.titleXSpacing;
    }

    // title horizontal layout
    if ( options.titleAlignX === 'left' ) {
      options.titleNode.left = titleLeftSpan;
    }
    else if ( options.titleAlignX === 'right' ) {
      options.titleNode.right = titleRightSpan;
    }
    else { // center
      options.titleNode.centerX = expandedBox.centerX;
    }

    // button & title vertical layout
    if ( options.titleAlignY === 'top' ) {
      this.expandCollapseButton.top = collapsedBox.top + Math.max( options.buttonYMargin, options.titleYMargin );
      options.titleNode.top = this.expandCollapseButton.top;
    }
    else { // center
      this.expandCollapseButton.centerY = collapsedBox.centerY;
      options.titleNode.centerY = this.expandCollapseButton.centerY;
    }

    // @private expand/collapse the box
    this.expandedPropertyObserver = function( expanded ) {
      expandedBox.visible = expanded;
      collapsedBox.visible = !expanded;

      // a11y Toggle the visibility of the buttons in the PDOM
      expandedTitleBar.accessibleHidden = !expanded;
      collapsedTitleBar.accessibleHidden = expanded;

      options.titleNode.visible = ( expanded && options.showTitleWhenExpanded ) || !expanded;
    };
    this.expandedProperty = options.expandedProperty; // @private
    this.expandedProperty.link( this.expandedPropertyObserver ); // must be unlinked in dispose

    this.mutate( _.omit( options, 'cursor' ) );
  }

  sun.register( 'AccordionBox', AccordionBox );

  return inherit( Node, AccordionBox, {

    // @public - ensures that this node is eligible for GC
    dispose: function() {
      this.expandCollapseButton.dispose();
      this.expandCollapseButton = null;
      this.expandedProperty.unlink( this.expandedPropertyObserver );
      Node.prototype.dispose.call( this );
    }
  } );
} );<commit_msg>Initial tandem cleanup for AccordionBox refactoring, see https://github.com/phetsims/sun/issues/304 and https://github.com/phetsims/phet-io/issues/1179<commit_after>// Copyright 2013-2015, University of Colorado Boulder

/**
 * Box that can be expanded/collapsed to show/hide contents.
 *
 * @author John Blanco (PhET Interactive Simulations)
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var BooleanProperty = require( 'AXON/BooleanProperty' );
  var Emitter = require( 'AXON/Emitter' );
  var ExpandCollapseButton = require( 'SUN/ExpandCollapseButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );
  var Text = require( 'SCENERY/nodes/Text' );

  // phet-io modules
  var TAccordionBox = require( 'SUN/TAccordionBox' );

  /**
   * @constructor
   *
   * @param {Node} contentNode - Content that will be shown or hidden as the accordion box is expanded/collapsed.
   * @param {Object} [options] - Various key-value pairs that control the appearance and behavior.  Some options are
   *                             specific to this class while some are passed to the superclass.  See the code where
   *                             the options are set in the early portion of the constructor for details.
   */
  function AccordionBox( contentNode, options ) {

    var self = this;

    options = _.extend( {
      // {Tandem}
      tandem: Tandem.tandemRequired(),

      // {Node} - If not provided, a Text node will be supplied. Should have and maintain well-defined bounds if passed
      //          in.
      titleNode: null,

      // {Property.<boolean>} - If not provided, a BooleanProperty will be created, defaulting to true.
      expandedProperty: null,

      // applied to multiple parts of this UI component
      cursor: 'pointer', // {string} default cursor
      lineWidth: 1,
      cornerRadius: 3,

      // box
      stroke: 'black',
      fill: 'rgb( 238, 238, 238 )',
      minWidth: 0,

      titleAlignX: 'center', // {string} horizontal alignment of the title, 'left'|'center'|'right'
      titleAlignY: 'center', // {string} vertical alignment of the title, relative to expand/collapse button 'top'|'center'
      titleXMargin: 10, // horizontal space between title and left|right edge of box
      titleYMargin: 2, // vertical space between title and top of box
      titleXSpacing: 5, // horizontal space between title and expand/collapse button
      showTitleWhenExpanded: true, // true = title is visible when expanded, false = title is hidden when expanded
      titleBarFill: null, // {Color|string} title bar fill
      titleBarStroke: null, // {Color|string} title bar stroke, used only for the expanded title bar
      titleBarExpandCollapse: true, // {boolean} clicking on the title bar expands/collapses the accordion box

      // expand/collapse button
      buttonLength: 16, // button is a square, this is the length of one side
      buttonAlign: 'left',  // {string} button alignment, 'left'|'right'
      buttonXMargin: 4, // horizontal space between button and left|right edge of box
      buttonYMargin: 2, // vertical space between button and top edge of box
      buttonTouchAreaXDilation: 0,
      buttonTouchAreaYDilation: 0,
      buttonMouseAreaXDilation: 0,
      buttonMouseAreaYDilation: 0,

      // content
      contentAlign: 'center', // {string} horizontal alignment of the content, 'left'|'center'|'right'
      contentXMargin: 15, // horizontal space between content and left/right edges of box
      contentYMargin: 8,  // horizontal space between content and bottom edge of box
      contentXSpacing: 5, // horizontal space between content and button, ignored if showTitleWhenExpanded is true
      contentYSpacing: 8, // vertical space between content and title+button, ignored if showTitleWhenExpanded is false

      // phet-io support
      phetioType: TAccordionBox,

      // a11y options
      tagName: 'div',
      accessibleAccordionTitle: 'No Title Given',

    }, options );

    // @private {Array.<function>} - Actions to take when this AccordionBox is disposed. Will be called with a proper
    //                               'this' reference to the AccordionBox.
    this.disposalActions = [];

    // @private {Node}
    this.titleNode = options.titleNode;

    // If there is no titleNode specified, we'll provide our own, and handle disposal.
    if ( !this.titleNode ) {
      this.titleNode = new Text( '', { tandem: options.tandem.createTandem( 'titleNode' ) } );
      this.disposalActions.push( function() {
        this.titleNode.dispose();
      } );
    }

    // @private {Property.<boolean>}
    this.expandedProperty = options.expandedProperty;

    if ( !this.expandedProperty ) {
      this.expandedProperty = new BooleanProperty( true, {
        tandem: options.tandem.createTandem( 'expandedProperty' )
      } );
      this.disposalActions.push( function() {
        this.expandedProperty.dispose();
      } );
    }

    // verify string options
    assert && assert( options.buttonAlign === 'left' || options.buttonAlign === 'right' );
    assert && assert( options.contentAlign === 'left' || options.contentAlign === 'right' || options.contentAlign === 'center' );
    assert && assert( options.titleAlignX === 'left' || options.titleAlignX === 'right' || options.titleAlignX === 'center' );
    assert && assert( options.titleAlignY === 'top' || options.titleAlignY === 'center' );

    // emitters for the PhET-iO data stream
    this.startedCallbacksForExpandedTitleBarDownEmitter = new Emitter();
    this.endedCallbacksForExpandedTitleBarDownEmitter = new Emitter();
    this.startedCallbacksForCollapsedTitleBarDownEmitter = new Emitter();
    this.endedCallbacksForCollapsedTitleBarDownEmitter = new Emitter();

    Node.call( this );

    // @private - expand/collapse button, links to expandedProperty, must be disposed of
    this.expandCollapseButton = new ExpandCollapseButton( this.expandedProperty, {
      sideLength: options.buttonLength,
      cursor: options.cursor,
      tandem: options.tandem.createTandem( 'expandCollapseButton' )
    } );
    this.expandCollapseButton.touchArea = this.expandCollapseButton.localBounds.dilatedXY(
      options.buttonTouchAreaXDilation,
      options.buttonTouchAreaYDilation
    );
    this.expandCollapseButton.mouseArea = this.expandCollapseButton.localBounds.dilatedXY(
      options.buttonMouseAreaXDilation,
      options.buttonMouseAreaYDilation
    );

    // Compute box dimensions
    var collapsedBoxHeight = Math.max( this.expandCollapseButton.height + ( 2 * options.buttonYMargin ), this.titleNode.height + ( 2 * options.titleYMargin ) );
    assert && assert( options.cornerRadius < collapsedBoxHeight / 2 );
    var boxWidth = Math.max( options.minWidth, this.expandCollapseButton.width + this.titleNode.width + options.buttonXMargin + options.titleXMargin + options.titleXSpacing );
    var expandedBoxHeight;
    if ( options.showTitleWhenExpanded ) {
      // content is below button+title
      boxWidth = Math.max( boxWidth, contentNode.width + ( 2 * options.contentXMargin ) );
      expandedBoxHeight = collapsedBoxHeight + contentNode.height + options.contentYMargin + options.contentYSpacing;
    }
    else {
      // content is next to button
      boxWidth = Math.max( boxWidth, this.expandCollapseButton.width + contentNode.width + options.buttonXMargin + options.contentXMargin + options.contentXSpacing );
      expandedBoxHeight = Math.max( this.expandCollapseButton.height + ( 2 * options.buttonYMargin ), contentNode.height + ( 2 * options.contentYMargin ) );
    }

    // Expanded box
    var boxOptions = { fill: options.fill };
    var expandedBox = new Rectangle(
      0,
      0,
      boxWidth,
      expandedBoxHeight,
      options.cornerRadius,
      options.cornerRadius,
      _.extend( {
        tandem: options.tandem.createTandem( 'expandedBox' ),

        // a11y
        tagName: 'div'
      }, boxOptions )
    );
    this.addChild( expandedBox );

    // Collapsed box
    var collapsedBox = new Rectangle(
      0,
      0,
      boxWidth,
      collapsedBoxHeight,
      options.cornerRadius,
      options.cornerRadius,
      _.extend( { tandem: options.tandem.createTandem( 'collapsedBox' ) }, boxOptions )
    );
    this.addChild( collapsedBox );

    // Expanded title bar has (optional) rounded top corners, square bottom corners. Clicking it operates like expand/collapse button.
    var expandedTitleBarShape = Shape.roundedRectangleWithRadii( 0, 0, boxWidth, collapsedBoxHeight, {
      topLeft: options.cornerRadius,
      topRight: options.cornerRadius
    } );
    var expandedTitleBar = new Path( expandedTitleBarShape, {
      fill: options.titleBarFill,
      stroke: options.titleBarStroke,
      lineWidth: options.lineWidth, // use same lineWidth as box, for consistent look
      cursor: options.cursor,
      tandem: options.tandem.createTandem( 'expandedTitleBar' ),

      // a11y
      tagName: 'button',
      accessibleLabel: options.accessibleAccordionTitle,
      labelTagName: 'p'
    } );
    expandedBox.addChild( expandedTitleBar );

    // Collapsed title bar has corners that match the box. Clicking it operates like expand/collapse button.
    var collapsedTitleBar = new Rectangle( 0, 0, boxWidth, collapsedBoxHeight, options.cornerRadius, options.cornerRadius, {
      fill: options.titleBarFill,
      cursor: options.cursor,
      tandem: options.tandem.createTandem( 'collapsedTitleBar' ),

      // a11y
      tagName: 'button',
      accessibleLabel: options.accessibleAccordionTitle,
      labelTagName: 'p'
    } );
    collapsedBox.addChild( collapsedTitleBar );
    if ( options.titleBarExpandCollapse ) {
      collapsedTitleBar.addInputListener( {
        down: function() {
          self.startedCallbacksForExpandedTitleBarDownEmitter.emit();
          self.expandedProperty.value = true;
          self.endedCallbacksForExpandedTitleBarDownEmitter.emit();
        }
      } );
    }

    // a11y we always want accessible tab focus on the title, even when titleBarExpandeCollapse === false
    collapsedTitleBar.addAccessibleInputListener( {
      click: function() {
        self.startedCallbacksForExpandedTitleBarDownEmitter.emit();
        self.expandedProperty.value = true;
        self.endedCallbacksForExpandedTitleBarDownEmitter.emit();

        // a11y Set focus to expanded title bar
        expandedTitleBar.focus();
      }
    } );

    // Set the input listeners for the expandedTitleBar
    // a11y we need to focus on the collapsedTitleBar when the expandedTitleBar is clicked
    if ( options.showTitleWhenExpanded ) {
      if ( options.titleBarExpandCollapse ) {
        expandedTitleBar.addInputListener( {
          down: function() {
            self.startedCallbacksForCollapsedTitleBarDownEmitter.emit();
            self.expandedProperty.value = false;
            self.endedCallbacksForCollapsedTitleBarDownEmitter.emit();
          }
        } );
      }

      // a11y we always want accessible tab focus on the title
      expandedTitleBar.addAccessibleInputListener( {
        click: function() {
          self.startedCallbacksForCollapsedTitleBarDownEmitter.emit();
          self.expandedProperty.value = false;
          self.endedCallbacksForCollapsedTitleBarDownEmitter.emit();

          // a11y Set focus to expanded title bar
          collapsedTitleBar.focus();
        }
      } );
    }

    // TODO: a11y
    this.addChild( this.titleNode );
    this.addChild( this.expandCollapseButton );

    // box outline, on top of everything else
    var expandedBoxOutline;
    var collapsedBoxOutline;
    if ( options.stroke ) {
      var outlineOptions = { stroke: options.stroke, lineWidth: options.lineWidth };
      expandedBoxOutline = new Rectangle(
        0,
        0,
        boxWidth,
        expandedBoxHeight,
        options.cornerRadius,
        options.cornerRadius,
        _.extend( { tandem: options.tandem.createTandem( 'expandedBoxOutline' ) }, outlineOptions )
      );
      collapsedBoxOutline = new Rectangle(
        0,
        0,
        boxWidth,
        collapsedBoxHeight,
        options.cornerRadius,
        options.cornerRadius,
        _.extend( { tandem: options.tandem.createTandem( 'collapsedBoxOutline' ) }, outlineOptions )
      );
      expandedBox.addChild( expandedBoxOutline );
      collapsedBox.addChild( collapsedBoxOutline );
    }

    // content layout
    contentNode.bottom = expandedBox.bottom - options.contentYMargin;
    var contentSpanLeft = expandedBox.left + options.contentXMargin;
    var contentSpanRight = expandedBox.right - options.contentXMargin;
    if ( !options.showTitleWhenExpanded ) {
      // content will be placed next to button
      if ( options.buttonAlign === 'left' ) {
        contentSpanLeft += this.expandCollapseButton.width + options.contentXSpacing;
      }
      else { // right on right
        contentSpanRight -= this.expandCollapseButton.width + options.contentXSpacing;
      }
    }
    if ( options.contentAlign === 'left' ) {
      contentNode.left = contentSpanLeft;
    }
    else if ( options.contentAlign === 'right' ) {
      contentNode.right = contentSpanRight;
    }
    else { // center
      contentNode.centerX = ( contentSpanLeft + contentSpanRight ) / 2;
    }
    expandedBox.addChild( contentNode ); // do this after layout computations, see sun#280

    // button horizontal layout
    var titleLeftSpan = expandedBox.left + options.titleXMargin;
    var titleRightSpan = expandedBox.right - options.titleXMargin;
    if ( options.buttonAlign === 'left' ) {
      this.expandCollapseButton.left = expandedBox.left + options.buttonXMargin;
      titleLeftSpan = this.expandCollapseButton.right + options.titleXSpacing;
    }
    else {
      this.expandCollapseButton.right = expandedBox.right - options.buttonXMargin;
      titleRightSpan = this.expandCollapseButton.left - options.titleXSpacing;
    }

    // title horizontal layout
    if ( options.titleAlignX === 'left' ) {
      this.titleNode.left = titleLeftSpan;
    }
    else if ( options.titleAlignX === 'right' ) {
      this.titleNode.right = titleRightSpan;
    }
    else { // center
      this.titleNode.centerX = expandedBox.centerX;
    }

    // button & title vertical layout
    if ( options.titleAlignY === 'top' ) {
      this.expandCollapseButton.top = collapsedBox.top + Math.max( options.buttonYMargin, options.titleYMargin );
      this.titleNode.top = this.expandCollapseButton.top;
    }
    else { // center
      this.expandCollapseButton.centerY = collapsedBox.centerY;
      this.titleNode.centerY = this.expandCollapseButton.centerY;
    }

    // @private expand/collapse the box
    this.expandedPropertyObserver = function( expanded ) {
      expandedBox.visible = expanded;
      collapsedBox.visible = !expanded;

      // a11y Toggle the visibility of the buttons in the PDOM
      expandedTitleBar.accessibleHidden = !expanded;
      collapsedTitleBar.accessibleHidden = expanded;

      self.titleNode.visible = ( expanded && options.showTitleWhenExpanded ) || !expanded;
    };
    this.expandedProperty.link( this.expandedPropertyObserver ); // must be unlinked in dispose

    this.mutate( _.omit( options, 'cursor' ) );
  }

  sun.register( 'AccordionBox', AccordionBox );

  return inherit( Node, AccordionBox, {
    /**
     * Ensures this node is eligible for GC.
     * @public
     */
    dispose: function() {
      while ( this.disposalActions.length ) {
        this.disposalActions.pop().call( this );
      }

      this.expandCollapseButton.dispose();
      this.expandCollapseButton = null;
      this.expandedProperty.unlink( this.expandedPropertyObserver );
      Node.prototype.dispose.call( this );
    }
  } );
} );<|endoftext|>


<commit_before>export default function createUpdate(pool) {
	return (table, id, data) => new Promise((resolve, reject) => {
		pool.getConnection((err, conn) => {
			if (err) {
				return reject(err);
			}

			const fields = [];
			const values = [];
			Object.keys(data).forEach(field => {
				fields.push(field);
				values.push(data[field]);
			});

			const query = [
				`UPDATE ${table} SET`,
				fields.map(field => `${field} = ?`).join(', '),
				`WHERE id = ${id}`,
			].join(' ');

			conn.query(query, values, (err, result) => {
				conn.release();
				if (err) {
					return reject(err);
				}

				resolve(result);
			});
		});
	});
}
<commit_msg>mysql: return the updated row<commit_after>import createSelect from './createSelect';

export default function createUpdate(pool) {
	return (table, id, data) => new Promise((resolve, reject) => {
		pool.getConnection((err, conn) => {
			if (err) {
				return reject(err);
			}

			const fields = [];
			const values = [];
			Object.keys(data).forEach(field => {
				fields.push(field);
				values.push(data[field]);
			});

			const query = [
				`UPDATE ${table} SET`,
				fields.map(field => `${field} = ?`).join(', '),
				`WHERE id = ${id}`,
			].join(' ');

			conn.query(query, values, err => {
				conn.release();
				if (err) {
					return reject(err);
				}

				createSelect(pool)('user', id)
					.then(resolve)
					.catch(reject);
			});
		});
	});
}
<|endoftext|>


<commit_before>// Preprocessor Class
(function () {

/**
 * @summary Data preprocessor.
 *
 * @description Data can be preprocessed before it is rendered by an adapter.
 *
 * This has several important performance considerations.  If data will be
 * rendered repeatedly or on slower browsers, it will be faster after being
 * optimized.
 *
 * First, data outside the boundaries does not need to be rendered.  Second,
 * the resolution of the data only needs to be at most the number of pixels
 * in the width of the visualization.
 *
 * Performing these optimizations will limit memory overhead, important
 * for garbage collection and performance on old browsers, as well as drawing
 * overhead, important for mobile devices, old browsers and large data sets.
 *
 * @param {Array} [data]  The data for processing.
 *
 * @memberof envision
 * @class
 */
function Preprocessor (options) {

  options = options || {};

  var
    data;

  /**
   * Returns data.
   */
  this.getData = function () {
    if (this.bounded) bound(this);
    return data;
  }

  /**
   * Set the data object.
   */
  this.setData = function (newData) {
    var
      i, length;
    if (!_.isArray(newData)) throw new Error('Array expected.');
    if (newData.length < 2) throw new Error('Data must contain at least two dimensions.');
    length = newData[0].length;
    for (i = newData.length; i--;) {
      if (!_.isArray(newData[i])) throw new Error('Data dimensions must be arrays.');
      if (newData[i].length !== length) throw new Error('Data dimensions must contain the same number of points.');
    }

    data = newData;

    return this;
  }

  if (options.data) this.setData(options.data);
}

function getStartIndex (data, min) {

  var
    length = data.length,
    i;

  for (i = 0; i < length; i++) {
    if (data[i] >= min) break;
  }

  // Include point outside range when not exact match
  if (data[i] > min && i > 0) i--;

  return i;
}

function getEndIndex (data, max) {

  var
    i;

  for (i = data.length; i--;) {
    if (data[i] <= max) break;
  }

  // Include point outside range when not exact match
  if (data[i] < max && i > 0) i++;

  return i;
}

function bound (that) {

  delete that.bounded;

  var
    data    = that.getData(),
    length  = that.length(),
    x       = data[0],
    y       = data[1],
    min     = that.min || 0,
    max     = that.max || that.length(),
    start   = getStartIndex(x, min),
    end     = getEndIndex(x, max);

  that.setData([
    x.slice(start, end + 1),
    y.slice(start, end + 1)
  ]);
};

Preprocessor.prototype = {

  /**
   * Returns the length of the data set.
   *
   * @return {Number} Length of the data set.
   */
  length : function () {
    return this.getData()[0].length;
  },

  /**
   * Bounds the data set at within a range.
   *
   * @param {Number} min
   * @param {Number} max
   */
  bound : function (min, max) {

    if (!_.isNumber(min) || !_.isNumber(max)) return this;

    this.min = min;
    this.max = max;
    this.bounded = true;

    return this;
  },

  /**
   * Subsample data using MinMax.
   *
   * MinMax will display the extrema of the subsample intervals.  This is
   * slower than regular interval subsampling but necessary for data that
   * is very non-homogenous.
   *
   * @param {Number} resolution
   */
  subsampleMinMax : function (resolution) {

    var bounded = this.bounded;
    delete this.bounded;

    var
      data    = this.getData(),
      length  = this.length(),
      x       = data[0],
      y       = data[1],
      start   = bounded ? getStartIndex(x, this.min) : 0,
      end     = bounded ? getEndIndex(x, this.max) : length - 1,
      count   = (resolution - 2) / 2,
      newX    = [],
      newY    = [],
      min     = Number.MAX_VALUE,
      max     = -Number.MAX_VALUE,
      minI    = 1,
      maxI    = 1,
      unit    = (end - start)/ count,
      position, min, max, datum, i, j;

    if (end - start + 1 > resolution) {

      newX.push(x[start]);
      newY.push(y[start]);

      position = start + unit;

      for (i = start; i < end; i++) {

        if (i === Math.round(position)) {

          position += unit;

          j = Math.min(maxI, minI);
          newX.push(x[j]);
          newY.push(y[j]);

          j = Math.max(maxI, minI);
          newX.push(x[j]);
          newY.push(y[j]);

          minI = i;
          min = y[minI];
          maxI = i;
          max = y[maxI];

        } else {
          if (y[i] > max) {
            max = y[i];
            maxI = i;
          }

          if (y[i] < min) {
            min = y[i];
            minI = i;
          }
        }
      }

      if (i < position) {
        newX.push(x[minI]);
        newY.push(min);
        newX.push(x[maxI]);
        newY.push(max);
      }

      // Last
      newX.push(x[end]);
      newY.push(y[end]);

      this.setData([newX, newY]);
    } else {
      this.bounded = true;
    }

    return this;
  },

  /**
   * Subsample data at a regular interval for resolution.
   *
   * This is the fastest subsampling and good for monotonic data and fairly
   * homogenous data (not a lot of up and down).
   *
   * @param {Number} resolution
   */
  subsample : function (resolution) {

    var bounded = this.bounded;
    delete this.bounded;

    var
      data    = this.getData(),
      length  = this.length(),
      x       = data[0],
      y       = data[1],
      start   = bounded ? getStartIndex(x, this.min) : 0,
      end     = bounded ? getEndIndex(x, this.max) : length - 1,
      unit    = (end - start + 1) / resolution,
      newX    = [],
      newY    = [],
      i, index;

    if (length > resolution) {

      // First
      newX.push(x[start]);
      newY.push(y[start]);

      for (i = 1; i < resolution; i++) {
        if (i * unit >= end - unit) break;
        index = Math.round(i * unit) + start;
        newX.push(x[index]);
        newY.push(y[index]);
      }

      // Last
      newX.push(x[end]);
      newY.push(y[end]);

      this.setData([newX, newY]);
    }

    return this;
  }
};

envision.Preprocessor = Preprocessor;

}());
<commit_msg>Add start and end dates indices for processed data.<commit_after>// Preprocessor Class
(function () {

/**
 * @summary Data preprocessor.
 *
 * @description Data can be preprocessed before it is rendered by an adapter.
 *
 * This has several important performance considerations.  If data will be
 * rendered repeatedly or on slower browsers, it will be faster after being
 * optimized.
 *
 * First, data outside the boundaries does not need to be rendered.  Second,
 * the resolution of the data only needs to be at most the number of pixels
 * in the width of the visualization.
 *
 * Performing these optimizations will limit memory overhead, important
 * for garbage collection and performance on old browsers, as well as drawing
 * overhead, important for mobile devices, old browsers and large data sets.
 *
 * @param {Array} [data]  The data for processing.
 *
 * @memberof envision
 * @class
 */
function Preprocessor (options) {

  options = options || {};

  var
    data;

  /**
   * Returns data.
   */
  this.getData = function () {
    if (this.bounded) bound(this);
    return data;
  }

  /**
   * Set the data object.
   */
  this.setData = function (newData) {
    var
      i, length;
    if (!_.isArray(newData)) throw new Error('Array expected.');
    if (newData.length < 2) throw new Error('Data must contain at least two dimensions.');
    length = newData[0].length;
    for (i = newData.length; i--;) {
      if (!_.isArray(newData[i])) throw new Error('Data dimensions must be arrays.');
      if (newData[i].length !== length) throw new Error('Data dimensions must contain the same number of points.');
    }

    data = newData;

    return this;
  }

  if (options.data) this.setData(options.data);
}

function getStartIndex (data, min) {

  var
    length = data.length,
    i;

  for (i = 0; i < length; i++) {
    if (data[i] >= min) break;
  }

  // Include point outside range when not exact match
  if (data[i] > min && i > 0) i--;

  return i;
}

function getEndIndex (data, max) {

  var
    i;

  for (i = data.length; i--;) {
    if (data[i] <= max) break;
  }

  // Include point outside range when not exact match
  if (data[i] < max && i > 0) i++;

  return i;
}

function bound (that) {

  delete that.bounded;

  var
    data    = that.getData(),
    length  = that.length(),
    x       = data[0],
    y       = data[1],
    min     = that.min || 0,
    max     = that.max || that.length(),
    start   = getStartIndex(x, min),
    end     = getEndIndex(x, max);

  that.setData([
    x.slice(start, end + 1),
    y.slice(start, end + 1)
  ]);
  that.start = start;
  that.end = end;
};

Preprocessor.prototype = {

  /**
   * Returns the length of the data set.
   *
   * @return {Number} Length of the data set.
   */
  length : function () {
    return this.getData()[0].length;
  },

  /**
   * Bounds the data set at within a range.
   *
   * @param {Number} min
   * @param {Number} max
   */
  bound : function (min, max) {

    if (!_.isNumber(min) || !_.isNumber(max)) return this;

    this.min = min;
    this.max = max;
    this.bounded = true;

    return this;
  },

  /**
   * Subsample data using MinMax.
   *
   * MinMax will display the extrema of the subsample intervals.  This is
   * slower than regular interval subsampling but necessary for data that
   * is very non-homogenous.
   *
   * @param {Number} resolution
   */
  subsampleMinMax : function (resolution) {

    var bounded = this.bounded;
    delete this.bounded;

    var
      data    = this.getData(),
      length  = this.length(),
      x       = data[0],
      y       = data[1],
      start   = bounded ? getStartIndex(x, this.min) : 0,
      end     = bounded ? getEndIndex(x, this.max) : length - 1,
      count   = (resolution - 2) / 2,
      newX    = [],
      newY    = [],
      min     = Number.MAX_VALUE,
      max     = -Number.MAX_VALUE,
      minI    = 1,
      maxI    = 1,
      unit    = (end - start)/ count,
      position, min, max, datum, i, j;

    if (end - start + 1 > resolution) {

      newX.push(x[start]);
      newY.push(y[start]);

      position = start + unit;

      for (i = start; i < end; i++) {

        if (i === Math.round(position)) {

          position += unit;

          j = Math.min(maxI, minI);
          newX.push(x[j]);
          newY.push(y[j]);

          j = Math.max(maxI, minI);
          newX.push(x[j]);
          newY.push(y[j]);

          minI = i;
          min = y[minI];
          maxI = i;
          max = y[maxI];

        } else {
          if (y[i] > max) {
            max = y[i];
            maxI = i;
          }

          if (y[i] < min) {
            min = y[i];
            minI = i;
          }
        }
      }

      if (i < position) {
        newX.push(x[minI]);
        newY.push(min);
        newX.push(x[maxI]);
        newY.push(max);
      }

      // Last
      newX.push(x[end]);
      newY.push(y[end]);

      this.setData([newX, newY]);
      this.start = start;
      this.end = end;
    } else {
      this.bounded = true;
    }

    return this;
  },

  /**
   * Subsample data at a regular interval for resolution.
   *
   * This is the fastest subsampling and good for monotonic data and fairly
   * homogenous data (not a lot of up and down).
   *
   * @param {Number} resolution
   */
  subsample : function (resolution) {

    var bounded = this.bounded;
    delete this.bounded;

    var
      data    = this.getData(),
      length  = this.length(),
      x       = data[0],
      y       = data[1],
      start   = bounded ? getStartIndex(x, this.min) : 0,
      end     = bounded ? getEndIndex(x, this.max) : length - 1,
      unit    = (end - start + 1) / resolution,
      newX    = [],
      newY    = [],
      i, index;

    if (length > resolution) {

      // First
      newX.push(x[start]);
      newY.push(y[start]);

      for (i = 1; i < resolution; i++) {
        if (i * unit >= end - unit) break;
        index = Math.round(i * unit) + start;
        newX.push(x[index]);
        newY.push(y[index]);
      }

      // Last
      newX.push(x[end]);
      newY.push(y[end]);

      this.setData([newX, newY]);
      this.start = start;
      this.end = end;
    }

    return this;
  }
};

envision.Preprocessor = Preprocessor;

}());
<|endoftext|>