<commit_before>function copyArray (a, na) {
  na = na || []
  for (var i = 0; i < a.length; i++) {
    na[i] = a[i]
  }
  return na
}

function arrayEquals (a, b) {
  for (var i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) {
      return false
    }
  }
  return true
}

function CommandQueue (ctx) {
  this._ctx = ctx
  this._commands = 0
  this._prevProgram = null
  this._prevTextures = []

  this.debug = false
  this.profile = false
}

var ID = 0

var updatedUniforms = { }
var createdCommands = 0

CommandQueue.prototype.getContext = function () {
  return this._ctx
}

// currenlty this doesn't do anything but in the future it can provide:
// - defaults
// - validation
CommandQueue.prototype.createDrawCommand = function (obj) {
  createdCommands++
  obj._type = 'draw'
  obj._id = ID++
  return obj
}

CommandQueue.prototype.createClearCommand = function (obj) {
  createdCommands++
  obj._type = 'clear'
  obj._id = ID++
  return obj
}

CommandQueue.prototype.createTextureUpdateCommand = function (obj) {
}

CommandQueue.prototype.createVertexArrayUpdateCommand = function (obj) {
}

function applyUniforms (ctx, program, uniforms, prevUniforms, prevTextures, debug) {
  if (debug) {
    // verify if we provide all uniforms required by the shader
    for (var requiredUniform in program._uniforms) {
      if (requiredUniform === 'uProjectionMatrix') continue
      if (requiredUniform === 'uViewMatrix') continue
      if (requiredUniform === 'uInverseViewMatrix') continue
      if (requiredUniform === 'uModelMatrix') continue
      if (requiredUniform === 'uNormalMatrix') continue
      if (uniforms[requiredUniform] === undefined) {
        console.log('missing uniform', requiredUniform)
      }
    }
  }

  var textureOffset = 0
  // console.log(Object.keys(uniforms).length)
  for (var uniformName in uniforms) {
    // TODO: can i do array index check instead of function call?
    // if (debug) console.log('  setUniform', uniformName, '' + uniforms[uniformName])
    // if (!program.hasUniform(uniformName)) {
    if (!program._uniforms[uniformName]) {
      // console.log('Unnecessary uniform', uniformName)
      continue
    }
    var value = uniforms[uniformName]
    if (debug) {
      if (value === null || value === undefined) {
        throw new Error('Null uniform value for ' + uniformName + ' in PBRMaterial')
      }
    }
    if (value.getTarget) { // it's a texture
      if (prevTextures[textureOffset] !== value) {
        ctx.bindTexture(value, textureOffset)
        prevTextures[textureOffset] = value
      }
      value = textureOffset++
    }

    var shouldUpdate = false
    var prevValue = prevUniforms[uniformName]
    var isArray = Array.isArray(value)

    if (prevValue === undefined) {
      shouldUpdate = true
    } else if (isArray) {
      if (!arrayEquals(value, prevValue)) {
        shouldUpdate = true
      }
    } else if (value !== prevValue) {
      shouldUpdate = true
    }

    if (shouldUpdate) {
      prevUniforms[uniformName] = isArray ? copyArray(value, prevUniforms[uniformName]) : value
      program.setUniform(uniformName, value)
      updatedUniforms[uniformName] = (updatedUniforms[uniformName] || 0) + 1
    }
  }
  return textureOffset
}

function debugObject (cmd) {
  for (var prop in cmd) {
    console.log('  ', prop, '' + cmd[prop])
  }
}

// cmd - a command to submit to the queue, the immediate execution is not guaranteed and will
// depend on the curren optimization strategy
// opts - submit call specific command overrides Object or Array of Objects
// subContextCallback - all submit calls within this callback will
// inherit defaults from the currently executing cmd
CommandQueue.prototype.submit = function (cmd, opts, subCommanDraw) {
  this._commands++// .push([cmd, opts, subCommanDraw])
  // if (this.debug) console.log('submit', cmd._type, cmd._id, cmd.name || '')

  if (this.debug && cmd.name) {
    console.log('prev uniforms')
    debugObject(this._prevUniforms)
  }

  if (!cmd._type) {
    if (this.debug) console.log(cmd.toString())
    throw new Error('Unknown cmd type')
  }
  if (opts) {
    // TODO: optimize this
    cmd = Object.assign({}, cmd, opts)
    if (cmd.uniforms && opts.uniforms) {
      cmd.uniforms = Object.assign({}, cmd.uniforms, opts.uniforms)
    }
  }

  var pushedStates = 0

  var ctx = this._ctx
  if (cmd.framebuffer) {
    ctx.pushState(ctx.FRAMEBUFFER_BIT)
    ctx.bindFramebuffer(cmd.framebuffer)
    pushedStates++
    if (cmd.framebufferColorAttachments) {
      // FIXME: this modifies the framebuffer permamently after the call
      for (var attachmentIndex in cmd.framebufferColorAttachments) {
        var colorAttachment = cmd.framebufferColorAttachments[attachmentIndex]
        var index = parseInt(attachmentIndex, 10)
        cmd.framebuffer.setColorAttachment(index, colorAttachment.target, colorAttachment.handle, colorAttachment.level)
      }
    }
    if (cmd.framebufferDepthAttachment) {
      // FIXME: this modifies the framebuffer permamently after the call
      var depthAttachment = cmd.framebufferDepthAttachment
      cmd.framebuffer.setDepthAttachment(depthAttachment.target, depthAttachment.handle, depthAttachment.level)
    }
  }

  var clearFlags = 0
  if (cmd.color !== undefined) {
    ctx.pushState(ctx.COLOR_BIT)
    pushedStates++
    ctx.setClearColor(cmd.color[0], cmd.color[1], cmd.color[2], cmd.color[3])
    clearFlags |= ctx.COLOR_BIT
  }
  if (cmd.depth !== undefined) {
    ctx.pushState(ctx.DEPTH_BIT)
    pushedStates++
    ctx.setClearDepth(cmd.depth)
    clearFlags |= ctx.DEPTH_BIT
  }
  if (clearFlags) {
    ctx.clear(clearFlags)
  }

  if (cmd.program !== undefined) {
    if (this._prevProgram !== cmd.program) {
      ctx.bindProgram(cmd.program)
      this._prevProgram = cmd.program
      if (!cmd.program._prevUniforms) {
        cmd.program._prevUniforms = {}
      }
    }
  }

  if (cmd.uniforms !== undefined) {
    applyUniforms(ctx, cmd.program, cmd.uniforms, cmd.program._prevUniforms, this._prevTextures, this.debug)
  }

  if (cmd.projectionMatrix !== undefined) {
    ctx.pushState(ctx.MATRIX_PROJECTION_BIT)
    pushedStates++
    ctx.setProjectionMatrix(cmd.projectionMatrix)
  }

  if (cmd.viewMatrix !== undefined) {
    ctx.pushState(ctx.MATRIX_VIEW_BIT)
    pushedStates++
    ctx.setViewMatrix(cmd.viewMatrix)
  }

  if (cmd.modelMatrix !== undefined) {
    ctx.pushState(ctx.MATRIX_MODEL_BIT)
    pushedStates++
    ctx.setModelMatrix(cmd.modelMatrix)
  }

  if (cmd.viewport !== undefined) {
    ctx.pushState(ctx.VIEWPORT_BIT)
    pushedStates++
    ctx.setViewport(cmd.viewport[0], cmd.viewport[1], cmd.viewport[2], cmd.viewport[3])
  }

  if (cmd.depthTest !== undefined) {
    ctx.pushState(ctx.DEPTH_BIT)
    pushedStates++
    ctx.setDepthTest(cmd.depthTest)
  }

  if (cmd.cullFace !== undefined) {
    ctx.pushState(ctx.CULL_BIT)
    pushedStates++
    ctx.setCullFace(cmd.cullFace)
  }

  if (cmd.cullFaceMode !== undefined) {
    ctx.pushState(ctx.CULL_BIT)
    pushedStates++
    ctx.setCullFaceMode(cmd.cullFaceMode)
  }

  if (cmd.colorMask !== undefined) {
    ctx.pushState(ctx.COLOR_BIT)
    pushedStates++
    ctx.setColorMask(cmd.colorMask[0], cmd.colorMask[1], cmd.colorMask[2], cmd.colorMask[3])
  }

  if (cmd.mesh) {
    ctx.bindMesh(cmd.mesh)
    ctx.drawMesh()
  }

  if (subCommanDraw) {
    subCommanDraw()
  }

  // if (this.debug) console.log(' ', 'pushedStates', pushedStates)
  for (var i = 0; i < pushedStates; i++) {
    ctx.popState()
  }
}

// force execute of all commands in the queue
CommandQueue.prototype.flush = function () {
  if (this.profile) {
    console.log('CommandQueue', 'numCommands', this._commands, 'updatedUniforms', Object.keys(updatedUniforms).length, 'createdCommands', createdCommands)
  }
  this._prevProgram = null
  this._prevTextures.length = 0
  this._commands = 0
  if (this.profile) {
    console.log('CommandQueue', 'updatedUniforms', updatedUniforms)
  }
  updatedUniforms = {}
  createdCommands = 0
}

module.exports = CommandQueue
<commit_msg>CommandQueue got rid of isArray checks and inlined array comp<commit_after>function copyArray (a, na) {
  na = na || []
  for (var i = 0; i < a.length; i++) {
    na[i] = a[i]
  }
  return na
}

function CommandQueue (ctx) {
  this._ctx = ctx
  this._commands = 0
  this._prevProgram = null
  this._prevTextures = []

  this.debug = false
  this.profile = false
}

var ID = 0

var updatedUniforms = { }
var createdCommands = 0

CommandQueue.prototype.getContext = function () {
  return this._ctx
}

// currenlty this doesn't do anything but in the future it can provide:
// - defaults
// - validation
CommandQueue.prototype.createDrawCommand = function (obj) {
  createdCommands++
  obj._type = 'draw'
  obj._id = ID++
  return obj
}

CommandQueue.prototype.createClearCommand = function (obj) {
  createdCommands++
  obj._type = 'clear'
  obj._id = ID++
  return obj
}

CommandQueue.prototype.createTextureUpdateCommand = function (obj) {
}

CommandQueue.prototype.createVertexArrayUpdateCommand = function (obj) {
}

function applyUniforms (ctx, program, uniforms, prevUniforms, prevTextures, debug) {
  if (debug) {
    // verify if we provide all uniforms required by the shader
    for (var requiredUniform in program._uniforms) {
      if (requiredUniform === 'uProjectionMatrix') continue
      if (requiredUniform === 'uViewMatrix') continue
      if (requiredUniform === 'uInverseViewMatrix') continue
      if (requiredUniform === 'uModelMatrix') continue
      if (requiredUniform === 'uNormalMatrix') continue
      if (uniforms[requiredUniform] === undefined) {
        console.log('missing uniform', requiredUniform)
      }
    }
  }

  var textureOffset = 0
  // console.log(Object.keys(uniforms).length)
  for (var uniformName in uniforms) {
    // TODO: can i do array index check instead of function call?
    // if (debug) console.log('  setUniform', uniformName, '' + uniforms[uniformName])
    // if (!program.hasUniform(uniformName)) {
    if (!program._uniforms[uniformName]) {
      // console.log('Unnecessary uniform', uniformName)
      continue
    }
    var value = uniforms[uniformName]
    if (debug) {
      if (value === null || value === undefined) {
        throw new Error('Null uniform value for ' + uniformName + ' in PBRMaterial')
      }
    }
    if (value.getTarget) { // it's a texture
      if (prevTextures[textureOffset] !== value) {
        ctx.bindTexture(value, textureOffset)
        prevTextures[textureOffset] = value
      }
      value = textureOffset++
    }

    var shouldUpdate = false
    var prevValue = prevUniforms[uniformName]
    var len = value.length

    if (prevValue === undefined) {
      shouldUpdate = true
    } else if (len) {
      for (var i = 0; i < len; i++) {
        if (value[i] !== prevValue[i]) {
          shouldUpdate = true
          break
        }
      }
    } else if (value !== prevValue) {
      shouldUpdate = true
    }

    if (shouldUpdate) {
      prevUniforms[uniformName] = len ? copyArray(value, prevUniforms[uniformName]) : value
      program.setUniform(uniformName, value)
      updatedUniforms[uniformName] = (updatedUniforms[uniformName] || 0) + 1
    }
  }
  return textureOffset
}

function debugObject (cmd) {
  for (var prop in cmd) {
    console.log('  ', prop, '' + cmd[prop])
  }
}

// cmd - a command to submit to the queue, the immediate execution is not guaranteed and will
// depend on the curren optimization strategy
// opts - submit call specific command overrides Object or Array of Objects
// subContextCallback - all submit calls within this callback will
// inherit defaults from the currently executing cmd
CommandQueue.prototype.submit = function (cmd, opts, subCommanDraw) {
  this._commands++// .push([cmd, opts, subCommanDraw])
  // if (this.debug) console.log('submit', cmd._type, cmd._id, cmd.name || '')

  if (this.debug && cmd.name) {
    console.log('prev uniforms')
    debugObject(this._prevUniforms)
  }

  if (!cmd._type) {
    if (this.debug) console.log(cmd.toString())
    throw new Error('Unknown cmd type')
  }
  if (opts) {
    // TODO: optimize this
    cmd = Object.assign({}, cmd, opts)
    if (cmd.uniforms && opts.uniforms) {
      cmd.uniforms = Object.assign({}, cmd.uniforms, opts.uniforms)
    }
  }

  var pushedStates = 0

  var ctx = this._ctx
  if (cmd.framebuffer) {
    ctx.pushState(ctx.FRAMEBUFFER_BIT)
    ctx.bindFramebuffer(cmd.framebuffer)
    pushedStates++
    if (cmd.framebufferColorAttachments) {
      // FIXME: this modifies the framebuffer permamently after the call
      for (var attachmentIndex in cmd.framebufferColorAttachments) {
        var colorAttachment = cmd.framebufferColorAttachments[attachmentIndex]
        var index = parseInt(attachmentIndex, 10)
        cmd.framebuffer.setColorAttachment(index, colorAttachment.target, colorAttachment.handle, colorAttachment.level)
      }
    }
    if (cmd.framebufferDepthAttachment) {
      // FIXME: this modifies the framebuffer permamently after the call
      var depthAttachment = cmd.framebufferDepthAttachment
      cmd.framebuffer.setDepthAttachment(depthAttachment.target, depthAttachment.handle, depthAttachment.level)
    }
  }

  var clearFlags = 0
  if (cmd.color !== undefined) {
    ctx.pushState(ctx.COLOR_BIT)
    pushedStates++
    ctx.setClearColor(cmd.color[0], cmd.color[1], cmd.color[2], cmd.color[3])
    clearFlags |= ctx.COLOR_BIT
  }
  if (cmd.depth !== undefined) {
    ctx.pushState(ctx.DEPTH_BIT)
    pushedStates++
    ctx.setClearDepth(cmd.depth)
    clearFlags |= ctx.DEPTH_BIT
  }
  if (clearFlags) {
    ctx.clear(clearFlags)
  }

  if (cmd.program !== undefined) {
    if (this._prevProgram !== cmd.program) {
      ctx.bindProgram(cmd.program)
      this._prevProgram = cmd.program
      if (!cmd.program._prevUniforms) {
        cmd.program._prevUniforms = {}
      }
    }
  }

  if (cmd.uniforms !== undefined) {
    applyUniforms(ctx, cmd.program, cmd.uniforms, cmd.program._prevUniforms, this._prevTextures, this.debug)
  }

  if (cmd.projectionMatrix !== undefined) {
    ctx.pushState(ctx.MATRIX_PROJECTION_BIT)
    pushedStates++
    ctx.setProjectionMatrix(cmd.projectionMatrix)
  }

  if (cmd.viewMatrix !== undefined) {
    ctx.pushState(ctx.MATRIX_VIEW_BIT)
    pushedStates++
    ctx.setViewMatrix(cmd.viewMatrix)
  }

  if (cmd.modelMatrix !== undefined) {
    ctx.pushState(ctx.MATRIX_MODEL_BIT)
    pushedStates++
    ctx.setModelMatrix(cmd.modelMatrix)
  }

  if (cmd.viewport !== undefined) {
    ctx.pushState(ctx.VIEWPORT_BIT)
    pushedStates++
    ctx.setViewport(cmd.viewport[0], cmd.viewport[1], cmd.viewport[2], cmd.viewport[3])
  }

  if (cmd.depthTest !== undefined) {
    ctx.pushState(ctx.DEPTH_BIT)
    pushedStates++
    ctx.setDepthTest(cmd.depthTest)
  }

  if (cmd.cullFace !== undefined) {
    ctx.pushState(ctx.CULL_BIT)
    pushedStates++
    ctx.setCullFace(cmd.cullFace)
  }

  if (cmd.cullFaceMode !== undefined) {
    ctx.pushState(ctx.CULL_BIT)
    pushedStates++
    ctx.setCullFaceMode(cmd.cullFaceMode)
  }

  if (cmd.colorMask !== undefined) {
    ctx.pushState(ctx.COLOR_BIT)
    pushedStates++
    ctx.setColorMask(cmd.colorMask[0], cmd.colorMask[1], cmd.colorMask[2], cmd.colorMask[3])
  }

  if (cmd.mesh) {
    ctx.bindMesh(cmd.mesh)
    ctx.drawMesh()
  }

  if (subCommanDraw) {
    subCommanDraw()
  }

  // if (this.debug) console.log(' ', 'pushedStates', pushedStates)
  for (var i = 0; i < pushedStates; i++) {
    ctx.popState()
  }
}

// force execute of all commands in the queue
CommandQueue.prototype.flush = function () {
  if (this.profile) {
    console.log('CommandQueue', 'numCommands', this._commands, 'updatedUniforms', Object.keys(updatedUniforms).length, 'createdCommands', createdCommands)
  }
  this._prevProgram = null
  this._prevTextures.length = 0
  this._commands = 0
  if (this.profile) {
    console.log('CommandQueue', 'updatedUniforms', updatedUniforms)
  }
  updatedUniforms = {}
  createdCommands = 0
}

module.exports = CommandQueue
<|endoftext|>


<commit_before>(function (factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define(['knockout','lodash'], factory);
    } else {
        factory(ko,_);
    }
})(function (ko, _) {
    function isLodash(lodashCalls) {
        return lodashCalls && typeof lodashCalls == 'object' && !_.isArray(lodashCalls) && hasOwnProperty.call(lodashCalls, '__wrapped__')
    };

    function kodashWrapper(obs) {
        this.__observable__ = obs;
        this.__funcs__ = [];
    };
    kodashWrapper['fn'] = kodashWrapper['prototype'];
    var kodashPushFunction = function (wrapper, loFunc, args) {
        wrapper.__funcs__.push({
            loFunc: loFunc,
            args: args,
            comp: null,
            rewrap: false
        });
    };
    var unwrapArgs = function (args) {
        var observableflag = false;
        argArr=[];
        _(args).reverse().each(function (item, index) {
            var arg=item;
            if (ko.isObservable(arg)) {
                arg = ko.utils.unwrapObservable(arg)
            }
            argArr.push(arg)
        });
        return argArr;
    };
    var hasObservableArgs = function (args) {
        var argRev = [];
        return _(args).any(function (item) {
            return ko.isObservable(item)
        });
    };
    _.mixin(kodashWrapper.prototype, _(_.prototype).keys().object().mapValues(function (val, key) {
        return function () {
            var wrapper = new kodashWrapper(this.__observable__)
            wrapper.__funcs__ = wrapper.__funcs__.concat(this.__funcs__);
            kodashPushFunction(wrapper, key, arguments);
            return wrapper;
        }
    }).value());

    var mixinOrig = _.mixin;
    _.mixin({
        mixin: function () {
            var premixin = _.keys(_.prototype);
            mixinOrig.apply(this, arguments);
            _(_.prototype).keys().difference(premixin).tap(function(arr) {
                if (arr.length) {
                    mixinOrig(kodashWrapper.prototype, _(arr).object().mapValues(function (val, key) {
                        return function () {
                            var wrapper = new kodashWrapper(this.__observable__)
                            wrapper.__funcs__ = wrapper.__funcs__.concat(this.__funcs__);
                            kodashPushFunction(wrapper, key, arguments);
                            return wrapper;
                        }
                    }).value())
                }
            })
        }
    });

    var rewrap=function(){
            lastFunc = _.last(this.__funcs__);
            if(lastFunc){
                lastFunc["rewrap"]=true;
            }
            return this;
        };
    var mutate = function () {
            if (ko.isWriteableObservable(this.__observable__)) {
                var loVal = this.wrappedValueOf();
                this.__funcs__ = [];
                this.__observable__((loVal).valueOf());
            }
            return this.__observable__;
        };
    var wrappedValueOf = function () {
            var lofuncs=_(this.__funcs__);
            var lastLofuncs=lofuncs.last(function(item){ return !item.comp });
            var length = -lastLofuncs.value().length;
            length = length? length: this.__funcs__.length;
            lastComp = lofuncs.slice(0,length).last();
            if(lastComp){
                var lodashCalls = _(lastComp.comp.peek());
                lastLofuncs =lastLofuncs.rest()
            }
            else{
                var lodashCalls = _(ko.unwrap(this.__observable__));
                lastLofuncs= lofuncs;
            }

            lastLofuncs.each(function (func) {
                args = unwrapArgs(func['args'])
                if (isLodash(lodashCalls)) {
                    lodashCalls = lodashCalls[func['loFunc']].apply(lodashCalls, args);
                }
            });
            return lodashCalls
        };
    var valueOf = function () {
            var val=this.wrappedValueOf();
            return val && val.valueOf();
        };
    var observe = function () {
            var result = [];
            _(this.__funcs__).each(function (item, index) {
                var lastgroup = result.length ? _.last(result).group : 0;
                var group = hasObservableArgs(item.args) ? lastgroup + 1 : lastgroup;
                result.push({
                    group: group,
                    func: item
                });
            });

            var computed = baseObservable = this.__observable__;
            _(result).groupBy('group').each(function (group) {
                computed = createComputedGroup(group, computed, baseObservable);
            });
            return createUnwrappedComputed(computed);
        }
    _.mixin(kodashWrapper.prototype, {
        _: rewrap,
        rewrap: rewrap,
        mutate: mutate,
        wrappedValueOf: wrappedValueOf,
        wrappedValue: wrappedValueOf,
        valueOf: valueOf,
        value: valueOf,
        observe: observe,
        observeValueOf: observe
    });
    function createUnwrappedComputed(observable){
        if(isLodash(observable.peek()))
            return ko.computed(function(){return observable().valueOf()})
        else
            return observable;
    }
    function createComputedGroup(group, observable, baseObservable) {
        var funcs = _(group).pluck('func')
        var last =funcs.last();
        if(last.comp){
            return last.comp
        }
        return last.comp = ko.computed(function () {
            var lodashCalls = ko.unwrap(observable);
            lodashCalls= (observable === baseObservable)? _(lodashCalls) : lodashCalls
            _(funcs).each(function (func) {
                if (isLodash(lodashCalls)) {
                    var args = unwrapArgs(func['args'])
                    lodashCalls = lodashCalls[func['loFunc']].apply(lodashCalls, args);
                    lodashCalls = func.rewrap? _(lodashCalls) : lodashCalls;
                }
            });
            return lodashCalls;
        })
    };
    ko.observable['fn']['_'] = ko.observable['fn']['kodash'] = function () {
        return new kodashWrapper(this)
    }
});
<commit_msg>Added options to observe<commit_after>(function (factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define(['knockout','lodash'], factory);
    } else {
        factory(ko,_);
    }
})(function (ko, _) {
    function isLodash(lodashCalls) {
        return lodashCalls && typeof lodashCalls == 'object' && !_.isArray(lodashCalls) && hasOwnProperty.call(lodashCalls, '__wrapped__')
    };

    function kodashWrapper(obs) {
        this.__observable__ = obs;
        this.__funcs__ = [];
    };
    kodashWrapper['fn'] = kodashWrapper['prototype'];
    var kodashPushFunction = function (wrapper, loFunc, args) {
        wrapper.__funcs__.push({
            loFunc: loFunc,
            args: args,
            comp: null,
            rewrap: false
        });
    };
    var unwrapArgs = function (args) {
        var observableflag = false;
        argArr=[];
        _(args).reverse().each(function (item, index) {
            var arg=item;
            if (ko.isObservable(arg)) {
                arg = ko.utils.unwrapObservable(arg)
            }
            argArr.push(arg)
        });
        return argArr;
    };
    var hasObservableArgs = function (args) {
        var argRev = [];
        return _(args).any(function (item) {
            return ko.isObservable(item)
        });
    };
    _.mixin(kodashWrapper.prototype, _(_.prototype).keys().object().mapValues(function (val, key) {
        return function () {
            var wrapper = new kodashWrapper(this.__observable__)
            wrapper.__funcs__ = wrapper.__funcs__.concat(this.__funcs__);
            kodashPushFunction(wrapper, key, arguments);
            return wrapper;
        }
    }).value());

    var mixinOrig = _.mixin;
    _.mixin({
        mixin: function () {
            var premixin = _.keys(_.prototype);
            mixinOrig.apply(this, arguments);
            _(_.prototype).keys().difference(premixin).tap(function(arr) {
                if (arr.length) {
                    mixinOrig(kodashWrapper.prototype, _(arr).object().mapValues(function (val, key) {
                        return function () {
                            var wrapper = new kodashWrapper(this.__observable__)
                            wrapper.__funcs__ = wrapper.__funcs__.concat(this.__funcs__);
                            kodashPushFunction(wrapper, key, arguments);
                            return wrapper;
                        }
                    }).value())
                }
            })
        }
    });

    var rewrap=function(){
            lastFunc = _.last(this.__funcs__);
            if(lastFunc){
                lastFunc["rewrap"]=true;
            }
            return this;
        };
    var mutate = function () {
            if (ko.isWriteableObservable(this.__observable__)) {
                var loVal = this.wrappedValueOf();
                this.__funcs__ = [];
                this.__observable__((loVal).valueOf());
            }
            return this.__observable__;
        };
    var wrappedValueOf = function () {
            var lofuncs=_(this.__funcs__);
            var lastLofuncs=lofuncs.last(function(item){ return !item.comp });
            var length = -lastLofuncs.value().length;
            length = length? length: this.__funcs__.length;
            lastComp = lofuncs.slice(0,length).last();
            if(lastComp){
                var lodashCalls = _(lastComp.comp.peek());
                lastLofuncs =lastLofuncs.rest()
            }
            else{
                var lodashCalls = _(ko.unwrap(this.__observable__));
                lastLofuncs= lofuncs;
            }

            lastLofuncs.each(function (func) {
                args = unwrapArgs(func['args'])
                if (isLodash(lodashCalls)) {
                    lodashCalls = lodashCalls[func['loFunc']].apply(lodashCalls, args);
                }
            });
            return lodashCalls
        };
    var valueOf = function () {
            var val=this.wrappedValueOf();
            return val && val.valueOf();
        };
    var observe = function (opts) {
            var result = [];
            _(this.__funcs__).each(function (item, index) {
                var lastgroup = result.length ? _.last(result).group : 0;
                var group = hasObservableArgs(item.args) ? lastgroup + 1 : lastgroup;
                result.push({
                    group: group,
                    func: item
                });
            });

            var computed = baseObservable = this.__observable__;
            _(result).groupBy('group').each(function (group) {
                computed = createComputedGroup(group, computed, baseObservable,opts);
            });
            return createUnwrappedComputed(computed);
        }
    _.mixin(kodashWrapper.prototype, {
        _: rewrap,
        rewrap: rewrap,
        mutate: mutate,
        wrappedValueOf: wrappedValueOf,
        wrappedValue: wrappedValueOf,
        valueOf: valueOf,
        value: valueOf,
        observe: observe,
        observeValueOf: observe
    });
    function createUnwrappedComputed(observable){
        if(isLodash(observable.peek()))
            return ko.computed(function(){return observable().valueOf()})
        else
            return observable;
    }
    function createComputedGroup(group, observable, baseObservable, opts) {
        opts = opts||{}
        var funcs = _(group).pluck('func')
        var last =funcs.last();
        if(last.comp){
            return last.comp
        }
        opts.read = function () {
            var lodashCalls = ko.unwrap(observable);
            lodashCalls = (observable === baseObservable) ? _(lodashCalls) : lodashCalls
            _(funcs).each(function (func) {
                if (isLodash(lodashCalls)) {
                    var args = unwrapArgs(func['args'])
                    lodashCalls = lodashCalls[func['loFunc']].apply(lodashCalls, args);
                    lodashCalls = func.rewrap ? _(lodashCalls) : lodashCalls;
                }
            });
            return ko.unwrap(lodashCalls);
        }
        return last.comp = ko.computed(opts)
    };
    ko.observable['fn']['_'] = ko.observable['fn']['kodash'] = function () {
        return new kodashWrapper(this)
    }
});
<|endoftext|>


<commit_before>/**
 * jQuery.contextMenu - Show a custom context when right clicking something
 * Jonas Arnklint, http://starksignal.com
 * Released into the public domain
 * Date: Oct 7, 2010
 * @author Jonas Arnklint
 * @version 1.0
 *
 *
 * Basic calling syntax:

$("selector").contextMenu({
    'Context Menu Item 1': {
        click: function(element){  // element is the jquery obj clicked on when contextMenu launched
            alert('Menu item 1 clicked');
            element.css({backgroundColor: 'pink'}); // just as example the clicked items backgorund is changed
        },
        class: "menu-item-1" // a custom css class for this menu item (usable for styling)
    },
    'Second menu item': {
        click: function(element){ alert('second clicked'); },
        class: "second-menu-item"
    }
});

 * These are the options you can pass into the plugin. No fancy schmancy, but KISS.
 *
 */
jQuery.fn.contextMenu = function ( actions ) {
    $(document).bind("contextmenu",function(e){ return false; }); // yuk :)

    var menu = $('

').appendTo('body'),
        active_element = null; // last clicked element that responds with contextMenu

    $.each(actions, function(me, item_options){
        $('
'+me+'
')
            .appendTo(menu)
            .bind('click', function(e){
                item_options.click(active_element);
            });
    });

    $('body').click(function() {
        menu.hide() //Hide the menus if visible
    });

    return $(this).live('mousedown', function(e){
        if( e.which == 3 ){
            menu.show()
                .css({
                    position: 'absolute',
                    top: $(this).position().top,
                    left: $(this).position().left,
                    zIndex: 1000
                });
            active_element = $(this);
            e.preventDefault();
        }
    });
}
<commit_msg>Hide context menu by default<commit_after>/**
 * jQuery.contextMenu - Show a custom context when right clicking something
 * Jonas Arnklint, http://starksignal.com
 * Released into the public domain
 * Date: Oct 7, 2010
 * @author Jonas Arnklint
 * @version 1.0
 *
 *
 * Basic calling syntax:

$("selector").contextMenu({
    'Context Menu Item 1': {
        click: function(element){  // element is the jquery obj clicked on when contextMenu launched
            alert('Menu item 1 clicked');
            element.css({backgroundColor: 'pink'}); // just as example the clicked items backgorund is changed
        },
        class: "menu-item-1" // a custom css class for this menu item (usable for styling)
    },
    'Second menu item': {
        click: function(element){ alert('second clicked'); },
        class: "second-menu-item"
    }
});

 * These are the options you can pass into the plugin. No fancy schmancy, but KISS.
 *
 */
jQuery.fn.contextMenu = function ( actions ) {
    $(document).bind("contextmenu",function(e){ return false; }); // yuk :)

    var menu = $('

').hide().appendTo('body'),
        active_element = null; // last clicked element that responds with contextMenu

    $.each(actions, function(me, item_options){
        $('
'+me+'
')
            .appendTo(menu)
            .bind('click', function(e){
                item_options.click(active_element);
            });
    });

    $('body').click(function() {
        menu.hide() //Hide the menus if visible
    });

    return $(this).live('mousedown', function(e){
        if( e.which == 3 ){
            menu.show()
                .css({
                    position: 'absolute',
                    top: $(this).position().top,
                    left: $(this).position().left,
                    zIndex: 1000
                });
            active_element = $(this);
            e.preventDefault();
        }
    });
}
<|endoftext|>


<commit_before>(function ($) {

    'use strict';

    $.liveSearch = function (options)
    {
        // setup
        var opts             = $.extend({}, $.liveSearch.defaults, options);
        var _self            = this;
        var selectorToSearch;
        var textElement;

        function init() {
            if (  opts.remote){
                selectorToSearch = opts.selectorContainer + " " + opts.selectorElementsToSearch;
            }else{
                selectorToSearch = opts.selectorContainer + " " + opts.selectorElementsToSearch;
            }

            bindInputSearch();
            preventSubmit();
            return _self;
        }

        var bindInputSearch = function() {
            $(opts.selectorInputSearch).bind("keyup", function(){
                textElement = $(this).val();
                if ( textElement.length >= opts.minCharacters ){
                    if ( opts.ajaxCallback ){
                        opts.ajaxCallback.call(this);
                    } else {
                        filterElements(textElement);
                    }
                }else if ( textElement.length === 0 ){
                    resetFilter();
                }
            });
        }

        var preventSubmit = function() {
            $(opts.selectorInputSearch).keydown(function(e){
                if(e.keyCode === 13){
                    return false;
                }
            });
        }

        var filterElements = function(textElement) {
            var pattern = new RegExp(textElement, "igm");
            if (opts.selectorToHide && opts.selectorElementsToSearch !== opts.selectorToHide) {
                $(selectorToSearch).parents(opts.selectorToHide).not(opts.selectorFixed).hide();
                $(selectorToSearch).filter(function(){
                    var content = $(this).html();
                    return content.match(pattern);
                }).parents(opts.selectorToHide).show();
            } else {
                $(selectorToSearch).not(opts.selectorFixed).hide();
                $(selectorToSearch).filter(function(){
                    var content = $(this).html();
                    return content.match(pattern);
                }).show();
            }
        }

        var filterAjax = function(page){
            $(opts.selectorContainer).find(opts.selectorToHide).remove();
            var contentAppend = $(page).find(opts.selectorContainer);
            var items = [],
                container,
                startTime = Date.now(),
                diffTime,
                self;

            // walk through the items on the next page
            // and add them to the items array
            container = $(opts.selectorContainer, page).eq(0);
            if (0 === container.length) {
                // incase the element is a root element (body > element),
                // try to filter it
                container = $(page).filter(opts.selectorContainer).eq(0);
            }

            if (container) {
                container.find(opts.selectorToHide).each(function () {
                    items.push(this);
                });
            }

            $(opts.selectorContainer).append(items);

            filterElements(textElement);
        }

        var resetFilter = function() {
            if (opts.selectorToHide) {
                $(selectorToSearch).parents(opts.selectorToHide).show();
            } else {
                $(selectorToSearch).show();
            }
        }

        //Public function
        this.reFilter = function() {
            filterElements(this.textElement);
        };

        this.ajaxDone = function(data) {
            filterAjax(data);
        }

        // initialize
        return init();
    };

    // Options Defaults
    $.liveSearch.defaults = {
        attributeToSearch: "data-search",
        selectorContainer: "table",
        selectorElementsToSearch: "td",
        selectorInputSearch: "input#search_query",
        selectorToHide: null,
        minCharacters: 3,
        ajaxCallback: false
    };

})(jQuery);<commit_msg>Perfect Code<commit_after>(function ($) {

    'use strict';

    $.liveSearch = function (options)
    {
        // setup
        var opts             = $.extend({}, $.liveSearch.defaults, options);
        var _self            = this;
        var selectorToSearch;
        var textElement;

        function init() {
            if (  opts.remote){
                selectorToSearch = opts.selectorContainer + " " + opts.selectorElementsToSearch;
            }else{
                selectorToSearch = opts.selectorContainer + " " + opts.selectorElementsToSearch;
            }

            bindInputSearch();
            preventSubmit();
            return _self;
        }

        var bindInputSearch = function() {
            $(opts.selectorInputSearch).bind("keyup", function(){
                textElement = $(this).val();
                if ( textElement.length >= opts.minCharacters ){
                    if ( opts.ajaxCallback ){
                        opts.ajaxCallback.call(this);
                    } else {
                        filterElements(textElement);
                    }
                }else if ( textElement.length === 0 ){
                    resetFilter();
                }
            });
        };

        var preventSubmit = function() {
            $(opts.selectorInputSearch).keydown(function(e){
                if(e.keyCode === 13){
                    return false;
                }
            });
        };

        var filterElements = function(textElement) {
            var pattern = new RegExp(textElement, "igm");
            if (opts.selectorToHide && opts.selectorElementsToSearch !== opts.selectorToHide) {
                $(selectorToSearch).parents(opts.selectorToHide).not(opts.selectorFixed).hide();
                $(selectorToSearch).filter(function(){
                    var content = $(this).html();
                    return content.match(pattern);
                }).parents(opts.selectorToHide).show();
            } else {
                $(selectorToSearch).not(opts.selectorFixed).hide();
                $(selectorToSearch).filter(function(){
                    var content = $(this).html();
                    return content.match(pattern);
                }).show();
            }
        };

        var filterAjax = function(page){
            $(opts.selectorContainer).find(opts.selectorToHide).remove();
            var items = [],
                container;

            // walk through the items on the next page
            // and add them to the items array
            container = $(opts.selectorContainer, page).eq(0);
            if (0 === container.length) {
                // incase the element is a root element (body > element),
                // try to filter it
                container = $(page).filter(opts.selectorContainer).eq(0);
            }

            if (container) {
                container.find(opts.selectorToHide).each(function () {
                    items.push(this);
                });
            }

            $(opts.selectorContainer).append(items);

            filterElements(textElement);
        };

        var resetFilter = function() {
            if (opts.selectorToHide) {
                $(selectorToSearch).parents(opts.selectorToHide).show();
            } else {
                $(selectorToSearch).show();
            }
        };

        //Public function
        this.reFilter = function() {
            filterElements(this.textElement);
        };

        this.ajaxDone = function(data) {
            filterAjax(data);
        };

        // initialize
        return init();
    };

    // Options Defaults
    $.liveSearch.defaults = {
        attributeToSearch: "data-search",
        selectorContainer: "table",
        selectorElementsToSearch: "td",
        selectorInputSearch: "input#search_query",
        selectorToHide: null,
        minCharacters: 3,
        ajaxCallback: false
    };

})(jQuery);<|endoftext|>


<commit_before>/*******************************************************************************
 * jQuery.splitbutton plugin.
 *
 * Combine two ui-buttons and one ui-menu.
 *
 * @see https://github.com/mar10/jquery-splitbutton
 *
 * Copyright (c) 2013, Martin Wendt (http://wwWendt.de). Licensed MIT.
 */

(function ($) {
    "use strict";
    function getMenuFromEvent(event){
        var menu = $(event.target).closest(":ui-menu"),
            $menu = $(menu);
        return $menu.data("ui-menu") || $menu.data("menu");
    }
    $.widget("ui.splitbutton", {
        options: {
            menu: null,      // selector
            mode: "split",   // 'split' | 'list'
            // Events:
            blur: $.noop,    // dropdown-menu option lost focus
            click: $.noop,   // default-button clicked
            close: $.noop,   // dropdown-menu closed
            focus: $.noop,   // dropdown-menu option focused
            init: $.noop,    // control was initialized
            open: $.noop,    // dropdown-menu opened
            select: $.noop   // dropdown-menu option selected
        },
        _create: function () {
            var self = this;
            // `this.element` is the default-button
            if(this.options.mode === "list"){
                this.element.button({
                    icons: {
                        secondary: "ui-icon-triangle-1-s"
                    }
                }).click($.proxy(this._openMenu, this));
            }else{
                // Append a dropdown-button and wrap both in a new div tag
                // that will be converted into a buttonset
                this.element.wrap($("<span>"));

                this.element.after(
                    $("<button>...</button>").button({
                        text: false,
                        icons: {
                            primary: "ui-icon-triangle-1-s"
                        }
                    }).click($.proxy(this._openMenu, this))
                );
                // Convert both buttons into a buttonset
                this.element.parent().buttonset();
            }
            this.element.click(function(event){
                self._trigger("click", event);
            });
            // Create - but hide - dropdown-menu
            $(this.options.menu)
                .hide()
                .addClass("ui-splitbutton-menu")
                // Create a menu instance
                // Note: the menu may be re-used by other splitbutton instances,
                // so we attach the splitbutton widget in _openMenu()
                .menu({
                    blur: function(event, ui){
                    	// Get splitbutton widget
                    	var widget = getMenuFromEvent(event).controller;

                    	if(widget !== null) {
                    		widget._trigger("blur", event, ui);
                    	} else {
                    		// Menu was closed and splitbutton widget detached
                    	}
                    },
                    focus: function(event, ui){
                        getMenuFromEvent(event).controller._trigger("focus", event, ui);
                    },
                    select: function(event, ui){
//                        var menuId = ui.item.find(">a").attr("href");
                        var widget = getMenuFromEvent(event).controller;

                        // If we have a sub menu, we don't trigger select
                        if($(ui.item[0].lastElementChild).is("ul.ui-menu")) {
                        	return false;
                        }

                        if( widget._trigger("select", event, ui) !== false ){
                            widget._closeMenu.call(widget);
                        }
                    }
                });
            // Register global event handlers that close the dropdown-menu
            $(document).bind("keydown.splitbutton", function(event){
                if( event.which === $.ui.keyCode.ESCAPE ){
                    self._closeMenu();
                }
            }).bind("mousedown.splitbutton", function(event){
                // Close menu when clicked outside menu
                if( $(event.target).closest(".ui-menu-item").length === 0 ){
                    self._closeMenu();
                }
            });
            this._trigger("init");
        },
        /** Return menu widget instance (works on pre and post jQueryUI 1.9). */
        _getMenuWidget: function(){
            var $menu = $(this.options.menu);
            return $menu.data("ui-menu") || $menu.data("menu");
        },
        /** Close dropdown. */
        _closeMenu: function(){
            $(this.options.menu).fadeOut(function() {
            	// Resetting position
            	$(this).css({
            		top: 0,
            		left: 0
                });
            });

            // Trigger blur before splitbutton widget gets detached
            this._trigger("blur");

            this._trigger("close");

            // Detach splitbutton widget from menu widget
            this._getMenuWidget().controller = null;
        },
        /** Open dropdown. */
        _openMenu: function(event){
        	// If menu is visible, we don't need to open it again
        	if ($(this.options.menu).is(":visible")) {
        		// TODO: Doesn't work quite right for multiple splitbuttons
        		return;
        	}
            // Click handler for dropdown-button: open dropdown-menu
            if( this._trigger("open", event) === false) {
                return;
            }
            // Attach splitbutton widget to the menu widget.
            this._getMenuWidget().controller = this;

            // Calculate minWidth
            var menuPadding = $(this.options.menu).outerWidth() - $(this.options.menu).width(),
            	minWidth = this.element.outerWidth(true) - menuPadding;

            $(this.options.menu)
                .css({
                    position: "absolute",
                    minWidth: minWidth
                })
                .position({
                    my: "left top",
                    at: "left bottom",
                    of: this.element,
                    collision: "fit"
                })
                .slideDown("fast");
        },
        /**
         * Handle $().splitbutton("option", ...) calls.
         */
        _setOption: function(key, value){
            $.Widget.prototype._setOption.apply(this, arguments);
            // TODO: redraw, if required
        }
    });
} (jQuery));
<commit_msg>Tried to add ui-state-active while menu is open, but failed (will be removed on mouseout event)<commit_after>/*******************************************************************************
 * jQuery.splitbutton plugin.
 *
 * Combine two ui-buttons and one ui-menu.
 *
 * @see https://github.com/mar10/jquery-splitbutton
 *
 * Copyright (c) 2013, Martin Wendt (http://wwWendt.de). Licensed MIT.
 */

(function ($) {
    "use strict";
    function getMenuFromEvent(event){
        var menu = $(event.target).closest(":ui-menu"),
            $menu = $(menu);
        return $menu.data("ui-menu") || $menu.data("menu");
    }
    $.widget("ui.splitbutton", {
        options: {
            menu: null,      // selector
            mode: "split",   // 'split' | 'list'
            // Events:
            blur: $.noop,    // dropdown-menu option lost focus
            click: $.noop,   // default-button clicked
            close: $.noop,   // dropdown-menu closed
            focus: $.noop,   // dropdown-menu option focused
            init: $.noop,    // control was initialized
            open: $.noop,    // dropdown-menu opened
            select: $.noop   // dropdown-menu option selected
        },
        _create: function () {
            var self = this;
            // `this.element` is the default-button
            if(this.options.mode === "list"){
                this.element.button({
                    icons: {
                        secondary: "ui-icon-triangle-1-s"
                    }
                }).click($.proxy(this._openMenu, this));
            }else{
                // Append a dropdown-button and wrap both in a new div tag
                // that will be converted into a buttonset
                this.element.wrap($("<span>"));

                this.element.after(
                    $("<button>...</button>").button({
                        text: false,
                        icons: {
                            primary: "ui-icon-triangle-1-s"
                        }
                    }).click($.proxy(this._openMenu, this))
                );
                // Convert both buttons into a buttonset
                this.element.parent().buttonset();
            }
            this.element.click(function(event){
                self._trigger("click", event);
            });
            // Create - but hide - dropdown-menu
            $(this.options.menu)
                .hide()
                .addClass("ui-splitbutton-menu")
                // Create a menu instance
                // Note: the menu may be re-used by other splitbutton instances,
                // so we attach the splitbutton widget in _openMenu()
                .menu({
                    blur: function(event, ui){
                    	// Get splitbutton widget
                    	var widget = getMenuFromEvent(event).controller;

                    	if(widget !== null) {
                    		widget._trigger("blur", event, ui);
                    	} else {
                    		// Menu was closed and splitbutton widget detached
                    	}
                    },
                    focus: function(event, ui){
                        getMenuFromEvent(event).controller._trigger("focus", event, ui);
                    },
                    select: function(event, ui){
//                        var menuId = ui.item.find(">a").attr("href");
                        var widget = getMenuFromEvent(event).controller;

                        // If we have a sub menu, we don't trigger select
                        if($(ui.item[0].lastElementChild).is("ul.ui-menu")) {
                        	return false;
                        }

                        if( widget._trigger("select", event, ui) !== false ){
                            widget._closeMenu.call(widget);
                        }
                    }
                });
            // Register global event handlers that close the dropdown-menu
            $(document).bind("keydown.splitbutton", function(event){
                if( event.which === $.ui.keyCode.ESCAPE ){
                    self._closeMenu();
                }
            }).bind("mousedown.splitbutton", function(event){
                // Close menu when clicked outside menu
                if( $(event.target).closest(".ui-menu-item").length === 0 ){
                    self._closeMenu();
                }
            });
            this._trigger("init");
        },
        /** Return menu widget instance (works on pre and post jQueryUI 1.9). */
        _getMenuWidget: function(){
            var $menu = $(this.options.menu);
            return $menu.data("ui-menu") || $menu.data("menu");
        },
        /** Close dropdown. */
        _closeMenu: function(){
            $(this.options.menu).fadeOut(function() {
            	// Resetting position
            	$(this).css({
            		top: 0,
            		left: 0
                });
            });

            // Trigger blur before splitbutton widget gets detached
            this._trigger("blur");

            this._trigger("close");

            // TODO: setting ui-state-active doesn't work, because .button()
            // plugin removes it on mouseout
//            if(this.options.mode === "list"){
//                this.element.removeClass("ui-state-active");
//            }else{
//                $(this.element).next().removeClass("ui-state-active");
//            }
            // Detach splitbutton widget from menu widget
            this._getMenuWidget().controller = null;
        },
        /** Open dropdown. */
        _openMenu: function(event){
        	// If menu is visible, we don't need to open it again
        	if ($(this.options.menu).is(":visible")) {
        		// TODO: Doesn't work quite right for multiple splitbuttons
        		return;
        	}
            // Click handler for dropdown-button: open dropdown-menu
            if( this._trigger("open", event) === false) {
                return;
            }
            // Attach splitbutton widget to the menu widget.
            this._getMenuWidget().controller = this;

            // TODO: setting ui-state-active doesn't work, because .button()
            // plugin removes it on mouseout
//            if(this.options.mode === "list"){
//                this.element.addClass("ui-state-active");
//            }else{
//                $(this.element).next().addClass("ui-state-active");
//            }
            // Calculate minWidth
            var menuPadding = $(this.options.menu).outerWidth() - $(this.options.menu).width(),
            	minWidth = this.element.outerWidth(true) - menuPadding;

            $(this.options.menu)
                .css({
                    position: "absolute",
                    minWidth: minWidth
                })
                .position({
                    my: "left top",
                    at: "left bottom",
                    of: this.element,
                    collision: "fit"
                })
                .slideDown("fast");
        },
        /**
         * Handle $().splitbutton("option", ...) calls.
         */
        _setOption: function(key, value){
            $.Widget.prototype._setOption.apply(this, arguments);
            // TODO: redraw, if required
        }
    });
} (jQuery));
<|endoftext|>


<commit_before>/**
 * textStretch.js pre-version alpha (2013.12.09)
 *
 * Copyright (c) 2012, 2013 Albin Larsson (mail@albinlarsson.com)
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 **/

(function ($) {
	"use strict";
	// add class for calculating width. let me know if you see this and know a better way (excluding insert/addRule)
	$("<style>.textStretch-calc { display: inline-block !important; *display: inline !important; white-space: nowrap !important; width: auto !important; padding:0 !important; text-align: left !important; }</style>").appendTo("head");

	$.fn.textStretch = function (options) {
		var _settings, _useElementWidth, _letterAverage, _fontSize, _width, _i, _recalc, element;

		// import user settings/arguments
		_settings = $.extend($.textStretch.defaults, options);

		// check user arguments
		(function(){
			for (_i = 0; _i < arguments.length; _i += 1) {
				if (typeof _settings[arguments[_i].name] !== arguments[_i].type) {
					throw "textStretch error. Argument \"" + arguments[_i].name + "\" (" + _settings[arguments[_i].name] + ") is not a number";
				}
			}
		}({name: "width", type: "number"}, {name: "minFontSize", type: "number"}, {name: "maxFontSize", type: "number"}));

		// no width specified. use element width (doesn't work for for inline or inline-blocks)
		_useElementWidth = (_settings.width === 0);

		function _getStyle(elem, prop, camelProp) {
			return elem.currentStyle ? elem.currentStyle[camelProp] : window.getComputedStyle(elem, null).getPropertyValue(prop);
		}

		function _textStretch(elements) {
			for (_i = 0; _i < elements.length; _i += 1) {
				element = elements[_i];

				// use element's width if no width specified
				_width = _useElementWidth ? element.clientWidth : _settings.width;

				// temporarily apply class for measuring width
				element.className += " textStretch-calc";

				// width of text / 100%
				_letterAverage = element.clientWidth / (parseInt(_getStyle(element,"font-size", "fontSize"), 10));

				// overwritten unless within specified font-size span
				_fontSize = Math.min(Math.max(parseInt(_width / _letterAverage, 10), _settings.minFontSize), _settings.maxFontSize);

				// apply font-size
				element.style.fontSize = _fontSize + "px";

				// sometimes new text will be too wide due to browsers rounding letter-spacing to even pixels
				if(element.clientWidth > _width) {
					element.style.fontSize = _fontSize - 1 + "px";
				}
				// remove measuring-class
				element.className = element.className.substr(0, element.className.length - 17);

				// determine if a vertical scrollbar has been added or removed, in which case we have to recalculate
				_recalc = (_useElementWidth && _width !== element.clientWidth);
			}
		}

		// run
		_textStretch(this);

		// recalculate if needed
		if (_recalc) {
			_textStretch(this);
		}

		// handle resize ands viewport-change. not needed for fixed width
		if (_useElementWidth) {
			// add new elements to list.
			$.textStretch.elements = $.textStretch.elements.add(this);

			// bind to events
			if(!$.textStretch.eventIsBound){
				$(window).on("orientationchange.textStretch resize.textStretch", function(){
					_textStretch($.textStretch.elements);
				});
				$.textStretch.eventIsBound = true;
			}
		}
		return this;
	};
	$.textStretch = {
		defaults: {
			width: 0,
			minFontSize: 0,
			maxFontSize: Number.POSITIVE_INFINITY
		},
		elements: $([]),
		eventIsBound : false
	};
}(jQuery));<commit_msg>simplifying user arg error check due to all args being numbers now<commit_after>/**
 * textStretch.js pre-version alpha (2013.12.09)
 *
 * Copyright (c) 2012, 2013 Albin Larsson (mail@albinlarsson.com)
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 **/

(function ($) {
	"use strict";
	// add class for calculating width. let me know if you see this and know a better way (excluding insert/addRule)
	$("<style>.textStretch-calc { display: inline-block !important; *display: inline !important; white-space: nowrap !important; width: auto !important; padding:0 !important; text-align: left !important; }</style>").appendTo("head");

	$.fn.textStretch = function (options) {
		var _settings, _useElementWidth, _letterAverage, _fontSize, _width, _i, _recalc, element;

		// import user settings/arguments
		_settings = $.extend($.textStretch.defaults, options);

		// check user arguments
		(function(args){
			for (_i = 0; _i < args.length; _i += 1) {
				if(typeof _settings[args[_i]] !== "number") {
					throw "textStretch error. Argument \"" + args[_i] + "\" must be a number. Argument given was \"" + _settings[args[_i]] + "\".";
				}
			}
		}(["width", "minFontSize", "maxFontSize"]));

		// no width specified. use element width (doesn't work for for inline or inline-blocks)
		_useElementWidth = (_settings.width === 0);

		function _getStyle(elem, prop, camelProp) {
			return elem.currentStyle ? elem.currentStyle[camelProp] : window.getComputedStyle(elem, null).getPropertyValue(prop);
		}

		function _textStretch(elements) {
			for (_i = 0; _i < elements.length; _i += 1) {
				element = elements[_i];

				// use element's width if no width specified
				_width = _useElementWidth ? element.clientWidth : _settings.width;

				// temporarily apply class for measuring width
				element.className += " textStretch-calc";

				// width of text / 100%
				_letterAverage = element.clientWidth / (parseInt(_getStyle(element,"font-size", "fontSize"), 10));

				// overwritten unless within specified font-size span
				_fontSize = Math.min(Math.max(parseInt(_width / _letterAverage, 10), _settings.minFontSize), _settings.maxFontSize);

				// apply font-size
				element.style.fontSize = _fontSize + "px";

				// sometimes new text will be too wide due to browsers rounding letter-spacing to even pixels
				if(element.clientWidth > _width) {
					element.style.fontSize = _fontSize - 1 + "px";
				}
				// remove measuring-class
				element.className = element.className.substr(0, element.className.length - 17);

				// determine if a vertical scrollbar has been added or removed, in which case we have to recalculate
				_recalc = (_useElementWidth && _width !== element.clientWidth);
			}
		}

		// run
		_textStretch(this);

		// recalculate if needed
		if (_recalc) {
			_textStretch(this);
		}

		// handle resize ands viewport-change. not needed for fixed width
		if (_useElementWidth) {
			// add new elements to list.
			$.textStretch.elements = $.textStretch.elements.add(this);

			// bind to events
			if(!$.textStretch.eventIsBound){
				$(window).on("orientationchange.textStretch resize.textStretch", function(){
					_textStretch($.textStretch.elements);
				});
				$.textStretch.eventIsBound = true;
			}
		}
		return this;
	};
	$.textStretch = {
		defaults: {
			width: 0,
			minFontSize: 0,
			maxFontSize: Number.POSITIVE_INFINITY
		},
		elements: $([]),
		eventIsBound : false
	};
}(jQuery));<|endoftext|>


<commit_before>// Copyright 2020, University of Colorado Boulder

/**
 * Model for the home screen.
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Klusendorf (PhET Interactive Simulations)
 */

import Property from '../../axon/js/Property.js';
import PropertyIO from '../../axon/js/PropertyIO.js';
import joist from './joist.js';
import ScreenIO from './ScreenIO.js';

class HomeScreenModel {

  /**
   * @param {Property.<Screen>} screenProperty
   * @param {Screen[]} simScreens
   * @param {Tandem} tandem
   */
  constructor( screenProperty, simScreens, tandem ) {

    // @public {Screen[]} - screens in the simulations that are not the HomeScreen
    this.simScreens = simScreens;

    // @public {Property<Screen>}
    this.screenProperty = screenProperty;

    // @public {Property<Screen>}
    this.selectedScreenProperty = new Property( simScreens[ 0 ], {
      validValues: simScreens,
      phetioType: PropertyIO( ScreenIO ),
      tandem: tandem.createTandem( 'selectedScreenProperty' )
    } );

    // the correct screen icon is selected when returning to the home screen
    this.screenProperty.link( screen => {
      if ( _.includes( simScreens, screen ) ) {
        this.selectedScreenProperty.value = screen;
      }
    } );
  }
}

joist.register( 'HomeScreenModel', HomeScreenModel );

export default HomeScreenModel;<commit_msg>Add documentation, see https://github.com/phetsims/joist/issues/655<commit_after>// Copyright 2020, University of Colorado Boulder

/**
 * Model for the home screen.
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Klusendorf (PhET Interactive Simulations)
 */

import Property from '../../axon/js/Property.js';
import PropertyIO from '../../axon/js/PropertyIO.js';
import joist from './joist.js';
import ScreenIO from './ScreenIO.js';

class HomeScreenModel {

  /**
   * @param {Property.<Screen>} screenProperty - the screen that is displayed to the user in the main area above the
   *                                           - navigation bar
   * @param {Screen[]} simScreens
   * @param {Tandem} tandem
   */
  constructor( screenProperty, simScreens, tandem ) {

    // @public {Screen[]} - screens in the simulations that are not the HomeScreen
    this.simScreens = simScreens;

    // @public {Property<Screen>} - the screen that is displayed to the user in the main area above the navigation bar
    this.screenProperty = screenProperty;

    // @public {Property<Screen>} - on the home screen only, the screen corresponding to the large button icon
    this.selectedScreenProperty = new Property( simScreens[ 0 ], {
      validValues: simScreens,
      phetioType: PropertyIO( ScreenIO ),
      tandem: tandem.createTandem( 'selectedScreenProperty' )
    } );

    // the correct screen icon is selected when returning to the home screen
    this.screenProperty.link( screen => {
      if ( _.includes( simScreens, screen ) ) {
        this.selectedScreenProperty.value = screen;
      }
    } );
  }
}

joist.register( 'HomeScreenModel', HomeScreenModel );

export default HomeScreenModel;<|endoftext|>


<commit_before>//  Copyright 2002-2014, University of Colorado Boulder

/**
 * A light sensor probe, used in simulations like Bending Light and Beer's Law Lab to show how much light is being
 * received. It is typically connected to a body with readouts with a curved wire.  The origin is in the center of the
 * circular part of the sensor (which is not vertically symmetrical).
 *
 * This code was generalized from Bending Light, see https://github.com/phetsims/bending-light/issues/165
 *
 * TODO: This is under development and not ready for review or usage in simulations other than Bending Light.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chandrashekar Bemagoni (Actual Concepts)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Shape = require( 'KITE/Shape' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Vector2 = require( 'DOT/Vector2' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var RadialGradient = require( 'SCENERY/util/RadialGradient' );

  // constants
  var DEFAULTS = {
    width: 100,
    height: 151
  };

  /**
   * Constructor for the LightSensorNode
   * @constructor
   */
  function LightSensorNode( options ) {

    options = _.extend( DEFAULTS, options );
    var width = options.width;
    var height = options.height;

    // the top of the handle, below the circle at the top of the sensor
    var handleTop = height - width / 2;

    // The shape of the outer body, circular at top with a handle at the bottom
    var sensorShape = new Shape()
      .ellipticalArc( 0, 0, width * 0.5, width * 0.5, 0, Math.PI * 0.8, Math.PI * 0.2, false )
      .quadraticCurveTo( width * 0.34, width * 0.37, width * 0.32, width * 0.5 )
      .quadraticCurveTo( width * 0.31, handleTop, width * 0.30, width * 0.8 )
      .quadraticCurveTo( width * 0.30, handleTop, width * 0.15, handleTop )
      .quadraticCurveTo( width * 0.00, handleTop, -width * 0.15, handleTop )
      .quadraticCurveTo( -width * 0.30, handleTop, -width * 0.30, width * 0.80 )
      .quadraticCurveTo( -width * 0.31, width * 0.65, -width * 0.32, width * 0.5 )
      .quadraticCurveTo( -width * 0.34, width * 0.37, -width * 0.39, width * 0.32 )
      .close();

    var outerShapePath = new Path( sensorShape, {
      stroke: new LinearGradient( -width / 2, -width / 2, -width / 2, handleTop )
        .addColorStop( 0, '#408260' ) // dark green
        .addColorStop( 1, '#005D2D' ), // darker green
      fill: new LinearGradient( -width / 2, -width / 2, -width / 2, handleTop )
        .addColorStop( 0, '#7CCAA2' ) // light green
        .addColorStop( 0.3, '#009348' ) // medium green
        .addColorStop( 1, '#008B44' ), // another medium green
      lineWidth: 2
    } );

    // the front flat "surface" of the sensor, makes it look 3d by putting a shiny glare on the top edge
    var innerPath = new Path( sensorShape, {
      fill: '#008541', // darkish green
      lineWidth: 2,
      scale: new Vector2( 0.9, 0.93 ),
      centerX: outerShapePath.centerX,

      // Shift it down a bit to make the face look a bit more 3d
      y: 2
    } );

    var innerCirclePath = new Path( Shape.circle( 0, 0, width * 0.35 ), {
      fill: new RadialGradient( -width * 0.15, -width * 0.325, 0, -width * 0.15, width * 0.20, width * 0.60 )
        .addColorStop( 0, 'white' )
        .addColorStop( 0.4, '#E6F5FF' ) // light blue
        .addColorStop( 1, '#C2E7FF' ), // slightly darker blue, like glass
      centerX: innerPath.centerX
    } );

    Node.call( this, {
      children: [ outerShapePath, innerPath, innerCirclePath ]
    } );

    this.mutate( options );
  }

  return inherit( Node, LightSensorNode, {},

    // statics
    {

      // @public {read-only}, make the defaults publicly available to clients in case they need to make
      // customizations, such as 0.9 x the default width
      DEFAULTS: DEFAULTS
    } );
} );<commit_msg>whitespace cleanup, see #https://github.com/phetsims/bending-light/issues/165<commit_after>//  Copyright 2002-2014, University of Colorado Boulder

/**
 * A light sensor probe, used in simulations like Bending Light and Beer's Law Lab to show how much light is being
 * received. It is typically connected to a body with readouts with a curved wire.  The origin is in the center of the
 * circular part of the sensor (which is not vertically symmetrical).
 *
 * This code was generalized from Bending Light, see https://github.com/phetsims/bending-light/issues/165
 *
 * TODO: This is under development and not ready for review or usage in simulations other than Bending Light.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chandrashekar Bemagoni (Actual Concepts)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Shape = require( 'KITE/Shape' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Vector2 = require( 'DOT/Vector2' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var RadialGradient = require( 'SCENERY/util/RadialGradient' );

  // constants
  var DEFAULTS = {
    width: 100,
    height: 151
  };

  /**
   * Constructor for the LightSensorNode
   * @constructor
   */
  function LightSensorNode( options ) {

    options = _.extend( DEFAULTS, options );
    var width = options.width;
    var height = options.height;

    // the top of the handle, below the circle at the top of the sensor
    var handleTop = height - width / 2;

    // The shape of the outer body, circular at top with a handle at the bottom
    var sensorShape = new Shape()
      .ellipticalArc( 0, 0, width * 0.5, width * 0.5, 0, Math.PI * 0.8, Math.PI * 0.2, false )
      .quadraticCurveTo( width * 0.34, width * 0.37, width * 0.32, width * 0.5 )
      .quadraticCurveTo( width * 0.31, handleTop, width * 0.30, width * 0.8 )
      .quadraticCurveTo( width * 0.30, handleTop, width * 0.15, handleTop )
      .quadraticCurveTo( width * 0.00, handleTop, -width * 0.15, handleTop )
      .quadraticCurveTo( -width * 0.30, handleTop, -width * 0.30, width * 0.80 )
      .quadraticCurveTo( -width * 0.31, width * 0.65, -width * 0.32, width * 0.5 )
      .quadraticCurveTo( -width * 0.34, width * 0.37, -width * 0.39, width * 0.32 )
      .close();

    var outerShapePath = new Path( sensorShape, {
      stroke: new LinearGradient( -width / 2, -width / 2, -width / 2, handleTop )
        .addColorStop( 0, '#408260' ) // dark green
        .addColorStop( 1, '#005D2D' ), // darker green
      fill: new LinearGradient( -width / 2, -width / 2, -width / 2, handleTop )
        .addColorStop( 0, '#7CCAA2' ) // light green
        .addColorStop( 0.3, '#009348' ) // medium green
        .addColorStop( 1, '#008B44' ), // another medium green
      lineWidth: 2
    } );

    // the front flat "surface" of the sensor, makes it look 3d by putting a shiny glare on the top edge
    var innerPath = new Path( sensorShape, {
      fill: '#008541', // darkish green
      lineWidth: 2,
      scale: new Vector2( 0.9, 0.93 ),
      centerX: outerShapePath.centerX,

      // Shift it down a bit to make the face look a bit more 3d
      y: 2
    } );

    var innerCirclePath = new Path( Shape.circle( 0, 0, width * 0.35 ), {
      fill: new RadialGradient( -width * 0.15, -width * 0.325, 0, -width * 0.15, width * 0.20, width * 0.60 )
        .addColorStop( 0, 'white' )
        .addColorStop( 0.4, '#E6F5FF' ) // light blue
        .addColorStop( 1, '#C2E7FF' ), // slightly darker blue, like glass
      centerX: innerPath.centerX
    } );

    Node.call( this, {
      children: [ outerShapePath, innerPath, innerCirclePath ]
    } );

    this.mutate( options );
  }

  return inherit( Node, LightSensorNode, {},

    // statics
    {

      // @public {read-only}, make the defaults publicly available to clients in case they need to make
      // customizations, such as 0.9 x the default width
      DEFAULTS: DEFAULTS
    } );
} );<|endoftext|>


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

/**
 * An observable array of items.
 *

 * Because the array is observable, we must be careful about the possibility of concurrent-modification errors.
 * Any time we iterate over the array, we must iterate over a copy, because callback may be modifying the array.
 *
 * @author Sam Reid
 * @author Chris Malley
 */
define( function( require ) {
  'use strict';

  // modules
  var Property = require( 'AXON/Property' );
  var axon = require( 'AXON/axon' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Events = require( 'AXON/Events' );

  /**
   * @param {[]} array
   * @param {Object} [options]
   * @constructor
   */
  function ObservableArray( array, options ) {

    // Special case that the user supplied options but no array
    if ( array instanceof Object && !(array instanceof Array) ) {
      options = array;
      array = null;
    }

    this._options = _.extend( {
      allowDuplicates: false, // are duplicate items allowed in the array?
      tandem: null            // Tandem is supported here.  This line doesn't do anything different than leaving tandem as undefined
                              // but this entry serves as an indicator that tandem is supported here.
    }, options );

    this._array = array || []; // @private internal, do not access directly
    this._addedListeners = []; // @private listeners called when an item is added
    this._removedListeners = []; // @private listeners called when an item is removed

    this.lengthProperty = new Property( this._array.length ); // @public (read-only) observe this, but don't set it

    // @private Store the initial array, if any, for resetting, see #4
    this.initialArray = array ? array.slice() : [];

    // @private Event stream for signifying begin/end of callbacks
    this.events = new Events();

    options && options.tandem && options.tandem.addInstance( this );
    this.disposeObservableArray = function() {
      options && options.tandem && options.tandem.removeInstance( this );
    };
  }

  axon.register( 'ObservableArray', ObservableArray );

  return inherit( Object, ObservableArray, {

    // @public
    dispose: function() {
      this.disposeObservableArray();
    },

    /**
     * Restore the array back to its initial state
     * Note: if an item is in the current array and original array, it is removed and added back
     * This may or may not change in the future, see #4
     * @public
     */
    reset: function() {
      for ( var i = 0; i < this._array.length; i++ ) {
        this._fireItemRemoved( this._array[ i ] );
      }
      this._array = this.initialArray.slice();
      for ( i = 0; i < this._array.length; i++ ) {
        this._fireItemAdded( this._array[ i ] );
      }
    },

    // @public
    get length() { return this._array.length; },

    /**
     * Adds a listener that will be notified when an item is added to the list.
     * @param listener function( item, observableArray )
     * @public
     */
    addItemAddedListener: function( listener ) {
      assert && assert( this._addedListeners.indexOf( listener ) === -1 ); // listener is not already registered
      this._addedListeners.push( listener );
    },

    /**
     * Removes a listener that was added via addItemAddedListener.
     * @param listener
     * @public
     */
    removeItemAddedListener: function( listener ) {
      var index = this._addedListeners.indexOf( listener );
      assert && assert( index !== -1 ); // listener is registered
      this._addedListeners.splice( index, 1 );
    },

    /**
     * Adds a listener that will be notified when an item is removed from the list.
     * @param listener function( item, observableArray )
     * @public
     */
    addItemRemovedListener: function( listener ) {
      assert && assert( this._removedListeners.indexOf( listener ) === -1 ); // listener is not already registered
      this._removedListeners.push( listener );
    },

    /**
     * Removes a listener that was added via addItemRemovedListener.
     * @param listener
     * @public
     */
    removeItemRemovedListener: function( listener ) {
      var index = this._removedListeners.indexOf( listener );
      assert && assert( index !== -1 ); // listener is registered
      this._removedListeners.splice( index, 1 );
    },

    /**
     * Convenience function for adding both types of listeners in one shot.
     * @param itemAddedListener
     * @param itemRemovedListener
     * @public
     */
    addListeners: function( itemAddedListener, itemRemovedListener ) {
      this.addItemAddedListener( itemAddedListener );
      this.addItemRemovedListener( itemRemovedListener );
    },

    // @private Internal: called when an item is added.
    _fireItemAdded: function( item ) {

      this.events.trigger1( 'startedCallbacksForItemAdded', item );

      //Signify that an item was added to the list
      var copy = this._addedListeners.slice( 0 ); // operate on a copy, firing could result in the listeners changing
      for ( var i = 0; i < copy.length; i++ ) {
        copy[ i ]( item, this );
      }

      this.events.trigger0( 'endedCallbacksForItemAdded' );
    },

    // Internal: called when an item is removed.
    _fireItemRemoved: function( item ) {

      this.events.trigger1( 'startedCallbacksForItemRemoved', item );

      //Signify that an item was removed from the list
      var copy = this._removedListeners.slice( 0 ); // operate on a copy, firing could result in the listeners changing
      for ( var i = 0; i < copy.length; i++ ) {
        copy[ i ]( item, this );
      }

      this.events.trigger1( 'startedCallbacksForItemRemoved', item );
    },

    /**
     * Adds an item to the end of the array.
     * This is a convenience function, and is the same as push.
     * @param item
     * @public
     */
    add: function( item ) {
      this.push( item );
    },

    /**
     * Add items to the end of the array.
     * This is a convenience function, and is the same as push.
     * @param {Array} items
     * @public
     */
    addAll: function( items ) {
      for ( var i = 0; i < items.length; i++ ) {
        this.add( items[ i ] );
      }
    },

    /**
     * Removes the first occurrence of an item from the array.
     * If duplicates are allowed (see options.allowDuplicates) you may need to call this multiple
     * times to totally purge item from the array.
     * @param item
     * @public
     */
    remove: function( item ) {
      var index = this._array.indexOf( item );
      if ( index !== -1 ) {
        this._array.splice( index, 1 );
        this.lengthProperty.set( this._array.length );
        this._fireItemRemoved( item );
      }
    },

    /**
     * Removes the first occurrence of each item in the specified array.
     * @param {Array} list a list of items to remove
     * @see ObservableArray.remove
     * @public
     */
    removeAll: function( list ) {
      for ( var i = 0; i < list.length; i++ ) {
        var item = list[ i ];
        this.remove( item );
      }
    },

    /**
     * Pushes an item onto the end of the array.
     * @param item
     * @throws Error if duplicates are not allowed (see options.allowDuplicates) and item is already in the array
     * @public
     */
    push: function( item ) {
      if ( !this._options.allowDuplicates && this.contains( item ) ) {
        throw new Error( 'duplicates are not allowed' );
      }
      this._array.push( item );
      this.lengthProperty.set( this._array.length );
      this._fireItemAdded( item );
    },

    /**
     * Removes an item from the end of the array and returns it.
     * @returns {*}
     * @public
     */
    pop: function() {
      var item = this._array.pop();
      if ( item !== undefined ) {
        this.lengthProperty.set( this._array.length );
        this._fireItemRemoved( item );
      }
      return item;
    },

    /**
     * Removes an item from the beginning of the array and returns it.
     * @returns {*}
     * @public
     */
    shift: function() {
      var item = this._array.shift();
      if ( item !== undefined ) {
        this.lengthProperty.set( this._array.length );
        this._fireItemRemoved( item );
      }
      return item;
    },

    /**
     * Does the array contain the specified item?
     * @param item
     * @returns {boolean}
     * @public
     */
    contains: function( item ) {
      return this.indexOf( item ) !== -1;
    },

    /**
     * Gets an item at the specified index.
     * @param index
     * @returns {*} the item, or undefined if there is no item at the specified index
     * @public
     */
    get: function( index ) {
      return this._array[ index ];
    },

    /**
     * Gets the index of a specified item.
     * @param item
     * @returns {*} -1 if item is not in the array
     * @public
     */
    indexOf: function( item ) {
      return this._array.indexOf( item );
    },

    /**
     * Removes all items from the array.
     * @public
     */
    clear: function() {
      var copy = this._array.slice( 0 );
      for ( var i = 0; i < copy.length; i++ ) {
        this.remove( copy[ i ] );
      }
    },

    /**
     * Applies a callback function to each item in the array
     * @param callback function(item)
     * @public
     */
    forEach: function( callback ) {
      this._array.slice().forEach( callback ); // do this on a copy of the array, in case callbacks involve array modification
    },

    /**
     * Maps the values in this ObservableArray using the specified function, and returns a new ObservableArray for chaining.
     * @param mapFunction
     * @returns {axon.ObservableArray}
     * @public
     */
    map: function( mapFunction ) {
      return new axon.ObservableArray( this._array.map( mapFunction ) );
    },

    /**
     * Filters the values in this ObservableArray using the predicate function, and returns a new ObservableArray for chaining.
     * @param mapFunction
     * @returns {axon.ObservableArray}
     * @public
     */
    filter: function( mapFunction ) {
      return new axon.ObservableArray( this._array.filter( mapFunction ) );
    },

    /**
     * Starting with the initial value, combine values from this ObservableArray to come up with a composite result.
     * Same as foldLeft.  In underscore this is called _.reduce aka _.foldl or _.inject
     * @param value
     * @param combiner
     * @returns {*}
     * @public
     */
    reduce: function( value, combiner ) {
      for ( var i = 0; i < this._array.length; i++ ) {
        value = combiner( value, this._array[ i ] );
      }
      return value;
    },

    /**
     * Return the underlying array
     * @returns {*|Array}
     * @public
     */
    getArray: function() {
      return this._array;
    },

    /**
     * Replaces all items in the array
     * @param {Array} items
     * @public
     */
    setItems: function( items ) {

      // TODO: do a better diff here for efficiency
      this.clear();
      this.addAll( items );
    }
  } );
} );<commit_msg>Use emitters instead of events, closes #85<commit_after>// Copyright 2013-2015, University of Colorado Boulder

/**
 * An observable array of items.
 *

 * Because the array is observable, we must be careful about the possibility of concurrent-modification errors.
 * Any time we iterate over the array, we must iterate over a copy, because callback may be modifying the array.
 *
 * @author Sam Reid
 * @author Chris Malley
 */
define( function( require ) {
  'use strict';

  // modules
  var Property = require( 'AXON/Property' );
  var axon = require( 'AXON/axon' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Emitter = require( 'AXON/Emitter' );

  /**
   * @param {[]} array
   * @param {Object} [options]
   * @constructor
   */
  function ObservableArray( array, options ) {

    // Special case that the user supplied options but no array
    if ( array instanceof Object && !(array instanceof Array) ) {
      options = array;
      array = null;
    }

    this._options = _.extend( {
      allowDuplicates: false, // are duplicate items allowed in the array?
      tandem: null            // Tandem is supported here.  This line doesn't do anything different than leaving tandem as undefined
                              // but this entry serves as an indicator that tandem is supported here.
    }, options );

    this._array = array || []; // @private internal, do not access directly
    this._addedListeners = []; // @private listeners called when an item is added
    this._removedListeners = []; // @private listeners called when an item is removed

    this.lengthProperty = new Property( this._array.length ); // @public (read-only) observe this, but don't set it

    // @private Store the initial array, if any, for resetting, see #4
    this.initialArray = array ? array.slice() : [];

    // @private Event stream for signifying begin/end of callbacks
    this.startedCallbacksForItemAddedEmitter = new Emitter();
    this.endedCallbacksForItemAddedEmitter = new Emitter();
    this.startedCallbacksForItemRemovedEmitter = new Emitter();
    this.endedCallbacksForItemRemovedEmitter = new Emitter();

    options && options.tandem && options.tandem.addInstance( this );
    this.disposeObservableArray = function() {
      options && options.tandem && options.tandem.removeInstance( this );
    };
  }

  axon.register( 'ObservableArray', ObservableArray );

  return inherit( Object, ObservableArray, {

    // @public
    dispose: function() {
      this.disposeObservableArray();
    },

    /**
     * Restore the array back to its initial state
     * Note: if an item is in the current array and original array, it is removed and added back
     * This may or may not change in the future, see #4
     * @public
     */
    reset: function() {
      for ( var i = 0; i < this._array.length; i++ ) {
        this._fireItemRemoved( this._array[ i ] );
      }
      this._array = this.initialArray.slice();
      for ( i = 0; i < this._array.length; i++ ) {
        this._fireItemAdded( this._array[ i ] );
      }
    },

    // @public
    get length() { return this._array.length; },

    /**
     * Adds a listener that will be notified when an item is added to the list.
     * @param listener function( item, observableArray )
     * @public
     */
    addItemAddedListener: function( listener ) {
      assert && assert( this._addedListeners.indexOf( listener ) === -1 ); // listener is not already registered
      this._addedListeners.push( listener );
    },

    /**
     * Removes a listener that was added via addItemAddedListener.
     * @param listener
     * @public
     */
    removeItemAddedListener: function( listener ) {
      var index = this._addedListeners.indexOf( listener );
      assert && assert( index !== -1 ); // listener is registered
      this._addedListeners.splice( index, 1 );
    },

    /**
     * Adds a listener that will be notified when an item is removed from the list.
     * @param listener function( item, observableArray )
     * @public
     */
    addItemRemovedListener: function( listener ) {
      assert && assert( this._removedListeners.indexOf( listener ) === -1 ); // listener is not already registered
      this._removedListeners.push( listener );
    },

    /**
     * Removes a listener that was added via addItemRemovedListener.
     * @param listener
     * @public
     */
    removeItemRemovedListener: function( listener ) {
      var index = this._removedListeners.indexOf( listener );
      assert && assert( index !== -1 ); // listener is registered
      this._removedListeners.splice( index, 1 );
    },

    /**
     * Convenience function for adding both types of listeners in one shot.
     * @param itemAddedListener
     * @param itemRemovedListener
     * @public
     */
    addListeners: function( itemAddedListener, itemRemovedListener ) {
      this.addItemAddedListener( itemAddedListener );
      this.addItemRemovedListener( itemRemovedListener );
    },

    // @private Internal: called when an item is added.
    _fireItemAdded: function( item ) {
      this.startedCallbacksForItemAddedEmitter.emit();

      //Signify that an item was added to the list
      var copy = this._addedListeners.slice( 0 ); // operate on a copy, firing could result in the listeners changing
      for ( var i = 0; i < copy.length; i++ ) {
        copy[ i ]( item, this );
      }

      this.endedCallbacksForItemAddedEmitter.emit();
    },

    // Internal: called when an item is removed.
    _fireItemRemoved: function( item ) {

      this.startedCallbacksForItemRemovedEmitter.emit();

      //Signify that an item was removed from the list
      var copy = this._removedListeners.slice( 0 ); // operate on a copy, firing could result in the listeners changing
      for ( var i = 0; i < copy.length; i++ ) {
        copy[ i ]( item, this );
      }

      this.endedCallbacksForItemRemovedEmitter.emit();
    },

    /**
     * Adds an item to the end of the array.
     * This is a convenience function, and is the same as push.
     * @param item
     * @public
     */
    add: function( item ) {
      this.push( item );
    },

    /**
     * Add items to the end of the array.
     * This is a convenience function, and is the same as push.
     * @param {Array} items
     * @public
     */
    addAll: function( items ) {
      for ( var i = 0; i < items.length; i++ ) {
        this.add( items[ i ] );
      }
    },

    /**
     * Removes the first occurrence of an item from the array.
     * If duplicates are allowed (see options.allowDuplicates) you may need to call this multiple
     * times to totally purge item from the array.
     * @param item
     * @public
     */
    remove: function( item ) {
      var index = this._array.indexOf( item );
      if ( index !== -1 ) {
        this._array.splice( index, 1 );
        this.lengthProperty.set( this._array.length );
        this._fireItemRemoved( item );
      }
    },

    /**
     * Removes the first occurrence of each item in the specified array.
     * @param {Array} list a list of items to remove
     * @see ObservableArray.remove
     * @public
     */
    removeAll: function( list ) {
      for ( var i = 0; i < list.length; i++ ) {
        var item = list[ i ];
        this.remove( item );
      }
    },

    /**
     * Pushes an item onto the end of the array.
     * @param item
     * @throws Error if duplicates are not allowed (see options.allowDuplicates) and item is already in the array
     * @public
     */
    push: function( item ) {
      if ( !this._options.allowDuplicates && this.contains( item ) ) {
        throw new Error( 'duplicates are not allowed' );
      }
      this._array.push( item );
      this.lengthProperty.set( this._array.length );
      this._fireItemAdded( item );
    },

    /**
     * Removes an item from the end of the array and returns it.
     * @returns {*}
     * @public
     */
    pop: function() {
      var item = this._array.pop();
      if ( item !== undefined ) {
        this.lengthProperty.set( this._array.length );
        this._fireItemRemoved( item );
      }
      return item;
    },

    /**
     * Removes an item from the beginning of the array and returns it.
     * @returns {*}
     * @public
     */
    shift: function() {
      var item = this._array.shift();
      if ( item !== undefined ) {
        this.lengthProperty.set( this._array.length );
        this._fireItemRemoved( item );
      }
      return item;
    },

    /**
     * Does the array contain the specified item?
     * @param item
     * @returns {boolean}
     * @public
     */
    contains: function( item ) {
      return this.indexOf( item ) !== -1;
    },

    /**
     * Gets an item at the specified index.
     * @param index
     * @returns {*} the item, or undefined if there is no item at the specified index
     * @public
     */
    get: function( index ) {
      return this._array[ index ];
    },

    /**
     * Gets the index of a specified item.
     * @param item
     * @returns {*} -1 if item is not in the array
     * @public
     */
    indexOf: function( item ) {
      return this._array.indexOf( item );
    },

    /**
     * Removes all items from the array.
     * @public
     */
    clear: function() {
      var copy = this._array.slice( 0 );
      for ( var i = 0; i < copy.length; i++ ) {
        this.remove( copy[ i ] );
      }
    },

    /**
     * Applies a callback function to each item in the array
     * @param callback function(item)
     * @public
     */
    forEach: function( callback ) {
      this._array.slice().forEach( callback ); // do this on a copy of the array, in case callbacks involve array modification
    },

    /**
     * Maps the values in this ObservableArray using the specified function, and returns a new ObservableArray for chaining.
     * @param mapFunction
     * @returns {axon.ObservableArray}
     * @public
     */
    map: function( mapFunction ) {
      return new axon.ObservableArray( this._array.map( mapFunction ) );
    },

    /**
     * Filters the values in this ObservableArray using the predicate function, and returns a new ObservableArray for chaining.
     * @param mapFunction
     * @returns {axon.ObservableArray}
     * @public
     */
    filter: function( mapFunction ) {
      return new axon.ObservableArray( this._array.filter( mapFunction ) );
    },

    /**
     * Starting with the initial value, combine values from this ObservableArray to come up with a composite result.
     * Same as foldLeft.  In underscore this is called _.reduce aka _.foldl or _.inject
     * @param value
     * @param combiner
     * @returns {*}
     * @public
     */
    reduce: function( value, combiner ) {
      for ( var i = 0; i < this._array.length; i++ ) {
        value = combiner( value, this._array[ i ] );
      }
      return value;
    },

    /**
     * Return the underlying array
     * @returns {*|Array}
     * @public
     */
    getArray: function() {
      return this._array;
    },

    /**
     * Replaces all items in the array
     * @param {Array} items
     * @public
     */
    setItems: function( items ) {

      // TODO: do a better diff here for efficiency
      this.clear();
      this.addAll( items );
    }
  } );
} );<|endoftext|>