jsmoo
Version:
JavaScript Minimalist Object Orientation
266 lines (227 loc) • 9.93 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/**
This file defines all the methods used on the internal HAS, includig the structure in which the values are saved
this structure is the following:
{
_has_: {} // All the internal options defined on the object
_attributes_: {} // All setted values
}
This are attributes defined on the Class
**/
// The following methods are getters and setter for the JSMOO internal structure
// Check if the ATTR has the given OPTION the HAS
function hasOption(attr, option) {
return Object.keys(this._has_[attr]).indexOf(option) >= 0;
}
// Check if the ATTR has any HAS
function hasOptionsFor(attr) {
return this._has_[attr];
}
// Returns the value of the ATTR OPTION on the HAS
function getOption(attr, option) {
return this._has_[attr][option];
}
// Sets the ATTR HAS with the VALUE
function setOption(attr, value) {
this._has_[attr] = value;
}
// Returns all the HAS
function getAllOptions() {
return this._has_;
}
// Returns the value of the ATTR
function getAttribute(attr) {
return this._attributes_[attr];
}
// Sets the VALUE of the ATTR
function setAttribute(attr, value) {
this._attributes_[attr] = value;
}
// Deletes the ATTR from the _attributes_
function deleteAttribute(attr) {
delete this._attributes_[attr];
}
// Returns all attributes
function getAttributes() {
return this._attributes_;
}
// Helper to define default function names from: predicate, trigger, default and builder options
function defineFunctionNameFromAttribute(prefix, attr) {
if (attr.match(/^_/)) {
return '_' + prefix + attr[1].toUpperCase() + attr.substring(2);
}
return '' + prefix + attr[0].toUpperCase() + attr.substring(1);
}
// Function executed when the ATTR has ISA in his options.
// Internally it uses the typeof and constructor.name to validate the type (Array.isArray() in case of Arrays)
function typeValidation(attr, value) {
if (!hasOption.bind(this)(attr, 'isa')) return true;
var isMaybe = false;
var expectedType = getOption.bind(this)(attr, 'isa');
if (typeof expectedType === 'function') return expectedType(value);
var foundType = typeof value === 'undefined' ? 'undefined' : _typeof(value);
var match = expectedType.match(/Maybe\[(.*)\]/);
if (match) {
expectedType = match[1];
isMaybe = true;
}
if (Array.isArray(value)) foundType = 'array';
if (value === null) foundType = 'null';
if ((value === null || value === undefined) && isMaybe) return true;
if ((value === null || value === undefined) && !isMaybe || foundType !== expectedType && value.constructor.name !== expectedType) {
throw new TypeError('Invalid type for the attribute ' + attr + ', it must be \'' + expectedType + '\' found \'' + foundType + '\'');
}
return true;
}
// Function executed when the ATTR has COERCE in his options, only functions are accepted in the COERCE
function executeCoerce(attr, value) {
if (!hasOption.bind(this)(attr, 'coerce')) return value;
var coerceValue = getOption.bind(this)(attr, 'coerce');
if (typeof coerceValue !== 'function') throw new TypeError('Invalid type of Coerce on \'' + attr + '\'');
return coerceValue(value);
}
// Function executed when the DEFAULT is needed
function executeDefault(attr) {
var defaultValue = getOption.bind(this)(attr, 'default');
var value = typeof defaultValue === 'function' ? defaultValue.bind(this)() : defaultValue;
value = executeCoerce.bind(this)(attr, value);
typeValidation.bind(this)(attr, value);
return value;
}
// Function executed when the BUILDER is needed
function executeBuilder(attr) {
var defaultValue = getOption.bind(this)(attr, 'builder');
var value = void 0;
if (typeof defaultValue === 'string') {
if (!this[defaultValue]) throw new TypeError('The builder function \'' + defaultValue + '\' is not defined');
value = this[defaultValue]();
} else {
var builderFunction = defineFunctionNameFromAttribute('build', attr);
if (!this[builderFunction]) throw new TypeError('The builder function \'' + builderFunction + '\' is not defined');
value = this[builderFunction]();
}
value = executeCoerce.bind(this)(attr, value);
typeValidation.bind(this)(attr, value);
return value;
}
// Function executed when the TRIGGER is defined onthe ATTR
function executeTrigger(attr, newValue, oldValue) {
if (!hasOption.bind(this)(attr, 'trigger')) return true;
var triggerValue = getOption.bind(this)(attr, 'trigger');
if (typeof triggerValue === 'function') {
triggerValue.bind(this)(newValue, oldValue);
} else {
var triggerFunction = defineFunctionNameFromAttribute('trigger', attr);
this[triggerFunction](newValue, oldValue);
}
}
// Function executed when definig the setters at the new of the Jsmoo class, it follows this order: coerce, isa, is and trigger
function defineSetter(attr, value) {
var newValue = value;
newValue = executeCoerce.bind(this)(attr, newValue);
typeValidation.bind(this)(attr, newValue);
if (getOption.bind(this)(attr, 'is') === 'ro') throw new TypeError('Can not set to a RO attribute ' + attr);
executeTrigger.bind(this)(attr, newValue, getAttribute.bind(this)(attr));
setAttribute.bind(this)(attr, newValue);
}
// Funciton executed when definig the getters of a new Jsmoo class, it follows this order: default/builder and coerce
function defineGetter(attr) {
var value = getAttribute.bind(this)(attr);
var isLazy = hasOption.bind(this)(attr, 'lazy');
if (value === undefined && isLazy && hasOption.bind(this)(attr, 'default')) {
value = executeDefault.bind(this)(attr);
value = executeCoerce.bind(this)(attr, value);
setAttribute.bind(this)(attr, value);
} else if (value === undefined && isLazy && hasOption.bind(this)(attr, 'builder')) {
value = executeBuilder.bind(this)(attr);
value = executeCoerce.bind(this)(attr, value);
setAttribute.bind(this)(attr, value);
}
return value;
}
// Function executed when the PREDICATE is defined on the ATTR
function definePredicate(attr) {
var _this = this;
if (!hasOption.bind(this)(attr, 'predicate')) return;
var predicateName = defineFunctionNameFromAttribute('has', attr);
this[predicateName] = function () {
var attributeValue = getAttribute.bind(_this)(attr);
return attributeValue !== undefined && attributeValue !== null;
};
}
// Function executed when the CLEARER is defined on the ATTR
function defineClearer(attr) {
if (!hasOption.bind(this)(attr, 'clearer')) return;
var clearerName = defineFunctionNameFromAttribute('clear', attr);
this[clearerName] = deleteAttribute.bind(this, attr);
}
// It defines all the HAS OPTS of the ATTR, validating the IS and checking the + sign to override
function defineAttribute(attr, opts) {
if (hasOptionsFor.bind(this.prototype)(attr)) return;
var newAttr = attr;
var newOpts = opts;
var isOverride = !!newAttr.match(/^\+/);
if (isOverride) newAttr = attr.replace(/^\+/, '');
if (!isOverride && (!newOpts || !newOpts.is)) throw new TypeError("'is' key is required");
if (isOverride && hasOptionsFor.bind(this.prototype)(newAttr)) {
// TODO: Remove the old property? if something was created
var beforeHas = hasOptionsFor.bind(this.prototype)(newAttr);
newOpts = _extends({}, beforeHas, opts);
}
if (Object.getPrototypeOf(this).name === 'Role') {
setOption.bind(this.prototype)(attr, opts);
return;
}
if (isOverride && !hasOptionsFor.bind(this.prototype)(newAttr)) throw new TypeError('Can\'t override an unexistent attribute \'' + newAttr + '\'');
setOption.bind(this.prototype)(newAttr, newOpts);
}
// It mounts all the HAS options for each attribute defined, defining properties with get and set
function mountGettersSetters() {
var _this2 = this;
Object.keys(getAllOptions.bind(this)()).forEach(function (attr) {
definePredicate.bind(_this2)(attr);
defineClearer.bind(_this2)(attr);
Object.defineProperty(_this2, attr, {
configurable: true,
enumerable: true,
get: defineGetter.bind(_this2, attr),
set: defineSetter.bind(_this2, attr)
});
});
}
// Functions executed to validate that all the required attributes are setted
function requireValidation() {
var _this3 = this;
Object.keys(getAllOptions.bind(this)()).forEach(function (attr) {
if (getOption.bind(_this3)(attr, 'required') && (getAttribute.bind(_this3)(attr) === undefined || getAttribute.bind(_this3)(attr) === null)) {
throw new TypeError('The attribute \'' + attr + '\' is required');
}
});
}
// Funciton exported to the global JSMOO class to have access to the HAS
function has(attrs) {
var _this4 = this;
if (!this.prototype._has_) this.prototype._has_ = {};
Object.keys(attrs).forEach(function (attr) {
return defineAttribute.bind(_this4)(attr, attrs[attr]);
});
}
exports.default = has;
exports.requireValidation = requireValidation;
exports.typeValidation = typeValidation;
exports.executeDefault = executeDefault;
exports.executeBuilder = executeBuilder;
exports.executeTrigger = executeTrigger;
exports.executeCoerce = executeCoerce;
exports.hasOption = hasOption;
exports.setAttribute = setAttribute;
exports.getAllOptions = getAllOptions;
exports.mountGettersSetters = mountGettersSetters;
exports.hasOptionsFor = hasOptionsFor;
exports.getAttributes = getAttributes;
exports.getAttribute = getAttribute;