Source: model.js

/**
 * @module stb/model
 * @author Stanislav Kalashnik <sk@infomir.eu>
 * @license GNU GENERAL PUBLIC LICENSE Version 3
 */

'use strict';

var Emitter = require('./emitter');


/**
 * Base model implementation.
 *
 * Represents domain-specific data or information that an application will be working with.
 * A typical example is a user account (e.g name, avatar, e-mail) or a music track (e.g title, year, album).
 * Holds information, but don’t handle behaviour and don’t format information or influence how data appears.
 *
 * @constructor
 * @extends Emitter
 *
 * @param {Object} [data={}] init attributes
 */
function Model ( data ) {
	if ( DEBUG ) {
		if ( data !== undefined && typeof data !== 'object' ) { throw 'wrong data type'; }
	}

	// parent init
	Emitter.call(this);

	/**
	 * Model attributes with given data or empty hash table.
	 *
	 * @member {Object.<string, *>}
	 **/
	this.data = data || {};
}


// inheritance
Model.prototype = Object.create(Emitter.prototype);
Model.prototype.constructor = Model;


// which of data fields is primary
Model.prototype.idName = 'id';


/**
 * Remove all attributes from the model event.
 *
 * @event module:stb/model~Model#clear
 *
 * @type {Object}
 * @property {Object} data old model attributes
 */


/**
 * Remove all attributes from the model.
 *
 * @return {boolean} operation status
 *
 * @fires module:stb/model~Model#clear
 */
Model.prototype.clear = function () {
	var data = this.data;

	if ( DEBUG ) {
		if ( typeof data !== 'object' ) { throw 'wrong data type'; }
	}

	// is there any data?
	if ( Object.keys(data).length > 0 ) {
		// reset
		this.data = {};

		// there are some listeners
		if ( this.events['clear'] !== undefined ) {
			// notify listeners
			this.emit('clear', {data: data});
		}

		return true;
	}

	return false;
};


/**
 * Set model data event.
 *
 * @event module:stb/model~Model#init
 *
 * @type {Object}
 * @property {Object} data new model attributes
 */


/**
 * Clear and set model data.
 *
 * @param {Object} data attributes
 * @return {boolean} operation status
 *
 * @fires module:stb/model~Model#clear
 * @fires module:stb/model~Model#init
 */
Model.prototype.init = function ( data ) {
	if ( DEBUG ) {
		if ( typeof data !== 'object' ) { throw 'wrong data type'; }
	}

	// valid input
	if ( data ) {
		// reset data
		this.clear();

		// init with given data
		this.data = data;

		// there are some listeners
		if ( this.events['init'] !== undefined ) {
			// notify listeners
			this.emit('init', {data: data});
		}

		return true;
	}

	return false;
};


/**
 * Check an attribute existence.
 *
 * @param {string} name attribute
 *
 * @return {boolean} attribute exists or not
 */
Model.prototype.has = function ( name ) {
	if ( DEBUG ) {
		if ( typeof this.data !== 'object' ) { throw 'wrong this.data type'; }
	}

	// hasOwnProperty method is not available directly in case of Object.create(null)
	//return Object.hasOwnProperty.call(this.data, name);
	return this.data.hasOwnProperty(name);
};

/**
 * Get the model attribute by name.
 *
 * @param {string} name attribute
 *
 * @return {*} associated value
 */
Model.prototype.get = function ( name ) {
	if ( DEBUG ) {
		if ( typeof this.data !== 'object' ) { throw 'wrong this.data type'; }
	}

	return this.data[name];
};


/**
 * Update or create a model attribute event.
 *
 * @event module:stb/model~Model#change
 *
 * @type {Object}
 * @property {string} name attribute name
 * @property {*} [prev] old/previous attribute value (can be absent on attribute creation)
 * @property {*} [curr] new/current attribute value (can be absent on attribute removal)
 */


/**
 * Update or create a model attribute.
 *
 * @param {string} name attribute
 * @param {*} value associated value
 * @return {boolean} operation status (true - attribute value was changed/created)
 *
 * @fires module:stb/model~Model#change
 */
Model.prototype.set = function ( name, value ) {
	var isAttrSet = name in this.data,
		emitData  = {name: name, curr: value};

	if ( DEBUG ) {
		if ( typeof this.data !== 'object' ) { throw 'wrong this.data type'; }
	}

	if ( isAttrSet ) {
		// update
		emitData.prev = this.data[name];
		// only if values are different
		if ( value !== emitData.prev ) {
			this.data[name] = value;

			// there are some listeners
			if ( this.events['change'] !== undefined ) {
				// notify listeners
				this.emit('change', emitData);
			}

			return true;
		}
	} else {
		// create
		this.data[name] = value;

		// there are some listeners
		if ( this.events['change'] !== undefined ) {
			// notify listeners
			this.emit('change', emitData);
		}

		return true;
	}

	return false;
};


/**
 * Delete the given attribute by name.
 *
 * @param {string} name attribute
 * @return {boolean} operation status (true - attribute was deleted)
 *
 * @fires module:stb/model~Model#change
 */
Model.prototype.unset = function ( name ) {
	var isAttrSet = name in this.data,
		emitData;

	if ( DEBUG ) {
		if ( typeof this.data !== 'object' ) { throw 'wrong this.data type'; }
	}

	if ( isAttrSet ) {
		emitData = {name: name, prev: this.data[name]};
		delete this.data[name];

		// there are some listeners
		if ( this.events['change'] !== undefined ) {
			// notify listeners
			this.emit('change', emitData);
		}

		return true;
	}

	return false;
};


///**
// * Extends the model with the given attribute list
// * @param {Object} data
// */
//Model.prototype.attributes = function ( data ) {
//	var index   = 0,
//		keyList = data && typeof data === 'object' ? Object.keys(data) : [];
//	for ( ; index < keyList.length; index++ ) {
//		this.set(keyList[index], data[keyList[index]]);
//	}
//};


///**
// * Prepare all data for sending to a server
// * @return {Object}
// */
//Model.prototype.pack = function () {
//	return this._data;
//};


///**
// * Restores the received data from a server to a model data
// * @param {Object} data
// * @return {Object}
// */
//Model.prototype.unpack = function ( data ) {
//	return data;
//};


///**
// * Sync model to a server
// */
//Model.prototype.save = function () {
//	var self = this;
//	if ( this.url ) {
//		// collect data
//		io.ajax(this.url, {
//			// request params
//			method: self._data[self.idName] ? 'put' : 'post',
//			data  : self.pack(),
//			onload: function ( data ) {
//				data = self.unpack(self.parse(data));
//				self.attributes(data);
//				console.log(data);
//				self.emit('save', true);
//			},
//			// error handlers
//			onerror:   this.saveFailure,
//			ontimeout: this.saveFailure
//		});
//	}
//};


///**
// * Error handler while model data fetch
// */
//Model.prototype.saveFailure = function () {
//	this.emit('save', false);
//};


///**
// * Converts received data from a server to a model attributes
// * @param {string} response
// * @return {Object}
// */
//Model.prototype.parse = function ( response ) {
//	var data = {};
//	try {
//		data = JSON.parse(response).data;
//	} catch(e){ console.log(e); }
//	return data;
//};


// public
module.exports = Model;