Программный интерфейс

Показывать:
/**
 * Конструкторы табличных частей
 *
 * © Evgeniy Malyarov http://www.oknosoft.ru 2014-2016
 *
 * @module  metadata
 * @submodule meta_tabulars
 * @requires common
 */


/**
 * ### Абстрактный объект табличной части
 * - Физически, данные хранятся в {{#crossLink "DataObj"}}{{/crossLink}}, а точнее - в поле типа массив и именем табчасти объекта `_obj`
 * - Класс предоставляет методы для доступа и манипуляции данными табчасти
 *
 * @class TabularSection
 * @constructor
 * @param name {String} - имя табчасти
 * @param owner {DataObj} - владелец табличной части
 * @menuorder 21
 * @tooltip Табличная часть
 */
function TabularSection(name, owner){

  // Если табчасти нет в данных владельца - создаём
  if(!owner._obj[name])
    owner._obj[name] = [];

  /**
   * Имя табличной части
   * @property _name
   * @type String
   */
  this.__define('_name', {
    value : name,
    enumerable : false
  });

  /**
   * Объект-владелец табличной части
   * @property _owner
   * @type DataObj
   */
  this.__define('_owner', {
    value : owner,
    enumerable : false
  });

  /**
   * ### Фактическое хранилище данных объекта
   * Оно же, запись в таблице объекта локальной базы данных
   * @property _obj
   * @type Object
   */
  this.__define("_obj", {
    value: owner._obj[name],
    writable: false,
    enumerable: false
  });
}

TabularSection.prototype.toString = function(){
  return "Табличная часть " + this._owner._manager.class_name + "." + this._name
};

/**
 * ### Возвращает строку табчасти по индексу
 * @method get
 * @param index {Number} - индекс строки табчасти
 * @return {TabularSectionRow}
 */
TabularSection.prototype.get = function(index){
  return this._obj[index]._row;
};

/**
 * ### Возвращает количество элементов в табчасти
 * @method count
 * @return {Number}
 *
 * @example
 *     // количество элементов в табчасти
 *     var count = ts.count();
 */
TabularSection.prototype.count = function(){return this._obj.length};

/**
 * ### Очищает табличнут часть
 * @method clear
 * @return {TabularSection}
 *
 * @example
 *     // Очищает табличнут часть
 *     ts.clear();
 *
 */
TabularSection.prototype.clear = function(silent){

  for(var i in this._obj)
    delete this._obj[i];
  this._obj.length = 0;

  if(!silent && !this._owner._data._silent)
    Object.getNotifier(this._owner).notify({
      type: 'rows',
      tabular: this._name
    });

  return this;
};

/**
 * ### Удаляет строку табличной части
 * @method del
 * @param val {Number|TabularSectionRow} - индекс или строка табчасти
 */
TabularSection.prototype.del = function(val, silent){
  
  var index, _obj = this._obj;
  
  if(typeof val == "undefined")
    return;
    
  else if(typeof val == "number")
    index = val;
    
  else if(_obj[val.row-1]._row === val)
    index = val.row-1;
    
  else{
    for(var i in _obj)
      if(_obj[i]._row === val){
        index = Number(i);
        delete _obj[i]._row;
        break;
      }
  }
  if(index == undefined)
    return;

  _obj.splice(index, 1);

  _obj.forEach(function (row, index) {
    row.row = index + 1;
  });

  if(!silent && !this._owner._data._silent)
    Object.getNotifier(this._owner).notify({
      type: 'rows',
      tabular: this._name
    });

  this._owner._data._modified = true;
};

/**
 * ### Находит первую строку, содержащую значение
 * @method find
 * @param val {*} - значение для поиска
 * @param columns {String|Array} - колонки, в которых искать
 * @return {TabularSectionRow}
 */
TabularSection.prototype.find = function(val, columns){
  var res = $p._find(this._obj, val, columns);
  if(res)
    return res._row;
};

/**
 * ### Находит строки, соответствующие отбору
 * Если отбор пустой, возвращаются все строки табчасти
 *
 * @method find_rows
 * @param [selection] {Object} - в ключах имена полей, в значениях значения фильтра или объект {like: "значение"}
 * @param [callback] {Function} - в который передается строка табчасти на каждой итерации
 * @return {Array}
 */
TabularSection.prototype.find_rows = function(selection, callback){

  var t = this,
    cb = callback ? function (row) {
      return callback.call(t, row._row);
    } : null;

  return $p._find_rows.call(t, t._obj, selection, cb);

};

/**
 * ### Меняет местами строки табчасти
 * @method swap
 * @param rowid1 {number}
 * @param rowid2 {number}
 */
TabularSection.prototype.swap = function(rowid1, rowid2){
  var tmp = this._obj[rowid1];
  this._obj[rowid1] = this._obj[rowid2];
  this._obj[rowid2] = tmp;

  if(!this._owner._data._silent)
    Object.getNotifier(this._owner).notify({
      type: 'rows',
      tabular: this._name
    });
};

/**
 * ### Добавляет строку табчасти
 * @method add
 * @param attr {object} - объект со значениями полей. если некого поля нет в attr, для него используется пустое значение типа
 * @return {TabularSectionRow}
 *
 * @example
 *     // Добавляет строку в табчасть и заполняет её значениями, переданными в аргументе
 *     var row = ts.add({field1: value1});
 */
TabularSection.prototype.add = function(attr, silent){

  var row = new $p[this._owner._manager.obj_constructor(this._name)](this);

  if(!attr)
    attr = {};

  // присваиваем типизированные значения по умолчанию
  for(var f in row._metadata.fields)
    row[f] = attr[f] || "";

  row._obj.row = this._obj.push(row._obj);
  row._obj.__define("_row", {
    value: row,
    enumerable: false
  });

  if(!silent && !this._owner._data._silent)
    Object.getNotifier(this._owner).notify({
      type: 'rows',
      tabular: this._name
    });

  attr = null;
  
  this._owner._data._modified = true;
  
  return row;
};

/**
 * ### Выполняет цикл "для каждого"
 * @method each
 * @param fn {Function} - callback, в который передается строка табчасти
 */
TabularSection.prototype.each = function(fn){
  var t = this;
  t._obj.forEach(function(row){
    return fn.call(t, row._row);
  });
};

/**
 * ### Псевдоним для each
 * @method forEach
 * @type {TabularSection.each|*}
 */
TabularSection.prototype.forEach = TabularSection.prototype.each;

/**
 * ### Сворачивает табличную часть
 * детали см. в {{#crossLink "TabularSection/aggregate:method"}}{{/crossLink}}
 * @method group_by
 * @param [dimensions] {Array|String}
 * @param [resources] {Array|String}
 */
TabularSection.prototype.group_by = function (dimensions, resources) {

  try{
    var res = this.aggregate(dimensions, resources, "SUM", true);
    return this.clear(true).load(res);

  }catch(err){}
};

/**
 * ### Сортирует табличную часть
 *
 * @method sort
 * @param fields {Array|String}
 */
TabularSection.prototype.sort = function (fields) {

  if(typeof fields == "string")
    fields = fields.split(",");

  var sql = "select * from ? order by ", res = true;
  fields.forEach(function (f) {
    f = f.trim().replace(/\s{1,}/g," ").split(" ");
    if(res)
      res = false;
    else
      sql += ", ";
    sql += "`" + f[0] + "`";
    if(f[1])
      sql += " " + f[1];
  });

  try{
    res = $p.wsql.alasql(sql, [this._obj]);
    return this.clear(true).load(res);

  }catch(err){
    $p.record_log(err);
  }
};

/**
 * ### Вычисляет агрегатную функцию по табличной части
 * - Не изменяет исходный объект. Если пропущен аргумент `aggr` - вычисляет сумму.
 * - Стандартные агрегаторы: SUM, COUNT, MIN, MAX, FIRST, LAST, AVG, AGGR, ARRAY, REDUCE
 * - AGGR - позволяет задать собственный агрегатор (функцию) для расчета итогов
 *
 * @method aggregate
 * @param [dimensions] {Array|String} - список измерений
 * @param [resources] {Array|String} - список ресурсов
 * @param [aggr] {String} - агрегатная функция
 * @param [ret_array] {Boolran} - указывает возвращать массив значений
 * @return {Number|Array} - Значение агрегатной фукнции или массив значений
 *
 * @example
 *     // вычисляем сумму (итог) по полю amount табличной части
 *     var total = ts.aggregate("", "amount");
 *
 *     // вычисляем максимальные суммы для всех номенклатур табличной части
 *     // вернёт массив объектов {nom, amount}
 *     var total = ts.aggregate("nom", "amount", "MAX", true);
 */
TabularSection.prototype.aggregate = function (dimensions, resources, aggr, ret_array) {

  if(typeof dimensions == "string")
    dimensions = dimensions.split(",");
  if(typeof resources == "string")
    resources = resources.split(",");
  if(!aggr)
    aggr = "sum";

  // для простых агрегатных функций, sql не используем
  if(!dimensions.length && resources.length == 1 && aggr == "sum"){
    return this._obj.reduce(function(sum, row, index, array) {
      return sum + row[resources[0]];
    }, 0);
  }

  var sql, res = true;

  resources.forEach(function (f) {
    if(!sql)
      sql = "select " + aggr + "(`" + f + "`) `" + f + "`";
    else
      sql += ", " + aggr + "(`" + f + "`) `" + f + "`";
  });
  dimensions.forEach(function (f) {
    if(!sql)
      sql = "select `" + f + "`";
    else
      sql += ", `" + f + "`";
  });
  sql += " from ? ";
  dimensions.forEach(function (f) {
    if(res){
      sql += "group by ";
      res = false;
    }
    else
      sql += ", ";
    sql += "`" + f + "`";
  });

  try{
    res = $p.wsql.alasql(sql, [this._obj]);
    if(!ret_array){
      if(resources.length == 1)
        res = res.length ? res[0][resources[0]] : 0;
      else
        res = res.length ? res[0] : {};
    }
    return res;

  }catch(err){
    $p.record_log(err);
  }
};

/**
 * ### Загружает табличнут часть из массива объектов
 *
 * @method load
 * @param aattr {Array} - массив объектов к загрузке
 */
TabularSection.prototype.load = function(aattr){

  var t = this, arr;

  t.clear(true);
  if(aattr instanceof TabularSection)
    arr = aattr._obj;
  else if(Array.isArray(aattr))
    arr = aattr;
  if(arr)
    arr.forEach(function(row){
      t.add(row, true);
  });

  if(!this._owner._data._silent)
    Object.getNotifier(t._owner).notify({
      type: 'rows',
      tabular: t._name
    });

  return t;
};

/**
 * ### Перезаполняет грид данными табчасти с учетом отбора
 * @method sync_grid
 * @param grid {dhtmlxGrid} - элемент управления
 * @param [selection] {Object} - в ключах имена полей, в значениях значения фильтра или объект {like: "значение"}
 */
TabularSection.prototype.sync_grid = function(grid, selection){
  var grid_data = {rows: []},
    columns = [];

  for(var i = 0; i<grid.getColumnCount(); i++)
    columns.push(grid.getColumnId(i));

  grid.clearAll();
  this.find_rows(selection, function(r){
    var data = [];
    columns.forEach(function (f) {
      if($p.utils.is_data_obj(r[f]))
        data.push(r[f].presentation);
      else
        data.push(r[f]);
    });
    grid_data.rows.push({ id: r.row, data: data });
  });
  if(grid.objBox){
    try{
      grid.parse(grid_data, "json");
      grid.callEvent("onGridReconstructed", []);
    } catch (e){}
  }
};

TabularSection.prototype.toJSON = function () {
  return this._obj;
};


/**
 * ### Aбстрактная строка табличной части
 * 
 * @class TabularSectionRow
 * @constructor
 * @param owner {TabularSection} - табличная часть, которой принадлежит строка
 * @menuorder 22
 * @tooltip Строка табчасти
 */
function TabularSectionRow(owner){

  var _obj = {};

  /**
   * Указатель на владельца данной строки табличной части
   * @property _owner
   * @type TabularSection
   */
  this.__define('_owner', {
    value : owner,
    enumerable : false
  });

  /**
   * ### Фактическое хранилище данных объекта
   * Отображается в поле типа json записи в таблице объекта локальной базы данных
   * @property _obj
   * @type Object
   */
  this.__define("_obj", {
    value: _obj,
    writable: false,
    enumerable: false
  });

}

/**
 * ### Метаданые строки табличной части
 * @property _metadata
 * @for TabularSectionRow
 * @type Number
 */
TabularSectionRow.prototype.__define('_metadata', {
  get : function(){ return this._owner._owner._metadata["tabular_sections"][this._owner._name]},
  enumerable : false
});

/**
 * ### Номер строки табличной части
 * @property row
 * @for TabularSectionRow
 * @type Number
 * @final
 */
TabularSectionRow.prototype.__define("row", {
  get : function(){ return this._obj.row || 0},
  enumerable : true
});

/**
 * ### Копирует строку табличной части
 * @method _clone
 * @for TabularSectionRow
 * @type Number
 */
TabularSectionRow.prototype.__define("_clone", {
  value : function(){
    return new $p[this._owner._owner._manager.obj_constructor(this._owner._name)](this._owner)._mixin(this._obj);
  },
  enumerable : false
});

TabularSectionRow.prototype._getter = DataObj.prototype._getter;

TabularSectionRow.prototype._setter = function (f, v) {

  if(this._obj[f] == v || (!v && this._obj[f] == $p.utils.blank.guid))
    return;

  if(!this._owner._owner._data._silent)
    Object.getNotifier(this._owner._owner).notify({
      type: 'row',
      row: this,
      tabular: this._owner._name,
      name: f,
      oldValue: this._obj[f]
    });

  // учтём связь по типу
  if(this._metadata.fields[f].choice_type){
    var prop;
    if(this._metadata.fields[f].choice_type.path.length == 2)
      prop = this[this._metadata.fields[f].choice_type.path[1]];
    else
      prop = this._owner._owner[this._metadata.fields[f].choice_type.path[0]];
    if(prop && prop.type)
      v = $p.utils.fetch_type(v, prop.type);
  }

  DataObj.prototype.__setter.call(this, f, v);
  this._owner._owner._data._modified = true;

};