<commit_before>var util = require('util');
var async = require('async');
var _ = require('underscore');
var moment = require('moment');
var createSchemaParser = require('./schema');

function Model(seraphDb, type) {
  var self = this;
  this.db = seraphDb;
  this.fields = [];
  this.compositions = {};
  this.upstreamCodependants = [];
  this.events = {
    compute: [],
    validate: [],
    beforeSave: [],
    afterSave: [],
    prepare: []
  };

  var allWhitelistedKeys = function() {
    return self.fields.concat(
      [ self.db.options.id ],
      _.keys(self.compositions),
      self.usingTimestamps ? [ self.createdField, self.updatedField ] : []
    );
  };

  this.on('prepare', function whitelist(obj, callback) {
    if (!Array.isArray(self.fields) || !self.fields.length) {
      callback(null, obj);
    } else {
      callback(null, _.pick(obj, allWhitelistedKeys()));
    }
  });

  this.on('prepare', function(object, callback) {
    if (!schemaParser) return callback(null, object);
    schemaParser(object, callback);
  });

  var schema, schemaParser;

  Object.defineProperty(this, 'type', {
    enumerable: true, configurable: false,
    get: function() { return type },
    set: function(newType) {
      type = newType;
    }
  });

  Object.defineProperty(this, 'schema', {
    enumerable: true, configurable: false,
    get: function() { return schema },
    set: function(newSchema) {
      schema = newSchema;
      if (schema) {
        this.keys = Object.keys(schema);
        schemaParser = createSchemaParser(schema)
      } else {
        this.keys = [];
        schemaParser = null;
      }
      return schema;
    }
  });

  if (type) this.type = type;
}

Model.prototype.addComputedField = function(field, computer) {
  this.on('prepare', function(obj, callback) {
    if (obj[field]) delete obj[field];
    callback(null, obj);
  });
  this.on('compute', function(obj, callback) {
    if (computer.length == 1) {
      obj[field] = computer(obj);
      callback(null, obj);
    } else {
      computer(obj, function(err, fieldVal) {
        if (err) return callback(err);
        obj[field] = fieldVal;
        callback(null, obj);
      });
    }
  });
};

Model.prototype.addComputer = function(fields, computer) {
  this.on('prepare', function(obj, callback) {
    callback(null, _.omit(obj, fields));
  });
  this.on('compute', computer);
};

Model.prototype.useTimestamps = function(createdField, updatedField) {
  if (this.usingTimestamps) return;

  var self = this;
  this.createdField = createdField || 'created';
  this.updatedField = updatedField || 'updated';
  this.usingTimestamps = true;
};

Model.prototype.touch = function(node, callback) {
  if (!this.usingTimestamps) return;
  var self = this;
  var query = [
    "START node=node({id})",
    "SET node." + this.updatedField + " = timestamp()",
    "RETURN node." + this.updatedField + " AS updated"
  ].join(' ');
  this.db.query(query, { id: node[this.db.options.id] }, function(err, result) {
    if (err) return callback(err);
    node[self.updatedField] = result.updated;
    callback(null, node);
  });
};

Model.prototype.setUniqueKey = function(key, returnOldOnConflict, callback) {
  this.on('validate', function(obj, cb) {
    if (obj[key] == null) {
      cb(util.format("The `%s` key was not set, but is required to save " +
                     "this object", key));
    } else cb();
  });
  if (typeof returnOldOnConflict == 'function') {
    callback = returnOldOnConflict;
    returnOldOnConflict = false;
  }
  this.uniqueness = { key: key, returnOld: !!returnOldOnConflict };
  this.db.constraints.uniqueness.createIfNone(this.type, key, callback || function(err) {
    if (err) throw err;
  });
};

Model.prototype.save = require('./write').save;
Model.prototype.push = require('./write').pushComposition;
Model.prototype.saveComposition = require('./write').saveComposition;
Model.prototype.read = require('./read').read;
Model.prototype.readComposition = require('./read').readComposition;

Model.prototype.prepare = function(obj, cb) {
  this.triggerTransformEvent('prepare', obj, cb);
};

Model.prototype.validate = function(obj, cb) {
  this.triggerProgressionEvent('validate', obj, cb);
};

Model.prototype.compute = function(obj, cb) {
  if (Array.isArray(obj)) {
    async.map(obj, this.compute.bind(this), cb);
  } else {
    this.triggerTransformEvent('compute', obj, cb);
  }
};

Model.prototype.on = function(event, action) {
  if (!this.events[event]) throw new Error("No such event - " + event);
  this.events[event].push(action);
};

// Triggers a regular event. A regular event is just a function, we do not wait
// for it to call back before continuing.
Model.prototype.triggerEvent = function(event, object, callback) {
  if (this.events[event].length) {
    this.events[event].forEach(function(listener) {
      listener(object);
    });
  }
  callback && callback(null, object);
};

// Triggers a progression event. A progression event is a function that takes a
// callback. We wait for that function to call the callback before continuing.
// The callback passed to `triggerProgressionEvent` will not be called until all
// of the attached progression events have called their callbacks.
Model.prototype.triggerProgressionEvent = function(event, object, callback) {
  if (!this.events[event].length) {
    callback(null, object);
  } else {
    async.forEach(this.events[event], function(listener, callback) {
      listener(object, callback);
    }, function(err) {
      callback(err, object);
    });
  }
}

// Triggers a transform event. A transform event is a function that takes a
// callback, and returns a transformed version of the object. `callback` is
// called when all of the transform events have finished, and is passed the
// final transformed version of `object`.
Model.prototype.triggerTransformEvent = function(event, object, callback) {
  if (!this.events[event].length) {
    callback(null, object);
  } else {
    var seed = function(cb) {
      cb(null, _.clone(object));
    };
    async.waterfall([seed].concat(this.events[event]), callback);
  }
}

Model.prototype.exists = function(idOrObj, callback) {
  var self = this;
  var id = this.db._getId(idOrObj);
  this.db.readLabels(id, function(err, labels) {
    if (err) {
      if (err.neo4jException == 'NodeNotFoundException') return callback(null, false);
      else return callback(err);
    }
    callback(null, !!~labels.indexOf(self.type));
  });
};

Model.prototype.findAll = function(opts, callback) {
  if (typeof opts == 'function') {
    callback = opts;
    opts = {};
  }
  this.read(null, opts, callback);
}

Model.prototype.query = function(query, params, opts, callback) {
  if (typeof params == 'function') {
    callback = params;
    params = {};
    opts = {};
  } else if (typeof opts == 'function') {
    callback = opts;
    opts = {};
  }

  opts.query = query;
  opts.params = params;

  this.read(null, opts, callback);
};

Model.prototype.where = function(predicate, opts, callback) {
  if (typeof opts == 'function') {
    callback = opts;
    opts = { any: false };
  }
  var self = this;

  opts.varName = opts.varName || 'node';

  var matchers = Object.keys(predicate).map(function(key) {
    if (predicate[key] instanceof RegExp) {
      predicate[key] = '(?i)' + predicate[key].source;
      return opts.varName + '.' + key + ' =~ {' + key + '}';
    }
    return opts.varName + '.' + key + ' = {' + key + '}';
  });

  var query = 'MATCH (' + opts.varName + ':' + this.type + ') ' +
              'WHERE ' + matchers.join(opts.any ? ' OR ' : ' AND ')

  this.query(query, predicate, opts, callback);
}

Model.prototype.compose = require('./compose');

module.exports = function createSeraphModel(db, type) {
  return new Model(db, type);
};
<commit_msg>match unicode and whitespace. ref #89<commit_after>var util = require('util');
var async = require('async');
var _ = require('underscore');
var moment = require('moment');
var createSchemaParser = require('./schema');

function Model(seraphDb, type) {
  var self = this;
  this.db = seraphDb;
  this.fields = [];
  this.compositions = {};
  this.upstreamCodependants = [];
  this.events = {
    compute: [],
    validate: [],
    beforeSave: [],
    afterSave: [],
    prepare: []
  };

  var allWhitelistedKeys = function() {
    return self.fields.concat(
      [ self.db.options.id ],
      _.keys(self.compositions),
      self.usingTimestamps ? [ self.createdField, self.updatedField ] : []
    );
  };

  this.on('prepare', function whitelist(obj, callback) {
    if (!Array.isArray(self.fields) || !self.fields.length) {
      callback(null, obj);
    } else {
      callback(null, _.pick(obj, allWhitelistedKeys()));
    }
  });

  this.on('prepare', function(object, callback) {
    if (!schemaParser) return callback(null, object);
    schemaParser(object, callback);
  });

  var schema, schemaParser;

  Object.defineProperty(this, 'type', {
    enumerable: true, configurable: false,
    get: function() { return type },
    set: function(newType) {
      type = newType;
    }
  });

  Object.defineProperty(this, 'schema', {
    enumerable: true, configurable: false,
    get: function() { return schema },
    set: function(newSchema) {
      schema = newSchema;
      if (schema) {
        this.keys = Object.keys(schema);
        schemaParser = createSchemaParser(schema)
      } else {
        this.keys = [];
        schemaParser = null;
      }
      return schema;
    }
  });

  if (type) this.type = type;
}

Model.prototype.addComputedField = function(field, computer) {
  this.on('prepare', function(obj, callback) {
    if (obj[field]) delete obj[field];
    callback(null, obj);
  });
  this.on('compute', function(obj, callback) {
    if (computer.length == 1) {
      obj[field] = computer(obj);
      callback(null, obj);
    } else {
      computer(obj, function(err, fieldVal) {
        if (err) return callback(err);
        obj[field] = fieldVal;
        callback(null, obj);
      });
    }
  });
};

Model.prototype.addComputer = function(fields, computer) {
  this.on('prepare', function(obj, callback) {
    callback(null, _.omit(obj, fields));
  });
  this.on('compute', computer);
};

Model.prototype.useTimestamps = function(createdField, updatedField) {
  if (this.usingTimestamps) return;

  var self = this;
  this.createdField = createdField || 'created';
  this.updatedField = updatedField || 'updated';
  this.usingTimestamps = true;
};

Model.prototype.touch = function(node, callback) {
  if (!this.usingTimestamps) return;
  var self = this;
  var query = [
    "START node=node({id})",
    "SET node." + this.updatedField + " = timestamp()",
    "RETURN node." + this.updatedField + " AS updated"
  ].join(' ');
  this.db.query(query, { id: node[this.db.options.id] }, function(err, result) {
    if (err) return callback(err);
    node[self.updatedField] = result.updated;
    callback(null, node);
  });
};

Model.prototype.setUniqueKey = function(key, returnOldOnConflict, callback) {
  this.on('validate', function(obj, cb) {
    if (obj[key] == null) {
      cb(util.format("The `%s` key was not set, but is required to save " +
                     "this object", key));
    } else cb();
  });
  if (typeof returnOldOnConflict == 'function') {
    callback = returnOldOnConflict;
    returnOldOnConflict = false;
  }
  this.uniqueness = { key: key, returnOld: !!returnOldOnConflict };
  this.db.constraints.uniqueness.createIfNone(this.type, key, callback || function(err) {
    if (err) throw err;
  });
};

Model.prototype.save = require('./write').save;
Model.prototype.push = require('./write').pushComposition;
Model.prototype.saveComposition = require('./write').saveComposition;
Model.prototype.read = require('./read').read;
Model.prototype.readComposition = require('./read').readComposition;

Model.prototype.prepare = function(obj, cb) {
  this.triggerTransformEvent('prepare', obj, cb);
};

Model.prototype.validate = function(obj, cb) {
  this.triggerProgressionEvent('validate', obj, cb);
};

Model.prototype.compute = function(obj, cb) {
  if (Array.isArray(obj)) {
    async.map(obj, this.compute.bind(this), cb);
  } else {
    this.triggerTransformEvent('compute', obj, cb);
  }
};

Model.prototype.on = function(event, action) {
  if (!this.events[event]) throw new Error("No such event - " + event);
  this.events[event].push(action);
};

// Triggers a regular event. A regular event is just a function, we do not wait
// for it to call back before continuing.
Model.prototype.triggerEvent = function(event, object, callback) {
  if (this.events[event].length) {
    this.events[event].forEach(function(listener) {
      listener(object);
    });
  }
  callback && callback(null, object);
};

// Triggers a progression event. A progression event is a function that takes a
// callback. We wait for that function to call the callback before continuing.
// The callback passed to `triggerProgressionEvent` will not be called until all
// of the attached progression events have called their callbacks.
Model.prototype.triggerProgressionEvent = function(event, object, callback) {
  if (!this.events[event].length) {
    callback(null, object);
  } else {
    async.forEach(this.events[event], function(listener, callback) {
      listener(object, callback);
    }, function(err) {
      callback(err, object);
    });
  }
}

// Triggers a transform event. A transform event is a function that takes a
// callback, and returns a transformed version of the object. `callback` is
// called when all of the transform events have finished, and is passed the
// final transformed version of `object`.
Model.prototype.triggerTransformEvent = function(event, object, callback) {
  if (!this.events[event].length) {
    callback(null, object);
  } else {
    var seed = function(cb) {
      cb(null, _.clone(object));
    };
    async.waterfall([seed].concat(this.events[event]), callback);
  }
}

Model.prototype.exists = function(idOrObj, callback) {
  var self = this;
  var id = this.db._getId(idOrObj);
  this.db.readLabels(id, function(err, labels) {
    if (err) {
      if (err.neo4jException == 'NodeNotFoundException') return callback(null, false);
      else return callback(err);
    }
    callback(null, !!~labels.indexOf(self.type));
  });
};

Model.prototype.findAll = function(opts, callback) {
  if (typeof opts == 'function') {
    callback = opts;
    opts = {};
  }
  this.read(null, opts, callback);
}

Model.prototype.query = function(query, params, opts, callback) {
  if (typeof params == 'function') {
    callback = params;
    params = {};
    opts = {};
  } else if (typeof opts == 'function') {
    callback = opts;
    opts = {};
  }

  opts.query = query;
  opts.params = params;

  this.read(null, opts, callback);
};

Model.prototype.where = function(predicate, opts, callback) {
  if (typeof opts == 'function') {
    callback = opts;
    opts = { any: false };
  }
  var self = this;

  opts.varName = opts.varName || 'node';

  var matchers = Object.keys(predicate).map(function(key) {
    if (predicate[key] instanceof RegExp) {
      predicate[key] = '(?isu)' + predicate[key].source;
      return opts.varName + '.' + key + ' =~ {' + key + '}';
    }
    return opts.varName + '.' + key + ' = {' + key + '}';
  });

  var query = 'MATCH (' + opts.varName + ':' + this.type + ') ' +
              'WHERE ' + matchers.join(opts.any ? ' OR ' : ' AND ')

  this.query(query, predicate, opts, callback);
}

Model.prototype.compose = require('./compose');

module.exports = function createSeraphModel(db, type) {
  return new Model(db, type);
};
<|endoftext|>


<commit_before>//test
<commit_msg>Update task.js<commit_after>// task 1
//Дана строка 'aaa@bbb@ccc@ddd@ggg@www@'.
//Найдите и замените все буквы находящиеся между символами "@ ... @" на пробелы.

var str = "aaa@bbb@ccc@ddd@ggg@www@";
console.log(str.replace(/@.*?@/g, "@   @"));

// task 2
//Запишите в replace выражение которое вернет следующий результат:

var date = "31/12/2025";
console.log(date.replace(/\//g, ' - ')); // 31 - 12 - 2025

// task 3 ?

// task 4 ?
function testNum(phoneNum){
      if(phoneNum.length > 8 ){
	  	return phoneNum.replace(/\D/g, '');
      } alert('Enter correct phone number');
}
var number = prompt('Enter phone number');
alert(testNum(number));

//task 5
//Write a pattern that matches e-mail addresses.

var reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
var str1 = 'aleksandr123@ua.net';
var str2 = 'aleksandr12..3@ua.net';
console.log(reg.test(str1));//true
console.log(reg.test(str2));//false

// task 6
//Write a JavaScript program to search a date within a string.

function testForDate(str){
   var reg = /\d+\/\d+\/\d+/g;
  var result = str.match(reg);
  console.log(result);
 }
testForDate("Hello, my birthday is on 20/06/1993"); // returns [20/06/1993]
testForDate("Hello, my favourite days are 20/06/1993 and 20/06/2010"); // returns [20/06/1993, 20/06/2010]
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOControllerConfig',
  documentation: `
    A customizable model to configure any DAOController
  `,

  requires: [
    'foam.comics.SearchMode',
    'foam.comics.v2.CannedQuery',
    'foam.comics.v2.namedViews.NamedViewCollection'
  ],

  properties: [
    {
      class: 'String',
      name: 'daoKey'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      view: { class: 'foam.u2.view.JSONTextView' }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'dao',
      hidden: true,
      expression: function(daoKey, predicate) {
        var dao = this.__context__[daoKey] || foam.dao.NullDAO.create({of: foam.core.FObject});
        if ( this.hasOwnProperty('of') ) {
          dao = foam.dao.ProxyDAO.create({
            of: this.of,
            delegate: dao
          });
        }
        if ( predicate ) {
          dao = dao.where(predicate);
        }
        return dao;
      }
    },
    {
      class: 'Class',
      name: 'of',
      expression: function(dao$of) { return dao$of; }
    },
    {
      class: 'String',
      name: 'browseTitle',
      expression: function(of) { return  of.model_.plural }
    },
    {
      class: 'String',
      name: 'browseSubtitle',
      expression: function(of) { return "View all " + of.model_.plural.toLowerCase() +'.' }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'summaryView',
    },
    {
      class: 'String',
      name: 'createTitle',
      expression: function(of) { return 'Create a New ' + of.model_.label; }
    },
    {
      class: 'Array',
      name: 'defaultColumns',
      factory: null,
      expression: function(of) {
        var tableColumns = of.getAxiomByName('tableColumns');

        return tableColumns
                ? tableColumns.columns
                : of.getAxiomsByClass(foam.core.Property).map(p => p.name);
      }
    },
    {
      class: 'Enum',
      of: 'foam.comics.SearchMode',
      name: 'searchMode',
      help: `
        The level of search capabilities that the controller should have.
      `,
      value: 'FULL'
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'browseBorder',
      expression: function() {
        // Can't use a value here because java tries to generate a HasMap
        // for it which doesn't jive with the AbstractFObjectPropertyInfo.
        return { class: 'foam.u2.borders.NullBorder' };
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.comics.v2.namedViews.NamedViewCollection',
      name: 'browseViews',
      factory: null,
      expression: function(of) {
        return of.getAxiomsByClass(this.NamedViewCollection);
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.comics.v2.CannedQuery',
      name: 'cannedQueries',
      factory: null,
      expression: function(of) {
        return of.getAxiomsByClass(this.CannedQuery);
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewBorder',
      factory: function() {
        // Can't use a value here because java tries to generate a HasMap
        // for it which doesn't jive with the AbstractFObjectPropertyInfo.
        return { class: 'foam.u2.borders.NullBorder' };
      }
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'createPredicate',
      documentation: 'If set to false, the "Create" button will not be visible.',
      factory: function() {
        return foam.mlang.predicate.True.create();
      },
      javaFactory: `
        return foam.mlang.MLang.TRUE;
      `
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'editPredicate',
      documentation: 'True to enable the edit button.',
      factory: function() {
        return foam.mlang.predicate.True.create();
      },
      javaFactory: `
        return foam.mlang.MLang.TRUE;
      `
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'deletePredicate',
      documentation: 'True to enable the delete button.',
      factory: function() {
        return foam.mlang.predicate.True.create();
      },
      javaFactory: `
        return foam.mlang.MLang.TRUE;
      `
    },
    {
      of: 'foam.mlang.predicate.Predicate',
      name: 'filterExportPredicate',
      documentation: 'Filtering the types of formats user is able to export from TableView'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.CRUDEnabledActionsAuth',
      name: 'CRUDEnabledActionsAuth'
    }
  ]
});
<commit_msg>Added default factory for summaryView of scrollTableView<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOControllerConfig',
  documentation: `
    A customizable model to configure any DAOController
  `,

  requires: [
    'foam.comics.SearchMode',
    'foam.comics.v2.CannedQuery',
    'foam.comics.v2.namedViews.NamedViewCollection'
  ],

  properties: [
    {
      class: 'String',
      name: 'daoKey'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      view: { class: 'foam.u2.view.JSONTextView' }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'dao',
      hidden: true,
      expression: function(daoKey, predicate) {
        var dao = this.__context__[daoKey] || foam.dao.NullDAO.create({of: foam.core.FObject});
        if ( this.hasOwnProperty('of') ) {
          dao = foam.dao.ProxyDAO.create({
            of: this.of,
            delegate: dao
          });
        }
        if ( predicate ) {
          dao = dao.where(predicate);
        }
        return dao;
      }
    },
    {
      class: 'Class',
      name: 'of',
      expression: function(dao$of) { return dao$of; }
    },
    {
      class: 'String',
      name: 'browseTitle',
      expression: function(of) { return  of.model_.plural }
    },
    {
      class: 'String',
      name: 'browseSubtitle',
      expression: function(of) { return "View all " + of.model_.plural.toLowerCase() +'.' }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'summaryView',
      factory: function() {
        return {
          class: 'foam.u2.view.ScrollTableView',
          enableDynamicTableHeight: false,
          css: {
            width: '100%',
            height: '424px'
          }
        };
      }
    },
    {
      class: 'String',
      name: 'createTitle',
      expression: function(of) { return 'Create a New ' + of.model_.label; }
    },
    {
      class: 'Array',
      name: 'defaultColumns',
      factory: null,
      expression: function(of) {
        var tableColumns = of.getAxiomByName('tableColumns');

        return tableColumns
                ? tableColumns.columns
                : of.getAxiomsByClass(foam.core.Property).map(p => p.name);
      }
    },
    {
      class: 'Enum',
      of: 'foam.comics.SearchMode',
      name: 'searchMode',
      help: `
        The level of search capabilities that the controller should have.
      `,
      value: 'FULL'
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'browseBorder',
      expression: function() {
        // Can't use a value here because java tries to generate a HasMap
        // for it which doesn't jive with the AbstractFObjectPropertyInfo.
        return { class: 'foam.u2.borders.NullBorder' };
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.comics.v2.namedViews.NamedViewCollection',
      name: 'browseViews',
      factory: null,
      expression: function(of) {
        return of.getAxiomsByClass(this.NamedViewCollection);
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.comics.v2.CannedQuery',
      name: 'cannedQueries',
      factory: null,
      expression: function(of) {
        return of.getAxiomsByClass(this.CannedQuery);
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewBorder',
      factory: function() {
        // Can't use a value here because java tries to generate a HasMap
        // for it which doesn't jive with the AbstractFObjectPropertyInfo.
        return { class: 'foam.u2.borders.NullBorder' };
      }
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'createPredicate',
      documentation: 'If set to false, the "Create" button will not be visible.',
      factory: function() {
        return foam.mlang.predicate.True.create();
      },
      javaFactory: `
        return foam.mlang.MLang.TRUE;
      `
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'editPredicate',
      documentation: 'True to enable the edit button.',
      factory: function() {
        return foam.mlang.predicate.True.create();
      },
      javaFactory: `
        return foam.mlang.MLang.TRUE;
      `
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'deletePredicate',
      documentation: 'True to enable the delete button.',
      factory: function() {
        return foam.mlang.predicate.True.create();
      },
      javaFactory: `
        return foam.mlang.MLang.TRUE;
      `
    },
    {
      of: 'foam.mlang.predicate.Predicate',
      name: 'filterExportPredicate',
      documentation: 'Filtering the types of formats user is able to export from TableView'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.CRUDEnabledActionsAuth',
      name: 'CRUDEnabledActionsAuth'
    }
  ]
});
<|endoftext|>


<commit_before>/*
---

name: ScrollView

description: Provides a view that scrolls when its content is larger than the
             view area.

license: MIT-style license.

authors:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- View
	- ScrollViewRoles

provides:
	- ScrollView

...
*/

/**
 * @class
 *
 * Provides a view that scrolls when its content is larger than the view area.
 *
 * @name    ScrollView
 * @extends View
 *
 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
 * @version 0.1
 */
Moobile.ScrollView = new Class( /** @lends ScrollView.prototype */ {

	Extends: Moobile.View,

	/**
	 * The scroller engine.
	 * @type {Scroller}
	 */
	scroller: null,

	/**
	 * The view content wrapper element.
	 * @type {Element}
	 */
	contentWrapper: null,

	/**
	 * The initial scroller position.
	 * @type {Object}
	 */
	defaultContentOffset: {
		x: 0,
		y: 0
	},

	/**
	 * Scroll to an x,y coordinates.
	 * @param {Integer} x The x position.
	 * @param {Integer} y The y position.
	 * @param {Integer} time The transition time.
	 * @param {Boolean} relative Whether the coordinates are relative to the current position.
	 * @return {ScrollView}
	 * @since 0.1
	 */
	scrollTo: function(x, y, time, relative) {
		this.scroller.scrollTo(x, y, time, relative);
		return this;
	},

	/**
	 * Scroll to an element.
	 * @param {Element} element The element to scroll to.
	 * @param {Integer} time The transition time.
	 * @return {ScrollView}
	 * @since 0.1
	 */
	scrollToElement: function(element, time) {
		this.scroller.scrollToElement(element, time);
		return this;
	},

	/**
	 * Scroll to a page.
	 * @param {Integer} pageX The horizontal page.
	 * @param {Integer} pageY The vertical page.
	 * @param {Integer} time The transition tiem.
	 * @return {ScrollView}
	 * @since 0.1
	 */
	scrollToPage: function (pageX, pageY, time) {
		this.scroller.scrollToPage(pageX, pageY, time);
		return this;
	},

	/**
	 * Return the scroller engine.
	 * @return {Scroller}
	 * @since 0.1
	 */
	getScroller: function() {
		return this.scroller;
	},

	/**
	 * Return the view content wrapper element.
	 * @return {Element}
	 * @since 0.1
	 */
	getContentWrapper: function() {
		return this.wrapper;
	},

	/**
	 * Return the size of the content.
	 * @return {Object}
	 * @since 0.1
	 */
	getContentSize: function() {
		return this.content.getScrollSize();
	},

	/**
	 * Return the content offset.
	 * @return {Object}
	 * @since 0.1
	 */
	getContentOffset: function() {
		return this.scroller.getOffset();
	},

	/**
	 * @see Entity#didLoad
	 */
	didLoad: function() {

		this.parent();

		this.element.addClass('scroll-view');

		this.contentWrapper = new Element('div.view-content-wrapper');
		this.contentWrapper.wraps(this.content);

		this.scroller = new Moobile.Scroller(this.contentWrapper, this.content);
		this.scroller.addEvent('scrollstart', this.bound('onViewScrollStart'));
		this.scroller.addEvent('scrollmove', this.bound('onViewScrollMove'));
		this.scroller.addEvent('scrollend', this.bound('onViewScrollEnd'));
		this.scroller.addEvent('refresh', this.bound('onViewScrollRefresh'));
	},

	/**
	 * @see Entity#didBecomeReady
	 */
	didBecomeReady: function() {
		this.scroller.refresh();
	},

	/**
	 * @see Entity#didBecomeReady
	 */
	willHide: function() {
		this.parent();
		this.defaultContentOffset = this.scroller.getOffset();
		this.scroller.disable();
	},

	/**
	 * @see Entity#didBecomeReady
	 */
	didShow: function() {
		this.parent();
		this.scroller.enable();
		this.scroller.scrollTo(this.defaultContentOffset.x, this.defaultContentOffset.y);
	},

	/**
	 * @see Entity#destroy
	 */
	destroy: function() {

		this.scroller.removeEvent('scrollstart', this.bound('onViewScrollStart'));
		this.scroller.removeEvent('scrollmove', this.bound('onViewScrollMove'));
		this.scroller.removeEvent('scrollend', this.bound('onViewScrollEnd'));
		this.scroller.removeEvent('refresh', this.bound('onViewScrollRefresh'));

		this.scroller.destroy();
		this.scroller = null;
		this.wrapper = null;

		this.parent();
	},

	/**
	 * Handler for the scroll refresh event.
	 * @param {Event} e The event.
	 * @private
	 * @since 0.1
	 */
	onViewScrollRefresh: function() {
		this.fireEvent('scrollrefresh');
	},

	/**
	 * Handler for the scroll start event.
	 * @param {Event} e The event.
	 * @private
	 * @since 0.1
	 */
	onViewScrollStart: function() {
		this.fireEvent('scrollstart');
	},

	/**
	 * Handler for the scroll move event.
	 * @param {Event} e The event.
	 * @private
	 * @since 0.1
	 */
	onViewScrollMove: function() {
		this.fireEvent('scrollmove');
	},

	/**
	 * Handler for the scroll end event.
	 * @param {Event} e The event.
	 * @private
	 * @since 0.1
	 */
	onViewScrollEnd: function() {
		this.fireEvent('scrollend');
	}

});

//------------------------------------------------------------------------------
// Roles
//------------------------------------------------------------------------------

/**
 * Defines the role 'scroll-view' under all entities.
 * @since 0.1
 */
Moobile.Entity.defineRole('scroll-view', null, function(element, name) {
	var instance = Moobile.Entity.fromElement(element, 'data-scroll-view', Moobile.ScrollView);
	this.addChild(instance);
});
<commit_msg>Removed comments from this class becase it's one of the next thing that's going to be refactored<commit_after>/*
---

name: ScrollView

description: Provides a view that scrolls when its content is larger than the
             view area.

license: MIT-style license.

authors:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- View
	- ScrollViewRoles

provides:
	- ScrollView

...
*/

Moobile.ScrollView = new Class( /** @lends ScrollView.prototype */ {

	Extends: Moobile.View,

	scroller: null,

	contentWrapper: null,

	defaultContentOffset: {
		x: 0,
		y: 0
	},

	scrollTo: function(x, y, time, relative) {
		this.scroller.scrollTo(x, y, time, relative);
		return this;
	},

	scrollToElement: function(element, time) {
		this.scroller.scrollToElement(element, time);
		return this;
	},

	scrollToPage: function (pageX, pageY, time) {
		this.scroller.scrollToPage(pageX, pageY, time);
		return this;
	},

	getScroller: function() {
		return this.scroller;
	},

	getContentWrapper: function() {
		return this.wrapper;
	},

	getContentSize: function() {
		return this.content.getScrollSize();
	},

	getContentOffset: function() {
		return this.scroller.getOffset();
	},

	didLoad: function() {

		this.parent();

		this.element.addClass('scroll-view');

		this.contentWrapper = new Element('div.view-content-wrapper');
		this.contentWrapper.wraps(this.content);

		this.scroller = new Moobile.Scroller(this.contentWrapper, this.content);
		this.scroller.addEvent('scrollstart', this.bound('onViewScrollStart'));
		this.scroller.addEvent('scrollmove', this.bound('onViewScrollMove'));
		this.scroller.addEvent('scrollend', this.bound('onViewScrollEnd'));
		this.scroller.addEvent('refresh', this.bound('onViewScrollRefresh'));
	},

	didBecomeReady: function() {
		this.scroller.refresh();
	},

	willHide: function() {
		this.parent();
		this.defaultContentOffset = this.scroller.getOffset();
		this.scroller.disable();
	},

	didShow: function() {
		this.parent();
		this.scroller.enable();
		this.scroller.scrollTo(this.defaultContentOffset.x, this.defaultContentOffset.y);
	},

	destroy: function() {

		this.scroller.removeEvent('scrollstart', this.bound('onViewScrollStart'));
		this.scroller.removeEvent('scrollmove', this.bound('onViewScrollMove'));
		this.scroller.removeEvent('scrollend', this.bound('onViewScrollEnd'));
		this.scroller.removeEvent('refresh', this.bound('onViewScrollRefresh'));

		this.scroller.destroy();
		this.scroller = null;
		this.wrapper = null;

		this.parent();
	},

	onViewScrollRefresh: function() {
		this.fireEvent('scrollrefresh');
	},

	onViewScrollStart: function() {
		this.fireEvent('scrollstart');
	},

	onViewScrollMove: function() {
		this.fireEvent('scrollmove');
	},

	onViewScrollEnd: function() {
		this.fireEvent('scrollend');
	}

});

//------------------------------------------------------------------------------
// Roles
//------------------------------------------------------------------------------

Moobile.Entity.defineRole('scroll-view', null, function(element, name) {
	var instance = Moobile.Entity.fromElement(element, 'data-scroll-view', Moobile.ScrollView);
	this.addChild(instance);
});
<|endoftext|>


<commit_before>/*
The MIT License

Copyright (c) 2010 Roger Chapman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

(function() {
  var initializing = false, fnTest = /xyz/.test(function() {
    var xyz;
  }) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.AnalyticsBase = function() {
  };

  // Create a new Class that inherits from this class
  AnalyticsBase.extend = function(prop) {
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
          typeof _super[name] == "function" && fnTest.test(prop[name]) ?
          (function(name, fn) {
            return function() {
              var tmp = this._super;

              // Add a new ._super() method that is the same method
              // but on the super-class
              this._super = _super[name];

              // The method only need to be bound temporarily, so we
              // remove it when we're done executing
              var ret = fn.apply(this, arguments);
              this._super = tmp;

              return ret;
            };
          })(name, prop[name]) :
          prop[name];
    }

    // The dummy class constructor
    function AnalyticsBase() {
      // All construction is actually done in the init method
      if (!initializing && this.init)
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    AnalyticsBase.prototype = prototype;

    // Enforce the constructor to be what we expect
    AnalyticsBase.constructor = Class;

    // And make this class extendable
    AnalyticsBase.extend = arguments.callee;

    return AnalyticsBase;
  };
})();

var Analytics = AnalyticsBase.extend({

	//Constants
	_PAGEVIEW:'__##PAGEVIEW##__',
	_USER_AGENT:'GoogleAnalytics/1.0 ('+ Titanium.Platform.username +'; U; CPU '+ Titanium.Platform.name + ' ' + Titanium.Platform.version + ' like Mac OS X; ' + Titanium.Platform.locale + '-' + Titanium.Platform.countryCode + ')',

	//Private properties
	_accountId: undefined,
	_db: undefined,
	_session: undefined,
	_storedEvents:0,
	_dispatcherIsBusy:false,
	_httpClient:undefined,

	//Public properties
	enabled: true,

	//Constructor: var analytics = new Analytics('UA-XXXXXXX-X');
	init: function(accountId){
		this._accountId = accountId;
		this._db = Titanium.Database.open('analytics');
		this._initialize_db();
	},

	//Main public methods
	start: function(dispatchPeriod){
		if (this.enabled) {
			this._startNewVisit();
			this._httpClient = Titanium.Network.createHTTPClient();

			var context = this;
			setInterval(function(){
				context._dispatchEvents();
			}, dispatchPeriod * 1000);
		}
	},

	stop: function(){
		this.enabled = false;
	},

	trackPageview: function(pageUrl){

		if (this._session && this.enabled) {

			this._createEvent(this._PAGEVIEW, pageUrl, null, -1);
		}
	},

	trackEvent: function(category, action, label, value){
		if (this._session && this.enabled) {
			this._createEvent(category, action, label, value);
		}
	},

	reset:function(){
		Titanium.App.Properties.setString('analytics_session', null);
	},

	// Private methods
	_initialize_db: function(){

		this._db.execute('CREATE TABLE IF NOT EXISTS events (' +
		'event_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
		'user_id INTEGER NOT NULL, ' +
		'random_val INTEGER NOT NULL, ' +
		'timestamp_first INTEGER NOT NULL, ' +
		'timestamp_previous INTEGER NOT NULL, ' +
		'timestamp_current INTEGER NOT NULL, ' +
		'visits INTEGER NOT NULL, ' +
		'category STRING NOT NULL, ' +
		'action STRING NOT NULL, ' +
		'label STRING NULL, ' +
		'value INTEGER NOT NULL);');

		var rowCount = this._db.execute('SELECT COUNT(*) FROM events');
	    while (rowCount.isValidRow()) {
	        this._storedEvents = rowCount.field(0);
	        rowCount.next();
	    }
	    rowCount.close();
	},

	_startNewVisit: function(){

		var now = Math.round(new Date().getTime() / 1000);
		if (!Titanium.App.Properties.hasProperty('analytics_session')) {

			this._session = {
				user_id:Math.floor(Math.random()*9999999999),
				timestamp_first:now,
				timestamp_previous:now,
				timestamp_current:now,
				visits:1
			};
		}
		else {
			var oldSession = JSON.parse(Titanium.App.Properties.getString('analytics_session'));

			this._session = {
				user_id:oldSession.user_id,
				timestamp_first:oldSession.timestamp_first,
				timestamp_previous:oldSession.timestamp_current,
				timestamp_current:now,
				visits:oldSession.visits + 1
			};

		}

		Titanium.App.Properties.setString('analytics_session', JSON.stringify(this._session));

	},

	_createEvent : function(category, action, label, value) {

		if(this._storedEvents >= 1000) {
			Titanium.API.warn('Analytics: Store full, not storing last event');
			return;
		}

		var rnd = Math.floor(Math.random()*999999999);

		this._db.execute('INSERT INTO events (user_id, random_val, timestamp_first, timestamp_previous, timestamp_current, visits, category, action, label, value) VALUES (?,?,?,?,?,?,?,?,?,?)', this._session.user_id, rnd, this._session.timestamp_first, this._session.timestamp_previous, this._session.timestamp_current, this._session.visits, category, action, label, value);
		this._storedEvents++;
	},

	_dispatchEvents : function() {

		if(!this._dispatcherIsBusy && Titanium.Network.online){

			this._dispatcherIsBusy = true;

			var eventRows = this._db.execute('SELECT * FROM events');

			var eventsToDelete = [];

			while(eventRows.isValidRow()) {

				var event = {
					event_id:eventRows.fieldByName('event_id'),
					user_id:eventRows.fieldByName('user_id'),
					random_val:eventRows.fieldByName('random_val'),
					timestamp_first:eventRows.fieldByName('timestamp_first'),
					timestamp_previous:eventRows.fieldByName('timestamp_previous'),
					timestamp_current:eventRows.fieldByName('timestamp_current'),
					visits:eventRows.fieldByName('visits'),
					category:eventRows.fieldByName('category'),
					action:eventRows.fieldByName('action'),
					label:eventRows.fieldByName('label'),
					value:eventRows.fieldByName('value'),
				};

				var path = this._constructRequestPath(event);

				this._httpClient.open('GET', 'http://www.google-analytics.com' + path, false);
				this._httpClient.setRequestHeader('User-Agent', this._USER_AGENT);
				this._httpClient.send();

				eventsToDelete.push(event.event_id);

				eventRows.next();
			}

			eventRows.close();

			for(var i = 0; i < eventsToDelete.length; i++) {
				this._db.execute('DELETE FROM events WHERE event_id = ?', eventsToDelete[i]);
			}

			this._dispatcherIsBusy = false;
		}
	},

	_constructRequestPath : function(event) {
		var path = new StringBuilder('/__utm.gif');
		path.append('?utmwv=4.4mi');
		path.append('&utmn=').append(event.random_val);
		path.append('&utmcs=UTF-8');
		path.append('&utmsr=' + Titanium.Platform.displayCaps.platformWidth + 'x' + Titanium.Platform.displayCaps.platformHeight);
		path.append('&utmsc=24-bit');
		path.append('&utmul='+ Titanium.Platform.locale + '-' + Titanium.Platform.countryCode);
		path.append('&utmp=').append(event.action);
		path.append('&utmac=').append(this._accountId);

		path.append('&utmcc=');

		var cookie = new StringBuilder('__utma=');
		cookie.append('737325').append('.');
		cookie.append(event.user_id).append('.');
		cookie.append(event.timestamp_first).append(".");
		cookie.append(event.timestamp_previous).append(".");
		cookie.append(event.timestamp_current).append(".");
		cookie.append(event.visits);

		path.append(cookie.toString());

		return path.toString();
	},
});

// Initializes a new instance of the StringBuilder class
// and appends the given value if supplied
function StringBuilder(value)
{
    this.strings = new Array('');
    this.append(value);
};

// Appends the given value to the end of this instance.
StringBuilder.prototype.append = function (value)
{
    if (value) {
        this.strings.push(value);
    }
	return this;
};

// Clears the string buffer
StringBuilder.prototype.clear = function (){
    this.strings.length = 1;
};

// Converts this instance to a String.
StringBuilder.prototype.toString = function (){
    return this.strings.join('');
};<commit_msg>Update small bug in analytics Base object<commit_after>/*
The MIT License

Copyright (c) 2010 Roger Chapman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

(function() {
  var initializing = false, fnTest = /xyz/.test(function() {
    var xyz;
  }) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.AnalyticsBase = function() {
  };

  // Create a new Class that inherits from this class
  AnalyticsBase.extend = function(prop) {
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
          typeof _super[name] == "function" && fnTest.test(prop[name]) ?
          (function(name, fn) {
            return function() {
              var tmp = this._super;

              // Add a new ._super() method that is the same method
              // but on the super-class
              this._super = _super[name];

              // The method only need to be bound temporarily, so we
              // remove it when we're done executing
              var ret = fn.apply(this, arguments);
              this._super = tmp;

              return ret;
            };
          })(name, prop[name]) :
          prop[name];
    }

    // The dummy class constructor
    function AnalyticsBase() {
      // All construction is actually done in the init method
      if (!initializing && this.init)
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    AnalyticsBase.prototype = prototype;

    // Enforce the constructor to be what we expect
    AnalyticsBase.constructor = AnalyticsBase;

    // And make this class extendable
    AnalyticsBase.extend = arguments.callee;

    return AnalyticsBase;
  };
})();

var Analytics = AnalyticsBase.extend({

	//Constants
	_PAGEVIEW:'__##PAGEVIEW##__',
	_USER_AGENT:'GoogleAnalytics/1.0 ('+ Titanium.Platform.username +'; U; CPU '+ Titanium.Platform.name + ' ' + Titanium.Platform.version + ' like Mac OS X; ' + Titanium.Platform.locale + '-' + Titanium.Platform.countryCode + ')',

	//Private properties
	_accountId: undefined,
	_db: undefined,
	_session: undefined,
	_storedEvents:0,
	_dispatcherIsBusy:false,
	_httpClient:undefined,

	//Public properties
	enabled: true,

	//Constructor: var analytics = new Analytics('UA-XXXXXXX-X');
	init: function(accountId){
		this._accountId = accountId;
		this._db = Titanium.Database.open('analytics');
		this._initialize_db();
	},

	//Main public methods
	start: function(dispatchPeriod){
		if (this.enabled) {
			this._startNewVisit();
			this._httpClient = Titanium.Network.createHTTPClient();

			var context = this;
			setInterval(function(){
				context._dispatchEvents();
			}, dispatchPeriod * 1000);
		}
	},

	stop: function(){
		this.enabled = false;
	},

	trackPageview: function(pageUrl){

		if (this._session && this.enabled) {

			this._createEvent(this._PAGEVIEW, pageUrl, null, -1);
		}
	},

	trackEvent: function(category, action, label, value){
		if (this._session && this.enabled) {
			this._createEvent(category, action, label, value);
		}
	},

	reset:function(){
		Titanium.App.Properties.setString('analytics_session', null);
	},

	// Private methods
	_initialize_db: function(){

		this._db.execute('CREATE TABLE IF NOT EXISTS events (' +
		'event_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
		'user_id INTEGER NOT NULL, ' +
		'random_val INTEGER NOT NULL, ' +
		'timestamp_first INTEGER NOT NULL, ' +
		'timestamp_previous INTEGER NOT NULL, ' +
		'timestamp_current INTEGER NOT NULL, ' +
		'visits INTEGER NOT NULL, ' +
		'category STRING NOT NULL, ' +
		'action STRING NOT NULL, ' +
		'label STRING NULL, ' +
		'value INTEGER NOT NULL);');

		var rowCount = this._db.execute('SELECT COUNT(*) FROM events');
	    while (rowCount.isValidRow()) {
	        this._storedEvents = rowCount.field(0);
	        rowCount.next();
	    }
	    rowCount.close();
	},

	_startNewVisit: function(){

		var now = Math.round(new Date().getTime() / 1000);
		if (!Titanium.App.Properties.hasProperty('analytics_session')) {

			this._session = {
				user_id:Math.floor(Math.random()*9999999999),
				timestamp_first:now,
				timestamp_previous:now,
				timestamp_current:now,
				visits:1
			};
		}
		else {
			var oldSession = JSON.parse(Titanium.App.Properties.getString('analytics_session'));

			this._session = {
				user_id:oldSession.user_id,
				timestamp_first:oldSession.timestamp_first,
				timestamp_previous:oldSession.timestamp_current,
				timestamp_current:now,
				visits:oldSession.visits + 1
			};

		}

		Titanium.App.Properties.setString('analytics_session', JSON.stringify(this._session));

	},

	_createEvent : function(category, action, label, value) {

		if(this._storedEvents >= 1000) {
			Titanium.API.warn('Analytics: Store full, not storing last event');
			return;
		}

		var rnd = Math.floor(Math.random()*999999999);

		this._db.execute('INSERT INTO events (user_id, random_val, timestamp_first, timestamp_previous, timestamp_current, visits, category, action, label, value) VALUES (?,?,?,?,?,?,?,?,?,?)', this._session.user_id, rnd, this._session.timestamp_first, this._session.timestamp_previous, this._session.timestamp_current, this._session.visits, category, action, label, value);
		this._storedEvents++;
	},

	_dispatchEvents : function() {

		if(!this._dispatcherIsBusy && Titanium.Network.online){

			this._dispatcherIsBusy = true;

			var eventRows = this._db.execute('SELECT * FROM events');

			var eventsToDelete = [];

			while(eventRows.isValidRow()) {

				var event = {
					event_id:eventRows.fieldByName('event_id'),
					user_id:eventRows.fieldByName('user_id'),
					random_val:eventRows.fieldByName('random_val'),
					timestamp_first:eventRows.fieldByName('timestamp_first'),
					timestamp_previous:eventRows.fieldByName('timestamp_previous'),
					timestamp_current:eventRows.fieldByName('timestamp_current'),
					visits:eventRows.fieldByName('visits'),
					category:eventRows.fieldByName('category'),
					action:eventRows.fieldByName('action'),
					label:eventRows.fieldByName('label'),
					value:eventRows.fieldByName('value'),
				};

				var path = this._constructRequestPath(event);

				this._httpClient.open('GET', 'http://www.google-analytics.com' + path, false);
				this._httpClient.setRequestHeader('User-Agent', this._USER_AGENT);
				this._httpClient.send();

				eventsToDelete.push(event.event_id);

				eventRows.next();
			}

			eventRows.close();

			for(var i = 0; i < eventsToDelete.length; i++) {
				this._db.execute('DELETE FROM events WHERE event_id = ?', eventsToDelete[i]);
			}

			this._dispatcherIsBusy = false;
		}
	},

	_constructRequestPath : function(event) {
		var path = new StringBuilder('/__utm.gif');
		path.append('?utmwv=4.4mi');
		path.append('&utmn=').append(event.random_val);
		path.append('&utmcs=UTF-8');
		path.append('&utmsr=' + Titanium.Platform.displayCaps.platformWidth + 'x' + Titanium.Platform.displayCaps.platformHeight);
		path.append('&utmsc=24-bit');
		path.append('&utmul='+ Titanium.Platform.locale + '-' + Titanium.Platform.countryCode);
		path.append('&utmp=').append(event.action);
		path.append('&utmac=').append(this._accountId);

		path.append('&utmcc=');

		var cookie = new StringBuilder('__utma=');
		cookie.append('737325').append('.');
		cookie.append(event.user_id).append('.');
		cookie.append(event.timestamp_first).append(".");
		cookie.append(event.timestamp_previous).append(".");
		cookie.append(event.timestamp_current).append(".");
		cookie.append(event.visits);

		path.append(cookie.toString());

		return path.toString();
	},
});

// Initializes a new instance of the StringBuilder class
// and appends the given value if supplied
function StringBuilder(value)
{
    this.strings = new Array('');
    this.append(value);
};

// Appends the given value to the end of this instance.
StringBuilder.prototype.append = function (value)
{
    if (value) {
        this.strings.push(value);
    }
	return this;
};

// Clears the string buffer
StringBuilder.prototype.clear = function (){
    this.strings.length = 1;
};

// Converts this instance to a String.
StringBuilder.prototype.toString = function (){
    return this.strings.join('');
};<|endoftext|>


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

foam.CLASS({
  package: 'foam.dao',
  name: 'ManyToManyRelationshipDAO',
  extends: 'foam.dao.RelationshipDAO',

  implements: [ 'foam.mlang.Expressions' ],

  documentation: 'Adapts a DAO based on a *:* Relationship.',

  properties: [
    'junctionProperty',
    'joinDAOKey',
    'targetProperty',
    'junctionCls',
    {
      name: 'predicate',
      documentation: `ManyToMany filtered querys are always "backward" to
        match inverse-namd property and source object's id.`,
      getter: function() {
        return this.EQ(
          this.junctionCls[foam.String.constantize(this.relationship.inverseName)],
          this.obj.id);
      }
    }
  ],

  methods: [
    function put(obj) {
      var self = this;
      return self.__context__[foam.String.daoize(self.of.name)].put(obj)
          .then(function(obj) {
            return self.delegate.put(self.relationship.adaptTarget(self.obj, obj))
          })
    },
    function select(sink, skip, limit, order, predicate) {
      var self = this;
      var joinDAO = this.__context__[this.joinDAOKey];

      return self.SUPER(self.MAP(self.junctionProperty)).then(function(map) {
        return joinDAO.select(sink, skip, limit, order, self.AND(
          predicate || self.TRUE,
          self.IN(self.targetProperty, map.delegate.a)));
      });
    }
  ]
});
<commit_msg>Fix .find() in ManyToManyRelationshipDAO.<commit_after>/**
 * @license
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'ManyToManyRelationshipDAO',
  extends: 'foam.dao.RelationshipDAO',

  implements: [ 'foam.mlang.Expressions' ],

  documentation: 'Adapts a DAO based on a *:* Relationship.',

  properties: [
    'junctionProperty',
    'joinDAOKey',
    'targetProperty',
    'junctionCls',
    {
      name: 'joinDAO',
      getter: function() {
        return this.__context__[this.joinDAOKey];
      }
    },
    {
      name: 'predicate',
      documentation: `ManyToMany filtered querys are always "backward" to
        match inverse-namd property and source object's id.`,
      getter: function() {
        return this.EQ(
          this.junctionCls[foam.String.constantize(this.relationship.inverseName)],
          this.obj.id);
      }
    }
  ],

  methods: [
    function put(obj) {
      var self = this;
      return self.__context__[foam.String.daoize(self.of.name)].put(obj)
          .then(function(obj) {
            return self.delegate.put(self.relationship.adaptTarget(self.obj, obj))
          })
    },
    function find(id) {
      return this.joinDAO.find(id);
    },
    function select(sink, skip, limit, order, predicate) {
      var self = this;

      return self.SUPER(self.MAP(self.junctionProperty)).then(function(map) {
        return self.joinDAO.select(sink, skip, limit, order, self.AND(
          predicate || self.TRUE,
          self.IN(self.targetProperty, map.delegate.a)));
      });
    }
  ]
});
<|endoftext|>


<commit_before>
'use strict';

var when  = require('when');
var Class = require('cakes').Class;

var Db    = require('./db');
var _     = require('./utils');
var errors = require('./errors');

/**
 * Model constructor.
 * @return {Functin} Constructor
 */
var Model = Class(function() {

    /**
     * Db instance
     * @type {Object}
     */
    var _dbInstances = {};

    /**
     * Fields cache
     * @type {Object}
     */
    var _tableFields = {};

    return {

        /**
         * Model name
         * @type {String}
         */
        name: '',

        /**
         * Database name
         * @type {String}
         */
        dbName: '',

        /**
         * Table name
         * @type {String}
         */
        tableName: '',

        /**
         * Table prefix
         * @type {String}
         */
        tablePrefix: '',

        /**
         * Table fields
         * @type {Object}
         */
        fields: {},

        /**
         * Primary key
         * @type {String}
         */
        pk: '',

        /**
         * Data of model
         * @type {Object}
         */
        data: {},

        /**
         * Db config
         * @type {Object}
         */
        config: null,

        /**
         * Options
         * @type {Object}
         */
        options: {},

        /**
         * Auto increament
         * @type {Boolean}
         */
        isAutoInc: false,

        /**
         * Auto check table fields
         * @type {Boolean}
         */
        isAutoCheckFields: true,

        __construct: function(name, tablePrefix, config) {
            // initialize hook
            this._initialize();

            if (_.isString(name) && name !== '') {
                if (name.indexOf('.') !== -1) {
                    var parts = name.split('.');
                    this.dbName = parts[0];
                    this.name = parts[1];
                } else {
                    this.name = name;
                }
            }

            config = config || {};
            this.config = _.mapKeys(config, function(key) {
                return key.toLowerCase();
            });

            if (_.isEmpty(tablePrefix)) {
                this.tablePrefix = '';
            } else if (_.isString(tablePrefix)) {
                this.tablePrefix = tablePrefix;
            } else if (_.isString(this.config.prefix)) {
                this.tablePrefix = this.config.prefix;
            } else {
                this.tablePrefix = '';
            }

            // initialize db instance
            this.initDb();
        },

        _initialize: function() {
            // hook
        },

        /**
         * Initialize db instance.
         * @return {Promise}
         */
        initDb: function() {
            var identifier = _.serialize(this.config);
            if (!this.instance) {
                if (!_dbInstances[identifier]) {
                    _dbInstances[identifier] = (new Db()).getInstance(this.config);
                }
            }

            // set this instance
            this.instance = _dbInstances[identifier];

            // hook
            this._afterDb();
        },

        /**
         * Get model fields info from table.
         * @return {Promise}
         */
        getFieldsInfo: function() {
            var tableName = this.getTableName();
            if (!_.isEmpty(_tableFields[tableName])) {
                this.fields = _tableFields[tableName];
                return when.resolve(this.fields);
            }

            var self = this;
            return this.instance.getFields(tableName).then(function(fields) {
                var _name = [];
                var _type = {};
                _.forOwn(fields, function(attr, field) {
                    _name.push(field);

                    _type[field] = attr.type;
                    if (attr.primary) {
                        self.pk = field;

                        if (attr.autoinc) {
                            self.isAutoinc = true;
                        }
                    }
                });

                self.fields._name = _name;
                self.fields._type = _type;
                self.fields._pk = self.pk;
                _tableFields[tableName] = self.fields;

                return self.fields;
            });
        },

        /**
         * Select.
         * @param  {Object} options Options
         * @return Promise
         */
        select: function(options) {
            var self = this;

            return this.getPk().then(function(pk) {
                var where = {};
                if (_.isString(options) || _.isNumber(options)) {
                    if (_.isString(options) && options.indexOf(',') !== -1) {
                        where[pk] = { IN: options };
                    } else {
                        where[pk] = options;
                    }

                    options = {};
                    options.where = where;
                }

                if (options === false) {
                    // return sql string, but not execute sql
                    return when.resolve(self.getBuildSql({}));
                }

                options = self.parseOptions(options);
                return self.instance.select(options).then(function(rows) {
                    // index
                    if (_.isString(options.index)) {
                        var index = options.index.split(',');
                        var key = null;
                        var result = {};

                        index = index.map(function(i) {
                            return i.trim();
                        });
                        rows.forEach(function(row) {
                            key = row[index[0]];
                            if (!_.isEmpty(key)) {
                                if (_.isset(row[index[1]])) {
                                    result[key] = row[index[1]];
                                } else {
                                    result[key] = row;
                                }
                            }
                        });

                        rows = result;
                    }

                    return rows;
                });
            });
        },

        /**
         * Find one record.
         * @param  {Object}  options Options
         * @return {Promise}
         */
        find: function(options) {
            var self = this;
            options = options || {};

            return this.getPk().then(function(pk) {
                var where = {};

                if (_.isNumber(options) || _.isString(options)) {
                    where[pk] = options;
                    options = {};
                    options.where = where;
                }

                options.limit = 1;
                options = self.parseOptions(options);

                return self.instance.select(options).then(function(result) {
                    return result[0] ? result[0] : null;
                });
            });
        },

        /**
         * Add a record.
         * @param  {Object}  data    Data of record
         * @param  {Object}  options Options
         * @param  {Boolean} replace Insert or replace
         * @return {Promise}
         */
        add: function(data, options, replace) {
            var self = this;

            if (!_.isObject(data) || _.isEmpty(data)) {
                if (!_.isEmpty(self.data)) {
                    data = self.data;
                    self.data = {};
                } else {
                    when.reject(new errors.DbError('[add]: Invalid data.'));
                }
            }

            return this.getPk().then(function(pk) {
                options = self.parseOptions(options);
                data = self.facade(data);
                if (_.isEmpty(data)) {
                    when.reject(new errors.DbError('[add]: Invalid data.'));
                }

                return self.instance.insert(data, options, replace).then(function(result) {
                    data[pk] = result.lastInsertId || 0;
                    return data;
                });
            });
        },

        /**
         * All a batch of records.
         * @param {Array}    data    Data array
         * @param {Object}   options Options
         * @param {Boolean} replace Insert or replace
         */
        addAll: function(data, options, replace) {
            if (!_.isArray(data) || _.isEmpty(data)) {
                when.reject(new errors.DbError('[addAll]: Invalid data.'));
            }

            var self = this;
            options = this.parseOptions(options);
            data.forEach(function(item, index) {
                data[index] = self.facade(item);
                if (_.isEmpty(data[index])) {
                    data[index] = null;
                }
            });

            return this.instance.insertAll(data, options, replace);
        },

        /**
         * Select add.
         * @param  {MIX}     fields  Fields
         * @param  {String}  table   Table name
         * @param  {Object}  options Options
         * @return {Promise}
         */
        selectAdd: function(fields, table, options) {
            if (_.isEmpty(fields) || (!_.isString(fields) && !_.isArray(fields))) {
                fields = options.fields;
            }

            if (_.isString(fields)) {
                fields = fields.split(',');
            }

            if (_.isEmpty(table) || !_.isString(table)) {
                table = this.getTableName();
            }

            options = this.parseOptions(options);
            return this.instance.selectInsert(fields, table, options);
        },

        /**
         * Save.
         * @param  {Object}  data    Data
         * @param  {Object}  options Options
         * @return {Promise}
         */
        save: function(data, options) {
            var self = this;

            if (!_.isObject(data) || _.isEmpty(data)) {
                if (!_.isEmpty(this.data)) {
                    data = this.data;
                    this.data = {};
                } else {
                    when.reject(new errors.DbError('[save]: Invalid data.'));
                }
            }

            return this.getPk().then(function(pk) {
                options = self.parseOptions(options);
                data = self.facade(data);
                if (_.isEmpty(data)) {
                    when.reject(new errors.DbError('[save]: Invalid data.'));
                }

                var where = {};
                if (!_.isset(options.where)) {
                    if (_.isset(data[pk])) {
                        where[pk] = data[pk];
                        options.where = where;
                    } else {
                        when.reject(new errors.DbError('[save]: No update conditions.'));
                    }
                }

                return self.instance.update(data, options);
            });
        },

        /**
         * Delete.
         * @param  {Object}  options Options
         * @return {Promise}
         */
        delete: function(options) {
            var self = this;

            return this.getPk().then(function(pk) {
                var where = {};

                if (_.isNumber(options) || _.isString(options)) {
                    if (_.isString(options) && options.indexOf(',') !== -1) {
                        where[pk] = { IN: options };
                    } else {
                        where[pk] = options;
                    }

                    options = {};
                    options.where = where;
                } else if (_.isEmpty(options) && _.isEmpty(self.options.where)) {
                    if (!_.isEmpty(self.data) && _.isset(self.data[pk])) {
                        return self.delete(self.data[pk]);
                    }
                }

                options = self.parseOptions(options);
                if (_.isEmpty(options.where)) {
                    when.reject('[delete]: No where arguments.');
                }

                return self.instance.delete(options);
            });
        },

        /**
         * Where conditions.
         * @param  {MIX}    where Query conditions
         * @return {Object}       This
         */
        where: function(where) {
            if (_.isString(where) && !_.isEmpty(where)) {
                var map = {};
                map._string = where;
                where = map;
            }

            if (_.isObject(this.options.where)) {
                this.options.where = _.assign(this.options.where, where);
            } else {
                this.options.where = where;
            }

            return this;
        },

        /**
         * Specify fields to query.
         * @param  {MIX}    fields Table field
         * @return {Object}        This
         */
        field: function(fields) {
            this.options.field = fields;

            return this;
        },

        /**
         * Return table name.
         * @return {String} Table name
         */
        getTableName: function() {
            if (_.isEmpty(this.tableName)) {
                this.tableName = !_.isEmpty(this.tablePrefix) ? this.tablePrefix : '';
                this.tableName += _.convertVarName(this.name);
                this.tableName = this.tableName.toLowerCase();
            }

            return !_.isEmpty(this.dbName) ? this.dbName + '.' + this.tableName : this.tableName;
        },

        /**
         * Return table primary key.
         * @return {Promise}
         */
        getPk: function() {
            return this.getFieldsInfo().then(function(fieldsInfo) {
                return fieldsInfo._pk;
            });
        },

        /**
         * Return last query or execute sql.
         * @return {String} Sql string.
         */
        getLastSql: function() {
            return this.instance.getLastSql();
        },

        /**
         * Get sql which can be used to sub query.
         * @param  {Object} options Options
         * @return {String}         Sql
         */
        getBuildSql: function(options) {
            options = this.parseOptions(options);
            return '(' + this.instance.buildSelectSql(options).trim() + ')';
        },

        /**
         * Retrun value.
         * @param  {String} key   Key
         * @param  {MIX}    value Value
         */
        setData: function(key, value) {
            if (!_.isObject(this.data)) {
                this.data = {};
            }

            this.data[key] = value;
            return this;
        },

        /**
         * Set model data.
         * @param  {String} key   Key name
         * @return {MIX}         Value
         */
        getData: function(key) {
            return this.data[key];
        },

        parseOptions: function(options) {
            options = options || {};
            var fields = null;

            if (_.isObject(options)) {
                // merge options
                options = _.assign(options, this.options);
            }

            if (!options.table) {
                options.table = this.getTableName();
            }

            options.model = this.name;
            this.options = {};

            return options;
        },

        /**
         * Filter data.
         * @param  {Object} data Data
         * @return {Object}
         */
        facade: function(data) {
            var fields = null;

            if (!_.isObject(data)) {
                data = {};
            }

            if (!_.isEmpty(this.options.field)) {
                fields = this.options.field;
                delete this.options.field;

                if (_.isString(fields)) {
                    fields = fields.split(',');
                }
            } else if (!_.isEmpty(this.fields)) {
                fields = this.fields._name;
            }

            var self = this;
            if (!_.isNull(fields)) {
                _.forOwn(data, function(value, key) {
                    if (!_.inArray(key, fields)) {
                        delete data[key];
                    } else if (_.isScalar(value)) {
                        data[key] = self.parseType(value, key);
                    }
                });
            }

            return data;
        },

        /**
         * Convert type of data field.
         * @param  {MIX}    value Value
         * @param  {String} key   Field name
         * @return {MIX}          Value
         */
        parseType: function(value, key) {
            if (this.fields._type && _.isEmpty(this.fields._type[key])) {
                return value;
            }

            var fieldType = this.fields._type[key].toLowerCase();
            if (fieldType.indexOf('bigint') === -1 && fieldType.indexOf('int') !== -1) {
                value = parseInt(value, 10);
            } else if (fieldType.indexOf('float') !== -1 || fieldType.indexOf('double') !== -1) {
                value = parseFloat(value);
            } else if (fieldType.indexOf('bool') !== -1) {
                value = Boolean(value);
            }

            return value;
        },

        _afterDb: function() {
        },
    }
});

module.exports = Model;
<commit_msg>Add `saveField` method for Model.<commit_after>
'use strict';

var when  = require('when');
var Class = require('cakes').Class;

var Db    = require('./db');
var _     = require('./utils');
var errors = require('./errors');

/**
 * Model constructor.
 * @return {Functin} Constructor
 */
var Model = Class(function() {

    /**
     * Db instance
     * @type {Object}
     */
    var _dbInstances = {};

    /**
     * Fields cache
     * @type {Object}
     */
    var _tableFields = {};

    return {

        /**
         * Model name
         * @type {String}
         */
        name: '',

        /**
         * Database name
         * @type {String}
         */
        dbName: '',

        /**
         * Table name
         * @type {String}
         */
        tableName: '',

        /**
         * Table prefix
         * @type {String}
         */
        tablePrefix: '',

        /**
         * Table fields
         * @type {Object}
         */
        fields: {},

        /**
         * Primary key
         * @type {String}
         */
        pk: '',

        /**
         * Data of model
         * @type {Object}
         */
        data: {},

        /**
         * Db config
         * @type {Object}
         */
        config: null,

        /**
         * Options
         * @type {Object}
         */
        options: {},

        /**
         * Auto increament
         * @type {Boolean}
         */
        isAutoInc: false,

        /**
         * Auto check table fields
         * @type {Boolean}
         */
        isAutoCheckFields: true,

        __construct: function(name, tablePrefix, config) {
            // initialize hook
            this._initialize();

            if (_.isString(name) && name !== '') {
                if (name.indexOf('.') !== -1) {
                    var parts = name.split('.');
                    this.dbName = parts[0];
                    this.name = parts[1];
                } else {
                    this.name = name;
                }
            }

            config = config || {};
            this.config = _.mapKeys(config, function(key) {
                return key.toLowerCase();
            });

            if (_.isEmpty(tablePrefix)) {
                this.tablePrefix = '';
            } else if (_.isString(tablePrefix)) {
                this.tablePrefix = tablePrefix;
            } else if (_.isString(this.config.prefix)) {
                this.tablePrefix = this.config.prefix;
            } else {
                this.tablePrefix = '';
            }

            // initialize db instance
            this.initDb();
        },

        _initialize: function() {
            // hook
        },

        /**
         * Initialize db instance.
         * @return {Promise}
         */
        initDb: function() {
            var identifier = _.serialize(this.config);
            if (!this.instance) {
                if (!_dbInstances[identifier]) {
                    _dbInstances[identifier] = (new Db()).getInstance(this.config);
                }
            }

            // set this instance
            this.instance = _dbInstances[identifier];

            // hook
            this._afterDb();
        },

        /**
         * Get model fields info from table.
         * @return {Promise}
         */
        getFieldsInfo: function() {
            var tableName = this.getTableName();
            if (!_.isEmpty(_tableFields[tableName])) {
                this.fields = _tableFields[tableName];
                return when.resolve(this.fields);
            }

            var self = this;
            return this.instance.getFields(tableName).then(function(fields) {
                var _name = [];
                var _type = {};
                _.forOwn(fields, function(attr, field) {
                    _name.push(field);

                    _type[field] = attr.type;
                    if (attr.primary) {
                        self.pk = field;

                        if (attr.autoinc) {
                            self.isAutoinc = true;
                        }
                    }
                });

                self.fields._name = _name;
                self.fields._type = _type;
                self.fields._pk = self.pk;
                _tableFields[tableName] = self.fields;

                return self.fields;
            });
        },

        /**
         * Select.
         * @param  {Object} options Options
         * @return Promise
         */
        select: function(options) {
            var self = this;

            return this.getPk().then(function(pk) {
                var where = {};
                if (_.isString(options) || _.isNumber(options)) {
                    if (_.isString(options) && options.indexOf(',') !== -1) {
                        where[pk] = { IN: options };
                    } else {
                        where[pk] = options;
                    }

                    options = {};
                    options.where = where;
                }

                if (options === false) {
                    // return sql string, but not execute sql
                    return when.resolve(self.getBuildSql({}));
                }

                options = self.parseOptions(options);
                return self.instance.select(options).then(function(rows) {
                    // index
                    if (_.isString(options.index)) {
                        var index = options.index.split(',');
                        var key = null;
                        var result = {};

                        index = index.map(function(i) {
                            return i.trim();
                        });
                        rows.forEach(function(row) {
                            key = row[index[0]];
                            if (!_.isEmpty(key)) {
                                if (_.isset(row[index[1]])) {
                                    result[key] = row[index[1]];
                                } else {
                                    result[key] = row;
                                }
                            }
                        });

                        rows = result;
                    }

                    return rows;
                });
            });
        },

        /**
         * Find one record.
         * @param  {Object}  options Options
         * @return {Promise}
         */
        find: function(options) {
            var self = this;
            options = options || {};

            return this.getPk().then(function(pk) {
                var where = {};

                if (_.isNumber(options) || _.isString(options)) {
                    where[pk] = options;
                    options = {};
                    options.where = where;
                }

                options.limit = 1;
                options = self.parseOptions(options);

                return self.instance.select(options).then(function(result) {
                    return result[0] ? result[0] : null;
                });
            });
        },

        /**
         * Add a record.
         * @param  {Object}  data    Data of record
         * @param  {Object}  options Options
         * @param  {Boolean} replace Insert or replace
         * @return {Promise}
         */
        add: function(data, options, replace) {
            var self = this;

            if (!_.isObject(data) || _.isEmpty(data)) {
                if (!_.isEmpty(self.data)) {
                    data = self.data;
                    self.data = {};
                } else {
                    when.reject(new errors.DbError('[add]: Invalid data.'));
                }
            }

            return this.getPk().then(function(pk) {
                options = self.parseOptions(options);
                data = self.facade(data);
                if (_.isEmpty(data)) {
                    when.reject(new errors.DbError('[add]: Invalid data.'));
                }

                return self.instance.insert(data, options, replace).then(function(result) {
                    data[pk] = result.lastInsertId || 0;
                    return data;
                });
            });
        },

        /**
         * All a batch of records.
         * @param {Array}    data    Data array
         * @param {Object}   options Options
         * @param {Boolean} replace Insert or replace
         */
        addAll: function(data, options, replace) {
            if (!_.isArray(data) || _.isEmpty(data)) {
                when.reject(new errors.DbError('[addAll]: Invalid data.'));
            }

            var self = this;
            options = this.parseOptions(options);
            data.forEach(function(item, index) {
                data[index] = self.facade(item);
                if (_.isEmpty(data[index])) {
                    data[index] = null;
                }
            });

            return this.instance.insertAll(data, options, replace);
        },

        /**
         * Select add.
         * @param  {MIX}     fields  Fields
         * @param  {String}  table   Table name
         * @param  {Object}  options Options
         * @return {Promise}
         */
        selectAdd: function(fields, table, options) {
            if (_.isEmpty(fields) || (!_.isString(fields) && !_.isArray(fields))) {
                fields = options.fields;
            }

            if (_.isString(fields)) {
                fields = fields.split(',');
            }

            if (_.isEmpty(table) || !_.isString(table)) {
                table = this.getTableName();
            }

            options = this.parseOptions(options);
            return this.instance.selectInsert(fields, table, options);
        },

        /**
         * Save.
         * @param  {Object}  data    Data
         * @param  {Object}  options Options
         * @return {Promise}
         */
        save: function(data, options) {
            var self = this;

            if (!_.isObject(data) || _.isEmpty(data)) {
                if (!_.isEmpty(this.data)) {
                    data = this.data;
                    this.data = {};
                } else {
                    when.reject(new errors.DbError('[save]: Invalid data.'));
                }
            }

            return this.getPk().then(function(pk) {
                options = self.parseOptions(options);
                data = self.facade(data);
                if (_.isEmpty(data)) {
                    when.reject(new errors.DbError('[save]: Invalid data.'));
                }

                var where = {};
                if (!_.isset(options.where)) {
                    if (_.isset(data[pk])) {
                        where[pk] = data[pk];
                        options.where = where;
                    } else {
                        when.reject(new errors.DbError('[save]: No update conditions.'));
                    }
                }

                return self.instance.update(data, options);
            });
        },

        /**
         * Save data with Specify field.
         * @param  {MIX}     field Field want to be update.
         * @param  {MIX}     value New value of field.
         * @return {Promise}
         */
        saveField: function(field, value) {
            var data = null;

            data = _.isArray(field) ? field : {};
            data[field] = value;

            return this.save(data);
        },

        /**
         * Delete.
         * @param  {Object}  options Options
         * @return {Promise}
         */
        delete: function(options) {
            var self = this;

            return this.getPk().then(function(pk) {
                var where = {};

                if (_.isNumber(options) || _.isString(options)) {
                    if (_.isString(options) && options.indexOf(',') !== -1) {
                        where[pk] = { IN: options };
                    } else {
                        where[pk] = options;
                    }

                    options = {};
                    options.where = where;
                } else if (_.isEmpty(options) && _.isEmpty(self.options.where)) {
                    if (!_.isEmpty(self.data) && _.isset(self.data[pk])) {
                        return self.delete(self.data[pk]);
                    }
                }

                options = self.parseOptions(options);
                if (_.isEmpty(options.where)) {
                    when.reject('[delete]: No where arguments.');
                }

                return self.instance.delete(options);
            });
        },

        /**
         * Where conditions.
         * @param  {MIX}    where Query conditions
         * @return {Object}       This
         */
        where: function(where) {
            if (_.isString(where) && !_.isEmpty(where)) {
                var map = {};
                map._string = where;
                where = map;
            }

            if (_.isObject(this.options.where)) {
                this.options.where = _.assign(this.options.where, where);
            } else {
                this.options.where = where;
            }

            return this;
        },

        /**
         * Specify fields to query.
         * @param  {MIX}    fields Table field
         * @return {Object}        This
         */
        field: function(fields) {
            this.options.field = fields;

            return this;
        },

        /**
         * Return table name.
         * @return {String} Table name
         */
        getTableName: function() {
            if (_.isEmpty(this.tableName)) {
                this.tableName = !_.isEmpty(this.tablePrefix) ? this.tablePrefix : '';
                this.tableName += _.convertVarName(this.name);
                this.tableName = this.tableName.toLowerCase();
            }

            return !_.isEmpty(this.dbName) ? this.dbName + '.' + this.tableName : this.tableName;
        },

        /**
         * Return table primary key.
         * @return {Promise}
         */
        getPk: function() {
            return this.getFieldsInfo().then(function(fieldsInfo) {
                return fieldsInfo._pk;
            });
        },

        /**
         * Return last query or execute sql.
         * @return {String} Sql string.
         */
        getLastSql: function() {
            return this.instance.getLastSql();
        },

        /**
         * Get sql which can be used to sub query.
         * @param  {Object} options Options
         * @return {String}         Sql
         */
        getBuildSql: function(options) {
            options = this.parseOptions(options);
            return '(' + this.instance.buildSelectSql(options).trim() + ')';
        },

        /**
         * Retrun value.
         * @param  {String} key   Key
         * @param  {MIX}    value Value
         */
        setData: function(key, value) {
            if (!_.isObject(this.data)) {
                this.data = {};
            }

            this.data[key] = value;
            return this;
        },

        /**
         * Set model data.
         * @param  {String} key   Key name
         * @return {MIX}         Value
         */
        getData: function(key) {
            return this.data[key];
        },

        parseOptions: function(options) {
            options = options || {};
            var fields = null;

            if (_.isObject(options)) {
                // merge options
                options = _.assign(options, this.options);
            }

            if (!options.table) {
                options.table = this.getTableName();
            }

            options.model = this.name;
            this.options = {};

            return options;
        },

        /**
         * Filter data.
         * @param  {Object} data Data
         * @return {Object}
         */
        facade: function(data) {
            var fields = null;

            if (!_.isObject(data)) {
                data = {};
            }

            if (!_.isEmpty(this.options.field)) {
                fields = this.options.field;
                delete this.options.field;

                if (_.isString(fields)) {
                    fields = fields.split(',');
                }
            } else if (!_.isEmpty(this.fields)) {
                fields = this.fields._name;
            }

            var self = this;
            if (!_.isNull(fields)) {
                _.forOwn(data, function(value, key) {
                    if (!_.inArray(key, fields)) {
                        delete data[key];
                    } else if (_.isScalar(value)) {
                        data[key] = self.parseType(value, key);
                    }
                });
            }

            return data;
        },

        /**
         * Convert type of data field.
         * @param  {MIX}    value Value
         * @param  {String} key   Field name
         * @return {MIX}          Value
         */
        parseType: function(value, key) {
            if (this.fields._type && _.isEmpty(this.fields._type[key])) {
                return value;
            }

            var fieldType = this.fields._type[key].toLowerCase();
            if (fieldType.indexOf('bigint') === -1 && fieldType.indexOf('int') !== -1) {
                value = parseInt(value, 10);
            } else if (fieldType.indexOf('float') !== -1 || fieldType.indexOf('double') !== -1) {
                value = parseFloat(value);
            } else if (fieldType.indexOf('bool') !== -1) {
                value = Boolean(value);
            }

            return value;
        },

        _afterDb: function() {
        },
    }
});

module.exports = Model;
<|endoftext|>