<commit_before>/**
 * @namespace
 */
Subclass.Event = {};

/**
 * @class
 * @constructor
 * @description
 *
 * Instance of current class is an object of event which holds
 * information about its name, context and listeners, can manipulate
 * by listeners (add, get, remove) and perform registered listener
 * callbacks in order according to its priorities.
 *
 * @throws {Error}
 *      Throws error if:

 *      - was specified invalid event manager argument;

 *      - was missed event name or it's not a string.
 *
 * @param {string} eventName
 *      The name of the creating event
 *
 * @param {Object} [context={}]
 *      An any object which link on it will be held
 *      in "this" variable inside every registered
 *      listener of current event.
 */
Subclass.Event.Event = (function()
{
    /**
     * @alias Subclass.Event.Event
     */
    function Event(eventName, context)
    {
        if (!eventName || typeof eventName != 'string') {
            Subclass.Error.create('InvalidArgument')
                .argument("the event name", false)
                .received(eventName)
                .expected('a string')
                .apply()
            ;
        }
        if (!context) {
            context = {};
        }

        /**
         * Name of the event
         *
         * @type {string}
         * @private
         */
        this._name = eventName;

        /**
         * Context of the event listeners
         * (i.e. what will inside "this" variable inside of event listeners)
         *
         * @type {Object}
         * @private
         */
        this._context = context;

        /**
         * Array of event listeners
         *
         * @type {Array.<Subclass.Event.EventListener>}
         * @private
         */
        this._listeners = [];

        /**
         * Default event listener which invokes in the least
         *
         * @type {null|function}
         * @private
         */
        this._defaultListener = null;
    }

    Event.prototype = {

        /**
         * Returns name of event
         *
         * @method getName
         * @memberOf Subclass.Event.Event.prototype
         *
         * @returns {string}
         */
        getName: function()
        {
            return this._name;
        },

        /**
         * Returns event context for event listeners
         *
         * @method getContext
         * @memberOf Subclass.Event.Event.prototype
         *
         * @returns {Object}
         */
        getContext: function()
        {
            return this._context;
        },

        /**
         * Sets default event listener which will be invoked in the least
         *
         * @param {function} listener
         * @returns {Subclass.Event.Event}
         */
        setDefaultListener: function(listener)
        {
            if (typeof listener != 'function') {
                Subclass.Error.create('InvalidArgument')
                    .argument('the default event listener', false)
                    .expected('a function')
                    .received(listener)
                    .apply()
                ;
            }
            this._defaultListener = listener;

            return this;
        },

        /**
         * Returns default event listener
         *
         * @returns {null|Function}
         */
        getDefaultListener: function()
        {
            return this._defaultListener;
        },

        /**
         * Registers new listener to the event.

         * Creates instance of {@link Subclass.Event.EventListener}
         *
         * @method addListener
         * @memberOf Subclass.Event.Event.prototype
         *
         * @param {(number|Function)} [priority]
         *      Event listener invoke priority
         *
         * @param {Function} callback
         *      Event listener callback function which will be invoked when event triggers.
         *
         * @returns {Subclass.Event.Event}
         */
        addListener: function(priority, callback)
        {
            var listener = Subclass.Tools.createClassInstance(Subclass.Event.EventListener,
                priority,
                callback
            );
            this._listeners.push(listener);

            return this;
        },

        /**
         * Removes specified event listener by its callback function
         *
         * @method removeListener
         * @memberOf Subclass.Event.Event.prototype
         *
         * @param {Function} callback
         *      Function which was early used in registering event listener
         *
         * @returns {Subclass.Event.Event}
         */
        removeListener: function(callback)
        {
            var listener = this.getListenerByCallback(callback);

            if (!listener) {
                return this;
            }

            var listeners = this.getListeners();
            var listenerIndex = listeners.indexOf(listener);

            if (listenerIndex >= 0) {
                listeners.splice(listenerIndex, 1);
            }

            return this;
        },

        /**
         * Returns all registered event listeners
         *
         * @method getListeners
         * @memberOf Subclass.Event.Event.prototype
         *
         * @returns {Object.<Subclass.Event.EventListener>}
         */
        getListeners: function()
        {
            return this._listeners;
        },

        /**
         * Returns event listener by specified callback function
         *
         * @method getListenerByCallback
         * @memberOf Subclass.Event.Event.prototype
         *
         * @throws {Error}
         *      Throws error if was specified not a function callback
         *
         * @param {Function} callback
         *      Function which was early used in registering event listener
         *
         * @returns {(Subclass.Event.EventListener|null)}
         */
        getListenerByCallback: function(callback)
        {
            if (!callback || typeof callback != 'Function') {
                Subclass.Error.create('InvalidArgument')
                    .argument("the callback", false)
                    .received(callback)
                    .expected('a function')
                    .apply()
                ;
            }
            var listeners = this.getListeners();

            for (var i = 0; i < listeners.length; i++) {
                if (listeners[i].getCallback() == callback) {
                    return listeners[i];
                }
            }
            return null;
        },

        /**
         * Checks whether current event contains any listeners
         *
         * @method hasListeners
         * @memberOf Subclass.Event.Event.prototype
         *
         * @return {boolean}
         */
        hasListeners: function()
        {
            return !!this.getListeners().length;
        },

        /**
         * Invokes all registered event listeners at order of descending its priorities.
         * The greater priority - the earlier event listener will invoked.

         *
         * Will be invoked all listener callback functions from all modules (from current
         * module and its plug-ins) of the event with name of current event.

         *
         * Each event listener callback function will receive as arguments all arguments from current method call.
         * If listener callback function returns false then it will bring to stop propagation of event.
         *
         * @method trigger
         * @memberOf Subclass.Event.Event.prototype
         *
         * @param [arguments]
         *      Any number of any needed arguments
         *
         * @returns {Subclass.Event.EventData}
         */
        trigger: function()
        {
            var listeners = this.getListeners();

            return this._processTrigger(listeners, arguments);
        },

        /**
         * Invokes event listeners
         *
         * @method _processTriggger
         *
         * @param {Array.<Subclass.Event.EventListener>} listeners
         *      An array of event listeners
         *
         * @param {Array} listenerArgs
         *      Arguments which will be transferred to each even listener callback function
         *
         * @returns {Subclass.Event.EventData}
         * @private
         * @ignore
         */
        _processTrigger: function(listeners, listenerArgs)
        {
            var uniqueFieldName = '_passed_' + Math.round(new Date().getTime() * Math.random());
            var eventData = Subclass.Tools.createClassInstance(Subclass.Event.EventData, this, this.getContext());
            var context = this.getContext();
            var args = [eventData];
            var priorities = [];

            for (var k = 0; k < listenerArgs.length; k++) {
                args.push(listenerArgs[k]);
            }

            // Preparing event listeners

            for (var i = 0; i < listeners.length; i++) {
                var listener = listeners[i];

                priorities.push(listener.getPriority());
                listener[uniqueFieldName] = false;
            }

            // Sorting priorities in descending order

            priorities = priorities.sort(function(a, b) {
                a = parseInt(a);
                b = parseInt(b);

                if (a > b) return -1;
                if (a < b) return 1;

                return 0;
            });

            // Invoking event listener callback function in order with priorities

            loop: for (i = 0; i < priorities.length; i++) {
                for (var j = 0; j < listeners.length; j++) {
                    listener = listeners[j];

                    if (!listener[uniqueFieldName] && listener.getPriority() == priorities[i]) {
                        listener[uniqueFieldName] = true;

                        var result = listener.getCallback().apply(context, args);
                        eventData.addResult(result);

                        if (eventData.isPropagationStopped()) {
                            break loop;
                        }
                    }
                }
            }

            // Invoking default event listener

            if (
                this.getDefaultListener()
                && !eventData.isPropagationStopped()
                && !eventData.isDefaultPrevented()
            ) {
                this.getDefaultListener().apply(context, args);
            }

            // Removing helper fields from listeners

            for (i = 0; i < listeners.length; i++) {
                listener = listeners[i];
                delete listener[uniqueFieldName];
            }

            return eventData;
        }
    };

    return Event;

})();<commit_msg>Added new method to Event class<commit_after>/**
 * @namespace
 */
Subclass.Event = {};

/**
 * @class
 * @constructor
 * @description
 *
 * Instance of current class is an object of event which holds
 * information about its name, context and listeners, can manipulate
 * by listeners (add, get, remove) and perform registered listener
 * callbacks in order according to its priorities.
 *
 * @throws {Error}
 *      Throws error if:

 *      - was specified invalid event manager argument;

 *      - was missed event name or it's not a string.
 *
 * @param {string} eventName
 *      The name of the creating event
 *
 * @param {Object} [context={}]
 *      An any object which link on it will be held
 *      in "this" variable inside every registered
 *      listener of current event.
 */
Subclass.Event.Event = (function()
{
    /**
     * @alias Subclass.Event.Event
     */
    function Event(eventName, context)
    {
        if (!eventName || typeof eventName != 'string') {
            Subclass.Error.create('InvalidArgument')
                .argument("the event name", false)
                .received(eventName)
                .expected('a string')
                .apply()
            ;
        }
        if (!context) {
            context = {};
        }

        /**
         * Name of the event
         *
         * @type {string}
         * @private
         */
        this._name = eventName;

        /**
         * Context of the event listeners
         * (i.e. what will inside "this" variable inside of event listeners)
         *
         * @type {Object}
         * @private
         */
        this._context = context;

        /**
         * Array of event listeners
         *
         * @type {Array.<Subclass.Event.EventListener>}
         * @private
         */
        this._listeners = [];

        /**
         * Default event listener which invokes in the least
         *
         * @type {null|function}
         * @private
         */
        this._defaultListener = null;
    }

    Event.prototype = {

        /**
         * Returns name of event
         *
         * @method getName
         * @memberOf Subclass.Event.Event.prototype
         *
         * @returns {string}
         */
        getName: function()
        {
            return this._name;
        },

        /**
         * Returns event context for event listeners
         *
         * @method getContext
         * @memberOf Subclass.Event.Event.prototype
         *
         * @returns {Object}
         */
        getContext: function()
        {
            return this._context;
        },

        /**
         * Sets default event listener which will be invoked in the least
         *
         * @param {function} listener
         * @returns {Subclass.Event.Event}
         */
        setDefaultListener: function(listener)
        {
            if (typeof listener != 'function') {
                Subclass.Error.create('InvalidArgument')
                    .argument('the default event listener', false)
                    .expected('a function')
                    .received(listener)
                    .apply()
                ;
            }
            this._defaultListener = listener;

            return this;
        },

        /**
         * Returns default event listener
         *
         * @returns {null|Function}
         */
        getDefaultListener: function()
        {
            return this._defaultListener;
        },

        /**
         * Registers new listener to the event.

         * Creates instance of {@link Subclass.Event.EventListener}
         *
         * @method addListener
         * @memberOf Subclass.Event.Event.prototype
         *
         * @param {(number|Function)} [priority]
         *      Event listener invoke priority
         *
         * @param {Function} callback
         *      Event listener callback function which will be invoked when event triggers.
         *
         * @returns {Subclass.Event.Event}
         */
        addListener: function(priority, callback)
        {
            var listener = Subclass.Tools.createClassInstance(Subclass.Event.EventListener,
                priority,
                callback
            );
            this._listeners.push(listener);

            return this;
        },

        /**
         * Removes specified event listener by its callback function
         *
         * @method removeListener
         * @memberOf Subclass.Event.Event.prototype
         *
         * @param {Function} callback
         *      Function which was early used in registering event listener
         *
         * @returns {Subclass.Event.Event}
         */
        removeListener: function(callback)
        {
            var listener = this.getListenerByCallback(callback);

            if (!listener) {
                return this;
            }

            var listeners = this.getListeners();
            var listenerIndex = listeners.indexOf(listener);

            if (listenerIndex >= 0) {
                listeners.splice(listenerIndex, 1);
            }

            return this;
        },

        /**
         * Removes all listeners
         */
        removeListeners: function()
        {
            this._listeners = [];
        },

        /**
         * Returns all registered event listeners
         *
         * @method getListeners
         * @memberOf Subclass.Event.Event.prototype
         *
         * @returns {Object.<Subclass.Event.EventListener>}
         */
        getListeners: function()
        {
            return this._listeners;
        },

        /**
         * Returns event listener by specified callback function
         *
         * @method getListenerByCallback
         * @memberOf Subclass.Event.Event.prototype
         *
         * @throws {Error}
         *      Throws error if was specified not a function callback
         *
         * @param {Function} callback
         *      Function which was early used in registering event listener
         *
         * @returns {(Subclass.Event.EventListener|null)}
         */
        getListenerByCallback: function(callback)
        {
            if (!callback || typeof callback != 'Function') {
                Subclass.Error.create('InvalidArgument')
                    .argument("the callback", false)
                    .received(callback)
                    .expected('a function')
                    .apply()
                ;
            }
            var listeners = this.getListeners();

            for (var i = 0; i < listeners.length; i++) {
                if (listeners[i].getCallback() == callback) {
                    return listeners[i];
                }
            }
            return null;
        },

        /**
         * Checks whether current event contains any listeners
         *
         * @method hasListeners
         * @memberOf Subclass.Event.Event.prototype
         *
         * @return {boolean}
         */
        hasListeners: function()
        {
            return !!this.getListeners().length;
        },

        /**
         * Invokes all registered event listeners at order of descending its priorities.
         * The greater priority - the earlier event listener will invoked.

         *
         * Will be invoked all listener callback functions from all modules (from current
         * module and its plug-ins) of the event with name of current event.

         *
         * Each event listener callback function will receive as arguments all arguments from current method call.
         * If listener callback function returns false then it will bring to stop propagation of event.
         *
         * @method trigger
         * @memberOf Subclass.Event.Event.prototype
         *
         * @param [arguments]
         *      Any number of any needed arguments
         *
         * @returns {Subclass.Event.EventData}
         */
        trigger: function()
        {
            var listeners = this.getListeners();

            return this._processTrigger(listeners, arguments);
        },

        /**
         * Invokes event listeners
         *
         * @method _processTriggger
         *
         * @param {Array.<Subclass.Event.EventListener>} listeners
         *      An array of event listeners
         *
         * @param {Array} listenerArgs
         *      Arguments which will be transferred to each even listener callback function
         *
         * @returns {Subclass.Event.EventData}
         * @private
         * @ignore
         */
        _processTrigger: function(listeners, listenerArgs)
        {
            var uniqueFieldName = '_passed_' + Math.round(new Date().getTime() * Math.random());
            var eventData = Subclass.Tools.createClassInstance(Subclass.Event.EventData, this, this.getContext());
            var context = this.getContext();
            var args = [eventData];
            var priorities = [];

            for (var k = 0; k < listenerArgs.length; k++) {
                args.push(listenerArgs[k]);
            }

            // Preparing event listeners

            for (var i = 0; i < listeners.length; i++) {
                var listener = listeners[i];

                priorities.push(listener.getPriority());
                listener[uniqueFieldName] = false;
            }

            // Sorting priorities in descending order

            priorities = priorities.sort(function(a, b) {
                a = parseInt(a);
                b = parseInt(b);

                if (a > b) return -1;
                if (a < b) return 1;

                return 0;
            });

            // Invoking event listener callback function in order with priorities

            loop: for (i = 0; i < priorities.length; i++) {
                for (var j = 0; j < listeners.length; j++) {
                    listener = listeners[j];

                    if (!listener[uniqueFieldName] && listener.getPriority() == priorities[i]) {
                        listener[uniqueFieldName] = true;

                        var result = listener.getCallback().apply(context, args);
                        eventData.addResult(result);

                        if (eventData.isPropagationStopped()) {
                            break loop;
                        }
                    }
                }
            }

            // Invoking default event listener

            if (
                this.getDefaultListener()
                && !eventData.isPropagationStopped()
                && !eventData.isDefaultPrevented()
            ) {
                this.getDefaultListener().apply(context, args);
            }

            // Removing helper fields from listeners

            for (i = 0; i < listeners.length; i++) {
                listener = listeners[i];
                delete listener[uniqueFieldName];
            }

            return eventData;
        }
    };

    return Event;

})();<|endoftext|>


<commit_before>(function() {
  'use strict';

  window.Ractive.controller('pl-dropdown-title', function(component, data, el, config, done) {

    data.selected = {
      name: '',
      index: 0
    };
    data.opened = false;

    if (data.titles) {
      $.each(data.titles, function(i) {
        if (this.selected) {
          data.selected.index = i;
          data.selected.name = this.name;
        }
      });
    }

    var dropdownTitle = component({
          plName: 'pl-dropdown-title',
          data: data,
          select: _select
        }),
        _$el = {
          body: $('body'),
          dropdownTitle: $(dropdownTitle.el)
        };

    function _select(indexOrName, fireFunc, callback) {
      if (!dropdownTitle) {
        return;
      }

      fireFunc = typeof fireFunc == 'undefined' ? true : fireFunc;

      var titles = dropdownTitle.get('titles'),
          titleToSelect = null;

      $.each(titles, function(i, title) {
        if (typeof indexOrName == 'string' && indexOrName == title.name) {
          indexOrName = i;
        }

        title.selected = i === indexOrName;

        if (i === indexOrName) {
          titleToSelect = title;
        }
      });

      dropdownTitle.set('titles', titles);

      dropdownTitle.set('selected.index', indexOrName);
      dropdownTitle.set('selected.name', data.titles[indexOrName].name);
      dropdownTitle.set('noAnimation', true);

      setTimeout(function() {
        _close(function() {
          if (fireFunc) {
            titleToSelect.select();
            dropdownTitle.fire('titleSelected', {
              title: titleToSelect
            });
          }

          if (callback) {
            callback();
          }
        });
      });
    }

    function _close(callback) {
      dropdownTitle.fire('close', {
        height: _$el.dropdownTitle.find('.pl-dropdown-title h2').height()
      });

      dropdownTitle.set('noAnimation', false);
      dropdownTitle.set('noCaret', true);
      dropdownTitle.set('opened', false);

      setTimeout(function() {
        dropdownTitle.set('noCaret', false);

        if (callback) {
          callback();
        }
      }, 550);
    }

    function _click() {
      _close();
    }

    dropdownTitle.fireSelected = function() {
      var selected = dropdownTitle.get('selected');

      if (selected) {
        dropdownTitle.fire('titleSelected', {
          title: dropdownTitle.get('titles')[selected.index]
        });
      }
    };

    dropdownTitle.toggle = function() {
      if (data.titles.length < 2) {
        return;
      }

      if (dropdownTitle.get('opened')) {
        _close();
      }
      else {
        dropdownTitle.fire('open', {
          height: _$el.dropdownTitle.find('.pl-dropdown-title h2').height() * (data.titles.length + 1)
        });
        dropdownTitle.set('opened', true);
      }
    };

    dropdownTitle.on('mainClick', function(event) {
      event.original.stopPropagation();
    });

    dropdownTitle.on('toggle', function(event) {
      dropdownTitle.toggle();
      event.original.stopPropagation();
    });

    dropdownTitle.selectApp = function(name, fireFunc, callback) {
      _select(name, fireFunc, callback);

      return dropdownTitle;
    };

    dropdownTitle.addTitle = function(title, indexOrPosition) {
      var titles = dropdownTitle.get('titles');

      indexOrPosition = typeof indexOrPosition == 'undefined' ? titles.length : indexOrPosition;

      if (typeof indexOrPosition == 'string') {
        indexOrPosition = indexOrPosition == 'top' ? 0 : titles.length;
      }

      titles.splice(indexOrPosition, 0, title);

      dropdownTitle.set('titles', titles);

      return dropdownTitle;
    };

    dropdownTitle.removeTitle = function(name) {
      var titles = dropdownTitle.get('titles');

      for (var i = 0; i < titles.length; i++) {
        if (titles[i].name == name) {
          titles.splice(i, 1);
          break;
        }
      }

      dropdownTitle.set('titles', titles);

      return dropdownTitle;
    };

    dropdownTitle.on('teardown', function() {
      _$el.body.unbind('click', _click);
    });

    _$el.body.click(_click);

    done();
  });

})();
<commit_msg>refactor(dropdown-title): rename dropdownTitle to DropdownTitle<commit_after>(function() {
  'use strict';

  window.Ractive.controller('pl-dropdown-title', function(component, data, el, config, done) {

    data.selected = {
      name: '',
      index: 0
    };
    data.opened = false;

    if (data.titles) {
      $.each(data.titles, function(i) {
        if (this.selected) {
          data.selected.index = i;
          data.selected.name = this.name;
        }
      });
    }

    var DropdownTitle = component({
          plName: 'pl-dropdown-title',
          data: data,
          select: _select
        }),
        _$el = {
          body: $('body'),
          dropdownTitle: $(DropdownTitle.el)
        };

    function _select(indexOrName, fireFunc, callback) {
      if (!DropdownTitle) {
        return;
      }

      fireFunc = typeof fireFunc == 'undefined' ? true : fireFunc;

      var titles = DropdownTitle.get('titles'),
          titleToSelect = null;

      $.each(titles, function(i, title) {
        if (typeof indexOrName == 'string' && indexOrName == title.name) {
          indexOrName = i;
        }

        title.selected = i === indexOrName;

        if (i === indexOrName) {
          titleToSelect = title;
        }
      });

      DropdownTitle.set('titles', titles);

      DropdownTitle.set('selected.index', indexOrName);
      DropdownTitle.set('selected.name', data.titles[indexOrName].name);
      DropdownTitle.set('noAnimation', true);

      setTimeout(function() {
        _close(function() {
          if (fireFunc) {
            titleToSelect.select();
            DropdownTitle.fire('titleSelected', {
              title: titleToSelect
            });
          }

          if (callback) {
            callback();
          }
        });
      });
    }

    function _close(callback) {
      DropdownTitle.fire('close', {
        height: _$el.dropdownTitle.find('.pl-dropdown-title h2').height()
      });

      DropdownTitle.set('noAnimation', false);
      DropdownTitle.set('noCaret', true);
      DropdownTitle.set('opened', false);

      setTimeout(function() {
        DropdownTitle.set('noCaret', false);

        if (callback) {
          callback();
        }
      }, 550);
    }

    function _click() {
      _close();
    }

    DropdownTitle.fireSelected = function() {
      var selected = DropdownTitle.get('selected');

      if (selected) {
        DropdownTitle.fire('titleSelected', {
          title: DropdownTitle.get('titles')[selected.index]
        });
      }
    };

    DropdownTitle.toggle = function() {
      if (data.titles.length < 2) {
        return;
      }

      if (DropdownTitle.get('opened')) {
        _close();
      }
      else {
        DropdownTitle.fire('open', {
          height: _$el.dropdownTitle.find('.pl-dropdown-title h2').height() * (data.titles.length + 1)
        });
        DropdownTitle.set('opened', true);
      }
    };

    DropdownTitle.on('mainClick', function(event) {
      event.original.stopPropagation();
    });

    DropdownTitle.on('toggle', function(event) {
      DropdownTitle.toggle();
      event.original.stopPropagation();
    });

    DropdownTitle.selectApp = function(name, fireFunc, callback) {
      _select(name, fireFunc, callback);

      return DropdownTitle;
    };

    DropdownTitle.addTitle = function(title, indexOrPosition) {
      var titles = DropdownTitle.get('titles');

      indexOrPosition = typeof indexOrPosition == 'undefined' ? titles.length : indexOrPosition;

      if (typeof indexOrPosition == 'string') {
        indexOrPosition = indexOrPosition == 'top' ? 0 : titles.length;
      }

      titles.splice(indexOrPosition, 0, title);

      DropdownTitle.set('titles', titles);

      return DropdownTitle;
    };

    DropdownTitle.removeTitle = function(name) {
      var titles = DropdownTitle.get('titles');

      for (var i = 0; i < titles.length; i++) {
        if (titles[i].name == name) {
          titles.splice(i, 1);
          break;
        }
      }

      DropdownTitle.set('titles', titles);

      return DropdownTitle;
    };

    DropdownTitle.on('teardown', function() {
      _$el.body.unbind('click', _click);
    });

    _$el.body.click(_click);

    done();
  });

})();
<|endoftext|>


<commit_before>/* Copyright (c) 2010, Sage Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * File Manager
 */
define('Mobile/SalesLogix/FileManager', [
    'dojo/_base/lang',
    'dojo/_base/declare',
    'dojo/number',
    'dojo/has',
    'dojo/_base/sniff'
], function(
    lang,
    declare,
    dNumber,
    has,
    sniff
) {
    return declare('Mobile.SalesLogix.FileManager', null, {
        unableToUploadText: 'This browser does not support HTML5 File API.',
        unknownSizeText: 'unknown',
        unknownErrorText: 'Warning: An error occured and the file failed to upload.',
        largeFileWarningText: 'Warning: This request exceeds the size limit set by your administrator and failed to upload.',
        largeFileWarningTitle: 'Warning',
        percentCompleteText: 'Uploading, please wait...',
        fileUploadOptions: { maxFileSize: 4000000 },
        _store: false,
        _totalProgress: 0,
        _files: null,
        _fileCount: 0,
        _filesUploadedCount: 0,
        _isUploading: false,

        constructor: function() {
            this._files = [];
            this.fileUploadOptions.maxFileSize = App.maxUploadFileSize;
        },
        isHTML5Supported:function(){
            var results = has('html5-file-api');
            return results;
        },
        isFileSizeAllowed: function(files) {
            var len = 0, maxfileSize, title, msg;
            maxfileSize = this.fileUploadOptions.maxFileSize;
            title = this.largeFileWarningTitle;
            msg = this.largeFileWarningText;

            for (var i = 0; i < files.length; i++) {
                if (files[i].size === 0) {
                    // do nothing.
                } else {
                    len += files[i].size || files[i].blob.length;
                }
            }

            if (len > (maxfileSize)) {
                return false;
            }

            return true;
        },
        uploadFile: function(file, url, progress, complete, error, scope, asPut) {
            this.uploadFileHTML5(file, url, progress, complete, error, scope, asPut);
        },
        uploadFileHTML5: function(file, url, progress, complete, error, scope, asPut) {
            if (!this.isFileSizeAllowed([file])) {
                this._onUnableToUploadError(this.largeFileWarningText, error);
                return;
            }
            if (this.isHTML5Supported()) {
                this._uploadFileHTML5_asBinary(file, url, progress, complete, error, scope, asPut);
            } else {
                this._onUnableToUploadError(this.unableToUploadText, error);
            }
        },
        _uploadFileHTML5_asBinary: function(file, url, progress, complete, error, scope, asPut) {
            if (!url) {
                //assume Attachment SData url
                url = 'slxdata.ashx/slx/system/-/attachments/file';// TODO: Remove this assumption from SDK
            }

            var request = new XMLHttpRequest(), service = App.getService(), reader;
            request.open((asPut) ? 'PUT' : 'POST', url);
            request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

            if (service) {
                request.setRequestHeader('Authorization', service.createBasicAuthToken());
                request.setRequestHeader('X-Authorization', service.createBasicAuthToken());
                request.setRequestHeader('X-Authorization-Mode', 'no-challenge');
            }

            reader = new FileReader();
            reader.onload = lang.hitch(this, function(evt) {
                var binary, boundary, dashdash, crlf, bb, unknownErrorText;
                unknownErrorText = this.unknownErrorText;
                bb = [];
                binary = evt.target.result;
                boundary = "---------------------------" + (new Date()).getTime();
                dashdash = '--';
                crlf = '\r\n';

                bb.push(dashdash + boundary + crlf);
                bb.push('Content-Disposition: attachment; ');
                bb.push('name="file_"; ');
                bb.push('filename*="' + encodeURI(file.name) + '" ');
                bb.push(crlf);
                bb.push('Content-Type: ' + file.type);
                bb.push(crlf + crlf);
                bb.push(binary);
                bb.push(crlf);
                bb.push(dashdash + boundary + dashdash + crlf);

                if (complete) {
                    request.onreadystatechange = function() {
                        if (request.readyState === 4) {
                            if (Math.floor(request.status / 100) !== 2) {
                                if (error) {
                                    error.call(scope || this, unknownErrorText);
                                    console.warn(unknownErrorText + " "+ request.responseText);
                                }
                            } else {
                                complete.call(scope || this, request);
                            }
                        }
                    };
                }

                if (progress) {
                    request.upload.addEventListener('progress', function(e) {
                        progress.call(scope || this, e);
                    });
                }
                request.setRequestHeader('Content-Type', 'multipart/attachment; boundary=' + boundary);
                request.send(new Blob(bb));
            });

            reader.readAsArrayBuffer(file);
        },
        _onUnableToUploadError: function(msg, onError) {
            if (!msg) {
                msg = this.unableToUploadText
            }
            if (onError) {
                onError([msg])
            } else {
                console.warn([msg]);
            }
        },
        formatFileSize: function(size) {
            size = parseInt(size, 10);
            if (size === 0) {
                return '0 KB';
            }
            if (!size || size < 0) {
                return this.unknownSizeText;
            }
            if (size < 1024) {
                return '1 KB';
            }
            return dNumber.format(Math.round(size / 1024)) + ' KB';
        },
        getFile: function(fileUrl, responseType , onSuccess) {
            var request, service, self;

            request = new XMLHttpRequest();
            service = App.getService();
            self = this;

            request.open("GET", fileUrl, true);

            if (responseType) {
                request.responseType = responseType;
            }
            request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

            if (service) {
                request.setRequestHeader('Authorization', service.createBasicAuthToken());
                request.setRequestHeader('X-Authorization', service.createBasicAuthToken());
                request.setRequestHeader('X-Authorization-Mode', 'no-challenge');
            }
            request.addEventListener("load", function() {
                var data, contentType, contentInfo, responseInfo, fileName;

                data = this.response;
                contentType = this.getResponseHeader("Content-Type");
                contentInfo = this.getResponseHeader("Content-Disposition");
                responseInfo = {};
                fileName = contentInfo.split('=')[1];

                responseInfo = {
                    request: this,
                    responseType: responseType,
                    response: this.response,
                    contentType: contentType,
                    fileName: fileName
                };
                if (onSuccess) {
                    onSuccess(responseInfo);
                }
            }, false);
            request.send(null);
        }
    });
});
<commit_msg>[13092074] Fix android upload issue using BlobReader instead of Blob.<commit_after>/* Copyright (c) 2010, Sage Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * File Manager
 */
define('Mobile/SalesLogix/FileManager', [
    'dojo/_base/lang',
    'dojo/_base/declare',
    'dojo/number',
    'dojo/has',
    'dojo/_base/sniff'
], function(
    lang,
    declare,
    dNumber,
    has,
    sniff
) {
    return declare('Mobile.SalesLogix.FileManager', null, {
        unableToUploadText: 'This browser does not support HTML5 File API.',
        unknownSizeText: 'unknown',
        unknownErrorText: 'Warning: An error occured and the file failed to upload.',
        largeFileWarningText: 'Warning: This request exceeds the size limit set by your administrator and failed to upload.',
        largeFileWarningTitle: 'Warning',
        percentCompleteText: 'Uploading, please wait...',
        fileUploadOptions: { maxFileSize: 4000000 },
        _store: false,
        _totalProgress: 0,
        _files: null,
        _fileCount: 0,
        _filesUploadedCount: 0,
        _isUploading: false,

        constructor: function() {
            this._files = [];
            this.fileUploadOptions.maxFileSize = App.maxUploadFileSize;
        },
        isHTML5Supported:function(){
            var results = has('html5-file-api');
            return results;
        },
        isFileSizeAllowed: function(files) {
            var len = 0, maxfileSize, title, msg;
            maxfileSize = this.fileUploadOptions.maxFileSize;
            title = this.largeFileWarningTitle;
            msg = this.largeFileWarningText;

            for (var i = 0; i < files.length; i++) {
                if (files[i].size === 0) {
                    // do nothing.
                } else {
                    len += files[i].size || files[i].blob.length;
                }
            }

            if (len > (maxfileSize)) {
                return false;
            }

            return true;
        },
        uploadFile: function(file, url, progress, complete, error, scope, asPut) {
            this.uploadFileHTML5(file, url, progress, complete, error, scope, asPut);
        },
        uploadFileHTML5: function(file, url, progress, complete, error, scope, asPut) {
            if (!this.isFileSizeAllowed([file])) {
                this._onUnableToUploadError(this.largeFileWarningText, error);
                return;
            }
            if (this.isHTML5Supported()) {
                this._uploadFileHTML5_asBinary(file, url, progress, complete, error, scope, asPut);
            } else {
                this._onUnableToUploadError(this.unableToUploadText, error);
            }
        },
        _uploadFileHTML5_asBinary: function(file, url, progress, complete, error, scope, asPut) {
            window.BlobBuilder = window.BlobBuilder ||
                         window.WebKitBlobBuilder ||
                         window.MozBlobBuilder ||
                         window.MSBlobBuilder;
            if (!url) {
                //assume Attachment SData url
                url = 'slxdata.ashx/slx/system/-/attachments/file';// TODO: Remove this assumption from SDK
            }

            var request = new XMLHttpRequest(), service = App.getService(), reader;
            request.open((asPut) ? 'PUT' : 'POST', url);
            request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

            if (service) {
                request.setRequestHeader('Authorization', service.createBasicAuthToken());
                request.setRequestHeader('X-Authorization', service.createBasicAuthToken());
                request.setRequestHeader('X-Authorization-Mode', 'no-challenge');
            }

            reader = new FileReader();
            reader.onload = lang.hitch(this, function(evt) {
                var binary, boundary, dashdash, crlf, bb, unknownErrorText, usingBlobBuilder, blobReader, blobData;
                unknownErrorText = this.unknownErrorText;
                blobReader = new FileReader();// read the blob as an ArrayBuffer to work around this android issue: https://code.google.com/p/android/issues/detail?id=39882

                try {
                    new Blob();
                    bb = [];
                } catch(e) {
                    bb = new window.BlobBuilder();
                    usingBlobBuilder = true;
                }

                binary = evt.target.result;
                boundary = "---------------------------" + (new Date()).getTime();
                dashdash = '--';
                crlf = '\r\n';

                this._append(bb, dashdash + boundary + crlf);
                this._append(bb, 'Content-Disposition: attachment; ');
                this._append(bb, 'name="file_"; ');
                this._append(bb, 'filename*="' + encodeURI(file.name) + '" ');
                this._append(bb, crlf);
                this._append(bb, 'Content-Type: ' + file.type);
                this._append(bb, crlf + crlf);
                this._append(bb, binary);
                this._append(bb, crlf);
                this._append(bb, dashdash + boundary + dashdash + crlf);

                if (complete) {
                    request.onreadystatechange = function() {
                        if (request.readyState === 4) {
                            if (Math.floor(request.status / 100) !== 2) {
                                if (error) {
                                    error.call(scope || this, unknownErrorText);
                                    console.warn(unknownErrorText + " "+ request.responseText);
                                }
                            } else {
                                complete.call(scope || this, request);
                            }
                        }
                    };
                }

                if (progress) {
                    request.upload.addEventListener('progress', function(e) {
                        progress.call(scope || this, e);
                    });
                }

                request.setRequestHeader('Content-Type', 'multipart/attachment; boundary=' + boundary);

                if (usingBlobBuilder) {
                    blobData = bb.getBlob(file.type);
                } else {
                    blobData = new Blob(bb);
                }

                // Read the blob back as an ArrayBuffer to work around this android issue:
                // https://code.google.com/p/android/issues/detail?id=39882
                blobReader.onload = function(e) {
                    request.send(e.target.result);
                }

                blobReader.readAsArrayBuffer(blobData);
            });

            reader.readAsArrayBuffer(file);
        },
        _append: function(arrayOrBlobBuilder, data) {
            if (arrayOrBlobBuilder && arrayOrBlobBuilder.constructor === Array) {
                arrayOrBlobBuilder.push(data);
            } else {
                arrayOrBlobBuilder.append(data);
            }
        },
        _onUnableToUploadError: function(msg, onError) {
            if (!msg) {
                msg = this.unableToUploadText
            }
            if (onError) {
                onError([msg])
            } else {
                console.warn([msg]);
            }
        },
        formatFileSize: function(size) {
            size = parseInt(size, 10);
            if (size === 0) {
                return '0 KB';
            }
            if (!size || size < 0) {
                return this.unknownSizeText;
            }
            if (size < 1024) {
                return '1 KB';
            }
            return dNumber.format(Math.round(size / 1024)) + ' KB';
        },
        getFile: function(fileUrl, responseType , onSuccess) {
            var request, service, self;

            request = new XMLHttpRequest();
            service = App.getService();
            self = this;

            request.open("GET", fileUrl, true);

            if (responseType) {
                request.responseType = responseType;
            }
            request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

            if (service) {
                request.setRequestHeader('Authorization', service.createBasicAuthToken());
                request.setRequestHeader('X-Authorization', service.createBasicAuthToken());
                request.setRequestHeader('X-Authorization-Mode', 'no-challenge');
            }
            request.addEventListener("load", function() {
                var data, contentType, contentInfo, responseInfo, fileName;

                data = this.response;
                contentType = this.getResponseHeader("Content-Type");
                contentInfo = this.getResponseHeader("Content-Disposition");
                responseInfo = {};
                fileName = contentInfo.split('=')[1];

                responseInfo = {
                    request: this,
                    responseType: responseType,
                    response: this.response,
                    contentType: contentType,
                    fileName: fileName
                };
                if (onSuccess) {
                    onSuccess(responseInfo);
                }
            }, false);
            request.send(null);
        }
    });
});
<|endoftext|>


<commit_before><commit_msg>Update project speakingurl to 9.0.0<commit_after><|endoftext|>


<commit_before>'use strict'

import cheerio from 'cheerio'
import fetch from 'node-fetch'
import url from 'url'
import querystring from 'querystring'

const removeSpaces = text => text
      .replace(/\s+/gm, ' ')
      .replace(/^\s+/, '')
      .replace(/\s+$/, '')

class Page {
  constructor({ body }) {
    this.page = {}
    this.$ = cheerio.load(body)
    this.pageElement = this.pageElement.bind(this)
  }

  pageElement(selector) {
    const element = this.$(selector)
    const superSpan = element.find('.super')
    superSpan.each((i, eTag) => {
      const e = this.$(eTag)
      e.replaceWith(` (${e.text()})`)
    })
    return element
  }

  parseTitle() {
    const element = this.pageElement('.definitionBoxTop')
    const title = {}
    title.title = element.find('.match').first().text()
    title.attributes = element.find('.tekstmedium').first().text()
    return title
  }

  parseDefinitions({ selector, withHeader = false }) {
    let element = this.pageElement(selector || '#content-betydninger .definition')
    const definitions = element.map((i, vTag) => {
      const item = {}
      const v = this.$(vTag)
      item.id = v.parents('.definitionBox').first().attr('id')
      const parent = v.parents('.definitionIndent')
      item.definition = parent.find('.definition').text()
      const synonyms = parent.find('span:contains(Synonym)').next().find('a')
      if (synonyms.length > 0) {
        item.synonyms = synonyms.map((i, e) => {
          const synonym = {}
          synonym.text = e.children[0].data
          synonym.link = e.attribs.href
          return synonym
        }).get()
      }

      const grammar = parent.find('.grammatik .inlineList')
      if (grammar.length > 0) {
        item.grammar = grammar.text().split('\xa0 ')
      }

      const quotes = parent.find('.citat')
      if (quotes.length > 0) {
        item.quotes = quotes.map((i, e) => {
          return e.children[0].data
        }).get()
      }

      const examples = parent.find('.definitionBox :contains(Eksempler)')
      if (examples.length > 0) {
        item.examples = examples.next().text().split('\xa0 ')
      }

      if (withHeader) {
        const headerTitle = removeSpaces(this.$(parent.prevAll('.definitionBox')[0]).text())
        item.title = headerTitle
      }
      return item
    }).get()
    return definitions
  }

  parsePronunciations() {
    const element = this.pageElement('#id-udt .tekstmedium')

    if (element.length === 0) {
      return []
    }

    const pronunciationsSoup = element.children()

    let item = {transcription: '', details: ''}
    const pronunciations = []
    pronunciationsSoup.each((i, vTag) => {
      const v = this.$(vTag)
      if (v.attr('class') === 'dividerDouble') {
        item.transcription = removeSpaces(item.transcription)
        item.details = removeSpaces(item.details)
        pronunciations.push(item)
        item = {transcription: '', details: ''}
        const next = v.next()
        if (next.attr('class') === 'diskret') {
          item.details = next.text()
        }
      } else if (v.attr('class') === 'lydskrift') {
        item.transcription += v.text() + ' '
      } else {
        return null
      }
      const audio = v.find('audio a').attr('href')
      if (typeof audio !== 'undefined') {
        item.audio = v.find('audio a').attr('href')
      }
    })
    item.details = removeSpaces(item.details)
    item.transcription = removeSpaces(item.transcription)
    pronunciations.push(item)
    return pronunciations
  }

  parseInflection() {
    const element = this.pageElement('#id-boj .tekstmedium')
    return element.text() || ''
  }

  parseSuggestions() {
    const element = this.pageElement('.searchResultBox').first()
    element.find('.arrow-mini').replaceWith('→')
    const items = element.find('a').map((i, eTag) => {
      const e = this.$(eTag)
      const item = {}
      item.link = querystring.parse(url.parse(e.attr('href')).query)
      item.text = removeSpaces(e.text())
      return item
    }).get()
    return items
  }

  parse() {
    let definitions = this.parseDefinitions({})
    let fasteUdtryk = []
    if (definitions.length === 0) {
      definitions = this.parseDefinitions({
        selector: '.definition'
      })
    } else {
      fasteUdtryk = this.parseDefinitions({
        selector: '#content-faste-udtryk .definition',
        withHeader: true,
      })
    }

    return {
      title: this.parseTitle(),
      pronunciations: this.parsePronunciations(),
      definitions,
      fasteUdtryk,
      inflection: this.parseInflection(),
      suggestions: this.parseSuggestions(),
    }
  }
}

function pageEntry(query) {
  if (typeof query.query === 'undefined' || query.query === '') {
    return Promise.reject(new Error("empty query"))
  }
  const queryString = querystring.stringify(query)
  return fetch(`http://ordnet.dk/ddo/ordbog?${queryString}`)
    .then((response) => {
      if (response.status >= 300) {
        return Promise.reject(new Error(response.statusText))
      }
      return response.text()
    })
    .then((data) => {
      const page = new Page({ body: data })
      return page.parse()
    })
}

export {
  pageEntry,
  Page
}
<commit_msg>Only parse basic inflection<commit_after>'use strict'

import cheerio from 'cheerio'
import fetch from 'node-fetch'
import url from 'url'
import querystring from 'querystring'

const removeSpaces = text => text
      .replace(/\s+/gm, ' ')
      .replace(/^\s+/, '')
      .replace(/\s+$/, '')

class Page {
  constructor({ body }) {
    this.page = {}
    this.$ = cheerio.load(body)
    this.pageElement = this.pageElement.bind(this)
  }

  pageElement(selector) {
    const element = this.$(selector)
    const superSpan = element.find('.super')
    superSpan.each((i, eTag) => {
      const e = this.$(eTag)
      e.replaceWith(` (${e.text()})`)
    })
    return element
  }

  parseTitle() {
    const element = this.pageElement('.definitionBoxTop')
    const title = {}
    title.title = element.find('.match').first().text()
    title.attributes = element.find('.tekstmedium').first().text()
    return title
  }

  parseDefinitions({ selector, withHeader = false }) {
    let element = this.pageElement(selector || '#content-betydninger .definition')
    const definitions = element.map((i, vTag) => {
      const item = {}
      const v = this.$(vTag)
      item.id = v.parents('.definitionBox').first().attr('id')
      const parent = v.parents('.definitionIndent')
      item.definition = parent.find('.definition').text()
      const synonyms = parent.find('span:contains(Synonym)').next().find('a')
      if (synonyms.length > 0) {
        item.synonyms = synonyms.map((i, e) => {
          const synonym = {}
          synonym.text = e.children[0].data
          synonym.link = e.attribs.href
          return synonym
        }).get()
      }

      const grammar = parent.find('.grammatik .inlineList')
      if (grammar.length > 0) {
        item.grammar = grammar.text().split('\xa0 ')
      }

      const quotes = parent.find('.citat')
      if (quotes.length > 0) {
        item.quotes = quotes.map((i, e) => {
          return e.children[0].data
        }).get()
      }

      const examples = parent.find('.definitionBox :contains(Eksempler)')
      if (examples.length > 0) {
        item.examples = examples.next().text().split('\xa0 ')
      }

      if (withHeader) {
        const headerTitle = removeSpaces(this.$(parent.prevAll('.definitionBox')[0]).text())
        item.title = headerTitle
      }
      return item
    }).get()
    return definitions
  }

  parsePronunciations() {
    const element = this.pageElement('#id-udt .tekstmedium')

    if (element.length === 0) {
      return []
    }

    const pronunciationsSoup = element.children()

    let item = {transcription: '', details: ''}
    const pronunciations = []
    pronunciationsSoup.each((i, vTag) => {
      const v = this.$(vTag)
      if (v.attr('class') === 'dividerDouble') {
        item.transcription = removeSpaces(item.transcription)
        item.details = removeSpaces(item.details)
        pronunciations.push(item)
        item = {transcription: '', details: ''}
        const next = v.next()
        if (next.attr('class') === 'diskret') {
          item.details = next.text()
        }
      } else if (v.attr('class') === 'lydskrift') {
        item.transcription += v.text() + ' '
      } else {
        return null
      }
      const audio = v.find('audio a').attr('href')
      if (typeof audio !== 'undefined') {
        item.audio = v.find('audio a').attr('href')
      }
    })
    item.details = removeSpaces(item.details)
    item.transcription = removeSpaces(item.transcription)
    pronunciations.push(item)
    return pronunciations
  }

  parseInflection() {
    const element = this.pageElement('#id-boj .tekstmedium')
    let text = ''
    if (element.length > 0) {
      text = removeSpaces(element[0].children[0].data)
    }
    return text
  }

  parseSuggestions() {
    const element = this.pageElement('.searchResultBox').first()
    element.find('.arrow-mini').replaceWith('→')
    const items = element.find('a').map((i, eTag) => {
      const e = this.$(eTag)
      const item = {}
      item.link = querystring.parse(url.parse(e.attr('href')).query)
      item.text = removeSpaces(e.text())
      return item
    }).get()
    return items
  }

  parse() {
    let definitions = this.parseDefinitions({})
    let fasteUdtryk = []
    if (definitions.length === 0) {
      definitions = this.parseDefinitions({
        selector: '.definition'
      })
    } else {
      fasteUdtryk = this.parseDefinitions({
        selector: '#content-faste-udtryk .definition',
        withHeader: true,
      })
    }

    return {
      title: this.parseTitle(),
      pronunciations: this.parsePronunciations(),
      definitions,
      fasteUdtryk,
      inflection: this.parseInflection(),
      suggestions: this.parseSuggestions(),
    }
  }
}

function pageEntry(query) {
  if (typeof query.query === 'undefined' || query.query === '') {
    return Promise.reject(new Error("empty query"))
  }
  const queryString = querystring.stringify(query)
  return fetch(`http://ordnet.dk/ddo/ordbog?${queryString}`)
    .then((response) => {
      if (response.status >= 300) {
        return Promise.reject(new Error(response.statusText))
      }
      return response.text()
    })
    .then((data) => {
      const page = new Page({ body: data })
      return page.parse()
    })
}

export {
  pageEntry,
  Page
}
<|endoftext|>


<commit_before>import Payment from 'payment';

const getStripeToken = (type = 'cc', data) => {

  // eslint-disable-next-line
  switch (type) {
    case 'cc': // credit card
      return stripe.createToken(data).then(res => {
        console.log(">>> stripe createToken result", res)
        return { token: res.token.id, card: res.token.card };
      });
      break;

    case 'btc': // bitcoin
      return stripe.createSource({
        type: 'bitcoin',
        amount: data.amount,
        currency: 'usd',
        metadata: data.metadata,
        owner: {
          email: data.email,
          name: data.name
        }
      }).then(function(res) {
        return { token: res.source.id, data: res.source.bitcoin };
      });
  }
}

const isValidCard = (card) => {
  if (typeof card.cvc !== 'string') {
    card.cvc = `${card.cvc}`;
  }
  return (card && card.cvc && card.cvc.length >= 3 && card.exp_month && card.exp_year && Payment.fns.validateCardNumber(card.number));
}

export { getStripeToken, isValidCard };<commit_msg>- console.log<commit_after>import Payment from 'payment';

const getStripeToken = (type = 'cc', data) => {

  // eslint-disable-next-line
  switch (type) {
    case 'cc': // credit card
      return stripe.createToken(data).then(res => {
        return { token: res.token.id, card: res.token.card };
      });
      break;

    case 'btc': // bitcoin
      return stripe.createSource({
        type: 'bitcoin',
        amount: data.amount,
        currency: 'usd',
        metadata: data.metadata,
        owner: {
          email: data.email,
          name: data.name
        }
      }).then(function(res) {
        return { token: res.source.id, data: res.source.bitcoin };
      });
  }
}

const isValidCard = (card) => {
  if (typeof card.cvc !== 'string') {
    card.cvc = `${card.cvc}`;
  }
  return (card && card.cvc && card.cvc.length >= 3 && card.exp_month && card.exp_year && Payment.fns.validateCardNumber(card.number));
}

export { getStripeToken, isValidCard };<|endoftext|>


<commit_before>define([
  './util',
  './webfinger',
  './wireClient',
  './sync',
  './schedule',
  './baseClient',
  './platform',
  './getputdelete',
  './widget/default'
], function(util, webfinger, wireClient, sync, schedule, BaseClient,
            platform, getputdelete, defaultView) {

  // Namespace: widget
  //
  // The remotestorage widget.
  //
  // See <remoteStorage.displayWidget>
  //
  //
  // Event: ready
  //   Fired when the user has connected and the initial synchronization
  //   has been performed. After this has fired your app can query and
  //   display data.
  //
  // Event: disconnect
  //   Fired when the user has clicked the 'disconnect' button and all
  //   locally cached data has been removed. At this point your app should
  //   remove all visible user-owned data from the screen and return to
  //   it's initial state.
  //
  // Event: state
  //   Fired whenever the internal state of the widget changes.
  //   Apps usually don't need to use this event, it is only included
  //   for debugging purposes and legacy support.
  //
  //   Parameters:
  //     state - A String. The new state the widget moved into.
  //

  "use strict";

  var settings = util.getSettingStore('remotestorage_widget');
  var events = util.getEventEmitter('ready', 'disconnect', 'state');
  var logger = util.getLogger('widget');

  var maxTimeout = 45000;
  var timeoutAdjustmentFactor = 1.5;

  // the view.
  var view = defaultView;
  // options passed to displayWidget
  var widgetOptions = {};
  // passed to display() to avoid circular deps
  var remoteStorage;

  var reconnectInterval = 10000;

  var offlineTimer = null;

  var viewState;

  var stateActions = {
    offline: function() {
      if(! offlineTimer) {
        offlineTimer = setTimeout(function() {
          offlineTimer = null;
          sync.fullSync().
            then(function() {
              schedule.enable();
            });
        }, reconnectInterval);
      }
    },
    connected: function() {
      if(offlineTimer) {
        clearTimeout(offlineTimer);
        offlineTimer = null;
      }
    }
  };

  function setState(state) {
    if(state === viewState) {
      return;
    }
    viewState = state;
    var action = stateActions[state];
    if(action) {
      action.apply(null, arguments);
    }
    view.setState.apply(view, arguments);
    events.emit('state', state);
  }

  function buildScopeRequest() {
    var scopes = remoteStorage.claimedModules;
    return Object.keys(remoteStorage.claimedModules).map(function(module) {
      return (module === 'root' ? '' : module) + ':' + scopes[module];
    }).join(' ');
  }

  function requestToken(authEndpoint) {
    logger.info('requestToken', authEndpoint);
    var redirectUri = view.getLocation().split('#')[0];
    var clientId = util.hostNameFromUri(redirectUri);
    authEndpoint += authEndpoint.indexOf('?') > 0 ? '&' : '?';
    authEndpoint += [
      ['redirect_uri', redirectUri],
      ['client_id', clientId],
      ['scope', buildScopeRequest()],
      ['response_type', 'token']
    ].map(function(kv) {
      return kv[0] + '=' + encodeURIComponent(kv[1]);
    }).join('&');
    return view.redirectTo(authEndpoint);
  }

  function connectStorage(userAddress) {
    settings.set('userAddress', userAddress);
    setState('authing');
    return webfinger.getStorageInfo(userAddress).
      then(wireClient.setStorageInfo, function(error) {
        if(error === 'timeout') {
          adjustTimeout();
        }
        setState((typeof(error) === 'string') ? 'typing' : 'error', error);
      }).
      get('properties').get('auth-endpoint').
      then(requestToken).
      then(schedule.enable, util.curry(setState, 'error'));
  }

  function reconnectStorage() {
    connectStorage(settings.get('userAddress'));
  }

  function disconnectStorage() {
    schedule.disable();
    remoteStorage.flushLocal();
    events.emit('state', 'disconnected');
    events.emit('disconnect');
  }

  // destructively parse query string from URI fragment
  function parseParams() {
    var md = view.getLocation().match(/^(.*?)#(.*)$/);
    var result = {};
    if(md) {
      var hash = md[2];
      hash.split('&').forEach(function(param) {
        var kv = param.split('=');
        if(kv[1]) {
          result[kv[0]] = decodeURIComponent(kv[1]);
        }
      });
      if(Object.keys(result).length > 0) {
        view.setLocation(md[1] + '#');
      }
    }
    return result;
  }

  function processParams() {
    var params = parseParams();

    // Query parameter: access_token
    if(params.access_token) {
      wireClient.setBearerToken(params.access_token);
    }
    // Query parameter: storage_root, storage_api
    if(params.storage_root && params.storage_api) {
      wireClient.setStorageInfo({
        type: params.storage_api,
        href: params.storage_root
      });
    }
    // Query parameter: authorize_endpoint
    if(params.authorize_endpoint) {
      requestToken(params.authorize_endpoint);
    }
    // Query parameter: user_address
    if(params.user_address) {
      view.setUserAddress(params.user_address);
    } else {
      var userAddress = settings.get('userAddress');
      if(userAddress) {
        view.setUserAddress(userAddress);
      }
    }
  }

  function handleSyncError(error) {
    if(error.message === 'unauthorized') {
      setState('unauthorized');
    } else if(error.message === 'network error') {
      setState('offline', error);
    } else {
      setState('error', error);
    }
  }

  function adjustTimeout() {
    var t = getputdelete.getTimeout();
    if(t < maxTimeout) {
      t *= timeoutAdjustmentFactor;
      webfinger.setTimeout(t);
      getputdelete.setTimeout(t);
    }
  }

  function handleSyncTimeout() {
    adjustTimeout();
    schedule.disable();
    setState('offline');
  }

  function initialSync() {
    setState('busy', true);
    sync.fullSync().then(function() {
      schedule.enable();
      events.emit('ready');
    }, handleSyncError);
  }

  function display(_remoteStorage, element, options) {
    remoteStorage = _remoteStorage;
    widgetOptions = options;
    if(! options) {
      options = {};
    }

    if(Object.keys(remoteStorage.claimedModules).length === 0) {
      throw new Error("displayWidget called, but no access claimed! Make sure to call displayWidget after remoteStorage.claimAccess is done.");
    }

    options.getLastSyncAt = function() {
      return sync.lastSyncAt && sync.lastSyncAt.getTime();
    };

    schedule.watch('/', 30000);

    view.display(element, options);

    view.on('sync', sync.forceSync);
    view.on('connect', connectStorage);
    view.on('disconnect', disconnectStorage);
    view.on('reconnect', reconnectStorage);

    sync.on('busy', util.curry(setState, 'busy'));
    sync.on('ready', util.curry(setState, 'connected'));
    wireClient.on('connected', function() {
      setState('connected');
      initialSync();
    });
    wireClient.on('disconnected', util.curry(setState, 'initial'));

    BaseClient.on('error', util.curry(setState, 'error'));
    sync.on('error', handleSyncError);
    sync.on('timeout', handleSyncTimeout);

    processParams();

    wireClient.calcState();
  }

  return util.extend({
    display : display,

    clearSettings: settings.clear,

    setView: function(_view) {
      view = _view;
    }
  }, events);
});
<commit_msg>check for remotestorage parameter as per current spec<commit_after>define([
  './util',
  './webfinger',
  './wireClient',
  './sync',
  './schedule',
  './baseClient',
  './platform',
  './getputdelete',
  './widget/default'
], function(util, webfinger, wireClient, sync, schedule, BaseClient,
            platform, getputdelete, defaultView) {

  // Namespace: widget
  //
  // The remotestorage widget.
  //
  // See <remoteStorage.displayWidget>
  //
  //
  // Event: ready
  //   Fired when the user has connected and the initial synchronization
  //   has been performed. After this has fired your app can query and
  //   display data.
  //
  // Event: disconnect
  //   Fired when the user has clicked the 'disconnect' button and all
  //   locally cached data has been removed. At this point your app should
  //   remove all visible user-owned data from the screen and return to
  //   it's initial state.
  //
  // Event: state
  //   Fired whenever the internal state of the widget changes.
  //   Apps usually don't need to use this event, it is only included
  //   for debugging purposes and legacy support.
  //
  //   Parameters:
  //     state - A String. The new state the widget moved into.
  //

  "use strict";

  var settings = util.getSettingStore('remotestorage_widget');
  var events = util.getEventEmitter('ready', 'disconnect', 'state');
  var logger = util.getLogger('widget');

  var maxTimeout = 45000;
  var timeoutAdjustmentFactor = 1.5;

  // the view.
  var view = defaultView;
  // options passed to displayWidget
  var widgetOptions = {};
  // passed to display() to avoid circular deps
  var remoteStorage;

  var reconnectInterval = 10000;

  var offlineTimer = null;

  var viewState;

  var stateActions = {
    offline: function() {
      if(! offlineTimer) {
        offlineTimer = setTimeout(function() {
          offlineTimer = null;
          sync.fullSync().
            then(function() {
              schedule.enable();
            });
        }, reconnectInterval);
      }
    },
    connected: function() {
      if(offlineTimer) {
        clearTimeout(offlineTimer);
        offlineTimer = null;
      }
    }
  };

  function setState(state) {
    if(state === viewState) {
      return;
    }
    viewState = state;
    var action = stateActions[state];
    if(action) {
      action.apply(null, arguments);
    }
    view.setState.apply(view, arguments);
    events.emit('state', state);
  }

  function buildScopeRequest() {
    var scopes = remoteStorage.claimedModules;
    return Object.keys(remoteStorage.claimedModules).map(function(module) {
      return (module === 'root' ? '' : module) + ':' + scopes[module];
    }).join(' ');
  }

  function requestToken(authEndpoint) {
    logger.info('requestToken', authEndpoint);
    var redirectUri = view.getLocation().split('#')[0];
    var clientId = util.hostNameFromUri(redirectUri);
    authEndpoint += authEndpoint.indexOf('?') > 0 ? '&' : '?';
    authEndpoint += [
      ['redirect_uri', redirectUri],
      ['client_id', clientId],
      ['scope', buildScopeRequest()],
      ['response_type', 'token']
    ].map(function(kv) {
      return kv[0] + '=' + encodeURIComponent(kv[1]);
    }).join('&');
    return view.redirectTo(authEndpoint);
  }

  function connectStorage(userAddress) {
    settings.set('userAddress', userAddress);
    setState('authing');
    return webfinger.getStorageInfo(userAddress).
      then(wireClient.setStorageInfo, function(error) {
        if(error === 'timeout') {
          adjustTimeout();
        }
        setState((typeof(error) === 'string') ? 'typing' : 'error', error);
      }).
      get('properties').get('auth-endpoint').
      then(requestToken).
      then(schedule.enable, util.curry(setState, 'error'));
  }

  function reconnectStorage() {
    connectStorage(settings.get('userAddress'));
  }

  function disconnectStorage() {
    schedule.disable();
    remoteStorage.flushLocal();
    events.emit('state', 'disconnected');
    events.emit('disconnect');
  }

  // destructively parse query string from URI fragment
  function parseParams() {
    var md = view.getLocation().match(/^(.*?)#(.*)$/);
    var result = {};
    if(md) {
      var hash = md[2];
      hash.split('&').forEach(function(param) {
        var kv = param.split('=');
        if(kv[1]) {
          result[kv[0]] = decodeURIComponent(kv[1]);
        }
      });
      if(Object.keys(result).length > 0) {
        view.setLocation(md[1] + '#');
      }
    }
    return result;
  }

  function processParams() {
    var params = parseParams();

    // Query parameter: access_token
    if(params.access_token) {
      wireClient.setBearerToken(params.access_token);
    }
    // Query parameter: remotestorage
    if(params.remotestorage) {
      view.setUserAddress(params.remotestorage);
    } else {
      var userAddress = settings.get('userAddress');
      if(userAddress) {
        view.setUserAddress(userAddress);
      }
    }
  }

  function handleSyncError(error) {
    if(error.message === 'unauthorized') {
      setState('unauthorized');
    } else if(error.message === 'network error') {
      setState('offline', error);
    } else {
      setState('error', error);
    }
  }

  function adjustTimeout() {
    var t = getputdelete.getTimeout();
    if(t < maxTimeout) {
      t *= timeoutAdjustmentFactor;
      webfinger.setTimeout(t);
      getputdelete.setTimeout(t);
    }
  }

  function handleSyncTimeout() {
    adjustTimeout();
    schedule.disable();
    setState('offline');
  }

  function initialSync() {
    setState('busy', true);
    sync.fullSync().then(function() {
      schedule.enable();
      events.emit('ready');
    }, handleSyncError);
  }

  function display(_remoteStorage, element, options) {
    remoteStorage = _remoteStorage;
    widgetOptions = options;
    if(! options) {
      options = {};
    }

    if(Object.keys(remoteStorage.claimedModules).length === 0) {
      throw new Error("displayWidget called, but no access claimed! Make sure to call displayWidget after remoteStorage.claimAccess is done.");
    }

    options.getLastSyncAt = function() {
      return sync.lastSyncAt && sync.lastSyncAt.getTime();
    };

    schedule.watch('/', 30000);

    view.display(element, options);

    view.on('sync', sync.forceSync);
    view.on('connect', connectStorage);
    view.on('disconnect', disconnectStorage);
    view.on('reconnect', reconnectStorage);

    sync.on('busy', util.curry(setState, 'busy'));
    sync.on('ready', util.curry(setState, 'connected'));
    wireClient.on('connected', function() {
      setState('connected');
      initialSync();
    });
    wireClient.on('disconnected', util.curry(setState, 'initial'));

    BaseClient.on('error', util.curry(setState, 'error'));
    sync.on('error', handleSyncError);
    sync.on('timeout', handleSyncTimeout);

    processParams();

    wireClient.calcState();
  }

  return util.extend({
    display : display,

    clearSettings: settings.clear,

    setView: function(_view) {
      view = _view;
    }
  }, events);
});
<|endoftext|>


<commit_before>import Auth0Lock from 'auth0-lock';
import Auth0 from 'auth0-js';
import config from '../config/config.js';

const { clientID, domain } = config.auth0;
const options = {
  auth: {
    redirect: false
  }
};
let lock = null;
let auth0 = null;

function getProfile(authResult) {
  console.log('get profile', authResult);
  lock.getProfile(authResult.idToken, (error, profile) => {
    if (error) return;

    localStorage.setItem('idToken', authResult.idToken);
    localStorage.setItem('profile', JSON.stringify(profile));
  });
}

export function initLock() {
  lock = new Auth0Lock(clientID, domain, options);
  lock.on('authenticated', getProfile);
}

export function showLock() {
  if (lock) lock.show();
  else {
    initLock();
    lock.show();
  }
}

export function init() {
  auth0 = new Auth0({
    domain,
    clientID,
    callbackURL: 'http://localhost:3000',
    responseType: 'token'
  });
}

export function requestMagicLink() {
  auth0.requestMagicLink({
    email: 'alexbeauchemin01@gmail.com'
  }, (err) => {
    if (err) console.log('err', err);
    else console.log('success');
    // the request was successful and you should receive
    // an email with the link at the specified address
  });
}
<commit_msg>Init lock<commit_after>// import Auth0Lock from 'auth0-lock';
import Auth0 from 'auth0-js';
import config from '../config/config.js';

const { clientID, domain } = config.auth0;
const options = {
  auth: {
    redirect: false
  }
};
let lock = null;
let auth0 = null;

function getProfile(authResult) {
  console.log('get profile', authResult);
  lock.getProfile(authResult.idToken, (error, profile) => {
    if (error) return;

    localStorage.setItem('idToken', authResult.idToken);
    localStorage.setItem('profile', JSON.stringify(profile));
  });
}

export function initLock() {
  lock = new Auth0Lock(clientID, domain, options);
  lock.on('authenticated', getProfile);
}

export function showLock() {
  if (lock) lock.show();
  else {
    initLock();
    lock.show();
  }
}

export function init() {
  auth0 = new Auth0({
    domain,
    clientID,
    callbackURL: 'http://localhost:3000',
    responseType: 'token'
  });
}

export function requestMagicLink() {
  auth0.requestMagicLink({
    email: 'alexbeauchemin01@gmail.com'
  }, (err) => {
    if (err) console.log('err', err);
    else console.log('success');
    // the request was successful and you should receive
    // an email with the link at the specified address
  });
}
<|endoftext|>


<commit_before>// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple, Inc.  All rights reserved.
// ========================================================================

require('controllers/controller');
require('mixins/selection_support');

/**
  @class

  An ArrayController provides a way for you to publish an array of objects
  for CollectionView or other controllers to work with.  To work with an
  ArrayController, set the content property to the array you want the
  controller to manage.  Then work directly with the controller object as if
  it were the array itself.

  When you want to display an array of objects in a CollectionView, bind the
  "arrangedObjects" of the array controller to the CollectionView's "content"
  property.  This will automatically display the array in the collection view.

  h2. Grouping

  You can also optionally turn on grouping for your array by setting the
  groupBy property on the ArrayController to a non-null value.  Note that when
  you turn on grouping, the ArrayController will need to search your entire
  content array to determine the groups.  If you have a large set of content,
  this can cause a performance problem.  Instead you may want to manage your
  groups manually by using a TreeController.

  @extends SC.Controller
  @extends SC.Array
  @extends SC.SelectionSupport
  @author Charles Jolley
  @since SproutCore 1.0
*/
SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
/** @scope SC.ArrayController.prototype */ {

  // ..........................................................
  // PROPERTIES
  //

  /**
    The content array managed by this controller.

    You can set the content of the ArrayController to any object that
    implements SC.Array or SC.Enumerable.  If you set the content to an object
    that implements SC.Enumerable only, you must also set the orderBy property
    so that the ArrayController can order the enumerable for you.

    If you set the content to a non-enumerable and non-array object, then the
    ArrayController will wrap the item in an array in an attempt to normalize
    the result.

    @type SC.Array
  */
  content: null,

  /**
    Makes the array editable or not.  If this is set to NO, then any attempts
    at changing the array content itself will throw an exception.

    @property
    @type Boolean
  */
  isEditable: YES,

  /**
    Used to sort the array.

    If you set this property to a key name, array of key names, or a function,
    then then ArrayController will automatically reorder your content array
    to match the sort order.  (If you set a function, the function will be
    used to sort).

    Normally, you should only use this property if you set the content of the
    controller to an unordered enumerable such as SC.Set or SC.SelectionSet.
    In this case the orderBy property is required in order for the controller
    to property order the content for display.

    If you set the content to an array, it is usually best to maintain the
    array in the proper order that you want to display things rather than
    using this method to order the array since it requires an extra processing
    step.  You can use this orderBy property, however, for displaying smaller
    arrays of content.

    Note that you can only to use addObject() to insert new objects into an
    array that is ordered.  You cannot manually reorder or insert new objects
    into specific locations because the order is managed by this property
    instead.

    If you pass a function, it should be suitable for use in compare().

    @property
    @type String|Array|Function
  */
  orderBy: null,

  /**
    Used to group the array.

    If you set this property with a key name, then the ArrayController will
    automatically generate a tree of groups for display in a CollectionView.
    You can get the group tree from the groups property, which will only be
    non-null when this property is set.

    @property
    @type String
  */
  groupBy: null,

  /**
    Set to YES if you want the controller to wrap non-enumerable content
    in an array and publish it.  Otherwise, it will treat single content like
    null content.

    @property
    @type Boolean
  */
  allowsSingleContent: YES,

  /**
    Set to YES if you want objects removed from the array to also be
    deleted.  This is a convenient way to manage lists of items owned
    by a parent record object.

    Note that even if this is set to NO, calling destroyObject() instead of
    removeObject() will still destroy the object in question as well as
    removing it from the parent array.

    @type {Boolean}
  */
  destroyOnRemoval: NO,

  /**
    Returns an SC.Array object suitable for use in a CollectionView.
    Depending on how you have your ArrayController configured, this property
    may be one of several different values.

    @property
    @type SC.Array
  */
  arrangedObjects: function() {

  }.property('content', 'orderBy', 'groupBy').cacheable(),

  /**
    Computed property indicates whether or not the array controller can
    remove content.  You can delete content only if the content is not single
    content and isEditable is YES.

    @property
    @type Boolean
  */
  canRemoveContent: function() {
    var content = this.get('content');
    if (!content || !this.get('isEditable') || !this.get('hasContent')) return NO ;
    if (content.isEnumerable && (SC.typeOf(content.removeObject) !== SC.T_FUNCTION)) return NO ;
    return YES;
  }.property('content', 'isEditable', 'hasContent'),

  /**
    Computed property indicates whether you can reorder content.  You can
    reorder content as long a the controller isEditable and the content is a
    real SC.Array-like object.  You cannot reorder content when orderBy is
    non-null.

    @property
    @type Boolean
  */
  canReorderContent: function() {
    var content = this.get('content');
    return content && this.get('isEditable') && !this.get('orderBy') && content.isSCArray;
  }.property('content', 'isEditable', 'orderBy'),

  /**
    Computed property insides whether you can add content.  You can add
    content as long as the controller isEditable and the content is not a
    single object.

    Note that the only way to simply add object to an ArrayController is to
    use the addObject() or pushObject() methods.  All other methods imply
    reordering and will fail.

    @property
    @type Boolean
  */
  canAddContent: function() {
    var content = this.get('content');
    return content && this.get('isEditable') && content.isEnumerable && (SC.typeOf(content.addObject) === SC.T_FUNCTION);
  }.property('content', 'isEditable'),

  /**
    Set to YES if the controller has valid content that can be displayed,
    even an empty array.  Returns NO if the content is null or not enumerable
    and allowsSingleContent is NO.

    @property
    @type Boolean
  */
  hasContent: function() {
    var content = this.get('content');
    return content && (content.isEnumerable||this.get('allowSingleContent'));
  }.property('content', 'allowSingleContent'),

  // ..........................................................
  // METHODS
  //

  /**
    Adds an object to the array.  If the content is ordered, this will add the
    object to the end of the content array.  The content is not ordered, the
    location depends on the implementation of the content.

    If the source content does not support adding an object, then this method
    will throw an exception.

    @param {Object} object the object to add
    @returns {SC.ArrayController} receiver
  */
  addObject: function(object) {
    if (!this.get('canAddContent')) throw "%@ cannot add content".fmt(this);

    var content = this.get('content');
    if (content.isSCArray) content.pushObject(object);
    else if (content.addObject) content.addObject(obj);
    else throw "%@.content does not support addObject".fmt(this);

    return this;
  },

  /**
    Removes the passed object from the array.  If the underyling content
    is a single object, then this simply sets the content to null.  Otherwise
    it will call removeObject() on the content.

    Also, if destroyOnRemoval is YES, this will actually destroy the object.

    @param {Object} object the object to remove
    @returns {SC.ArrayController} receiver
  */
  removeObject: function(object) {
    if (!this.get('canRemoveContent')) {
      throw "%@ cannot remove content".fmt(this);
    }

    var content = this.get('content');
    if (content.isEnumerable) content.removeObject(object);
    else this.set('content', null);

    if (this.get('destroyOnRemoval') && object.destroy) object.destroy();
    return this;
  }

  // ..........................................................
  // SC.ARRAY SUPPORT
  //

  length: function() {

  }.property(),

  objectAt: function(idx) {

  },

  replace: function(start, amt, objects) {

  },

  // ..........................................................
  // INTERNAL SUPPORT
  //

  init: function() {
    sc_super();
    this._scac_contentDidChange();
  },

  _scac_contentDidChange: function() {

  }

});
<commit_msg>Further work on ArrayController.<commit_after>// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple, Inc.  All rights reserved.
// ========================================================================

require('controllers/controller');
require('mixins/selection_support');

/**
  @class

  An ArrayController provides a way for you to publish an array of objects
  for CollectionView or other controllers to work with.  To work with an
  ArrayController, set the content property to the array you want the
  controller to manage.  Then work directly with the controller object as if
  it were the array itself.

  When you want to display an array of objects in a CollectionView, bind the
  "arrangedObjects" of the array controller to the CollectionView's "content"
  property.  This will automatically display the array in the collection view.

  h2. Grouping

  You can also optionally turn on grouping for your array by setting the
  groupBy property on the ArrayController to a non-null value.  Note that when
  you turn on grouping, the ArrayController will need to search your entire
  content array to determine the groups.  If you have a large set of content,
  this can cause a performance problem.  Instead you may want to manage your
  groups manually by using a TreeController.

  @extends SC.Controller
  @extends SC.Array
  @extends SC.SelectionSupport
  @author Charles Jolley
  @since SproutCore 1.0
*/
SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
/** @scope SC.ArrayController.prototype */ {

  // ..........................................................
  // PROPERTIES
  //

  /**
    The content array managed by this controller.

    You can set the content of the ArrayController to any object that
    implements SC.Array or SC.Enumerable.  If you set the content to an object
    that implements SC.Enumerable only, you must also set the orderBy property
    so that the ArrayController can order the enumerable for you.

    If you set the content to a non-enumerable and non-array object, then the
    ArrayController will wrap the item in an array in an attempt to normalize
    the result.

    @type SC.Array
  */
  content: null,

  /**
    Makes the array editable or not.  If this is set to NO, then any attempts
    at changing the array content itself will throw an exception.

    @property
    @type Boolean
  */
  isEditable: YES,

  /**
    Used to sort the array.

    If you set this property to a key name, array of key names, or a function,
    then then ArrayController will automatically reorder your content array
    to match the sort order.  (If you set a function, the function will be
    used to sort).

    Normally, you should only use this property if you set the content of the
    controller to an unordered enumerable such as SC.Set or SC.SelectionSet.
    In this case the orderBy property is required in order for the controller
    to property order the content for display.

    If you set the content to an array, it is usually best to maintain the
    array in the proper order that you want to display things rather than
    using this method to order the array since it requires an extra processing
    step.  You can use this orderBy property, however, for displaying smaller
    arrays of content.

    Note that you can only to use addObject() to insert new objects into an
    array that is ordered.  You cannot manually reorder or insert new objects
    into specific locations because the order is managed by this property
    instead.

    If you pass a function, it should be suitable for use in compare().

    @property
    @type String|Array|Function
  */
  orderBy: null,

  /**
    Used to group the array.

    If you set this property with a key name, then the ArrayController will
    automatically generate a tree of groups for display in a CollectionView.
    You can get the group tree from the groups property, which will only be
    non-null when this property is set.

    @property
    @type String
  */
  groupBy: null,

  /**
    Set to YES if you want the controller to wrap non-enumerable content
    in an array and publish it.  Otherwise, it will treat single content like
    null content.

    @property
    @type Boolean
  */
  allowsSingleContent: YES,

  /**
    Set to YES if you want objects removed from the array to also be
    deleted.  This is a convenient way to manage lists of items owned
    by a parent record object.

    Note that even if this is set to NO, calling destroyObject() instead of
    removeObject() will still destroy the object in question as well as
    removing it from the parent array.

    @type {Boolean}
  */
  destroyOnRemoval: NO,

  /**
    Returns an SC.Array object suitable for use in a CollectionView.
    Depending on how you have your ArrayController configured, this property
    may be one of several different values.

    @property
    @type SC.Array
  */
  arrangedObjects: function() {

  }.property('content', 'orderBy', 'groupBy').cacheable(),

  /**
    Computed property indicates whether or not the array controller can
    remove content.  You can delete content only if the content is not single
    content and isEditable is YES.

    @property
    @type Boolean
  */
  canRemoveContent: function() {
    var content = this.get('content');
    if (!content || !this.get('isEditable') || !this.get('hasContent')) return NO ;
    if (content.isEnumerable && (SC.typeOf(content.removeObject) !== SC.T_FUNCTION)) return NO ;
    return YES;
  }.property('content', 'isEditable', 'hasContent'),

  /**
    Computed property indicates whether you can reorder content.  You can
    reorder content as long a the controller isEditable and the content is a
    real SC.Array-like object.  You cannot reorder content when orderBy is
    non-null.

    @property
    @type Boolean
  */
  canReorderContent: function() {
    var content = this.get('content');
    return content && this.get('isEditable') && !this.get('orderBy') && content.isSCArray;
  }.property('content', 'isEditable', 'orderBy'),

  /**
    Computed property insides whether you can add content.  You can add
    content as long as the controller isEditable and the content is not a
    single object.

    Note that the only way to simply add object to an ArrayController is to
    use the addObject() or pushObject() methods.  All other methods imply
    reordering and will fail.

    @property
    @type Boolean
  */
  canAddContent: function() {
    var content = this.get('content');
    return content && this.get('isEditable') && content.isEnumerable && (SC.typeOf(content.addObject) === SC.T_FUNCTION);
  }.property('content', 'isEditable'),

  /**
    Set to YES if the controller has valid content that can be displayed,
    even an empty array.  Returns NO if the content is null or not enumerable
    and allowsSingleContent is NO.

    @property
    @type Boolean
  */
  hasContent: function() {
    var content = this.get('content');
    return content && (content.isEnumerable||this.get('allowSingleContent'));
  }.property('content', 'allowSingleContent'),

  /**
    The actual array this controller is managing.  You should not usually
    access this property directly; it is mostly used internally.

    Most of the time this property is the same as the content property, but
    it may differ on occasion.  Particularly if you set the content property
    to a single object.  Also, if you set the orderBy property this will
    represent the ordered array.

    @property
    @type SC.Array
  */
  observableContent: function() {
    var content = this.get('content'),
        orderBy = this.get('orderBy'),
        ret, func, t, len;

    if (!content) return null ; // nothing to do
    if (!orderBy && content.isSCArray) return content; // no wrap
    if (!orderBy) {
      throw "%@.orderBy is required for unordered content".fmt(this);
    }

    // build array - then sort it
    switch(SC.typeOf(orderBy)) {
    case SC.T_STRING:
      orderBy = [orderBy];
      break;
    case SC.T_FUNCTION:
      func = orderBy ;
      break;
    case SC.T_ARRAY:
      break;
    default:
      throw "%@.orderBy must be Array, String, or Function".fmt(this);
    }

    len = orderBy.get('length');

    // generate comparison function if needed - use orderBy
    if (!func) {
      func = function(a,b) {
        var idx=0, status=0, key, aValue, bValue;
        for(idx=0;(idx<len)&&(status===0);idx++) {
          key = orderBy.objectAt(key);

          if (a) aValue = a ;
          else if (a.isObservable) aValue = a.get(key);
          else aValue = a[key];

          if (b) bValue = b ;
          else if (b.isObservable) bValue = b.get(key);
          else bValue = b[key];

          status = SC.compare(aValue, bValue);
        }
        return ret ;
      };
    }

    ret = [];
    content.forEach(function(o) { ret.push(o); });
    ret.sort(func);

    func = null ; // avoid memory leaks
    return ret ;

  }.property('content', 'allowsSingleContent', 'orderBy').cacheable(),

  // ..........................................................
  // METHODS
  //

  /**
    Adds an object to the array.  If the content is ordered, this will add the
    object to the end of the content array.  The content is not ordered, the
    location depends on the implementation of the content.

    If the source content does not support adding an object, then this method
    will throw an exception.

    @param {Object} object the object to add
    @returns {SC.ArrayController} receiver
  */
  addObject: function(object) {
    if (!this.get('canAddContent')) throw "%@ cannot add content".fmt(this);

    var content = this.get('content');
    if (content.isSCArray) content.pushObject(object);
    else if (content.addObject) content.addObject(obj);
    else throw "%@.content does not support addObject".fmt(this);

    return this;
  },

  /**
    Removes the passed object from the array.  If the underyling content
    is a single object, then this simply sets the content to null.  Otherwise
    it will call removeObject() on the content.

    Also, if destroyOnRemoval is YES, this will actually destroy the object.

    @param {Object} object the object to remove
    @returns {SC.ArrayController} receiver
  */
  removeObject: function(object) {
    if (!this.get('canRemoveContent')) {
      throw "%@ cannot remove content".fmt(this);
    }

    var content = this.get('content');
    if (content.isEnumerable) content.removeObject(object);
    else this.set('content', null);

    if (this.get('destroyOnRemoval') && object.destroy) object.destroy();
    return this;
  },

  // ..........................................................
  // SC.ARRAY SUPPORT
  //

  length: function() {
  }.property(),

  objectAt: function(idx) {

  },

  replace: function(start, amt, objects) {

  },

  // ..........................................................
  // INTERNAL SUPPORT
  //

  init: function() {
    sc_super();
    this._scac_contentDidChange();
  },

  _scac_contentDidChange: function() {

  }

});
<|endoftext|>