Skip to content

Instantly share code, notes, and snippets.

@sergeysova
Created November 7, 2015 00:03
Show Gist options
  • Save sergeysova/b4fa01c7dc1a19a0c64a to your computer and use it in GitHub Desktop.
Save sergeysova/b4fa01c7dc1a19a0c64a to your computer and use it in GitHub Desktop.
JS ActiveRecord
import Dispatcher, {EventObject} from "Dispatcher";
/**
* Items collection
*/
export default class Collection {
static E_ADD = 'add';
static E_REMOVE = 'remove';
static E_CHANGE = 'change';
/**
* @param elements
* @param dispatcher
*/
constructor(elements = [], dispatcher:Dispatcher = null) {
dispatcher = dispatcher || new Dispatcher();
Object.defineProperty(this, 'events', { get: () => dispatcher });
Object.defineProperty(this, 'elements', { get: () => elements });
this.events.listen(this.constructor.E_ADD, () => {
this.events.fire(this.constructor.E_CHANGE, this.elements);
});
this.events.listen(this.constructor.E_REMOVE, () => {
this.events.fire(this.constructor.E_CHANGE, this.elements);
});
for (var i = 0; i < elements.length; i++) {
this.push(elements[i]);
}
}
/**
* @param event
* @param callback
* @returns {EventObject}
*/
on(event, callback:Function) {
return this.events.listen(event, callback);
}
/**
* @param event
* @returns {Dispatcher}
*/
off(event:EventObject) {
return event.remove();
}
/**
* @param event
* @param callback
* @returns {Event}
*/
once(event, callback:Function) {
return this.on(event, callback).once();
}
/**
* @param item
* @returns {Collection}
*/
push(item) {
this.elements.push(item);
this.events.fire(this.constructor.E_ADD, item);
return this;
}
/**
* @param item
* @returns {Collection}
*/
unshift(item) {
this.elements.unshift(item);
this.events.fire(this.constructor.E_ADD, item);
return this;
}
/**
* @returns {Collection}
*/
pop() {
var item = this.elements.pop();
this.events.fire(this.constructor.E_REMOVE, item);
return item;
}
/**
* @returns {Collection}
*/
shift() {
var item = this.elements.shift();
this.events.fire(this.constructor.E_REMOVE, item);
return item;
}
/**
* @param callback
* @returns {Collection}
*/
remove(callback:Function) {
var items = [];
for (var i = 0; i < this.elements.length; i++) {
if (!callback(this.elements[i])) {
items.push(this.elements[i]);
}
}
return new this.constructor(items);
}
/**
* @param callback
* @returns {Collection}
*/
find(callback:Function) {
var items = [];
for (var i = 0; i < this.elements.length; i++) {
if (callback(this.elements[i])) {
items.push(this.elements[i]);
}
}
return new this.constructor(items);
}
/**
* @param key
* @param op
* @param value
* @returns {Collection}
*/
where(key, op, value) {
if (typeof op === 'undefined') {
op = '=';
value = true;
} else if (typeof value === 'undefined') {
value = op;
op = '=';
}
return this.find(item => {
switch (op) {
case '>': return item[key] > value;
case '<': return item[key] < value;
case '>=': return item[key] >= value;
case '<=': return item[key] <= value;
case '<>':
case '!=': return item[key] != value;
default: return item[key] == value;
}
});
}
/**
* @param callback
* @param order
* @return {Collection}
*/
sort(callback:Function, order = 1) {
switch (order.toString().toLowerCase()) {
case 'asc':
order = 1;
break;
case 'desc':
order = -1;
break;
}
order = order > 0 ? 1 : -1;
return new this.constructor(this.elements.sort((a, b) => {
a = callback(a);
b = callback(b);
if (a === b) {
return 0;
}
return a > b ? order : -order;
}));
}
/**
* @param callback
* @returns {Collection}
*/
each(callback:Function) {
for (var i = 0; i < this.elements.length; i++) {
callback(this.elements[i]);
}
return this;
}
/**
* @param callback
* @returns {Collection}
*/
map(callback:Function) {
return new this.constructor(this.elements.map(item => callback(item)));
}
/**
* @param delimiter
* @param property
* @returns {*}
*/
join(delimiter = ', ', property = null) {
if (this.length > 0) {
var items = this.elements.map(item => {
if (property) {
return item[property] ? item[property].toString() : '';
}
return item.toString();
});
return items.join(delimiter);
}
return '';
}
/**
* @param count
* @returns {Collection}
*/
take(count = 1) {
return new this.constructor(this.elements.slice(0, count));
}
/**
* @param field
* @param order
* @returns {Collection}
*/
orderBy(field, order = 1) {
return this.sort(item => item[field], order);
}
/**
* @param shift
* @returns {*}
*/
first(shift = 0) {
if (this.elements.length > 0) {
return this.elements.slice(shift, 1)[0];
}
return null;
}
/**
* @returns {Collection}
*/
clear() {
this.elements = [];
return this;
}
/**
* @returns {Collection}
*/
clone() {
return new this.constructor(this.all());
}
/**
* @returns {Array}
*/
all() {
return this.elements.slice(0);
}
/**
* @param target
* @returns {Array}
*/
toArray(target = null) {
var result = this.all();
if (target instanceof Function) {
target(result);
}
return result;
}
/**
* @returns {Number}
*/
get length() {
return this.elements.length;
}
/**
* @returns {Generator}
*/
*[Symbol.iterator]() {
for (var i = 0; i < this.elements.length; i++) {
yield this.elements[i];
}
}
}
/**
* Event
*/
export class EventObject {
/**
* @type {number}
*/
static id = 0;
/**
* @type {number}
*/
$id = 0;
/**
* @type {Dispatcher}
*/
$dispatcher = null;
/**
* @type {String}
*/
$name = null;
/**
* @type {Function}
*/
$callback = null;
/**
* @type {boolean}
*/
$once = false;
/**
* @param {Dispatcher} dispatcher
* @param {String} name
* @param {Function} callback
*/
constructor(dispatcher:Dispatcher, name:String, callback:Function) {
this.$id = this.constructor.id++;
this.$name = name;
this.$dispatcher = dispatcher;
this.$callback = callback;
}
/**
* @returns {number}
*/
get id() {
return this.$id;
}
/**
* @returns {String}
*/
get name() {
return this.$name;
}
/**
* @param once
* @returns {Event}
*/
once(once = true) {
this.$once = once;
return this;
}
/**
* @param args
* @returns {*}
*/
fire(...args) {
var result = this.$callback(...args);
if (this.$once) {
this.remove();
}
return result;
}
/**
* @returns {Dispatcher}
*/
remove() {
var handlers = this.$dispatcher.getHandlers(this.$name);
for (var i = 0; i < handlers.length; i++) {
if (handlers[i].$id === this.$id) {
handlers.splice(i, 1);
break;
}
}
return this.$dispatcher;
}
}
/**
* Event Dispatcher
*/
export default class Dispatcher {
/**
* @constructor
*/
constructor() {
this.events = {};
}
/**
* @param name
* @param callback
* @returns {EventObject}
*/
listen(name:String, callback:Function) {
var event = new EventObject(this, name, callback);
this.getHandlers(name).push(event);
return event;
}
/**
* @param name
* @param callback
* @returns {Event}
*/
once(name:String, callback:Function) {
return this.listen(name, callback).once();
}
/**
* @param name
* @param args
* @returns {Array}
*/
fire(name:String, ...args) {
var result = [];
var handlers = this.getHandlers(name);
for (var i = 0; i < handlers.length; i++) {
result.push(handlers[i].fire(...args));
}
return result;
}
/**
* @param name
* @returns {*}
*/
getHandlers(name) {
if (!this.events[name]) {
this.events[name] = [];
}
return this.events[name];
}
}
import Dispatcher from "Dispatcher";
import Collection from "Collection";
/**
* Model
*/
export default class Model {
/**
* @type {WeakMap}
*/
static $collections = new WeakMap();
/**
* @returns {Collection}
*/
static get collection() {
if (!this.$collections.has(this)) {
this.$collections.set(this, new Collection([]));
// Import collection methods
for (let method in this.$collections.get(this)) {
if (method instanceof Function && typeof this[method] == 'undefined') {
Object.defineProperty(this, method, {
enumerable: true,
get: () => this.collection[method]
})
}
}
}
return this.$collections.get(this);
}
/**
* @type {WeakMap}
*/
static $dispatchers = new WeakMap();
/**
* @returns {Dispatcher}
*/
static get dispatcher() {
if (!this.$dispatchers.has(this)) {
this.$dispatchers.set(this, new Dispatcher);
}
return this.$dispatchers.get(this);
}
/**
* @param event
* @param callback
* @returns {EventObject}
*/
static on(event, callback: Function) {
return this.dispatcher.listen(event, callback);
}
/**
* @type {WeakMap}
*/
static $booted = new WeakMap();
/**
* @returns {boolean}
*/
static get booted() {
if (!this.$booted.has(this)) {
this.$booted.set(this, false);
}
return this.$booted.get(this);
}
/**
* @param {boolean} value
*/
static set booted(value) {
this.$booted.set(this, value);
}
/**
* @returns {Model}
*/
static bootIfNotBooted() {
if (!this.booted) {
this.constructor();
}
return this;
}
/**
* @return void
*/
static constructor() {
// Do nothing
}
/**
* @param attributes
* @returns {Model}
*/
static create(attributes = {}) {
var model = new this(attributes);
this.dispatcher.fire('creating', model);
this.collection.push(model);
this.dispatcher.fire('created', model);
return this;
}
// ================ INSTANCE ================ //
/**
* @type {{}}
*/
original = {};
/**
* @type {{}}
*/
attributes = {};
/**
* @type {{}}
*/
updated = {};
/**
* @param attributes
*/
constructor(attributes = {}) {
this.constructor.bootIfNotBooted();
this.fill(attributes);
}
/**
* @param attributes
*/
fill(attributes = {}) {
this.original = this.attributes = attributes;
for (let attribute in attributes) {
if (typeof this[attribute] == 'undefined') {
Object.defineProperty(this, attribute, {
enumerable: true,
get: () => this.get(attribute),
set: value => this.set(attribute, value)
})
}
}
}
/**
* @returns {Model}
*/
reset() {
this.attributes = this.original;
return this;
}
/**
* @returns {{}}
*/
save() {
this.constructor.dispatcher.fire('saving', this);
this.original = this.attributes;
var fields = this.updated;
this.updated = {};
if (!this.saved()) {
this.constructor.collection.push(this);
}
this.constructor.dispatcher.fire('saved', this);
return fields;
}
/**
* @returns {boolean}
*/
saved() {
return this.constructor.collection
.find(item => this === item)
.length > 0;
}
/**
* @returns {Collection}
*/
remove() {
this.constructor.dispatcher.fire('deleting', this);
var result = this.constructor.collection
.remove(item => item === this);
this.constructor.dispatcher.fire('deleted', this);
return result;
}
/**
* @returns {boolean}
*/
dirty() {
return Object.keys(this.updated).length > 0;
}
/**
* @param attribute
* @returns {*}
*/
get(attribute) {
if (this.has(attribute)) {
return this.attributes[attribute];
}
return null;
}
/**
* @param attribute
* @param value
* @returns {Model}
*/
set(attribute, value) {
if (this.has(attribute)) {
this.attributes[attribute] = value;
this.updated[attribute] = value;
}
return this;
}
/**
* @param attribute
* @returns {boolean}
*/
has(attribute) {
return !!this.attributes[attribute];
}
/**
* @returns {string}
*/
toJson() {
return JSON.stringify(this.attributes);
}
/**
* @returns {Generator}
*/
*[Symbol.iterator]() {
yield this.constructor.collection[Symbol.iterator];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment