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

  var ng = window.angular;

  /* Default configuration */
  var config = {
    signOutUrl: '/api/users/sign-out',
    signInUrl: '/api/users/sign-in',
    updateUrl: '/api/session'
  };

  /**
   * ngSession service function.
   */
  function ngSessionServiceFn($rootScope, $http, $q) {
    $rootScope.session = {};

    /**
     * Session update successful.
     */
    function onSessionUpdateSuccess(deferred, res) {
      $rootScope.session.user = res.data;

      deferred.resolve(res);
    }

    /**
     * On Sign In success.
     */
    function onSingInSuccess(deferred) {
      return update(null, deferred);
    }

    /**
     * On Sign Out success.
     */
    function onSingOutSuccess(deferred, res) {
      $rootScope.session.user = null;

      deferred.resolve(res);
    }

    /**
     * Signs a user in.
     *
     * It will perform a POST to the `config.signOutUrl` path and assign the
     * user object from the `res.data` object on success.
     *
     * @param {Object} data The data to send for sign in.
     * @param {Object} options Optional AngularJS HTTP request options.
     *
     * @returns {Promise} req The AngularJS HTTP promise. Will pass along the
     * request's `res` object.
     */
    function signIn(data, options) {
      var deferred = $q.defer();

      /* Remove previous user object */
      $rootScope.session.user = null;

      $http.post(config.signInUrl, data, options)
        .then(onSingInSuccess.bind(null, deferred))
        .catch(deferred.reject);

      return deferred.promise;
    }

    /**
     * Signs a user out.
     *
     * It will perform a POST to the `config.signOutUrl` path and delete the
     * user object on success.
     *
     * @param {Object} data The optional data to send for sign out.
     * @param {Object} options Optional AngularJS HTTP request options.
     *
     * @returns {Promise} req The AngularJS HTTP promise. Will pass along the
     * request's `res` object.
     */
    function signOut(data, options) {
      var deferred = $q.defer();

      $http.post(config.signOutUrl, data, options)
        .then(onSingOutSuccess.bind(null, deferred))
        .catch(deferred.reject);

      return deferred.promise;
    }

    /**
     * Updates the session user object.
     *
     * It will perform a GET to the `config.updateUrl` path and set the
     * session's user object on success with the request's `res.data`.
     *
     * @param {Object} options Optional AngularJS HTTP request options.
     * @param {Promise} deferred Optional deferred promise object.
     *
     * @returns {Promise} req The AngularJS HTTP promise. Will pass along the
     * request's `res` object.
     */
    function update(options, deferred) {
      if (!deferred) {
        deferred = $q.defer();
      }

      /* Retrieve current session */
      $http.get(config.updateUrl, options || {})
        .then(onSessionUpdateSuccess.bind(null, deferred))
        .catch(deferred.reject);

      return deferred.promise;
    }

    /**
     * Retrieves a user data value by property name.
     *
     * @param {String} prop The property name to retrieve.
     *
     * @returns {Mixed} value The property's value or the user object if no
     * property name is provided.
     */
    function user(prop) {
      if (prop && $rootScope.session.user) {
        return $rootScope.session.user[prop];
      }

      return $rootScope.session.user;
    }

    /**
     * Obtains a value from the session object.
     *
     * @param {String} prop The property name to obtain.
     *
     * @returns {Mixed} value The property's value.
     */
    function get(prop) {
      return $rootScope.session[prop];
    }

    /**
     * Sets a value in the session object.
     *
     * @param {String} prop The property name to set.
     * @param {Mixed} value The property value to set.
     */
    function set(prop, value) {
      $rootScope.session[prop] = value;
    }

    /**
     * Deletes a property from the session object.
     *
     * @param {String} prop The property name.
     */
    function del(prop) {
      delete $rootScope.session[prop];
    }

    /* Session service definition */
    var ngSessionServiceDef = {
      signOut: signOut,
      signIn: signIn,
      update: update,
      user: user,
      get: get,
      set: set,
      del: del
    };

    return ngSessionServiceDef;
  }

  /**
   * ngSession run function.
   */
  function ngSessionRunFn($session) {
    $session.update();
  }

  /**
   * Configuration method.
   *
   * @param {Object} config The configuration object.
   */
  function configure(cfg) {
    /* Sets session update GET URL */
    if (ng.isString(cfg.updateUrl)) {
      config.updateUrl = cfg.updateUrl;
    }

    /* Sets sign in POST URL */
    if (ng.isString(cfg.signInUrl)) {
      config.signInUrl = cfg.signInUrl;
    }

    /* Sets sign out POST URL */
    if (ng.isString(cfg.signOutUrl)) {
      config.signOutUrl = cfg.signOutUrl;
    }
  }

  /**
   * ngSession provider definition.
   */
  var ngSessionProviderDef = {
    configure: configure,

    $get: [
      '$rootScope', '$http', '$q',

      ngSessionServiceFn
    ]
  };

  /**
   * ngSession provider function.
   */
  function ngSessionProviderFn() {
    return ngSessionProviderDef;
  }

  /* Define AngularJS module */
  ng.module('ngSession', [])

  /* Define service provider */
  .provider('ngSession', ngSessionProviderFn)

  /* Define AngularJS module run function */
  .run([
    'ngSession',

    ngSessionRunFn
  ]);

}(window));
<commit_msg>Updated source docs<commit_after>/**
 * ngSession
 *
 * @description Session handler module for AngularJS.
 * @module ngSession
 * @author Final Development Studio
 * @license MIT
 */

(function (window) {
  'use strict';

  var ng = window.angular;

  /* Default configuration */
  var config = {
    signOutUrl: '/api/users/sign-out',
    signInUrl: '/api/users/sign-in',
    updateUrl: '/api/session'
  };

  /**
   * ngSession service function.
   *
   * @private
   */
  function ngSessionServiceFn($rootScope, $http, $q) {
    $rootScope.session = {};

    /**
     * @module ngSession
     * @description ngSession Service
     */

    /**
     * Session update successful.
     *
     * @private
     */
    function onSessionUpdateSuccess(deferred, res) {
      $rootScope.session.user = res.data;

      deferred.resolve(res);
    }

    /**
     * On Sign In success.
     *
     * @private
     */
    function onSingInSuccess(deferred) {
      return update(null, deferred);
    }

    /**
     * On Sign Out success.
     *
     * @private
     */
    function onSingOutSuccess(deferred, res) {
      $rootScope.session.user = null;

      deferred.resolve(res);
    }

    /**
     * Signs a user in.
     *
     * It will perform a `POST` to the `config.signOutUrl` path and assign the
     * user object from the `res.data` object on success.
     *
     * @param {Object} data The data to send for sign in.
     * @param {Object} options Optional AngularJS HTTP request options.
     *
     * @returns {Promise} req The AngularJS HTTP promise. Will pass along the
     * request's `res` object.
     *
     * @example
     * ngSession.signIn($scope.data)
     *   .then(function (res) {})
     *   .catch(function (res) {})
     */
    function signIn(data, options) {
      var deferred = $q.defer();

      /* Remove previous user object */
      $rootScope.session.user = null;

      $http.post(config.signInUrl, data, options)
        .then(onSingInSuccess.bind(null, deferred))
        .catch(deferred.reject);

      return deferred.promise;
    }

    /**
     * Signs a user out.
     *
     * It will perform a POST to the `config.signOutUrl` path and delete the
     * user object on success.
     *
     * @param {Object} data The optional data to send for sign out.
     * @param {Object} options Optional AngularJS HTTP request options.
     *
     * @returns {Promise} req The AngularJS HTTP promise. Will pass along the
     * request's `res` object.
     *
     * @example
     * ngSession.signOut($scope.data)
     *   .then(function (res) {})
     *   .catch(function (res) {})
     */
    function signOut(data, options) {
      var deferred = $q.defer();

      $http.post(config.signOutUrl, data, options)
        .then(onSingOutSuccess.bind(null, deferred))
        .catch(deferred.reject);

      return deferred.promise;
    }

    /**
     * Updates the session user object.
     *
     * It will perform a GET to the `config.updateUrl` path and set the
     * session's user object on success with the request's `res.data`.
     *
     * @param {Object} options Optional AngularJS HTTP request options.
     * @param {Promise} deferred Optional deferred promise object.
     *
     * @returns {Promise} req The AngularJS HTTP promise. Will pass along the
     * request's `res` object.
     *
     * @example
     * ngSession.update($scope.data)
     *   .then(function (res) {})
     *   .catch(function (res) {})
     */
    function update(options, deferred) {
      if (!deferred) {
        deferred = $q.defer();
      }

      /* Retrieve current session */
      $http.get(config.updateUrl, options || {})
        .then(onSessionUpdateSuccess.bind(null, deferred))
        .catch(deferred.reject);

      return deferred.promise;
    }

    /**
     * Retrieves a user data value by property name.
     *
     * @param {String} prop The property name to retrieve.
     *
     * @returns {Mixed} value The property's value or the user object if no
     * property name is provided.
     *
     * @example ngSession.user('name'); // => 'John Smith'
     */
    function user(prop) {
      if (prop && $rootScope.session.user) {
        return $rootScope.session.user[prop];
      }

      return $rootScope.session.user;
    }

    /**
    * Sets a value in the session object.
    *
    * @param {String} prop The property name to set.
    * @param {Mixed} value The property value to set.
    *
    * @example ngSession.set('test', 'Test Value');
    */
    function set(prop, value) {
      $rootScope.session[prop] = value;
    }

    /**
     * Obtains a value from the session object.
     *
     * @param {String} prop The property name to obtain.
     *
     * @returns {Mixed} value The property's value.
     *
     * @example ngSession.get('test'); // => 'Test Value'
     */
    function get(prop) {
      return $rootScope.session[prop];
    }

    /**
     * Deletes a property from the session object.
     *
     * @param {String} prop The property name.
     *
     * @example
     * ngSession.del('test');
     * ngSession.get('test'); // => null
     */
    function del(prop) {
      delete $rootScope.session[prop];
    }

    /* Session service definition */
    var ngSessionServiceDef = {
      signOut: signOut,
      signIn: signIn,
      update: update,
      user: user,
      get: get,
      set: set,
      del: del
    };

    return ngSessionServiceDef;
  }

  /**
   * ngSession run function.
   *
   * @private
   */
  function ngSessionRunFn($session) {
    $session.update();
  }

  /**
   * @module ngSessionProvider
   * @description ngSession Provider
   */

  /**
   * Configuration method.
   *
   * @param {Object} config The configuration object.
   *
   * @example
   * ngSessionProvider.configure({
   *   signOutUrl: '/api/users/sign-out',
   *   signInUrl: '/api/users/sign-in',
   *   updateUrl: '/api/session'
   * });
   */
  function configure(cfg) {
    /* Sets session update GET URL */
    if (ng.isString(cfg.updateUrl)) {
      config.updateUrl = cfg.updateUrl;
    }

    /* Sets sign in POST URL */
    if (ng.isString(cfg.signInUrl)) {
      config.signInUrl = cfg.signInUrl;
    }

    /* Sets sign out POST URL */
    if (ng.isString(cfg.signOutUrl)) {
      config.signOutUrl = cfg.signOutUrl;
    }
  }

  /**
   * ngSession provider definition.
   *
   * @private
   */
  var ngSessionProviderDef = {
    configure: configure,

    $get: [
      '$rootScope', '$http', '$q',

      ngSessionServiceFn
    ]
  };

  /**
   * ngSession provider function.
   *
   * @private
   */
  function ngSessionProviderFn() {
    return ngSessionProviderDef;
  }

  /* Define AngularJS module */
  ng.module('ngSession', [])

  /* Define service provider */
  .provider('ngSession', ngSessionProviderFn)

  /* Define AngularJS module run function */
  .run([
    'ngSession',

    ngSessionRunFn
  ]);

}(window));
<|endoftext|>


<commit_before>(function() {

//  ---------------------------------------------------------------------------------------------------------------  //
//  no.View
//  ---------------------------------------------------------------------------------------------------------------  //

no.View = function() {};

//  ---------------------------------------------------------------------------------------------------------------  //

no.extend(no.View.prototype, no.Events);

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype.init = function(id, params) {
    this.id = id;
    this.params = params || {};

    this.info = no.View.info(id);

    this.key = no.View.getKey(id, params, this.info);

    //  Создаем нужные модели (или берем их из кэша, если они уже существуют).
    var model_ids = this.info.models;
    var models = this.models = {};
    for (var i = 0, l = model_ids.length; i < l; i++) {
        var model_id = model_ids[i];
        models[model_id] = no.Model.create(model_id, params);
    }

    //  Создаем подблоки.
    var view_ids = no.layout.view(id);
    var views = this.views = {};
    for (var view_id in view_ids) {
        if (view_ids[view_id] === 'box') {
            this._addBox(view_id, params);
        } else {
            this._addView(view_id, params);
        }
    }

    this.node = null;
    this.status = 'none';
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.getKey = function(id, params, info) {
    //  Ключ можно вычислить даже для неопределенных view,
    //  в частности, для боксов.
    info = info || no.View.info(id) || {};

    var key = 'view=' + id;

    var pNames = info.pNames || [];
    for (var i = 0, l = pNames.length; i < l; i++) {
        var pName = pNames[i];
        var pValue = params[pName];
        if (pValue != null) {
            key += '&' + pName + '=' + pValue;
        }
    }

    return key;
};

//  ---------------------------------------------------------------------------------------------------------------  //

var _infos = {};
var _ctors = {};

no.View.define = function(id, info, ctor) {
    info = info || {};
    ctor = ctor || no.View;

    var models = info.models = info.models || [];

    var params = {};
    for (var i = 0, l = models.length; i < l; i++) {
        no.extend( params, no.Model.info( models[i] ).params );
    }
    if (info.params) {
        no.extend(params, info.params);
    }
    info.pNames = no.object.keys(params);

    _infos[id] = info;
    _ctors[id] = ctor;
};

no.View.info = function(id) {
    return _infos[id];
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.create = function(id, params) {
    var ctor = _ctors[id];
    var view = new ctor();
    view.init(id, params);

    return view;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype._getView = function(id, params) {
    var key = no.View.getKey(id, params);
    return this.views[key];
};

no.View.prototype._addView = function(id, params) {
    var view = this._getView(id, params);
    if (!view) {
        view = no.View.create(id, params);
        this.views[view.id] = view;
    }
    return view;
};

no.View.prototype._addBox = function(id, params) {
    var box = this._getView(id, params);
    if (!box) {
        box = new no.Box(id, params);
        this.views[box.id] = box;
    }
    return box;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype._hide = function() {
    if (this._isDummy) {
        return;
    }

    if (this._visible === true) {
        console.log('hide', this.id);
        this.node.style.display = 'none';
        this._visible = false;
        this.onhide();
    }
};

//  При создании блока у него this._visible === undefined.
no.View. prototype._show = function() {
    if (this._isDummy) {
        return;
    }

    if (this._visible !== true) {
        console.log('show', this.id);
        this.node.style.display = '';
        this._visible = true;
        this.onshow();
    }
};

//  ---------------------------------------------------------------------------------------------------------------  //

function log(s) {
    return function() {
        console.log(s, this.id);
    }
};

no.View.prototype.onhtmlinit = no.pe; // log('onhtmlinit');

no.View.prototype.onhtmldestroy = no.pe; // log('onhtmldestroy');

no.View.prototype.onshow = no.pe; // log('onshow');

no.View.prototype.onhide = no.pe; // log('onhide');

no.View.prototype.onrepaint = no.pe; // log('onrepaint');

//  ---------------------------------------------------------------------------------------------------------------  //

//  При описании view можно задать поле events в виде:
//
//  events: {
//      'click a.foo': 'doFoo',
//      'keyup': function(e) { ... }
//  }
//
no.View.prototype._bindEvents = function() {
    var $node = $(this.node);

    var attachedEvents = this._attachedEvents = [];

    var that = this;
    var events = this.info.events = {};

    for (var event in events) {
        (function(event) {
            // Метод -- это либо строка с именем нужного метода, либо же функция.
            var method = events[event];
            if (typeof method === 'string') {
                method = that[method];
            }

            // Делим строку с event на имя события и опциональный селектор.
            var parts = event.split(/\s/);
            var name = parts.shift();
            var selector = parts.join(' ');

            var handler = function() {
                //  FIXME: Откуда это взялось?!
                // Не теряем остальные аргументы.
                var args = Array.prototype.slice.call(arguments, 0);
                return method.apply(that, args);
            };

            // Вешаем событие.
            if (selector) {
                $node.on(name, selector, handler);
            } else {
                $node.on(name, handler);
            }

            // Запоминаем, что повесили, чтобы знать потом, что удалять в _unbindEvents.
            attachedEvents.push({
                name: name,
                selector: selector,
                handler: handler
            });
        }(event));
    }
};

no.View.prototype._unbindEvents = function() {
    var $node = $(this.node);

    var attachedEvents = this._attachedEvents = [];

    for (var event in attachedEvents) {
        $node.off(event.name, event.selector, event.handler);
    }

    this._attachedEvents = null;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype.isValid = function() {
    return ( this.status === 'ok' && this.isModelsValid() );
};

no.View.prototype.isModelsValid = function() {
    var models = this.models;
    for (var id in models) {
        if ( !models[id].isValid() ) {
            return false;
        }
    }

    return true;
};

//  ---------------------------------------------------------------------------------------------------------------  //

//  Вызываем callback для всех подблоков.
no.View.prototype._apply = function(callback) {
    var views = this.views;
    for (var id in views) {
        callback(views[id], id);
    }
};

//  ---------------------------------------------------------------------------------------------------------------  //

//  Рекурсивно проходимся по дереву блоков (построенному по layout) и выбираем новые блоки или
//  требующие перерисовки. Раскладываем их в две "кучки": sync и async.
no.View.prototype._getRequestViews = function(updated, layout, params) {
    if  ( !this.isValid() ) {
        if (layout === false) {
            updated.async.push(this);
        } else {
            updated.sync.push(this);
        }
    }

    this._apply(function(view, id) {
        view._getRequestViews(updated, layout[id], params);
    });

    return updated;
};

//  ---------------------------------------------------------------------------------------------------------------  //

//  Строим дерево для шаблонизатора.
//
//  В tree.views будет дерево блоков, которые нужно сгенерить,
//  причем на верхнем уровне будут т.н. toplevel-блоки --
//  это невалидные блоки и выше их все блоки валидны.
//  В частности, это значит, что если блок невалидный, то он будет перерисован
//  со всеми своими подблоками.
//
//  В tree.models будут все модели, требуемые для этих блоков.
//
no.View.prototype._getUpdateTree = function(tree, layout, params) {
    if ( !this.isValid() ) {
        tree.views[this.id] = this._getViewTree(tree.models, layout, params);
    } else {
        this._apply(function(view, id) {
            view._getUpdateTree(tree, layout[id], params);
        });
    }

    return tree;
};

//  Строим дерево блоков, попутно собираем все нужные для него модели.
no.View.prototype._getViewTree = function(models, layout, params) {
    //  Если это асинхронный блок и для него на самом деле нет еще всех моделей,
    //  помечаем его как асинхронный (false).
    //  Но может случиться так, что асинхронный запрос пришел раньше синхронного,
    //  тогда этот асинхронный блок будет нарисован вместе с остальными синхронными блоками.
    if ( layout === false && !this.isModelsValid() ) {
        return false;
    }

    //  Собираем все нужные модели.
    for (var id in this.models) {
        var model = this.models[id];
        //  FIXME: Кажется, эта провека тут не нужна.
        //  Нужно перед началом апдейта проверять, что все данные в наличии.
        if ( model.isValid() ) {
            models[model.id] = model.getData();
        }
    }

    //  Это блок без подблоков и он не асинхронный.
    if (typeof layout !== 'object') {
        return true;
    }

    //  Собираем дерево рекурсивно из подблоков.
    var tree = {};
    this._apply(function(view, id) {
        tree[id] = view._getViewTree(models, layout[id], params);
    });

    return tree;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype._getDescendants = function(views) {
    views.push(this);
    this._apply(function(view) {
        view._getDescendants(views);
    });

    return views;
};

//  ---------------------------------------------------------------------------------------------------------------  //

//  Обновляем (если нужно) ноду блока.
no.View.prototype._updateHTML = function(node, layout, params, options) {
    var toplevel = options.toplevel;
    var async = options.async;

    var wasDummy = isDummy(this.node);

    var viewNode;
    //  Если блок уже валидный, ничего не делаем, идем ниже по дереву.
    if ( !this.isValid() ) {
        //  Ищем новую ноду блока.
        viewNode = no.byClass('view-' + this.id, node)[0];
        if (viewNode) {
            //  toplevel-блок -- это невалидный блок, выше которого все блоки валидны.
            //  Для таких блоков нужно вставить их ноду в DOM, а все его подблоки
            //  автоматически попадут на нужное место.
            if (toplevel) {
                if (!wasDummy) {
                    this._unbindEvents();
                    this.onhtmldestroy();
                }

                //  Старая нода показывает место, где должен быть блок.
                //  Если старой ноды нет, то это блок, который вставляется в бокс.
                if (this.node) {
                    no.replaceNode(this.node, viewNode);
                }
                //  Все подблоки уже не toplevel.
                toplevel = false;
            }
            //  Запоминаем новую ноду.
            this.node = viewNode;

            this._isDummy = isDummy(viewNode);

            if (!this._isDummy) {
                this.status = 'ok';

                this._bindEvents();
                this.onhtmlinit();
            }
        } else {
            //  FIXME: А что делать, если новой ноды не обнаружено?
        }
    }

    //  В асинхронном запросе вызываем onrepaint для блоков, которые были заглушкой.
    //  В синхронном запросе вызывает onrepaint для всех блоков.
    //  Кроме того, не вызываем onrepaint для все еще заглушек.
    //
    //  FIXME: Может тут нужно просто проверять this.status === 'ok' ?
    if ( !this._isDummy && ( (async && wasDummy) || !async) ) {
        this.onrepaint();
    }

    //  Т.к. мы, возможно, сделали replaceNode, то внутри node уже может не быть
    //  никаких подблоков. В этом случае, нужно брать viewNode.
    viewNode = viewNode || node;

    //  Рекурсивно идем вниз по дереву.
    this._apply(function(view, id) {
        view._updateHTML(viewNode, layout[id], params, {
            toplevel: toplevel,
            async: async
        });
    });

    function isDummy(node) {
        return node && !!node.getAttribute('dummy');
    }
};

//  ---------------------------------------------------------------------------------------------------------------  //

})();

<commit_msg>view._setNode<commit_after>(function() {

//  ---------------------------------------------------------------------------------------------------------------  //
//  no.View
//  ---------------------------------------------------------------------------------------------------------------  //

no.View = function() {};

//  ---------------------------------------------------------------------------------------------------------------  //

no.extend(no.View.prototype, no.Events);

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype.init = function(id, params) {
    this.id = id;
    this.params = params || {};

    this.info = no.View.info(id);

    this.key = no.View.getKey(id, params, this.info);

    //  Создаем нужные модели (или берем их из кэша, если они уже существуют).
    var model_ids = this.info.models;
    var models = this.models = {};
    for (var i = 0, l = model_ids.length; i < l; i++) {
        var model_id = model_ids[i];
        models[model_id] = no.Model.create(model_id, params);
    }

    //  Создаем подблоки.
    var view_ids = no.layout.view(id);
    var views = this.views = {};
    for (var view_id in view_ids) {
        if (view_ids[view_id] === 'box') {
            this._addBox(view_id, params);
        } else {
            this._addView(view_id, params);
        }
    }

    this.node = null;
    this.status = 'none';
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.getKey = function(id, params, info) {
    //  Ключ можно вычислить даже для неопределенных view,
    //  в частности, для боксов.
    info = info || no.View.info(id) || {};

    var key = 'view=' + id;

    var pNames = info.pNames || [];
    for (var i = 0, l = pNames.length; i < l; i++) {
        var pName = pNames[i];
        var pValue = params[pName];
        if (pValue != null) {
            key += '&' + pName + '=' + pValue;
        }
    }

    return key;
};

//  ---------------------------------------------------------------------------------------------------------------  //

var _infos = {};
var _ctors = {};

no.View.define = function(id, info, ctor) {
    info = info || {};
    ctor = ctor || no.View;

    var models = info.models = info.models || [];

    var params = {};
    for (var i = 0, l = models.length; i < l; i++) {
        no.extend( params, no.Model.info( models[i] ).params );
    }
    if (info.params) {
        no.extend(params, info.params);
    }
    info.pNames = no.object.keys(params);

    _infos[id] = info;
    _ctors[id] = ctor;
};

no.View.info = function(id) {
    return _infos[id];
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.create = function(id, params) {
    var ctor = _ctors[id];
    var view = new ctor();
    view.init(id, params);

    return view;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype._getView = function(id, params) {
    var key = no.View.getKey(id, params);
    return this.views[key];
};

no.View.prototype._addView = function(id, params) {
    var view = this._getView(id, params);
    if (!view) {
        view = no.View.create(id, params);
        this.views[view.id] = view;
    }
    return view;
};

no.View.prototype._addBox = function(id, params) {
    var box = this._getView(id, params);
    if (!box) {
        box = new no.Box(id, params);
        this.views[box.id] = box;
    }
    return box;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype._hide = function() {
    if (this._isDummy) {
        return;
    }

    if (this._visible === true) {
        this.node.style.display = 'none';
        this._visible = false;
        this.onhide();
    }
};

//  При создании блока у него this._visible === undefined.
no.View. prototype._show = function() {
    if (this._isDummy) {
        return;
    }

    if (this._visible !== true) {
        this.node.style.display = '';
        this._visible = true;
        this.onshow();
    }
};

//  ---------------------------------------------------------------------------------------------------------------  //

function log(s) {
    return function() {
        console.log(s, this.id);
    }
};

no.View.prototype.onhtmlinit = no.pe; // log('onhtmlinit');

no.View.prototype.onhtmldestroy = no.pe; // log('onhtmldestroy');

no.View.prototype.onshow = no.pe; // log('onshow');

no.View.prototype.onhide = no.pe; // log('onhide');

no.View.prototype.onrepaint = no.pe; // log('onrepaint');

//  ---------------------------------------------------------------------------------------------------------------  //

//  При описании view можно задать поле events в виде:
//
//  events: {
//      'click a.foo': 'doFoo',
//      'keyup': function(e) { ... }
//  }
//
no.View.prototype._bindEvents = function() {
    var $node = $(this.node);

    var attachedEvents = this._attachedEvents = [];

    var that = this;
    var events = this.info.events = {};

    for (var event in events) {
        (function(event) {
            // Метод -- это либо строка с именем нужного метода, либо же функция.
            var method = events[event];
            if (typeof method === 'string') {
                method = that[method];
            }

            // Делим строку с event на имя события и опциональный селектор.
            var parts = event.split(/\s/);
            var name = parts.shift();
            var selector = parts.join(' ');

            var handler = function() {
                //  FIXME: Откуда это взялось?!
                // Не теряем остальные аргументы.
                var args = Array.prototype.slice.call(arguments, 0);
                return method.apply(that, args);
            };

            // Вешаем событие.
            if (selector) {
                $node.on(name, selector, handler);
            } else {
                $node.on(name, handler);
            }

            // Запоминаем, что повесили, чтобы знать потом, что удалять в _unbindEvents.
            attachedEvents.push({
                name: name,
                selector: selector,
                handler: handler
            });
        }(event));
    }
};

no.View.prototype._unbindEvents = function() {
    var $node = $(this.node);

    var attachedEvents = this._attachedEvents = [];

    for (var event in attachedEvents) {
        $node.off(event.name, event.selector, event.handler);
    }

    this._attachedEvents = null;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype.isValid = function() {
    return ( this.status === 'ok' && this.isModelsValid() );
};

no.View.prototype.isModelsValid = function() {
    var models = this.models;
    for (var id in models) {
        if ( !models[id].isValid() ) {
            return false;
        }
    }

    return true;
};

//  ---------------------------------------------------------------------------------------------------------------  //

//  Вызываем callback для всех подблоков.
no.View.prototype._apply = function(callback) {
    var views = this.views;
    for (var id in views) {
        callback(views[id], id);
    }
};

//  ---------------------------------------------------------------------------------------------------------------  //

//  Рекурсивно проходимся по дереву блоков (построенному по layout) и выбираем новые блоки или
//  требующие перерисовки. Раскладываем их в две "кучки": sync и async.
no.View.prototype._getRequestViews = function(updated, layout, params) {
    if  ( !this.isValid() ) {
        if (layout === false) {
            updated.async.push(this);
        } else {
            updated.sync.push(this);
        }
    }

    this._apply(function(view, id) {
        view._getRequestViews(updated, layout[id], params);
    });

    return updated;
};

//  ---------------------------------------------------------------------------------------------------------------  //

//  Строим дерево для шаблонизатора.
//
//  В tree.views будет дерево блоков, которые нужно сгенерить,
//  причем на верхнем уровне будут т.н. toplevel-блоки --
//  это невалидные блоки и выше их все блоки валидны.
//  В частности, это значит, что если блок невалидный, то он будет перерисован
//  со всеми своими подблоками.
//
//  В tree.models будут все модели, требуемые для этих блоков.
//
no.View.prototype._getUpdateTree = function(tree, layout, params) {
    if ( !this.isValid() ) {
        tree.views[this.id] = this._getViewTree(tree.models, layout, params);
    } else {
        this._apply(function(view, id) {
            view._getUpdateTree(tree, layout[id], params);
        });
    }

    return tree;
};

//  Строим дерево блоков, попутно собираем все нужные для него модели.
no.View.prototype._getViewTree = function(models, layout, params) {
    //  Если это асинхронный блок и для него на самом деле нет еще всех моделей,
    //  помечаем его как асинхронный (false).
    //  Но может случиться так, что асинхронный запрос пришел раньше синхронного,
    //  тогда этот асинхронный блок будет нарисован вместе с остальными синхронными блоками.
    if ( layout === false && !this.isModelsValid() ) {
        return false;
    }

    //  Собираем все нужные модели.
    for (var id in this.models) {
        var model = this.models[id];
        //  FIXME: Кажется, эта провека тут не нужна.
        //  Нужно перед началом апдейта проверять, что все данные в наличии.
        if ( model.isValid() ) {
            models[model.id] = model.getData();
        }
    }

    //  Это блок без подблоков и он не асинхронный.
    if (typeof layout !== 'object') {
        return true;
    }

    //  Собираем дерево рекурсивно из подблоков.
    var tree = {};
    this._apply(function(view, id) {
        tree[id] = view._getViewTree(models, layout[id], params);
    });

    return tree;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype._getDescendants = function(views) {
    views.push(this);
    this._apply(function(view) {
        view._getDescendants(views);
    });

    return views;
};

//  ---------------------------------------------------------------------------------------------------------------  //

no.View.prototype._setNode = function(node) {
    this.node = node;
    this._isDummy = !!node.getAttribute('dummy');
};

//  ---------------------------------------------------------------------------------------------------------------  //

//  Обновляем (если нужно) ноду блока.
no.View.prototype._updateHTML = function(node, layout, params, options) {
    var toplevel = options.toplevel;
    var async = options.async;

    var wasDummy = this._isDummy;

    var viewNode;
    //  Если блок уже валидный, ничего не делаем, идем ниже по дереву.
    if ( !this.isValid() ) {
        //  Ищем новую ноду блока.
        viewNode = no.byClass('view-' + this.id, node)[0];
        if (viewNode) {
            //  toplevel-блок -- это невалидный блок, выше которого все блоки валидны.
            //  Для таких блоков нужно вставить их ноду в DOM, а все его подблоки
            //  автоматически попадут на нужное место.
            if (toplevel) {
                if (!wasDummy) {
                    this._unbindEvents();
                    this.onhtmldestroy();
                }

                //  Старая нода показывает место, где должен быть блок.
                //  Если старой ноды нет, то это блок, который вставляется в бокс.
                if (this.node) {
                    no.replaceNode(this.node, viewNode);
                }
                //  Все подблоки уже не toplevel.
                toplevel = false;
            }
            //  Запоминаем новую ноду.
            /*
            this.node = viewNode;

            this._isDummy = isDummy(viewNode);
            */
            this._setNode(viewNode);

            if (!this._isDummy) {
                this.status = 'ok';

                this._bindEvents();
                this.onhtmlinit();
            }
        } else {
            //  FIXME: А что делать, если новой ноды не обнаружено?
        }
    }

    //  В асинхронном запросе вызываем onrepaint для блоков, которые были заглушкой.
    //  В синхронном запросе вызывает onrepaint для всех блоков.
    //  Кроме того, не вызываем onrepaint для все еще заглушек.
    if ( !this._isDummy && ( (async && wasDummy) || !async) ) {
        this.onrepaint();
    }

    //  Т.к. мы, возможно, сделали replaceNode, то внутри node уже может не быть
    //  никаких подблоков. В этом случае, нужно брать viewNode.
    viewNode = viewNode || node;

    //  Рекурсивно идем вниз по дереву.
    this._apply(function(view, id) {
        view._updateHTML(viewNode, layout[id], params, {
            toplevel: toplevel,
            async: async
        });
    });
};

//  ---------------------------------------------------------------------------------------------------------------  //

})();

<|endoftext|>


<commit_before>const zlib = require('zlib');
const http = require('http');
const https = require('https');
const URL = require('url');
const Stream = require('stream');
const FormData = require('./FormData');

const transports = {
  http,
  https,
  file: require('./transports/file'),
  http2: require('./transports/http2'),
};

const agents = {};

function buildRequest(method, url) {
  const options = URL.parse(url);
  if (!options.protocol) throw new Error('URL must have a valid protocol');
  const transport = this.options.version === 2 ? transports.http2 : transports[options.protocol.replace(':', '')];
  options.method = method.toUpperCase();
  if (this.options.headers) options.headers = this.options.headers;
  if (this.options.agent) {
    options.agent = this.options.agent;
  } else if (transport.Agent && this.options.followRedirects !== false) {
    options.agent = agents[options.hostname] = agents[options.hostname] || new transport.Agent({ keepAlive: true });
  }
  if (options.port) options.port = parseInt(options.port);
  this.options._req = options;
  const request = transport.request(options);
  if (request.setNoDelay) request.setNoDelay(true);
  return request;
}

function finalizeRequest() {
  return new Promise((resolve, reject) => {
    const request = this.request;

    let socket;

    const handleError = (err) => {
      if (!err) err = new Error('Unknown error occured');
      err.request = request;
      reject(err);
      delete agents[this.options._req.hostname];
      if (socket) socket.removeListener('error', handleError);
    };

    request.once('abort', handleError);
    request.once('error', handleError);
    request.once('socket', (s) => {
      socket = s;
      s.once('error', handleError);
    });

    request.once('response', (response) => {
      if (socket) socket.removeListener('error', handleError);
      let stream = response;
      if (this._shouldUnzip(response)) {
        stream = response.pipe(zlib.createUnzip({
          flush: zlib.Z_SYNC_FLUSH,
          finishFlush: zlib.Z_SYNC_FLUSH,
        }));
      }

      const body = [];

      stream.on('data', (chunk) => {
        if (!this.push(chunk)) this.pause();
        body.push(chunk);
      });

      stream.once('end', () => {
        this.push(null);
        const raw = Buffer.concat(body);

        let redirect = false;
        if (this._shouldRedirect(response)) {
          redirect = /^https?:\/\//i.test(response.headers.location) ?
            response.headers.location :
            URL.resolve(makeURLFromRequest(request), response.headers.location);
        }
        if (!redirect && agents[this.options._req.hostname]) delete agents[this.options._req.hostname];
        resolve({ response, raw, redirect });
      });
    });

    this._finalizeRequest();
    let data = this.data;
    if (data && data.end) data = data.end();
    if (Array.isArray(data)) {
      for (const chunk of data) request.write(chunk);
      request.end();
    } else if (data instanceof Stream) {
      data.pipe(request);
    } else if (data instanceof Buffer) {
      request.end(data);
    } else if (data) {
      request.end(data);
    } else {
      request.end();
    }
  });
}

function shouldSendRaw(data) {
  return data instanceof Buffer || data instanceof Stream;
}

function makeURLFromRequest(request) {
  return URL.format({
    protocol: request.connection.encrypted ? 'https:' : 'http:',
    hostname: request.getHeader('host'),
    pathname: request.path.split('?')[0],
    query: request.query,
  });
}

module.exports = {
  buildRequest, finalizeRequest, shouldSendRaw,
  METHODS: http.METHODS,
  STATUS_CODES: http.STATUS_CODES,
  FormData,
  Extension: Stream.Readable,
};
<commit_msg>more scoping of agent key<commit_after>const zlib = require('zlib');
const http = require('http');
const https = require('https');
const URL = require('url');
const Stream = require('stream');
const FormData = require('./FormData');

const transports = {
  http,
  https,
  file: require('./transports/file'),
  http2: require('./transports/http2'),
};

const agents = {};
const kagent = (r) => `${r.protocol}${r.host}`;

function buildRequest(method, url) {
  const options = URL.parse(url);
  if (!options.protocol) throw new Error('URL must have a valid protocol');
  const transport = this.options.version === 2 ? transports.http2 : transports[options.protocol.replace(':', '')];
  options.method = method.toUpperCase();
  if (this.options.headers) options.headers = this.options.headers;
  if (this.options.agent) {
    options.agent = this.options.agent;
  } else if (transport.Agent && this.options.followRedirects !== false) {
    const key = kagent(options);
    options.agent = agents[key] = agents[key] || new transport.Agent({ keepAlive: true });
  }
  if (options.port) options.port = parseInt(options.port);
  this.options._req = options;
  const request = transport.request(options);
  if (request.setNoDelay) request.setNoDelay(true);
  return request;
}

function finalizeRequest() {
  return new Promise((resolve, reject) => {
    const request = this.request;

    let socket;

    const handleError = (err) => {
      if (!err) err = new Error('Unknown error occured');
      err.request = request;
      reject(err);
      delete agents[kagent(this.options._req)];
      if (socket) socket.removeListener('error', handleError);
    };

    request.once('abort', handleError);
    request.once('error', handleError);
    request.once('socket', (s) => {
      socket = s;
      s.once('error', handleError);
    });

    request.once('response', (response) => {
      if (socket) socket.removeListener('error', handleError);
      let stream = response;
      if (this._shouldUnzip(response)) {
        stream = response.pipe(zlib.createUnzip({
          flush: zlib.Z_SYNC_FLUSH,
          finishFlush: zlib.Z_SYNC_FLUSH,
        }));
      }

      const body = [];

      stream.on('data', (chunk) => {
        if (!this.push(chunk)) this.pause();
        body.push(chunk);
      });

      stream.once('end', () => {
        this.push(null);
        const raw = Buffer.concat(body);

        let redirect = false;
        if (this._shouldRedirect(response)) {
          redirect = /^https?:\/\//i.test(response.headers.location) ?
            response.headers.location :
            URL.resolve(makeURLFromRequest(request), response.headers.location);
        }
        const key = kagent(this.options._req);
        if (!redirect && agents[key]) delete agents[key];
        resolve({ response, raw, redirect });
      });
    });

    this._finalizeRequest();
    let data = this.data;
    if (data && data.end) data = data.end();
    if (Array.isArray(data)) {
      for (const chunk of data) request.write(chunk);
      request.end();
    } else if (data instanceof Stream) {
      data.pipe(request);
    } else if (data instanceof Buffer) {
      request.end(data);
    } else if (data) {
      request.end(data);
    } else {
      request.end();
    }
  });
}

function shouldSendRaw(data) {
  return data instanceof Buffer || data instanceof Stream;
}

function makeURLFromRequest(request) {
  return URL.format({
    protocol: request.connection.encrypted ? 'https:' : 'http:',
    hostname: request.getHeader('host'),
    pathname: request.path.split('?')[0],
    query: request.query,
  });
}

module.exports = {
  buildRequest, finalizeRequest, shouldSendRaw,
  METHODS: http.METHODS,
  STATUS_CODES: http.STATUS_CODES,
  FormData,
  Extension: Stream.Readable,
};
<|endoftext|>


<commit_before>define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/window",
        "dojo/_base/config", "dojo/dom-construct"
], function(lang, declare, window, config, construct){

	/*=====
	dojo.mixin(djConfig,{
		// urchin: String
		//		Used by `dojox.analytics.Urchin` as the default UA-123456-7 account
		//		number used when being created. Alternately, you can pass an acct:""
		//		parameter to the constructor a la: new dojox.analytics.Urchin({ acct:"UA-123456-7" });
		urchin: ""
	});
	=====*/

	return declare("dojox.analytics.Urchin", null, {
		// summary: A Google-analytics helper, for post-onLoad inclusion of the tracker, and
		//		dynamic tracking during long-lived page cycles.
		//
		// description:
		//		A small class object will allows for lazy-loading the Google Analytics API
		//		at any point during a page lifecycle. Most commonly, Google-Analytics is loaded
		//		via a synchronous script tag in the body, which causes `dojo.addOnLoad` to
		//		stall until the external API has been completely loaded. The Urchin helper
		//		will load the API on the fly, and provide a convenient API to use, wrapping
		//		Analytics for Ajaxy or single page applications.
		//
		//		The class can be instantiated two ways: Programatically, by passing an
		//		`acct:` parameter, or via Markup / dojoType and defining a djConfig
		//		parameter `urchin:`
		//
		//		IMPORTANT:
		//		This module will not work simultaneously with the core dojox.analytics
		//		package. If you need the ability to run Google Analytics AND your own local
		//		analytics system, you MUST include dojox.analytics._base BEFORE dojox.analytics.Urchin
		//
		//	example:
		//	|	// create the tracker programatically:
		//	|	var tracker = new dojox.analytics.Urchin({ acct:"UA-123456-7" });
		//
		//	example:
		//	|	// define the urchin djConfig option:
		//	|	var djConfig = { urchin: "UA-123456-7" };
		//	|
		//	|	// and in markup:
		//	|

		//	|	// or code:
		//	|	new dojox.analytics.Urchin();
		//
		//	example:
		//	|	// create and define all analytics with one tag.
		//	|

		//
		// acct: String
		//		your GA urchin tracker account number. Overrides `djConfig.urchin`
		acct: "",

		constructor: function(args){
			// summary:
			//		Initialize this Urchin instance. Immediately starts the load
			//		sequence, so defer construction until (ideally) after onLoad and
			//		potentially widget parsing.
			this.tracker = null;
			lang.mixin(this, args);
			this.acct = this.acct || config.urchin;

			var re = /loaded|complete/,
				gaHost = ("https:" == window.doc.location.protocol) ? "https://ssl." : "http://www.",
				h = window.doc.getElementsByTagName("head")[0],
				n = construct.create('script', {
					src: gaHost + "google-analytics.com/ga.js"
				}, h);

			n.onload = n.onreadystatechange = lang.hitch(this, function(e){
				if(e && e.type == "load" || re.test(n.readyState)){
					n.onload = n.onreadystatechange = null;
					this._gotGA();
					h.removeChild(n);
				}
			});

		},

		_gotGA: function(){
			// summary: initialize the tracker
			this.tracker = _gat._getTracker(this.acct);
			this.GAonLoad.apply(this, arguments);
		},

		GAonLoad: function(){
			// summary:
			//		Stub function to fire when urchin is complete
			//	description:
			//		This function is executed when the tracker variable is
			//		complete and initialized. The initial trackPageView (with
			//		no arguments) is called here as well, so remeber to call
			//		manually if overloading this method.
			//
			//	example:
			//	Create an Urchin tracker that will track a specific page on init
			//	after page load (or parsing, if parseOnLoad is true)
			//	|	dojo.addOnLoad(function(){
			//	|		new dojox.ananlytics.Urchin({
			//	|			acct:"UA-12345-67",
			//	|			GAonLoad: function(){
			//	|				this.trackPageView("/custom-page");
			//	|			}
			//	|		});
			//	|	});

			this.trackPageView();
		},

		trackPageView: function(/* string */url){
			// summary: A public API attached to this widget instance, allowing you
			//		Ajax-like notification of updates.
			//
			//	url: String
			//		A location to tell the tracker to track, eg: "/my-ajaxy-endpoint"
			//
			//	example:
			//	Track clicks from a container of anchors and populate a `ContentPane`
			//	|	// 'tracker' is our `Urchin` instance, pane is the `ContentPane` ref.
			//	|	dojo.connect(container, "onclick", function(e){
			//	|		var ref = dojo.attr(e.target, "href");
			//	|		tracker.trackPageView(ref);
			//	|		pane.attr("href", ref);
			//	|	});

			this.tracker._trackPageview.apply(this, arguments);
		}

	});
});
<commit_msg>Fix #14534. Thanks to alerque for the patch.<commit_after>define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/window",
        "dojo/_base/config", "dojo/dom-construct"
], function(lang, declare, window, config, construct){

	/*=====
	dojo.mixin(djConfig,{
		// urchin: String
		//		Used by `dojox.analytics.Urchin` as the default UA-123456-7 account
		//		number used when being created. Alternately, you can pass an acct:""
		//		parameter to the constructor a la: new dojox.analytics.Urchin({ acct:"UA-123456-7" });
		urchin: ""
	});
	=====*/

	return declare("dojox.analytics.Urchin", null, {
		// summary: A Google-analytics helper, for post-onLoad inclusion of the tracker, and
		//		dynamic tracking during long-lived page cycles.
		//
		// description:
		//		A small class object will allows for lazy-loading the Google Analytics API
		//		at any point during a page lifecycle. Most commonly, Google-Analytics is loaded
		//		via a synchronous script tag in the body, which causes `dojo.addOnLoad` to
		//		stall until the external API has been completely loaded. The Urchin helper
		//		will load the API on the fly, and provide a convenient API to use, wrapping
		//		Analytics for Ajaxy or single page applications.
		//
		//		The class can be instantiated two ways: Programatically, by passing an
		//		`acct:` parameter, or via Markup / dojoType and defining a djConfig
		//		parameter `urchin:`
		//
		//		IMPORTANT:
		//		This module will not work simultaneously with the core dojox.analytics
		//		package. If you need the ability to run Google Analytics AND your own local
		//		analytics system, you MUST include dojox.analytics._base BEFORE dojox.analytics.Urchin
		//
		//	example:
		//	|	// create the tracker programatically:
		//	|	var tracker = new dojox.analytics.Urchin({ acct:"UA-123456-7" });
		//
		//	example:
		//	|	// define the urchin djConfig option:
		//	|	var djConfig = { urchin: "UA-123456-7" };
		//	|
		//	|	// and in markup:
		//	|

		//	|	// or code:
		//	|	new dojox.analytics.Urchin();
		//
		//	example:
		//	|	// create and define all analytics with one tag.
		//	|

		//
		// acct: String
		//		your GA urchin tracker account number. Overrides `djConfig.urchin`
		acct: "",

		constructor: function(args){
			// summary:
			//		Initialize this Urchin instance. Immediately starts the load
			//		sequence, so defer construction until (ideally) after onLoad and
			//		potentially widget parsing.
			this.tracker = null;
			lang.mixin(this, args);
			this.acct = this.acct || config.urchin;

			var re = /loaded|complete/,
				gaHost = ("https:" == window.doc.location.protocol) ? "https://ssl." : "http://www.",
				h = window.doc.getElementsByTagName("head")[0],
				n = construct.create('script', {
					src: gaHost + "google-analytics.com/ga.js"
				}, h);

			n.onload = n.onreadystatechange = lang.hitch(this, function(e){
				if(e && e.type == "load" || re.test(n.readyState)){
					n.onload = n.onreadystatechange = null;
					this._gotGA();
					h.removeChild(n);
				}
			});

		},

		_gotGA: function(){
			// summary: initialize the tracker
			this.tracker = _gat._getTracker(this.acct);
			this.GAonLoad.apply(this, arguments);
		},

		GAonLoad: function(){
			// summary:
			//		Stub function to fire when urchin is complete
			//	description:
			//		This function is executed when the tracker variable is
			//		complete and initialized. The initial trackPageView (with
			//		no arguments) is called here as well, so remeber to call
			//		manually if overloading this method.
			//
			//	example:
			//	Create an Urchin tracker that will track a specific page on init
			//	after page load (or parsing, if parseOnLoad is true)
			//	|	dojo.addOnLoad(function(){
			//	|		new dojox.ananlytics.Urchin({
			//	|			acct:"UA-12345-67",
			//	|			GAonLoad: function(){
			//	|				this.trackPageView("/custom-page");
			//	|			}
			//	|		});
			//	|	});

			this.trackPageView();
		},

		trackPageView: function(/* string */url){
			// summary: A public API attached to this widget instance, allowing you
			//		Ajax-like notification of updates.
			//
			//	url: String
			//		A location to tell the tracker to track, eg: "/my-ajaxy-endpoint"
			//
			//	example:
			//	Track clicks from a container of anchors and populate a `ContentPane`
			//	|	// 'tracker' is our `Urchin` instance, pane is the `ContentPane` ref.
			//	|	dojo.connect(container, "onclick", function(e){
			//	|		var ref = dojo.attr(e.target, "href");
			//	|		tracker.trackPageView(ref);
			//	|		pane.attr("href", ref);
			//	|	});

			this.tracker._trackPageview.apply(this.tracker, arguments);
		}

	});
});
<|endoftext|>


<commit_before>(function() {

    /**
     * Делает запрос моделей с сервера.
     * Аргументы можно передавать в 3 форматах:
     *   - string item, params - одна модель и опциональные параметры для нее
     *   - array item[], params - массив моделей и опциональные единые для всех параметры
     *   - array item[] - массив моделей вида {id: modelName, params: modelParams}
     * @param {String|Array|Object} items Массив названий моделей.
     * @param {object} [params] Параметры моделей.
     * @param {object} [options] Опции запроса.
     * @param {Boolean} [options.forced=false] Не учитывать закешированность моделей при запросе.
     * @returns {Vow.Promise}
     * @namespace
     */
    ns.request = function(items, params, options) {
        return ns.request.models(items2models(items, params), options);
    };

    /**
     * Делает запрос моделей с сервера, не учитывая их закешированности.
     * @see ns.request
     * @param {String|Array|Object} items Массив названий моделей.
     * @param {object} [params] Параметры моделей.
     * @param {object} [options] Опции запроса.
     * @param {Boolean} [options.forced=false] Не учитывать закешированность моделей при запросе.
     * @returns {Vow.Promise}
     * @memberOf ns.request
     * @name forcedRequest
     */
    ns.forcedRequest = function(items, params, options) {
        options = options || {};
        options.forced = true;
        return ns.request.models(items2models(items, params), options);
    };

    /**
     * Делает запрос моделей с сервера.
     * @param {ns.Model[]} models Массив моделей.
     * @param {object} [options] Опции запроса.
     * @param {Boolean} [options.forced=false] Не учитывать закешированность моделей при запросе.
     * @returns {Vow.Promise}
     */
    ns.request.models = function(models, options) {

        // Загрузка порционных данных. В этом случае грузим не саму модель, а порцию данных.
        models = models.map(function(model) {
            return model.getRequest ? model.getRequest() : model;
        });

        var request = new Request(models, options);

        return request.start();
    };

    /**
     * Дополнительные общие параметры запроса.
     * Эти параметры добавляются в любой запрос.
     * @public
     * @type {object}
     */
    ns.request.requestParams = {};

    /**
     * Добавляет к params, параметры из ns.request.requestParams.
     * @param {object} params Параметры запроса.
     */
    ns.request.addRequestParams = function(params) {
        no.extend(params, ns.request.requestParams);
    };

    /**
     * Урл до сервера.
     * @type {string}
     */
    ns.request.URL = '/models/';

    /**
     * Метод для проверки ответа данных.
     * Может использоваться, например, при проверки авторизации.
     * @returns {boolean}
     */
    ns.request.canProcessResponse = function() {
        return true;
    };

    /**
     * Устанавливает начальное состояние
     * @private
     */
    ns.request._reset = function() {
        ns.request.manager._keys = {};
    };

    /**
     * Обрабатывает ответ модели от сервера.
     * @param {ns.Model} model
     * @param {*} result
     */
    ns.request.extractModel = function(model, result) {
        var data;
        var error;
        if (!result) {
            error = {
                id: 'NO_DATA',
                reason: 'Server returned no data'
            };
        } else {
            data = model.extractData(result);
            if (!data) {
                error = model.extractError(result);
                if (!error) {
                    error = {
                        id: 'INVALID_FORMAT',
                        reason: 'response should contain result or error'
                    };
                }
            }
        }

        if (data) {
            model.setData(data);
        } else {
            model.setError(error);
        }
    };

    var REQUEST_ID = 0;

    var Request = function(models, options) {
        /**
         * ID запроса.
         * @type {number}
         * @private
         */
        this.id = REQUEST_ID++;

        /**
         * Массив запрашиваемых моделей
         * @type {ns.Model[]}
         * @private
         */
        this.models = models;

        /**
         * Опции запроса.
         * @type {object}
         * @private
         */
        this.options = options || {};

        this.promise = new Vow.Promise();
    };

    // ------------------------------------------------------------------------------------------------------------- //

    Request.prototype.start = function() {
        var loading = [];
        var requesting = [];

        var models = this.models;
        for (var i = 0, l = models.length; i < l; i++) {
            var model = models[i];

            var addRequest = ns.request.manager.add(model, this.id, this.options.forced);
            if (addRequest === true) {
                requesting.push(model);

            } else if (addRequest instanceof ns.Model) {
                loading.push(model);
            }
        }

        this.request(loading, requesting);

        return this.promise;
    };

    /**
     * Делаем запрос за моделями.
     * @param {ns.Model[]} loading Список моделей, которые уже грузятся.
     * @param {ns.Model[]} requesting Список моделей, которые надо запросить.
     */
    Request.prototype.request = function(loading, requesting) {
        var all = [];
        // promise от http-запроса
        var httpRequest;

        if (requesting.length) {

            var regularRequests = [];
            var customRequests = [];

            requesting.forEach(function(model) {
                // если у модели есть метод request, это значит, что она запросит себя сама
                if (typeof model.request === 'function') {

                    // Вызываем #request, он должен вернуть Vow.Promise.
                    // После завершения говорим об этом менеджеру, чтобы убрать запрос из очереди.
                    var modelRequestPromise = model.request().then(function() {
                        ns.request.manager.done(model);
                    }, function() {
                        ns.request.manager.done(model);
                    });

                    // это промис надо прописать модели,
                    // чтобы повторные запросы тоже отрезолвились
                    // @see ns.Model#prepareRequest
                    model.promise = modelRequestPromise;

                    // добавляем ко всем промисам, чтобы дождаться
                    customRequests.push(modelRequestPromise);
                } else {
                    regularRequests.push(model);
                }
            });

            all = all.concat(customRequests);

            if (regularRequests.length) {

                //  Запрашиваем модели, которые нужно запросить.
                var params = models2params(regularRequests);
                var modelsNames = regularRequests.map(models2name);
                ns.request.addRequestParams(params);
                // отдельный http-promise нужен для того, чтобы реквест с этой моделью, запрашиваемой в другом запросе,
                // мог зарезолвится без завершения http-запроса
                httpRequest = ns.http(ns.request.URL + '?_m=' + modelsNames.join(','), params);

                all = all.concat(regularRequests.map(model2Promise));
            }

        }/* else {
            //TODO: надо перепроверить поведение, если нет запросов
            // создаем фейковый зарезолвленный promise
            httpRequest = new no.Promise().resolve();
        }
        */

        if (loading.length) {
            //  Ждем все остальные модели, которые должны загрузиться (уже были запрошены).
            all = all.concat( loading.map(model2Promise) );
        }

        //  Мы ждём какие-то данные:
        //    * либо мы запросили какие-то новые модели;
        //    * либо мы ждем ранее запрошенные модели.
        if (all.length) {
            var that = this;

            if (httpRequest) {
                httpRequest.then(function(r) {
                    if (ns.request.canProcessResponse(r) === false) {
                        // если ответ обработать нельзя, то удаляем модели из запроса и отклоняем промис
                        ns.request.manager.clean(that.models);
                        that.promise.reject({
                            error: 'CANT_PROCESS',
                            invalid: that.models,
                            valid: []
                        });

                    } else {
                        that.extract(regularRequests, r);
                    }

                }, function(e, xhr) {
                    ns.log.error('ns.request.http', {
                        error: e,
                        xhr: xhr
                    });
                    // уходим в извлечение, чтобы пометить запрос завершенным
                    that.extract(regularRequests, {});
                });
            }

            // Ждем резолва всех моделей и "повторяем" запрос
            // Если какие-то ключи не пришли, они будут перезапрошены.
            Vow.all(all).then(this.start.bind(this));

        } else {
            // у всех моделей есть какой-то статус (ERROR или OK)
            // вызываем чистку менеджера
            ns.request.manager.clean(this.models);

            // сортируем модели на валидные и нет
            var validModels = [];
            var invalidModels = [];

            for (var i = 0, j = this.models.length; i < j; i++) {
                var model = this.models[i];
                if (model.isValid()) {
                    validModels.push(model);
                } else {
                    invalidModels.push(model);
                }
            }

            // если есть невалидные модели
            if (invalidModels.length) {
                this.promise.reject({
                    invalid: invalidModels,
                    valid: validModels
                });

            } else {
                // и резолвим весь ns.request
                this.promise.fulfill(this.models);
            }
        }
    };

    Request.prototype.extract = function(models, response) {
        // response - это объект, чтобы дать возможность напихать туда сервисных данных
        // ответы хендлеров приходят в массиве response.models
        var results = (response && response.models) || [];
        for (var i = 0, l = models.length; i < l; i++) {
            var model = models[i];
            var result = results[i];

            // если модель запрашиваем кто-то другой, то этот ответ игнорируем
            if (model.requestID > this.id) {
                continue;
            }

            ns.request.extractModel(model, result);

            // сообщаем менеджеру о завершении запроса этой модели
            // это не означает, что завершится весь ns.request
            ns.request.manager.done(model);

            model.promise.fulfill();
        }
    };

    function models2params(models) {
        var params = {};

        for (var i = 0, l = models.length; i < l; i++) {
            var suffix = '.' + i; // Чтобы не путать параметры с одинаковыми именами разных моделей,
                                  // добавляем к именам параметров специальный суффикс.
            var model = models[i];

            //  Добавляем служебный параметр, содержащий id модели, которую мы хотим запросить.
            params[ '_model' + suffix ] = model.id;

            //  Каждая модель прокидывает в params все свои параметры.
            //  При этом к имени параметра добавляется суффикс.
            var mParams = model.getRequestParams();
            for (var name in mParams) {
                params[name + suffix] = mParams[name];
            }

        }

        return params;
    }

    /**
     * Приводит запрашиваемые модели к формату №3 из ns.request.
     * @param {Array} items Массив названией моделей.
     * @param {object} params Параметры моделей.
     * @returns {Array}
     */
    function normalizeItems(items, params) {
        var _items = [];
        for (var i = 0, l = items.length; i < l; i++) {
            _items.push({
                id: items[i],
                params: params
            });
        }

        return _items;
    }

    /**
     * Возвращает promise из model
     * @param {ns.Model} model Модель
     * @returns {Vow.Promise}
     */
    function model2Promise(model) {
        return model.promise;
    }

    /**
     * Приводит аргументы из ns.request к моделям.
     * @param {String|Array|Object} items Массив названий моделей.
     * @param {object} [params] Параметры моделей.
     * @returns {ns.Model[]}
     */
    function items2models(items, params) {
        // приводим к формату №2
        if (typeof items === 'string') {
            items = [ items ];
        }

        // приводим №2 к формату №3
        if (typeof items[0] === 'string') {
            params = params || {};
            items = normalizeItems(items, params);
        }

        var models = [];
        for (var i = 0, l = items.length; i < l; i++) {
            var item = items[i];
            if (item.model && item.model instanceof ns.Model) {
                models.push(item.model);
            } else {
                models.push(ns.Model.get(item.id, item.params));
            }
        }

        return models;
    }

    function models2name(model) {
        return model.id;
    }

})();
<commit_msg>Копал #556, понял что ошибка логируется неправильно<commit_after>(function() {

    /**
     * Делает запрос моделей с сервера.
     * Аргументы можно передавать в 3 форматах:
     *   - string item, params - одна модель и опциональные параметры для нее
     *   - array item[], params - массив моделей и опциональные единые для всех параметры
     *   - array item[] - массив моделей вида {id: modelName, params: modelParams}
     * @param {String|Array|Object} items Массив названий моделей.
     * @param {object} [params] Параметры моделей.
     * @param {object} [options] Опции запроса.
     * @param {Boolean} [options.forced=false] Не учитывать закешированность моделей при запросе.
     * @returns {Vow.Promise}
     * @namespace
     */
    ns.request = function(items, params, options) {
        return ns.request.models(items2models(items, params), options);
    };

    /**
     * Делает запрос моделей с сервера, не учитывая их закешированности.
     * @see ns.request
     * @param {String|Array|Object} items Массив названий моделей.
     * @param {object} [params] Параметры моделей.
     * @param {object} [options] Опции запроса.
     * @param {Boolean} [options.forced=false] Не учитывать закешированность моделей при запросе.
     * @returns {Vow.Promise}
     * @memberOf ns.request
     * @name forcedRequest
     */
    ns.forcedRequest = function(items, params, options) {
        options = options || {};
        options.forced = true;
        return ns.request.models(items2models(items, params), options);
    };

    /**
     * Делает запрос моделей с сервера.
     * @param {ns.Model[]} models Массив моделей.
     * @param {object} [options] Опции запроса.
     * @param {Boolean} [options.forced=false] Не учитывать закешированность моделей при запросе.
     * @returns {Vow.Promise}
     */
    ns.request.models = function(models, options) {

        // Загрузка порционных данных. В этом случае грузим не саму модель, а порцию данных.
        models = models.map(function(model) {
            return model.getRequest ? model.getRequest() : model;
        });

        var request = new Request(models, options);

        return request.start();
    };

    /**
     * Дополнительные общие параметры запроса.
     * Эти параметры добавляются в любой запрос.
     * @public
     * @type {object}
     */
    ns.request.requestParams = {};

    /**
     * Добавляет к params, параметры из ns.request.requestParams.
     * @param {object} params Параметры запроса.
     */
    ns.request.addRequestParams = function(params) {
        no.extend(params, ns.request.requestParams);
    };

    /**
     * Урл до сервера.
     * @type {string}
     */
    ns.request.URL = '/models/';

    /**
     * Метод для проверки ответа данных.
     * Может использоваться, например, при проверки авторизации.
     * @returns {boolean}
     */
    ns.request.canProcessResponse = function() {
        return true;
    };

    /**
     * Устанавливает начальное состояние
     * @private
     */
    ns.request._reset = function() {
        ns.request.manager._keys = {};
    };

    /**
     * Обрабатывает ответ модели от сервера.
     * @param {ns.Model} model
     * @param {*} result
     */
    ns.request.extractModel = function(model, result) {
        var data;
        var error;
        if (!result) {
            error = {
                id: 'NO_DATA',
                reason: 'Server returned no data'
            };
        } else {
            data = model.extractData(result);
            if (!data) {
                error = model.extractError(result);
                if (!error) {
                    error = {
                        id: 'INVALID_FORMAT',
                        reason: 'response should contain result or error'
                    };
                }
            }
        }

        if (data) {
            model.setData(data);
        } else {
            model.setError(error);
        }
    };

    var REQUEST_ID = 0;

    var Request = function(models, options) {
        /**
         * ID запроса.
         * @type {number}
         * @private
         */
        this.id = REQUEST_ID++;

        /**
         * Массив запрашиваемых моделей
         * @type {ns.Model[]}
         * @private
         */
        this.models = models;

        /**
         * Опции запроса.
         * @type {object}
         * @private
         */
        this.options = options || {};

        this.promise = new Vow.Promise();
    };

    // ------------------------------------------------------------------------------------------------------------- //

    Request.prototype.start = function() {
        var loading = [];
        var requesting = [];

        var models = this.models;
        for (var i = 0, l = models.length; i < l; i++) {
            var model = models[i];

            var addRequest = ns.request.manager.add(model, this.id, this.options.forced);
            if (addRequest === true) {
                requesting.push(model);

            } else if (addRequest instanceof ns.Model) {
                loading.push(model);
            }
        }

        this.request(loading, requesting);

        return this.promise;
    };

    /**
     * Делаем запрос за моделями.
     * @param {ns.Model[]} loading Список моделей, которые уже грузятся.
     * @param {ns.Model[]} requesting Список моделей, которые надо запросить.
     */
    Request.prototype.request = function(loading, requesting) {
        var all = [];
        // promise от http-запроса
        var httpRequest;

        if (requesting.length) {

            var regularRequests = [];
            var customRequests = [];

            requesting.forEach(function(model) {
                // если у модели есть метод request, это значит, что она запросит себя сама
                if (typeof model.request === 'function') {

                    // Вызываем #request, он должен вернуть Vow.Promise.
                    // После завершения говорим об этом менеджеру, чтобы убрать запрос из очереди.
                    var modelRequestPromise = model.request().then(function() {
                        ns.request.manager.done(model);
                    }, function() {
                        ns.request.manager.done(model);
                    });

                    // это промис надо прописать модели,
                    // чтобы повторные запросы тоже отрезолвились
                    // @see ns.Model#prepareRequest
                    model.promise = modelRequestPromise;

                    // добавляем ко всем промисам, чтобы дождаться
                    customRequests.push(modelRequestPromise);
                } else {
                    regularRequests.push(model);
                }
            });

            all = all.concat(customRequests);

            if (regularRequests.length) {

                //  Запрашиваем модели, которые нужно запросить.
                var params = models2params(regularRequests);
                var modelsNames = regularRequests.map(models2name);
                ns.request.addRequestParams(params);
                // отдельный http-promise нужен для того, чтобы реквест с этой моделью, запрашиваемой в другом запросе,
                // мог зарезолвится без завершения http-запроса
                httpRequest = ns.http(ns.request.URL + '?_m=' + modelsNames.join(','), params);

                all = all.concat(regularRequests.map(model2Promise));
            }

        }/* else {
            //TODO: надо перепроверить поведение, если нет запросов
            // создаем фейковый зарезолвленный promise
            httpRequest = new no.Promise().resolve();
        }
        */

        if (loading.length) {
            //  Ждем все остальные модели, которые должны загрузиться (уже были запрошены).
            all = all.concat( loading.map(model2Promise) );
        }

        //  Мы ждём какие-то данные:
        //    * либо мы запросили какие-то новые модели;
        //    * либо мы ждем ранее запрошенные модели.
        if (all.length) {
            var that = this;

            if (httpRequest) {
                httpRequest.then(function(r) {
                    if (ns.request.canProcessResponse(r) === false) {
                        // если ответ обработать нельзя, то удаляем модели из запроса и отклоняем промис
                        ns.request.manager.clean(that.models);
                        that.promise.reject({
                            error: 'CANT_PROCESS',
                            invalid: that.models,
                            valid: []
                        });

                    } else {
                        that.extract(regularRequests, r);
                    }

                }, function(error) {
                    ns.log.error('ns.request.http', error);

                    // уходим в извлечение, чтобы пометить запрос завершенным
                    that.extract(regularRequests, {});
                });
            }

            // Ждем резолва всех моделей и "повторяем" запрос
            // Если какие-то ключи не пришли, они будут перезапрошены.
            Vow.all(all).then(this.start.bind(this));

        } else {
            // у всех моделей есть какой-то статус (ERROR или OK)
            // вызываем чистку менеджера
            ns.request.manager.clean(this.models);

            // сортируем модели на валидные и нет
            var validModels = [];
            var invalidModels = [];

            for (var i = 0, j = this.models.length; i < j; i++) {
                var model = this.models[i];
                if (model.isValid()) {
                    validModels.push(model);
                } else {
                    invalidModels.push(model);
                }
            }

            // если есть невалидные модели
            if (invalidModels.length) {
                this.promise.reject({
                    invalid: invalidModels,
                    valid: validModels
                });

            } else {
                // и резолвим весь ns.request
                this.promise.fulfill(this.models);
            }
        }
    };

    Request.prototype.extract = function(models, response) {
        // response - это объект, чтобы дать возможность напихать туда сервисных данных
        // ответы хендлеров приходят в массиве response.models
        var results = (response && response.models) || [];
        for (var i = 0, l = models.length; i < l; i++) {
            var model = models[i];
            var result = results[i];

            // если модель запрашиваем кто-то другой, то этот ответ игнорируем
            if (model.requestID > this.id) {
                continue;
            }

            ns.request.extractModel(model, result);

            // сообщаем менеджеру о завершении запроса этой модели
            // это не означает, что завершится весь ns.request
            ns.request.manager.done(model);

            model.promise.fulfill();
        }
    };

    function models2params(models) {
        var params = {};

        for (var i = 0, l = models.length; i < l; i++) {
            var suffix = '.' + i; // Чтобы не путать параметры с одинаковыми именами разных моделей,
                                  // добавляем к именам параметров специальный суффикс.
            var model = models[i];

            //  Добавляем служебный параметр, содержащий id модели, которую мы хотим запросить.
            params[ '_model' + suffix ] = model.id;

            //  Каждая модель прокидывает в params все свои параметры.
            //  При этом к имени параметра добавляется суффикс.
            var mParams = model.getRequestParams();
            for (var name in mParams) {
                params[name + suffix] = mParams[name];
            }

        }

        return params;
    }

    /**
     * Приводит запрашиваемые модели к формату №3 из ns.request.
     * @param {Array} items Массив названией моделей.
     * @param {object} params Параметры моделей.
     * @returns {Array}
     */
    function normalizeItems(items, params) {
        var _items = [];
        for (var i = 0, l = items.length; i < l; i++) {
            _items.push({
                id: items[i],
                params: params
            });
        }

        return _items;
    }

    /**
     * Возвращает promise из model
     * @param {ns.Model} model Модель
     * @returns {Vow.Promise}
     */
    function model2Promise(model) {
        return model.promise;
    }

    /**
     * Приводит аргументы из ns.request к моделям.
     * @param {String|Array|Object} items Массив названий моделей.
     * @param {object} [params] Параметры моделей.
     * @returns {ns.Model[]}
     */
    function items2models(items, params) {
        // приводим к формату №2
        if (typeof items === 'string') {
            items = [ items ];
        }

        // приводим №2 к формату №3
        if (typeof items[0] === 'string') {
            params = params || {};
            items = normalizeItems(items, params);
        }

        var models = [];
        for (var i = 0, l = items.length; i < l; i++) {
            var item = items[i];
            if (item.model && item.model instanceof ns.Model) {
                models.push(item.model);
            } else {
                models.push(ns.Model.get(item.id, item.params));
            }
        }

        return models;
    }

    function models2name(model) {
        return model.id;
    }

})();
<|endoftext|>


<commit_before>// namespace a value and assign to a given object.
// convenience function. I tend to do this often.

// set a top-level (flatten) object property
// differs from `set` in that `obj` is optional and does
// not create nested properties

function nsp (obj, key, val) {
  if (!key && !val) return obj
  if (val == null) {
    val = key
    key = obj
    obj = {}
  }

  obj[key] = val
  return obj
}

module.exports = nsp
<commit_msg>update object/nsp.js<commit_after>// namespace a value and assign to a given object.
// convenience function. I tend to do this often.

// Oh well, required `set`... so this is super `set`
// `obj` is optional. returns modified obj

var set = require('./set')

function nsp (obj, key, val) {
  if (!key && !val) return obj
  if (val == null) {
    val = key
    key = obj
    obj = {}
  }

  if (key.indexOf('.') > -1) {
    set(obj, key, val)
  } else {
    obj[key] = val
  }

  return obj
}

module.exports = nsp
<|endoftext|>


<commit_before>var fs = require('fs')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var merge = require('webpack-merge')
var ProgressBarPlugin = require('progress-bar-webpack-plugin')
var projectRoot = process.cwd()
var projectModules = path.join(projectRoot, '/node_modules')
var firstProgressBarRun = true

var clientEnvironment = {
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT),
  'process.env.TESTING_TYPE': JSON.stringify(process.env.TESTING_TYPE)
}

// Check if there is a typescript file as the main entry point
var entry = './index.js'
try {
  var typescriptFile = path.join(projectRoot, 'src', 'index.ts')
  fs.statSync(typescriptFile)
  entry = './index.ts'
} catch (err) {}

try {
  var projectClientEnv = require(path.join(projectRoot, 'env.js'))

  for (var key of Object.keys(projectClientEnv)) {
    clientEnvironment[key] = JSON.stringify(projectClientEnv[key])
  }
} catch (err) {}

var config = {
  // Path to main source folder where your files reside
  context: path.join(projectRoot, 'src'),
  // Main file entry point - must be an object as we append dev injections to that path
  entry: {
    index: entry
  },
  output: {
    path: path.resolve(projectRoot, 'dist'),
    filename: '[name].js', // filename based upon entry variable - ex: index.js
    publicPath: '/' // Important for dev server main path
  },
  resolve: {
    modules: [projectRoot + '/src', projectModules],
    // If you dont put the extension on an import it will
    // try to resolve it by looking for these extensions first
    extensions: ['.scss', '.ts', '.tsx', '.js', '.vue'],
    enforceExtension: false, // Whether or not to force user to add .ext to end of files
    // Aliases - Used for pointing to reusable parts of your app
    alias: {
      src: projectRoot + '/src'
    }
  },
  resolveLoader: {
    alias: {
      // This will allow you to do lang="scss" in your style tags
      'scss-loader': 'sass-loader'
    }
  },
  module: {
    rules: [
      // Loaders
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            js: 'babel-loader',
            postcss: [
              require('autoprefixer')({
                browsers: ['last 3 versions']
              })
            ]
          }
        }
      },
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
          logLevel: 'warn'
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [path.join(projectRoot, 'src'), path.join(projectRoot, 'test')]
      },
      {
        test: /\.(scss|css)$/,
        loader: ['style-loader', 'css-loader?sourceMap', 'resolve-url-loader', 'sass-loader?sourceMap']
      },
      {
        test: /\.json$/,
        loader: 'json-loader'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[hash:7].[ext]'
        }
      }
    ]
  },

  plugins: [
    // Temporarily removed to fix hot module reloading issues
    // new webpack.optimize.ModuleConcatenationPlugin(), // Scope Hoisting for better performance
    new webpack.DefinePlugin(clientEnvironment),
    new ProgressBarPlugin({
      format: '[:bar] :percent :msg',
      callback: function (info) {
        // Output message on development
        if (process.env.ENVIRONMENT === 'development' && firstProgressBarRun) {
          console.log(chalk.green('Dev server started'))
          console.log(chalk.blue('http://localhost:' + process.env.PORT + '.....PID:' + process.pid))
          firstProgressBarRun = false
        }
      }
    })
  ]
}

// If a user has a .babelrc file lets just let it use that
// If user doesnt have a .babelrc file lets create a default for them
try {
  var babelrcFile = path.join(projectRoot, '.babelrc')
  fs.statSync(babelrcFile)
} catch (err) {
  var babelSettings = {
    presets: [['env', {'modules': false}], 'stage-2'],
    plugins: ['transform-runtime'],
    comments: false
  }
  if (process.env.COVERAGE === 'true') {
    babelSettings.plugins.push('istanbul')
  }

  let rules = config.module.rules
  for (var i = 0; i < rules.length; i++) {
    // Add babel options to babel-loader
    if (rules[i].loader === 'babel-loader') {
      rules[i].options = babelSettings
    }

    // Add babel options to vue-loader
    if (rules[i].loader === 'vue-loader') {
      rules[i].options.loaders.js = 'babel-loader?' + JSON.stringify(babelSettings)
    }
  }
}

// If user has a .eslintrc file lets add loaders
try {
  var eslintFile = projectRoot + '/.eslintrc'
  fs.statSync(eslintFile)

  config.unshift({
    enforce: 'pre',
    test: /\.(js|vue)$/,
    loader: 'eslint-loader',
    include: [path.join(projectRoot, 'src'), path.join(projectRoot, 'test')]
  })
} catch (err) {}

// If there is a webpack in the main project merge it
try {
  var webpackConfig = projectRoot + '/webpack.config.js'
  fs.statSync(webpackConfig)

  config = merge(config, require(webpackConfig))
} catch (err) {}

// Check entry index to make sure files exist
try {
  if (typeof config.entry.index === 'string') {
    fs.statSync(path.join(projectRoot, 'src', config.entry.index))
  } else {
    for (let file of config.entry.index) {
      fs.statSync(path.join(projectRoot, 'src', file))
    }
  }
} catch (err) {
  var error = `Could not find file ${err.path}. Vue build uses index.js as the main entry point. Please make sure that file exists.`
  throw new Error(error)
}

module.exports = config
<commit_msg>ts-loader - added better compile option<commit_after>var fs = require('fs')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var merge = require('webpack-merge')
var ProgressBarPlugin = require('progress-bar-webpack-plugin')
var projectRoot = process.cwd()
var projectModules = path.join(projectRoot, '/node_modules')
var firstProgressBarRun = true

var clientEnvironment = {
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT),
  'process.env.TESTING_TYPE': JSON.stringify(process.env.TESTING_TYPE)
}

// Check if there is a typescript file as the main entry point
var entry = './index.js'
try {
  var typescriptFile = path.join(projectRoot, 'src', 'index.ts')
  fs.statSync(typescriptFile)
  entry = './index.ts'
} catch (err) {}

try {
  var projectClientEnv = require(path.join(projectRoot, 'env.js'))

  for (var key of Object.keys(projectClientEnv)) {
    clientEnvironment[key] = JSON.stringify(projectClientEnv[key])
  }
} catch (err) {}

var config = {
  // Path to main source folder where your files reside
  context: path.join(projectRoot, 'src'),
  // Main file entry point - must be an object as we append dev injections to that path
  entry: {
    index: entry
  },
  output: {
    path: path.resolve(projectRoot, 'dist'),
    filename: '[name].js', // filename based upon entry variable - ex: index.js
    publicPath: '/' // Important for dev server main path
  },
  resolve: {
    modules: [projectRoot + '/src', projectModules],
    // If you dont put the extension on an import it will
    // try to resolve it by looking for these extensions first
    extensions: ['.scss', '.ts', '.tsx', '.js', '.vue'],
    enforceExtension: false, // Whether or not to force user to add .ext to end of files
    // Aliases - Used for pointing to reusable parts of your app
    alias: {
      src: projectRoot + '/src'
    }
  },
  resolveLoader: {
    alias: {
      // This will allow you to do lang="scss" in your style tags
      'scss-loader': 'sass-loader'
    }
  },
  module: {
    rules: [
      // Loaders
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            js: 'babel-loader',
            postcss: [
              require('autoprefixer')({
                browsers: ['last 3 versions']
              })
            ]
          }
        }
      },
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: {
          onlyCompileBundledFiles: true,
          appendTsSuffixTo: [/\.vue$/],
          logLevel: 'warn'
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [path.join(projectRoot, 'src'), path.join(projectRoot, 'test')]
      },
      {
        test: /\.(scss|css)$/,
        loader: ['style-loader', 'css-loader?sourceMap', 'resolve-url-loader', 'sass-loader?sourceMap']
      },
      {
        test: /\.json$/,
        loader: 'json-loader'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[hash:7].[ext]'
        }
      }
    ]
  },

  plugins: [
    // Temporarily removed to fix hot module reloading issues
    // new webpack.optimize.ModuleConcatenationPlugin(), // Scope Hoisting for better performance
    new webpack.DefinePlugin(clientEnvironment),
    new ProgressBarPlugin({
      format: '[:bar] :percent :msg',
      callback: function (info) {
        // Output message on development
        if (process.env.ENVIRONMENT === 'development' && firstProgressBarRun) {
          console.log(chalk.green('Dev server started'))
          console.log(chalk.blue('http://localhost:' + process.env.PORT + '.....PID:' + process.pid))
          firstProgressBarRun = false
        }
      }
    })
  ]
}

// If a user has a .babelrc file lets just let it use that
// If user doesnt have a .babelrc file lets create a default for them
try {
  var babelrcFile = path.join(projectRoot, '.babelrc')
  fs.statSync(babelrcFile)
} catch (err) {
  var babelSettings = {
    presets: [['env', {'modules': false}], 'stage-2'],
    plugins: ['transform-runtime'],
    comments: false
  }
  if (process.env.COVERAGE === 'true') {
    babelSettings.plugins.push('istanbul')
  }

  let rules = config.module.rules
  for (var i = 0; i < rules.length; i++) {
    // Add babel options to babel-loader
    if (rules[i].loader === 'babel-loader') {
      rules[i].options = babelSettings
    }

    // Add babel options to vue-loader
    if (rules[i].loader === 'vue-loader') {
      rules[i].options.loaders.js = 'babel-loader?' + JSON.stringify(babelSettings)
    }
  }
}

// If user has a .eslintrc file lets add loaders
try {
  var eslintFile = projectRoot + '/.eslintrc'
  fs.statSync(eslintFile)

  config.unshift({
    enforce: 'pre',
    test: /\.(js|vue)$/,
    loader: 'eslint-loader',
    include: [path.join(projectRoot, 'src'), path.join(projectRoot, 'test')]
  })
} catch (err) {}

// If there is a webpack in the main project merge it
try {
  var webpackConfig = projectRoot + '/webpack.config.js'
  fs.statSync(webpackConfig)

  config = merge.smart(config, require(webpackConfig))
} catch (err) {}

// Check entry index to make sure files exist
try {
  if (typeof config.entry.index === 'string') {
    fs.statSync(path.join(projectRoot, 'src', config.entry.index))
  } else {
    for (let file of config.entry.index) {
      fs.statSync(path.join(projectRoot, 'src', file))
    }
  }
} catch (err) {
  var error = `Could not find file ${err.path}. Vue build uses index.js as the main entry point. Please make sure that file exists.`
  throw new Error(error)
}

module.exports = config
<|endoftext|>