<commit_before>// Store models in *PouchDB*.
Backbone.sync = (function() {
  // match read request to get, query or allDocs call
  function read(db, model, options, callback) {
    // get single model
    if (model.id) return db.get(model.id, options, callback);

    // query view
    if (options.view) return db.query(options.view, options, callback);

    // all docs
    db.allDocs(options, callback);
  }

  // the sync adapter function
  var sync = function(method, model, options) {
    var pouch = model.pouch || (model.collection && model.collection.pouch);

    options || (options = {});

    if (!pouch) {
      throw('missing pouch: ' + method);
    }

    function callback(err, resp) {
      if (err) {
        if (options.error) options.error(model, err, options);
        return;
      }
      if (options.success) options.success(model, resp, options);
    }

    model.trigger('request', model, pouch, options);

    pouch(function(err, db, defaults) {
      if (err) {
        if (options.error) options.error(model, err, options);
        return;
      }
      var opts = _.extend({}, defaults, options);
      switch (method) {
        case 'create': db.post(model.toJSON(), opts, callback);   break;
        case 'update': db.put(model.toJSON(), opts, callback);    break;
        case 'delete': db.remove(model.toJSON(), opts, callback); break;
        case 'read':   read(db, model, opts, callback);           break;
      }
    });
  };

  // extend the sync adapter function
  // to init pouch via Backbone.sync.pouch(url, options)
  sync.pouch = function(url, options) {
    var err, db, initialized,
        waiting = [];

    options || (options = {});

    return function open(callback) {
      if (initialized) {
        if (err || db) {
          // we alreay have a pouch adapter available
          callback(err, db, options);
        } else {
          waiting.push(callback);
        }
      } else {
        initialized = true;
        // open pouch
        new Pouch(url, function(e, d) {
          callback(err = e, db = d, options);
          _.each(waiting, open);
          waiting = [];
        });
      }
    }
  };

  return sync;
})();
<commit_msg>(#8) - success and error callback arguments<commit_after>// Store models in *PouchDB*.
Backbone.sync = (function() {
  // match read request to get, query or allDocs call
  function read(db, model, options, callback) {
    // get single model
    if (model.id) return db.get(model.id, options, callback);

    // query view
    if (options.view) return db.query(options.view, options, callback);

    // all docs
    db.allDocs(options, callback);
  }

  // the sync adapter function
  var sync = function(method, model, options) {
    var pouch = model.pouch || (model.collection && model.collection.pouch);

    options || (options = {});

    if (!pouch) {
      throw('missing pouch: ' + method);
    }

    function callback(err, resp) {
      if (err) {
        if (options.error) options.error(resp);
        return;
      }
      if (options.success) options.success(resp);
    }

    model.trigger('request', model, pouch, options);

    pouch(function(err, db, defaults) {
      if (err) {
        if (options.error) options.error(err);
        return;
      }
      var opts = _.extend({}, defaults, options);
      switch (method) {
        case 'create': db.post(model.toJSON(), opts, callback);   break;
        case 'update': db.put(model.toJSON(), opts, callback);    break;
        case 'delete': db.remove(model.toJSON(), opts, callback); break;
        case 'read':   read(db, model, opts, callback);           break;
      }
    });
  };

  // extend the sync adapter function
  // to init pouch via Backbone.sync.pouch(url, options)
  sync.pouch = function(url, options) {
    var err, db, initialized,
        waiting = [];

    options || (options = {});

    return function open(callback) {
      if (initialized) {
        if (err || db) {
          // we alreay have a pouch adapter available
          callback(err, db, options);
        } else {
          waiting.push(callback);
        }
      } else {
        initialized = true;
        // open pouch
        new Pouch(url, function(e, d) {
          callback(err = e, db = d, options);
          _.each(waiting, open);
          waiting = [];
        });
      }
    }
  };

  return sync;
})();
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------- \\
// File: MenuView.js                                                          \\
// Module: ControlViews                                                       \\
// Requires: Core, Foundation, DOM, View                                      \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2013 FastMail Pty Ltd. All rights reserved.                \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS ) {

// A menu option must have:
// filter( pattern ): RegExp -> Boolean
// isFocussed: Boolean
// isHidden: Boolean
// isDisabled: Boolean

var MenuController = NS.Class({

    Extends: NS.Object,

    options: [],

    // --- Focus ---

    canSelect: function ( option ) {
        return !option.get( 'isHidden' ) && !option.get( 'isDisabled' );
    },

    focussedOption: null,

    getAdjacentOption: function ( step ) {
        var options = this.get( 'options' ),
            l = options.get( 'length' ),
            i = options.indexOf( this.get( 'focussedOption' ) ),
            current;

        if ( i < 0 && step < 0 ) {
            i = l;
        }
        current = i.mod( l );

        do {
            i = ( i + step ).mod( l );
        } while ( l &&
            !this.canSelect( options.getObjectAt( i ) ) && i !== current );

        return options.getObjectAt( i );
    },

    focusPrevious: function ( event ) {
        if ( event ) { event.preventDefault(); }
        return this.focusOption( this.getAdjacentOption( -1 ) );
    },

    focusNext: function ( event ) {
        if ( event ) { event.preventDefault(); }
        return this.focusOption( this.getAdjacentOption( 1 ) );
    },

    focusOption: function ( option ) {
        var current = this.get( 'focussedOption' );
        if ( current !== option ) {
            if ( current ) {
                current.set( 'isFocussed', false );
            }
            if ( option ) {
                if ( !this.canSelect( option ) ) {
                    option = null;
                } else {
                    option.set( 'isFocussed', true );
                }
            }
            this.set( 'focussedOption', option );
        }
        return this;
    },

    blurOption: function ( option ) {
        if ( this.get( 'focussedOption' ) === option ) {
            this.focusOption( null );
        }
        return this;
    },

    selectFocussed: function ( event ) {
        if ( event ) { event.preventDefault(); }
        var focussedOption = this.get( 'focussedOption' );
        if ( focussedOption && this.canSelect( focussedOption ) ) {
            focussedOption.activate( this );
        }
        return this;
    },

    // --- Filter ---

    filter: '',

    filterDidChange: function () {
        var value = this.get( 'filter' ).escapeRegExp(),
            pattern = value ? new RegExp( '(?:^|\\W)' + value, 'i' ) : null,
            options = this.get( 'options' ),
            l = options.get( 'length' ),
            focussedOption = this.get( 'focussedOption' );

        while ( l-- ) {
            options.getObjectAt( l ).filter( pattern );
        }
        if ( !focussedOption || !this.canSelect( focussedOption ) ) {
            this.focusOption( null ).focusNext();
        }
    }.observes( 'filter' ),

    // --- Keyboard support ---

    keyBindings: {
        esc: 'onEscape',
        enter: 'selectFocussed',
        up: 'focusPrevious',
        down: 'focusNext',
        left: 'closeIfSub',
        right: 'activateIfMenu'
    },

    triggerKeyBinding: function ( event ) {
        var key = NS.DOMEvent.lookupKey( event ),
            bindings = this.get( 'keyBindings' );
        if ( bindings[ key ] ) {
            event.stopPropagation();
            this[ bindings[ key ] ]( event, key );
        }
    }.on( 'keydown' ),

    onEscape: function ( event ) {
        event.preventDefault();
        var filter = this.get( 'filter' );
        if ( filter ) {
            this.set( 'filter', '' );
        } else {
            this.get( 'view' ).hide();
        }
    },

    closeIfSub: function () {
        var view = this.get( 'view' ),
            popOverView;
        if ( !view.get( 'showFilter' ) &&
                ( popOverView = view.getParent( NS.PopOverView ) ) &&
                  popOverView.get( 'parentPopOverView' ) ) {
            view.hide();
        }
    },

    activateIfMenu: function () {
        var focussedOption = this.get( 'focussedOption' );
        if ( focussedOption && !this.get( 'view' ).get( 'showFilter' ) &&
                focussedOption.get( 'button' ) instanceof NS.MenuButtonView ) {
            this.selectFocussed();
        }
    }
});

var MenuOptionView = NS.Class({

    Extends: NS.View,

    isHidden: false,
    isDisabled: function () {
        return this.getFromPath( 'button.isDisabled' );
    }.property( 'button.isDisabled' ),
    isFocussed: false,
    isFocussable: function () {
        return !this.get( 'isHidden' ) && !this.get( 'isDisabled' );
    }.property( 'isHidden', 'isDisabled' ),

    layerTag: 'li',

    className: function () {
        return 'MenuOptionView' +
            ( this.get( 'isFocussed' ) ? ' focussed' : '' ) +
            ( this.get( 'isHidden' ) ? ' hidden' : '' );
    }.property( 'isFocussed', 'isHidden' ),

    init: function ( view, controller ) {
        this.childViews = [ view ];
        this.button = view;
        this.controller = controller;
        MenuOptionView.parent.init.call( this );
    },

    scrollIntoView: function () {
        if ( this.get( 'isFocussed' ) ) {
            var scrollView = this.getParent( NS.ScrollView );
            if ( scrollView ) {
                var scrollHeight = scrollView.get( 'pxHeight' ),
                    scrollTop = scrollView.get( 'scrollTop' ),
                    top = this.getPositionRelativeTo( scrollView ).top,
                    height = this.get( 'pxHeight' );

                if ( top < scrollTop ) {
                    scrollView.scrollTo( 0, top - ( height >> 1 ), true );
                } else if ( top + height > scrollTop + scrollHeight ) {
                    scrollView.scrollTo( 0,
                        top + height - scrollHeight + ( height >> 1 ), true );
                }
            }
        }
    }.observes( 'isFocussed' ),

    _focusTimeout: null,

    takeFocus: function () {
        if ( this.get( 'isInDocument' ) ) {
            this.get( 'controller' ).focusOption( this )
                .activateIfMenu();
        }
    },

    mouseMove: function () {
        if ( !this.get( 'isFocussed' ) && !this._focusTimeout ) {
            var popOverView = this.getParent( NS.PopOverView );
            if ( popOverView && popOverView.hasSubView() ) {
                this._focusTimeout = NS.RunLoop.invokeAfterDelay(
                    this.takeFocus, 75, this );
            } else {
                this.takeFocus();
            }
        }
    }.on( 'mousemove' ),

    mouseOut: function () {
        if ( this._focusTimeout ) {
            NS.RunLoop.cancel( this._focusTimeout );
            this._focusTimeout = null;
        }
        if ( !this.get( 'button' ).get( 'isActive' ) ) {
            this.get( 'controller' ).blurOption( this );
        }
    }.on( 'mouseout' ),

    filter: function ( pattern ) {
        var label = this.get( 'button' ).get( 'label' );
        this.set( 'isHidden', !!pattern && !pattern.test( label ) );
    },

    activate: function () {
        var button = this.get( 'button' );
        if ( button.activate ) { button.activate(); }
    }
});

var MenuView = NS.Class({

    Extends: NS.View,

    className: 'MenuView',

    showFilter: false,
    closeOnActivate: true,

    didCreateLayer: function ( layer ) {
        MenuView.parent.didCreateLayer.call( this, layer );
        layer.addEventListener( 'mousemove', this, false );
        layer.addEventListener( 'mouseout', this, false );
    },

    willDestroyLayer: function ( layer ) {
        layer.removeEventListener( 'mouseout', this, false );
        layer.removeEventListener( 'mousemove', this, false );
        MenuView.parent.willDestroyLayer.call( this, layer );
    },

    didEnterDocument: function () {
        MenuView.parent.didEnterDocument.call( this );
        var scrollView = this._scrollView,
            delta, controller, input;
        if ( scrollView && !this.getParent( NS.ScrollView ) ) {
            delta = this.get( 'layer' ).getBoundingClientRect().bottom -
                this.getParent( NS.RootView ).get( 'pxHeight' );
            // Must redraw immediately so size is correct when PopOverView
            // checks if it is positioned off screen.
            scrollView.set( 'layout', {
                maxHeight: scrollView.get( 'pxHeight' ) - delta - 10
            }).redraw();
        }

        if ( this.get( 'showFilter' ) ) {
            controller = this.get( 'controller' );
            input = this._input;
            if ( !controller.get( 'focussedOption' ) ) {
                controller.focusNext();
            }
            NS.RunLoop.invokeInNextEventLoop( function () {
                input.focus().set( 'selection', {
                    start: 0,
                    end: input.get( 'value' ).length
                });
            });
        }
        return this;
    },

    didLeaveDocument: function () {
        var controller = this.get( 'controller' );
        if ( this.get( 'showFilter' ) ) {
            controller.set( 'filter', '' );
        } else {
            controller.focusOption( null );
        }
        return MenuView.parent.didLeaveDocument.call( this );
    },

    mayHaveResized: function () {
        this.parentViewDidResize();
    }.queue( 'after' ).observes( 'controller.filter' ),

    nextEventTarget: function () {
        return this.get( 'controller' );
    }.property( 'controller' ),

    controller: function () {
        return new MenuController({
            view: this
        });
    }.property(),

    ItemView: MenuOptionView,

    draw: function ( layer, Element, el ) {
        var controller = this.get( 'controller' ),
            MenuOptionView = this.get( 'ItemView' ),
            optionViews = this.get( 'options' ).map( function ( view ) {
                return new MenuOptionView( view, controller );
            });
        controller.set( 'options', optionViews );
        return [
            this.get( 'showFilter' ) ? el( 'div', [
                this._input = new NS.TextView({
                    blurOnEscape: false,
                    type: 'text filter',
                    value: NS.bindTwoWay( 'filter', this.get( 'controller' ) )
                })
            ]) : null,
            this._scrollView = new NS.ScrollView({
                positioning: 'relative',
                layout: {},
                layerTag: 'ul',
                childViews: optionViews
            })
        ];
    },

    hide: function () {
        var parent = this.get( 'parentView' );
        if ( parent ) {
            NS.RunLoop.invokeInNextEventLoop( parent.hide, parent );
        }
    },

    hideAll: function () {
        if ( this.get( 'closeOnActivate' ) ) {
            var popOverView = this.getParent( NS.PopOverView ) ||
                    this.get( 'parentView' ),
                parent;
            if ( popOverView ) {
                while ( parent = popOverView.get( 'parentPopOverView' ) ) {
                    popOverView = parent;
                }
                NS.RunLoop.invokeInNextEventLoop(
                    popOverView.hide, popOverView );
            }
        }
    }.on( 'button:activate' ),

    fireShortcut: function ( event ) {
        if ( !this.get( 'showFilter' ) ) {
            var key = NS.DOMEvent.lookupKey( event ),
                handler = NS.ViewEventsController
                            .kbShortcuts.getHandlerForKey( key ),
                parent, object, method;
            if ( handler ) {
                parent = object = handler[0];
                method = handler[1];
                // Check object is child view of the menu; we want to ignore any
                // other keyboard shortcuts.
                if ( object instanceof NS.View ) {
                    while ( parent && parent !== this ) {
                        parent = parent.get( 'parentView' );
                    }
                    if ( parent ) {
                        object[ method ]( event );
                        event.preventDefault();
                    }
                }
            }
        }
    }.on( 'keypress' )
});

NS.MenuController = MenuController;
NS.MenuOptionView = MenuOptionView;
NS.MenuView = MenuView;

}( this.O ) );
<commit_msg>Activate submenu on mouseover even if filter.<commit_after>// -------------------------------------------------------------------------- \\
// File: MenuView.js                                                          \\
// Module: ControlViews                                                       \\
// Requires: Core, Foundation, DOM, View                                      \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2013 FastMail Pty Ltd. All rights reserved.                \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS ) {

// A menu option must have:
// filter( pattern ): RegExp -> Boolean
// isFocussed: Boolean
// isHidden: Boolean
// isDisabled: Boolean

var MenuController = NS.Class({

    Extends: NS.Object,

    options: [],

    // --- Focus ---

    canSelect: function ( option ) {
        return !option.get( 'isHidden' ) && !option.get( 'isDisabled' );
    },

    focussedOption: null,

    getAdjacentOption: function ( step ) {
        var options = this.get( 'options' ),
            l = options.get( 'length' ),
            i = options.indexOf( this.get( 'focussedOption' ) ),
            current;

        if ( i < 0 && step < 0 ) {
            i = l;
        }
        current = i.mod( l );

        do {
            i = ( i + step ).mod( l );
        } while ( l &&
            !this.canSelect( options.getObjectAt( i ) ) && i !== current );

        return options.getObjectAt( i );
    },

    focusPrevious: function ( event ) {
        if ( event ) { event.preventDefault(); }
        return this.focusOption( this.getAdjacentOption( -1 ) );
    },

    focusNext: function ( event ) {
        if ( event ) { event.preventDefault(); }
        return this.focusOption( this.getAdjacentOption( 1 ) );
    },

    focusOption: function ( option ) {
        var current = this.get( 'focussedOption' );
        if ( current !== option ) {
            if ( current ) {
                current.set( 'isFocussed', false );
            }
            if ( option ) {
                if ( !this.canSelect( option ) ) {
                    option = null;
                } else {
                    option.set( 'isFocussed', true );
                }
            }
            this.set( 'focussedOption', option );
        }
        return this;
    },

    blurOption: function ( option ) {
        if ( this.get( 'focussedOption' ) === option ) {
            this.focusOption( null );
        }
        return this;
    },

    selectFocussed: function ( event ) {
        if ( event ) { event.preventDefault(); }
        var focussedOption = this.get( 'focussedOption' );
        if ( focussedOption && this.canSelect( focussedOption ) ) {
            focussedOption.activate( this );
        }
        return this;
    },

    // --- Filter ---

    filter: '',

    filterDidChange: function () {
        var value = this.get( 'filter' ).escapeRegExp(),
            pattern = value ? new RegExp( '(?:^|\\W)' + value, 'i' ) : null,
            options = this.get( 'options' ),
            l = options.get( 'length' ),
            focussedOption = this.get( 'focussedOption' );

        while ( l-- ) {
            options.getObjectAt( l ).filter( pattern );
        }
        if ( !focussedOption || !this.canSelect( focussedOption ) ) {
            this.focusOption( null ).focusNext();
        }
    }.observes( 'filter' ),

    // --- Keyboard support ---

    keyBindings: {
        esc: 'onEscape',
        enter: 'selectFocussed',
        up: 'focusPrevious',
        down: 'focusNext',
        left: 'closeIfSub',
        right: 'activateIfMenu'
    },

    triggerKeyBinding: function ( event ) {
        var key = NS.DOMEvent.lookupKey( event ),
            bindings = this.get( 'keyBindings' );
        if ( bindings[ key ] ) {
            event.stopPropagation();
            this[ bindings[ key ] ]( event, key );
        }
    }.on( 'keydown' ),

    onEscape: function ( event ) {
        event.preventDefault();
        var filter = this.get( 'filter' );
        if ( filter ) {
            this.set( 'filter', '' );
        } else {
            this.get( 'view' ).hide();
        }
    },

    closeIfSub: function () {
        var view = this.get( 'view' ),
            popOverView;
        if ( !view.get( 'showFilter' ) &&
                ( popOverView = view.getParent( NS.PopOverView ) ) &&
                  popOverView.get( 'parentPopOverView' ) ) {
            view.hide();
        }
    },

    activateIfMenu: function () {
        var focussedOption = this.get( 'focussedOption' );
        if ( focussedOption &&
                focussedOption.get( 'button' ) instanceof NS.MenuButtonView ) {
            this.selectFocussed();
        }
    }
});

var MenuOptionView = NS.Class({

    Extends: NS.View,

    isHidden: false,
    isDisabled: function () {
        return this.getFromPath( 'button.isDisabled' );
    }.property( 'button.isDisabled' ),
    isFocussed: false,
    isFocussable: function () {
        return !this.get( 'isHidden' ) && !this.get( 'isDisabled' );
    }.property( 'isHidden', 'isDisabled' ),

    layerTag: 'li',

    className: function () {
        return 'MenuOptionView' +
            ( this.get( 'isFocussed' ) ? ' focussed' : '' ) +
            ( this.get( 'isHidden' ) ? ' hidden' : '' );
    }.property( 'isFocussed', 'isHidden' ),

    init: function ( view, controller ) {
        this.childViews = [ view ];
        this.button = view;
        this.controller = controller;
        MenuOptionView.parent.init.call( this );
    },

    scrollIntoView: function () {
        if ( this.get( 'isFocussed' ) ) {
            var scrollView = this.getParent( NS.ScrollView );
            if ( scrollView ) {
                var scrollHeight = scrollView.get( 'pxHeight' ),
                    scrollTop = scrollView.get( 'scrollTop' ),
                    top = this.getPositionRelativeTo( scrollView ).top,
                    height = this.get( 'pxHeight' );

                if ( top < scrollTop ) {
                    scrollView.scrollTo( 0, top - ( height >> 1 ), true );
                } else if ( top + height > scrollTop + scrollHeight ) {
                    scrollView.scrollTo( 0,
                        top + height - scrollHeight + ( height >> 1 ), true );
                }
            }
        }
    }.observes( 'isFocussed' ),

    _focusTimeout: null,

    takeFocus: function () {
        if ( this.get( 'isInDocument' ) ) {
            this.get( 'controller' ).focusOption( this )
                .activateIfMenu();
        }
    },

    mouseMove: function () {
        if ( !this.get( 'isFocussed' ) && !this._focusTimeout ) {
            var popOverView = this.getParent( NS.PopOverView );
            if ( popOverView && popOverView.hasSubView() ) {
                this._focusTimeout = NS.RunLoop.invokeAfterDelay(
                    this.takeFocus, 75, this );
            } else {
                this.takeFocus();
            }
        }
    }.on( 'mousemove' ),

    mouseOut: function () {
        if ( this._focusTimeout ) {
            NS.RunLoop.cancel( this._focusTimeout );
            this._focusTimeout = null;
        }
        if ( !this.get( 'button' ).get( 'isActive' ) ) {
            this.get( 'controller' ).blurOption( this );
        }
    }.on( 'mouseout' ),

    filter: function ( pattern ) {
        var label = this.get( 'button' ).get( 'label' );
        this.set( 'isHidden', !!pattern && !pattern.test( label ) );
    },

    activate: function () {
        var button = this.get( 'button' );
        if ( button.activate ) { button.activate(); }
    }
});

var MenuView = NS.Class({

    Extends: NS.View,

    className: 'MenuView',

    showFilter: false,
    closeOnActivate: true,

    didCreateLayer: function ( layer ) {
        MenuView.parent.didCreateLayer.call( this, layer );
        layer.addEventListener( 'mousemove', this, false );
        layer.addEventListener( 'mouseout', this, false );
    },

    willDestroyLayer: function ( layer ) {
        layer.removeEventListener( 'mouseout', this, false );
        layer.removeEventListener( 'mousemove', this, false );
        MenuView.parent.willDestroyLayer.call( this, layer );
    },

    didEnterDocument: function () {
        MenuView.parent.didEnterDocument.call( this );
        var scrollView = this._scrollView,
            delta, controller, input;
        if ( scrollView && !this.getParent( NS.ScrollView ) ) {
            delta = this.get( 'layer' ).getBoundingClientRect().bottom -
                this.getParent( NS.RootView ).get( 'pxHeight' );
            // Must redraw immediately so size is correct when PopOverView
            // checks if it is positioned off screen.
            scrollView.set( 'layout', {
                maxHeight: scrollView.get( 'pxHeight' ) - delta - 10
            }).redraw();
        }

        if ( this.get( 'showFilter' ) ) {
            controller = this.get( 'controller' );
            input = this._input;
            if ( !controller.get( 'focussedOption' ) ) {
                controller.focusNext();
            }
            NS.RunLoop.invokeInNextEventLoop( function () {
                input.focus().set( 'selection', {
                    start: 0,
                    end: input.get( 'value' ).length
                });
            });
        }
        return this;
    },

    didLeaveDocument: function () {
        var controller = this.get( 'controller' );
        if ( this.get( 'showFilter' ) ) {
            controller.set( 'filter', '' );
        } else {
            controller.focusOption( null );
        }
        return MenuView.parent.didLeaveDocument.call( this );
    },

    mayHaveResized: function () {
        this.parentViewDidResize();
    }.queue( 'after' ).observes( 'controller.filter' ),

    nextEventTarget: function () {
        return this.get( 'controller' );
    }.property( 'controller' ),

    controller: function () {
        return new MenuController({
            view: this
        });
    }.property(),

    ItemView: MenuOptionView,

    draw: function ( layer, Element, el ) {
        var controller = this.get( 'controller' ),
            MenuOptionView = this.get( 'ItemView' ),
            optionViews = this.get( 'options' ).map( function ( view ) {
                return new MenuOptionView( view, controller );
            });
        controller.set( 'options', optionViews );
        return [
            this.get( 'showFilter' ) ? el( 'div', [
                this._input = new NS.TextView({
                    blurOnEscape: false,
                    type: 'text filter',
                    value: NS.bindTwoWay( 'filter', this.get( 'controller' ) )
                })
            ]) : null,
            this._scrollView = new NS.ScrollView({
                positioning: 'relative',
                layout: {},
                layerTag: 'ul',
                childViews: optionViews
            })
        ];
    },

    hide: function () {
        var parent = this.get( 'parentView' );
        if ( parent ) {
            NS.RunLoop.invokeInNextEventLoop( parent.hide, parent );
        }
    },

    hideAll: function () {
        if ( this.get( 'closeOnActivate' ) ) {
            var popOverView = this.getParent( NS.PopOverView ) ||
                    this.get( 'parentView' ),
                parent;
            if ( popOverView ) {
                while ( parent = popOverView.get( 'parentPopOverView' ) ) {
                    popOverView = parent;
                }
                NS.RunLoop.invokeInNextEventLoop(
                    popOverView.hide, popOverView );
            }
        }
    }.on( 'button:activate' ),

    fireShortcut: function ( event ) {
        if ( !this.get( 'showFilter' ) ) {
            var key = NS.DOMEvent.lookupKey( event ),
                handler = NS.ViewEventsController
                            .kbShortcuts.getHandlerForKey( key ),
                parent, object, method;
            if ( handler ) {
                parent = object = handler[0];
                method = handler[1];
                // Check object is child view of the menu; we want to ignore any
                // other keyboard shortcuts.
                if ( object instanceof NS.View ) {
                    while ( parent && parent !== this ) {
                        parent = parent.get( 'parentView' );
                    }
                    if ( parent ) {
                        object[ method ]( event );
                        event.preventDefault();
                    }
                }
            }
        }
    }.on( 'keypress' )
});

NS.MenuController = MenuController;
NS.MenuOptionView = MenuOptionView;
NS.MenuView = MenuView;

}( this.O ) );
<|endoftext|>


<commit_before>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27,
    'up': 38,
    'down': 40
};
const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'click.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.addClass('search__input--loaded');
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {

            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            this.setSearchInactive();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        }).on('click', e => {
            e.preventDefault();
            this.setSearchInactive();
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.clearInput();
                this.$inputField.blur();
                this.setSearchInactive();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component or on the search results page.
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element && !this.$element.is('.search--resultpage')) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.clearInput();
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.$searchResults.hide().html('').removeClass('search__results--showed-results');
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();
    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            let matchIndex = item.name.toString().toLowerCase().indexOf(query);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;
        const resultLength = results.length;

        this.$searchResults.html('');

        results.map((result, index) => {
            /*** for testing purposes only ****/
            let origin = window.location.origin;
            let url = result.url;
            if (url.indexOf(origin) === -1) { // not production
                url = url.split('srf.ch')[1];
            }

            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            // const $link = $('<a>', { class: 'search-result__link', href: result.url, 'data-referrer-track' : 'srg_mod1=search_suggestions&srg_mod2=search_result&srg_mod3=' + query + '&srg_mod4=' + resultLength + '-' + (index + 1)});
            const $link = $('<a>', { class: 'search-result__link', href: url, 'data-referrer-track' : 'srg_mod1=search_suggestions&srg_mod2=search_result&srg_mod3=' + query + '&srg_mod4=' + resultLength + '-' + (index + 1)});

            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<commit_msg>there's ONE relative url :/<commit_after>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27,
    'up': 38,
    'down': 40
};
const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'click.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.addClass('search__input--loaded');
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {

            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            this.setSearchInactive();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        }).on('click', e => {
            e.preventDefault();
            this.setSearchInactive();
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.clearInput();
                this.$inputField.blur();
                this.setSearchInactive();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component or on the search results page.
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element && !this.$element.is('.search--resultpage')) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.clearInput();
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.$searchResults.hide().html('').removeClass('search__results--showed-results');
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();
    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            let matchIndex = item.name.toString().toLowerCase().indexOf(query);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;
        const resultLength = results.length;

        this.$searchResults.html('');

        results.map((result, index) => {
            /*** for testing purposes only ****/
            let origin = window.location.origin;
            let url = result.url;
            if (url.indexOf(origin) === -1) { // not production
                url = url.split('srf.ch')[1] || url.split('srf.ch')[0];
            }

            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            // const $link = $('<a>', { class: 'search-result__link', href: result.url, 'data-referrer-track' : 'srg_mod1=search_suggestions&srg_mod2=search_result&srg_mod3=' + query + '&srg_mod4=' + resultLength + '-' + (index + 1)});
            const $link = $('<a>', { class: 'search-result__link', href: url, 'data-referrer-track' : 'srg_mod1=search_suggestions&srg_mod2=search_result&srg_mod3=' + query + '&srg_mod4=' + resultLength + '-' + (index + 1)});

            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<|endoftext|>


<commit_before>const path = require('path');
const webpackConfig = require('../webpack/config.test.babel');

module.exports = (config) => {
  config.set({
    browsers: ['PhantomJS'],

    singleRun: true,

    frameworks: ['mocha'],

    files: ['./test-bundler.js'],

    preprocessors: {
      './test-bundler.js': ['webpack', 'sourcemap'],
    },

    reporters: ['mocha', 'coverage'],

    webpack: webpackConfig,

    // Make Webpack bundle generation quiet
    webpackMiddleware: {
      noInfo: true,
      stats: 'errors-only',
    },

    // Set the format of reporter
    coverageReporter: {
      dir: path.join(process.cwd(), 'coverage'),
      reporters: [
        { type: 'html', subdir: 'html' },
        { type: 'lcov', subdir: 'lcov' },
        { type: 'text-summary', subdir: '.', file: 'text-summary.txt' },
      ],
    },
  });
};
<commit_msg>Code refine<commit_after>'use strict'; // eslint-disable-line

const path = require('path');
const webpackConfig = require('../webpack/config.test.babel');

module.exports = (config) => {
  config.set({
    browsers: ['PhantomJS'],

    singleRun: true,

    frameworks: ['mocha'],

    files: ['./test-bundler.js'],

    preprocessors: {
      './test-bundler.js': ['webpack', 'sourcemap'],
    },

    reporters: ['mocha', 'coverage'],

    webpack: webpackConfig,

    // Make Webpack bundle generation quiet
    webpackMiddleware: {
      noInfo: true,
      stats: 'errors-only',
    },

    // Set the format of reporter
    coverageReporter: {
      dir: path.join(process.cwd(), 'coverage'),
      reporters: [
        { type: 'html', subdir: 'html' },
        { type: 'lcov', subdir: 'lcov' },
        { type: 'text-summary', subdir: '.', file: 'text-summary.txt' },
      ],
    },
  });
};
<|endoftext|>


<commit_before>var selftest = require('../selftest.js');
var Sandbox = selftest.Sandbox;
var archinfo = require('../archinfo.js');
var release = require('../release.js');

selftest.define("argument parsing", function () {
  var s = new Sandbox;
  var run;

  // bad command
  run = s.run("aoeuasdf");
  run.matchErr("not a Meteor command");
  run.expectExit(1);

  // bad subcommand
  run = s.run("admin", "aoeuasdf");
  run.matchErr("not a Meteor command");
  run.expectExit(1);

  // missing subcommand
  run = s.run("admin");
  run.matchErr("for available commands");
  run.expectExit(1);

  // conflicting command-like options
  run = s.run("aoeuasdf", "--version");
  run.matchErr("pass anything else along with --version");
  run.expectExit(1);

  run = s.run("--arch", "--version");
  run.matchErr("pass anything else");
  run.expectExit(1);

  run = s.run("run", "--version");
  run.matchErr("pass anything else");
  run.expectExit(1);

  run = s.run("--arch", "--arch");
  run.matchErr("more than once");
  run.expectExit(1);

  // --release takes exactly one value
  run = s.run("--release");
  run.matchErr("needs a value");
  run.expectExit(1);

  run = s.run("--release", "abc", "--release", "def");
  run.matchErr("should only be passed once");
  run.expectExit(1);

  // required option missing
  run = s.run("dummy");
  run.matchErr("option is required");
  run.matchErr("Usage: meteor dummy");
  run.expectExit(1);

  // successful command invocation, correct parsing of arguments
  run = s.run("dummy", "--ething", "x");
  run.read('"x" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  // The tests below fail on Windows. There is a bug in Node about empty
  // arguments that was fixed recently:
  // https://github.com/joyent/node/issues/7138
  if (process.platform !== "win32") {
    run = s.run("dummy", "--ething", "");
    run.read('"" "3000" none []\n');
    run.expectEnd();
    run.expectExit(0);

    run = s.run("dummy", "--ething", "x", "", "");
    run.read('"x" "3000" none ["",""]\n');
    run.expectEnd();
    run.expectExit(0);
  }

  run = s.run("dummy", "--ething=");
  run.read('"" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "-e=");
  run.read('"" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-");
  run.read('"x" "3000" none ["-"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "-e", "x");
  run.read('"x" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  // See comment above about empty arguments
  if (process.platform !== "win32") {
    run = s.run("dummy", "-e", "");
    run.read('"" "3000" none []\n');
    run.expectEnd();
    run.expectExit(0);
  }

  run = s.run("dummy", "-exxx");
  run.read('"xxx" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "-");
  run.read('"-" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--port", "1234", "--changed");
  run.read('"x" 1234 true []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--port", "0", "true");
  run.read('"x" 0 none ["true"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--port", "01234", "12", "0013");
  run.read('"x" 1234 none ["12","0013"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "--port", "1234", "--changed");
  run.read('"--port" "3000" true ["1234"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething=x=y=z", "-Up=3000");
  run.read('"x=y=z" 3000 none []\nurl\n');
  run.expectEnd();
  run.expectExit(0);

  // bad option
  run = s.run("dummy", "--ething", "x", "--foo");
  run.matchErr("--foo: unknown option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-z");
  run.matchErr("-z: unknown option");
  run.expectExit(1);

  // passing short and long options
  run = s.run("dummy", "--ething", "x", "-p", "2000", "--port", "2000");
  run.matchErr("can't pass both -p and --port");
  run.expectExit(1);

  // multiple values for an option
  run = s.run("dummy", "--ething", "x", "--port", "2000", "--port", "3000");
  run.matchErr("can only take one --port option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-p", "2000", "-p", "2000");
  run.matchErr("can only take one --port (-p) option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "--changed", "--changed");
  run.matchErr("can only take one --changed option");
  run.expectExit(1);

  // missing option value
  run = s.run("dummy", "--ething", "x", "--port");
  run.matchErr("the --port option needs a value");
  run.expectExit(1);

  run = s.run("dummy", "--ething");
  run.matchErr("--ething option needs a value");
  run.expectExit(1);

  run = s.run("dummy", "-e");
  run.matchErr("--ething (-e) option needs a value");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "--changed", "-p");
  run.matchErr("the --port (-p) option needs a value");
  run.expectExit(1);

  // non-numeric value for numeric option
  run = s.run("dummy", "--ething", "x", "--port", "kitten");
  run.matchErr("--port must be a number");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-p", "1234k");
  run.matchErr("--port (-p) must be a number");
  run.expectExit(1);

  // bad use of =
  run = s.run("dummy", "--=");
  run.readErr("Option names cannot begin with '='.\n");
  run.expectExit(1);

  run = s.run("dummy", "--=asdf");
  run.readErr("Option names cannot begin with '='.\n");
  run.expectExit(1);

  run = s.run("dummy", "-=");
  run.readErr("Option names cannot begin with '='.\n");
  run.expectExit(1);

  run = s.run("dummy", "-ex", "--changed=foo");
  run.matchErr("the --changed option does not need a value.\n");
  run.expectExit(1);

  run = s.run("dummy", "-ex", "-D=foo");
  run.matchErr("the --delete (-D) option does not need a value.\n");
  run.expectExit(1);

  run = s.run("dummy", "-ex", "-UD=foo");
  run.matchErr("the --delete (-D) option does not need a value.\n");
  run.expectExit(1);

  // incorrect number of arguments
  run = s.run("dummy", "--ething", "x", "1", "2", "3");
  run.matchErr("too many arguments");
  run.matchErr("Usage: meteor dummy");
  run.expectExit(1);

  run = s.run("bundle");
  run.matchErr("not enough arguments");
  run.matchErr("This command has been deprecated");
  run.expectExit(1);

  run = s.run("bundle", "a", "b");
  run.matchErr("too many arguments");
  run.matchErr("This command has been deprecated");
  run.expectExit(1);

  run = s.run("build");
  run.matchErr("not enough arguments");
  run.matchErr("Usage: meteor build");
  run.expectExit(1);

  run = s.run("build", "a", "b");
  run.matchErr("too many arguments");
  run.matchErr("Usage: meteor build");
  run.expectExit(1);

  // '--' to end parsing
  run = s.run("dummy", "--ething", "x", "--", "-p", "4000");
  run.read('"x" "3000" none ["-p","4000"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--", "--changed", "--changed");
  run.read('"x" "3000" none ["--changed","--changed"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--");
  run.read('"x" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  // compact short options
  run = s.run("dummy", "--ething", "x", "-p4000", "--changed");
  run.read('"x" 4000 true []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UD", "--changed");
  run.read('"x" "3000" true []\nurl\n\delete\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UDp4000", "--changed");
  run.read('"x" 4000 true []\nurl\ndelete\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UDp4000", "--changed");
  run.read('"x" 4000 true []\nurl\ndelete\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UDp4000");
  run.read('"x" 4000 none []\nurl\ndelete\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UDkp4000", "--changed");
  run.matchErr("-k: unknown option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-UDp4000k", "--changed");
  run.matchErr("--port (-p) must be a number");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-UD4000k", "--changed");
  run.matchErr("-4: unknown option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-UDDp4000", "--changed");
  run.matchErr("one --delete (-D) option");
  run.expectExit(1);

  // requiring an app dir
  run = s.run("list");
  run.matchErr("not in a Meteor project");
  run.matchErr("meteor create"); // new user help
  run.expectExit(1);

  s.createApp('myapp', 'standard-app');
  s.cd('myapp', function () {
    run = s.run("list");
    run.expectExit(0);
  });
});

selftest.define("command-like options", function () {
  var s = new Sandbox;
  var run;

  run = s.run("--version");
  if (release.current.isCheckout()) {
    run.matchErr("Unreleased");
    run.expectExit(1);
  } else {
    run.read(release.current.getDisplayName() + "\n");
    run.expectEnd();
    run.expectExit(0);
  }

  run = s.run("--arch");
  run.read(archinfo.host() + "\n");
  run.expectEnd();
  run.expectExit(0);
});
<commit_msg>Fix command line test by adding small wait<commit_after>var selftest = require('../selftest.js');
var Sandbox = selftest.Sandbox;
var archinfo = require('../archinfo.js');
var release = require('../release.js');

selftest.define("argument parsing", function () {
  var s = new Sandbox;
  var run;

  // bad command
  run = s.run("aoeuasdf");
  run.matchErr("not a Meteor command");
  run.expectExit(1);

  // bad subcommand
  run = s.run("admin", "aoeuasdf");
  run.matchErr("not a Meteor command");
  run.expectExit(1);

  // missing subcommand
  run = s.run("admin");
  run.matchErr("for available commands");
  run.expectExit(1);

  // conflicting command-like options
  run = s.run("aoeuasdf", "--version");
  run.waitSecs(5);
  run.matchErr("pass anything else along with --version");
  run.expectExit(1);

  run = s.run("--arch", "--version");
  run.matchErr("pass anything else");
  run.expectExit(1);

  run = s.run("run", "--version");
  run.matchErr("pass anything else");
  run.expectExit(1);

  run = s.run("--arch", "--arch");
  run.matchErr("more than once");
  run.expectExit(1);

  // --release takes exactly one value
  run = s.run("--release");
  run.matchErr("needs a value");
  run.expectExit(1);

  run = s.run("--release", "abc", "--release", "def");
  run.matchErr("should only be passed once");
  run.expectExit(1);

  // required option missing
  run = s.run("dummy");
  run.matchErr("option is required");
  run.matchErr("Usage: meteor dummy");
  run.expectExit(1);

  // successful command invocation, correct parsing of arguments
  run = s.run("dummy", "--ething", "x");
  run.read('"x" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  // The tests below fail on Windows. There is a bug in Node about empty
  // arguments that was fixed recently:
  // https://github.com/joyent/node/issues/7138
  if (process.platform !== "win32") {
    run = s.run("dummy", "--ething", "");
    run.read('"" "3000" none []\n');
    run.expectEnd();
    run.expectExit(0);

    run = s.run("dummy", "--ething", "x", "", "");
    run.read('"x" "3000" none ["",""]\n');
    run.expectEnd();
    run.expectExit(0);
  }

  run = s.run("dummy", "--ething=");
  run.read('"" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "-e=");
  run.read('"" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-");
  run.read('"x" "3000" none ["-"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "-e", "x");
  run.read('"x" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  // See comment above about empty arguments
  if (process.platform !== "win32") {
    run = s.run("dummy", "-e", "");
    run.read('"" "3000" none []\n');
    run.expectEnd();
    run.expectExit(0);
  }

  run = s.run("dummy", "-exxx");
  run.read('"xxx" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "-");
  run.read('"-" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--port", "1234", "--changed");
  run.read('"x" 1234 true []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--port", "0", "true");
  run.read('"x" 0 none ["true"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--port", "01234", "12", "0013");
  run.read('"x" 1234 none ["12","0013"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "--port", "1234", "--changed");
  run.read('"--port" "3000" true ["1234"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething=x=y=z", "-Up=3000");
  run.read('"x=y=z" 3000 none []\nurl\n');
  run.expectEnd();
  run.expectExit(0);

  // bad option
  run = s.run("dummy", "--ething", "x", "--foo");
  run.matchErr("--foo: unknown option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-z");
  run.matchErr("-z: unknown option");
  run.expectExit(1);

  // passing short and long options
  run = s.run("dummy", "--ething", "x", "-p", "2000", "--port", "2000");
  run.matchErr("can't pass both -p and --port");
  run.expectExit(1);

  // multiple values for an option
  run = s.run("dummy", "--ething", "x", "--port", "2000", "--port", "3000");
  run.matchErr("can only take one --port option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-p", "2000", "-p", "2000");
  run.matchErr("can only take one --port (-p) option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "--changed", "--changed");
  run.matchErr("can only take one --changed option");
  run.expectExit(1);

  // missing option value
  run = s.run("dummy", "--ething", "x", "--port");
  run.matchErr("the --port option needs a value");
  run.expectExit(1);

  run = s.run("dummy", "--ething");
  run.matchErr("--ething option needs a value");
  run.expectExit(1);

  run = s.run("dummy", "-e");
  run.matchErr("--ething (-e) option needs a value");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "--changed", "-p");
  run.matchErr("the --port (-p) option needs a value");
  run.expectExit(1);

  // non-numeric value for numeric option
  run = s.run("dummy", "--ething", "x", "--port", "kitten");
  run.matchErr("--port must be a number");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-p", "1234k");
  run.matchErr("--port (-p) must be a number");
  run.expectExit(1);

  // bad use of =
  run = s.run("dummy", "--=");
  run.readErr("Option names cannot begin with '='.\n");
  run.expectExit(1);

  run = s.run("dummy", "--=asdf");
  run.readErr("Option names cannot begin with '='.\n");
  run.expectExit(1);

  run = s.run("dummy", "-=");
  run.readErr("Option names cannot begin with '='.\n");
  run.expectExit(1);

  run = s.run("dummy", "-ex", "--changed=foo");
  run.matchErr("the --changed option does not need a value.\n");
  run.expectExit(1);

  run = s.run("dummy", "-ex", "-D=foo");
  run.matchErr("the --delete (-D) option does not need a value.\n");
  run.expectExit(1);

  run = s.run("dummy", "-ex", "-UD=foo");
  run.matchErr("the --delete (-D) option does not need a value.\n");
  run.expectExit(1);

  // incorrect number of arguments
  run = s.run("dummy", "--ething", "x", "1", "2", "3");
  run.matchErr("too many arguments");
  run.matchErr("Usage: meteor dummy");
  run.expectExit(1);

  run = s.run("bundle");
  run.matchErr("not enough arguments");
  run.matchErr("This command has been deprecated");
  run.expectExit(1);

  run = s.run("bundle", "a", "b");
  run.matchErr("too many arguments");
  run.matchErr("This command has been deprecated");
  run.expectExit(1);

  run = s.run("build");
  run.matchErr("not enough arguments");
  run.matchErr("Usage: meteor build");
  run.expectExit(1);

  run = s.run("build", "a", "b");
  run.matchErr("too many arguments");
  run.matchErr("Usage: meteor build");
  run.expectExit(1);

  // '--' to end parsing
  run = s.run("dummy", "--ething", "x", "--", "-p", "4000");
  run.read('"x" "3000" none ["-p","4000"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--", "--changed", "--changed");
  run.read('"x" "3000" none ["--changed","--changed"]\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "--");
  run.read('"x" "3000" none []\n');
  run.expectEnd();
  run.expectExit(0);

  // compact short options
  run = s.run("dummy", "--ething", "x", "-p4000", "--changed");
  run.read('"x" 4000 true []\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UD", "--changed");
  run.read('"x" "3000" true []\nurl\n\delete\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UDp4000", "--changed");
  run.read('"x" 4000 true []\nurl\ndelete\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UDp4000", "--changed");
  run.read('"x" 4000 true []\nurl\ndelete\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UDp4000");
  run.read('"x" 4000 none []\nurl\ndelete\n');
  run.expectEnd();
  run.expectExit(0);

  run = s.run("dummy", "--ething", "x", "-UDkp4000", "--changed");
  run.matchErr("-k: unknown option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-UDp4000k", "--changed");
  run.matchErr("--port (-p) must be a number");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-UD4000k", "--changed");
  run.matchErr("-4: unknown option");
  run.expectExit(1);

  run = s.run("dummy", "--ething", "x", "-UDDp4000", "--changed");
  run.matchErr("one --delete (-D) option");
  run.expectExit(1);

  // requiring an app dir
  run = s.run("list");
  run.matchErr("not in a Meteor project");
  run.matchErr("meteor create"); // new user help
  run.expectExit(1);

  s.createApp('myapp', 'standard-app');
  s.cd('myapp', function () {
    run = s.run("list");
    run.expectExit(0);
  });
});

selftest.define("command-like options", function () {
  var s = new Sandbox;
  var run;

  run = s.run("--version");
  if (release.current.isCheckout()) {
    run.matchErr("Unreleased");
    run.expectExit(1);
  } else {
    run.read(release.current.getDisplayName() + "\n");
    run.expectEnd();
    run.expectExit(0);
  }

  run = s.run("--arch");
  run.read(archinfo.host() + "\n");
  run.expectEnd();
  run.expectExit(0);
});
<|endoftext|>


<commit_before>/* eslint-env browser */

'use strict';

var _ = require('object-iterators');

var propertyNames = [
    'index',
    'name',
    'header',
    'calculator',
    'type'
];

/** @summary Create a new `Column` object.
 * @constructor
 * @param behavior
 * @param {number|object} indexOrOptions - If a number, shorthand for `options.index`.
 *
 * For positive values of `options.index`, see {@link Column#initialize|initialize}. Note that for new columns, you must supply either `index` or `name`. If you supply both, they must match the definitiion in data model's `fields` list.
 *
 * Negative values are special cases:
 * `index` | Meaning
 * :-----: | --------
 *    -1   | Row header column
 *    -2   | Tree (drill-down) column
 *
 *
 */
function Column(behavior, indexOrOptions) {
    this.behavior = behavior;
    this.dataModel = behavior.dataModel;
    this.cellProperties = [];

    var options = typeof indexOrOptions === 'object' ? indexOrOptions : { index: indexOrOptions },
        index = options.index;

    switch (index) {

        case -1:
            this.index = index;
            this.name = '';
            this.header = '';
            break;

        case -2:
            this.index = index;
            this.name = 'tree';
            this.header = 'Tree';
            break;

        default:
            if (index < 0) {
                throw '`index` out of range';
            } else {
                this.set(options);
            }

    }
}

Column.prototype = {
    constructor: Column.prototype.constructor,

    /** @summary Set or reset the properties of a column object.
     * @desc When (re)setting a column object, the object must end up with fully defined `index` and `name` properties. If one is missing it will be derived from the data model's `fields` list.
     * Note: These properties of the column object should not be confused with the members of the columnProperties object which supports grid render and is something else entirely.
     * @param {object} options - Required because you must supply at least `index` or `name`.
     * @param {object} [options.index]
     * @param {object} [options.name]
     * @param {object} [options.header]
     * @param {object} [options.type]
     */
    set: function(options) {
        var fields = this.dataModel.getFields();
        var column = this;
        propertyNames.forEach(function(option) {
            if (option in options) {
                column[option] = options[option];
            }

            if (option === 'name') {
                if (column.name === undefined) {
                    column.name = fields[column.index];
                } else if (column.index === undefined) {
                    column.index = fields.indexOf(column.name);
                }

                if (column.index === undefined || column.name === undefined) {
                    throw 'Expected column name or index.';
                } else if (fields[column.index] !== column.name) {
                    throw 'Expected to find `column.name` in position `column.index` in data model\'s fields list.';
                }
            }
        });
    },

    /**
     * @summary Get or set the text of the column's header.
     * @desc The _header_ is the label at the top of the column.
     *
     * Setting the value here updates the header in both this column object as well as the `fields` (aka, header) array in the underlying data source.
     *
     * The new text will appear on the next repaint.
     * @type {string}
     */
    set header(headerText) {
        this.dataModel.getHeaders()[this.index] = this._header = headerText;
    },
    get header() {
        return this._header;
    },

    set calculator(calculator) {
        this.dataModel.getCalculators()[this.index] = this._calculator = calculator;
    },
    get calculator() {
        return this._calculator;
    },

    getUnfilteredValue: function(y) {
        return this.dataModel.getUnfilteredValue(this.index, y);
    },

    getValue: function(y) {
        return this.dataModel.getValue(this.index, y);
    },

    setValue: function(y, value) {
        return this.dataModel.setValue(this.index, y, value);
    },

    getWidth: function() {
        var properties = this.getProperties();
        return properties && properties.width || this.behavior.resolveProperty('defaultColumnWidth');
    },

    setWidth: function(width) {
        this.getProperties().width = Math.max(5, width);
    },

    getCellRenderer: function(config, x, y) {
        config.untranslatedX = x;
        config.y = y;

        config.x = this.index;
        config.normalizedY = y - this.behavior.getHeaderRowCount();

        // Legacy config.x and config.y were confusing because the x was translated while the y was not.
        // These have been deprecated and are currently implemented as getters with deprecation warnings.
        // Rather than defining these getters here on every cell render, they are defined once in Behavior.prototype.getDefaultState.
        //config.x = this.index;
        //config.y = y;

        var declaredRendererName =
            this.getCellProperties(y).renderer ||
            this.getProperties().renderer;

        var renderer = this.dataModel.getCell(config, declaredRendererName);
        renderer.config = config;
        return renderer;
    },

    getCellProperties: function(y) {
        y = this.dataModel.getDataIndex(y);
        return this.cellProperties[y] || {};
    },

    setCellProperties: function(y, value) {
        this.cellProperties[y] = value;
    },

    clearAllCellProperties: function() {
        this.cellProperties.length = 0;
    },

    checkColumnAutosizing: function(force) {
        var properties = this.getProperties();
        var a, b, d;
        if (properties) {
            a = properties.width;
            b = properties.preferredWidth || a;
            d = properties.columnAutosized && !force;
            if (a !== b || !d) {
                properties.width = !d ? b : Math.max(a, b);
                properties.columnAutosized = !isNaN(properties.width);
            }
        }
    },

    getCellType: function(y) {
        var value = this.getValue(y);
        var type = this.typeOf(value);
        return type;
    },

    getType: function() {
        var props = this.getProperties();
        var type = props.type;
        if (!type) {
            type = this.computeColumnType();
            if (type !== 'unknown') {
                props.type = type;
            }
        }
        return type;
    },

    computeColumnType: function() {
        var headerRowCount = this.behavior.getHeaderRowCount();
        var height = this.behavior.getRowCount();
        var value = this.getValue(headerRowCount);
        var eachType = this.typeOf(value);
        if (!eachType) {
            return 'unknown';
        }
        var type = this.typeOf(value);
        var isNumber = ((typeof value) === 'number');
        for (var y = headerRowCount; y < height; y++) {
            value = this.getValue(y);
            eachType = this.typeOf(value);
            if (type !== eachType) {
                if (isNumber && (typeof value === 'number')) {
                    type = 'float';
                } else {
                    return 'mixed';
                }
            }
        }
        return type;
    },

    typeOf: function(something) {
        if (something == null) {
            return null;
        }
        var typeOf = typeof something;
        switch (typeOf) {
            case 'object':
                return something.constructor.name.toLowerCase();
            case 'number':
                return parseInt(something) === something ? 'int' : 'float';
            default:
                return typeOf;
        }
    },

    getProperties: function() {
        return this.behavior.getPrivateState().columnProperties[this.index];
    },

    setProperties: function(properties) {
        var current = this.getProperties();
        this.clearObjectProperties(current, false);
        _(current).extendOwn(properties);
    },

    toggleSort: function(keys) {
        this.dataModel.toggleSort(this.index, keys);
    },

    unSort: function(deferred) {
        this.dataModel.unSortColumn(this.index, deferred);
    },

    /**
     * This method determines the proposed cell editor name from the render properties. The algorithm is:
     * 1. `editor` render property (cell editor name)
     * 2. `format` render property (localizer name)
     * 3. `type` column property (type name)
     *
     * Note that "render property" means in each case the first defined property found on the cell, column, or grid.
     *
     * @param {number} y - The original untranslated row index.
     * @param {object} options - Will be decorated with `format` and `column`.
     * @param {Point} options.editPoint
     * @returns {undefined|CellEditor} Falsy value means either no declared cell editor _or_ instantiation aborted by falsy return return from fireRequestCellEdit.
     */
    getCellEditorAt: function(y, options) {
        var cellEditor,
            cellProps = this.getCellProperties(y),
            columnProps = this.getProperties(),
            editorName = cellProps.editor || columnProps.editor;

        options.format = cellProps.format || columnProps.format;

        cellEditor = this.dataModel.getCellEditorAt(this.index, y, editorName, options);

        if (cellEditor && !cellEditor.grid) {
            // cell editor returned but not fully instantiated (aborted by falsy return from fireRequestCellEdit)
            cellEditor = undefined;
        }

        return cellEditor;
    },

    getFormatter: function() {
        var localizerName = this.getProperties().format;
        return this.behavior.grid.localization.get(localizerName).format;
    }
};

module.exports = Column;
<commit_msg>update filter schema on set column.calculator<commit_after>/* eslint-env browser */

'use strict';

var _ = require('object-iterators');

var propertyNames = [
    'index',
    'name',
    'header',
    'calculator',
    'type'
];

/** @summary Create a new `Column` object.
 * @constructor
 * @param behavior
 * @param {number|object} indexOrOptions - If a number, shorthand for `options.index`.
 *
 * For positive values of `options.index`, see {@link Column#initialize|initialize}. Note that for new columns, you must supply either `index` or `name`. If you supply both, they must match the definitiion in data model's `fields` list.
 *
 * Negative values are special cases:
 * `index` | Meaning
 * :-----: | --------
 *    -1   | Row header column
 *    -2   | Tree (drill-down) column
 *
 *
 */
function Column(behavior, indexOrOptions) {
    this.behavior = behavior;
    this.dataModel = behavior.dataModel;
    this.cellProperties = [];

    var options = typeof indexOrOptions === 'object' ? indexOrOptions : { index: indexOrOptions },
        index = options.index;

    switch (index) {

        case -1:
            this.index = index;
            this.name = '';
            this.header = '';
            break;

        case -2:
            this.index = index;
            this.name = 'tree';
            this.header = 'Tree';
            break;

        default:
            if (index < 0) {
                throw '`index` out of range';
            } else {
                this.set(options);
            }

    }
}

Column.prototype = {
    constructor: Column.prototype.constructor,

    /** @summary Set or reset the properties of a column object.
     * @desc When (re)setting a column object, the object must end up with fully defined `index` and `name` properties. If one is missing it will be derived from the data model's `fields` list.
     * Note: These properties of the column object should not be confused with the members of the columnProperties object which supports grid render and is something else entirely.
     * @param {object} options - Required because you must supply at least `index` or `name`.
     * @param {object} [options.index]
     * @param {object} [options.name]
     * @param {object} [options.header]
     * @param {object} [options.type]
     */
    set: function(options) {
        var fields = this.dataModel.getFields();
        var column = this;
        propertyNames.forEach(function(option) {
            if (option in options) {
                column[option] = options[option];
            }

            if (option === 'name') {
                if (column.name === undefined) {
                    column.name = fields[column.index];
                } else if (column.index === undefined) {
                    column.index = fields.indexOf(column.name);
                }

                if (column.index === undefined || column.name === undefined) {
                    throw 'Expected column name or index.';
                } else if (fields[column.index] !== column.name) {
                    throw 'Expected to find `column.name` in position `column.index` in data model\'s fields list.';
                }
            }
        });
    },

    /**
     * @summary Get or set the text of the column's header.
     * @desc The _header_ is the label at the top of the column.
     *
     * Setting the value here updates the header in both this column object as well as the `fields` (aka, header) array in the underlying data source.
     *
     * The new text will appear on the next repaint.
     * @type {string}
     */
    set header(headerText) {
        this.dataModel.getHeaders()[this.index] = this._header = headerText;
    },
    get header() {
        return this._header;
    },

    set calculator(calculator) {
        var name = this.name,
            filter = this.behavior.grid.getGlobalFilter();

        if (filter && filter.schema) {
            filter.schema.find(function(item) { return item.name === name; }).calculator = calculator;
        }

        this.dataModel.getCalculators()[this.index] = this._calculator = calculator;
    },
    get calculator() {
        return this._calculator;
    },

    getUnfilteredValue: function(y) {
        return this.dataModel.getUnfilteredValue(this.index, y);
    },

    getValue: function(y) {
        return this.dataModel.getValue(this.index, y);
    },

    setValue: function(y, value) {
        return this.dataModel.setValue(this.index, y, value);
    },

    getWidth: function() {
        var properties = this.getProperties();
        return properties && properties.width || this.behavior.resolveProperty('defaultColumnWidth');
    },

    setWidth: function(width) {
        this.getProperties().width = Math.max(5, width);
    },

    getCellRenderer: function(config, x, y) {
        config.untranslatedX = x;
        config.y = y;

        config.x = this.index;
        config.normalizedY = y - this.behavior.getHeaderRowCount();

        // Legacy config.x and config.y were confusing because the x was translated while the y was not.
        // These have been deprecated and are currently implemented as getters with deprecation warnings.
        // Rather than defining these getters here on every cell render, they are defined once in Behavior.prototype.getDefaultState.
        //config.x = this.index;
        //config.y = y;

        var declaredRendererName =
            this.getCellProperties(y).renderer ||
            this.getProperties().renderer;

        var renderer = this.dataModel.getCell(config, declaredRendererName);
        renderer.config = config;
        return renderer;
    },

    getCellProperties: function(y) {
        y = this.dataModel.getDataIndex(y);
        return this.cellProperties[y] || {};
    },

    setCellProperties: function(y, value) {
        this.cellProperties[y] = value;
    },

    clearAllCellProperties: function() {
        this.cellProperties.length = 0;
    },

    checkColumnAutosizing: function(force) {
        var properties = this.getProperties();
        var a, b, d;
        if (properties) {
            a = properties.width;
            b = properties.preferredWidth || a;
            d = properties.columnAutosized && !force;
            if (a !== b || !d) {
                properties.width = !d ? b : Math.max(a, b);
                properties.columnAutosized = !isNaN(properties.width);
            }
        }
    },

    getCellType: function(y) {
        var value = this.getValue(y);
        var type = this.typeOf(value);
        return type;
    },

    getType: function() {
        var props = this.getProperties();
        var type = props.type;
        if (!type) {
            type = this.computeColumnType();
            if (type !== 'unknown') {
                props.type = type;
            }
        }
        return type;
    },

    computeColumnType: function() {
        var headerRowCount = this.behavior.getHeaderRowCount();
        var height = this.behavior.getRowCount();
        var value = this.getValue(headerRowCount);
        var eachType = this.typeOf(value);
        if (!eachType) {
            return 'unknown';
        }
        var type = this.typeOf(value);
        var isNumber = ((typeof value) === 'number');
        for (var y = headerRowCount; y < height; y++) {
            value = this.getValue(y);
            eachType = this.typeOf(value);
            if (type !== eachType) {
                if (isNumber && (typeof value === 'number')) {
                    type = 'float';
                } else {
                    return 'mixed';
                }
            }
        }
        return type;
    },

    typeOf: function(something) {
        if (something == null) {
            return null;
        }
        var typeOf = typeof something;
        switch (typeOf) {
            case 'object':
                return something.constructor.name.toLowerCase();
            case 'number':
                return parseInt(something) === something ? 'int' : 'float';
            default:
                return typeOf;
        }
    },

    getProperties: function() {
        return this.behavior.getPrivateState().columnProperties[this.index];
    },

    setProperties: function(properties) {
        var current = this.getProperties();
        this.clearObjectProperties(current, false);
        _(current).extendOwn(properties);
    },

    toggleSort: function(keys) {
        this.dataModel.toggleSort(this.index, keys);
    },

    unSort: function(deferred) {
        this.dataModel.unSortColumn(this.index, deferred);
    },

    /**
     * This method determines the proposed cell editor name from the render properties. The algorithm is:
     * 1. `editor` render property (cell editor name)
     * 2. `format` render property (localizer name)
     * 3. `type` column property (type name)
     *
     * Note that "render property" means in each case the first defined property found on the cell, column, or grid.
     *
     * @param {number} y - The original untranslated row index.
     * @param {object} options - Will be decorated with `format` and `column`.
     * @param {Point} options.editPoint
     * @returns {undefined|CellEditor} Falsy value means either no declared cell editor _or_ instantiation aborted by falsy return return from fireRequestCellEdit.
     */
    getCellEditorAt: function(y, options) {
        var cellEditor,
            cellProps = this.getCellProperties(y),
            columnProps = this.getProperties(),
            editorName = cellProps.editor || columnProps.editor;

        options.format = cellProps.format || columnProps.format;

        cellEditor = this.dataModel.getCellEditorAt(this.index, y, editorName, options);

        if (cellEditor && !cellEditor.grid) {
            // cell editor returned but not fully instantiated (aborted by falsy return from fireRequestCellEdit)
            cellEditor = undefined;
        }

        return cellEditor;
    },

    getFormatter: function() {
        var localizerName = this.getProperties().format;
        return this.behavior.grid.localization.get(localizerName).format;
    }
};

module.exports = Column;
<|endoftext|>