Example #1
0
	constructor: function Component(el, props) {
		EventEmitter.call(this);
		DisposableMixin.call(this);

		if (el == null) {
			el = document.createElement(this.constructor.elementIs);
		} else if (typeof el == 'string') {
			let elementIs = this.constructor.elementIs;
			let html = el;

			el = document.createElement(elementIs);
			el.innerHTML = html;

			let firstChild = el.firstChild;

			if (
				firstChild == el.lastChild && firstChild.nodeType == 1 &&
					firstChild.tagName.toLowerCase() == elementIs
			) {
				el = firstChild;
			}
		}

		this.element = el;

		Object.defineProperty(el, '$c', { value: this });

		if (props) {
			let properties = this.props;

			for (let name in props) {
				properties[camelize(name)] = props[name];
			}
		}

		this._elementAttached = new Cell(false, {
			owner: this,
			onChange: this._onElementAttachedChange
		});

		this.created();
	},
Example #2
0
let Attributes = EventEmitter.extend({
	Static: {
		typeHandlers
	},

	constructor: function Attributes(component) {
		let el = component.element;
		let schema = component.constructor.elementAttributes;

		for (let name in schema) {
			let defaultValue = schema[name];
			let type = typeof defaultValue;
			let handlers = typeHandlers.get(type == 'function' ? defaultValue : type);

			if (!handlers) {
				throw new TypeError('Unsupported attribute type');
			}

			let camelizedName = camelize(name);
			let hyphenizedName = hyphenize(name);

			let attrValue = this['_' + camelizedName] = this['_' + hyphenizedName] = new Cell(
				el.getAttribute(hyphenizedName),
				{
					merge(value, oldValue) {
						return oldValue && value === oldValue[0] ?
							oldValue :
							[value, handlers[0](value, defaultValue, component)];
					},

					onChange({ oldValue: { 1: oldValue }, value: { 1: value } }) {
						if (component.isReady) {
							component.emit({
								type: `element-attribute-${ hyphenizedName }-change`,
								oldValue,
								value
							});
							component.emit({
								type: 'element-attribute-change',
								name: hyphenizedName,
								oldValue,
								value
							});

							if (component.elementAttributeChanged) {
								component.elementAttributeChanged(hyphenizedName, oldValue, value);
							}
						}
					}
				}
			);

			let descriptor = {
				configurable: true,
				enumerable: true,

				get() {
					return attrValue.get()[1];
				},

				set(value) {
					value = handlers[1](value, defaultValue);

					if (value === null) {
						el.removeAttribute(hyphenizedName);
					} else {
						el.setAttribute(hyphenizedName, value);
					}

					attrValue.set(value);
				}
			};

			defineProperty(this, camelizedName, descriptor);

			if (hyphenizedName != camelizedName) {
				defineProperty(this, hyphenizedName, descriptor);
			}
		}
	}
});
Example #3
0
let Component = EventEmitter.extend({
	Implements: [DisposableMixin],

	Static: {
		extend(elementIs, description) {
			description.Extends = this;

			let Static = description.Static || (description.Static = {});

			Static.elementIs = elementIs;

			let props = Static.props;

			if (props) {
				if (props.content || props.context) {
					throw new TypeError(
						`It is not necessary to declare property "${ props.content ? 'content' : 'context' }"`
					);
				}

				Static.elementAttributes = props;
			} else if (Static.elementAttributes) {
				Static.props = Static.elementAttributes;
			}

			return registerComponent(createClass(description));
		},

		elementIs: void 0,
		elementExtends: void 0,

		elementAttributes: null,
		props: null,

		i18n: null,

		template: null,

		assets: null
	},

	/**
	 * @type {?Rionite.Component}
	 */
	ownerComponent: null,

	_parentComponent: null,

	/**
	 * @type {?Rionite.Component}
	 */
	get parentComponent() {
		if (this._parentComponent !== void 0) {
			return this._parentComponent;
		}

		for (let node; (node = (node || this.element).parentNode);) {
			if (node.$c) {
				return (this._parentComponent = node.$c);
			}
		}

		return (this._parentComponent = null);
	},

	/**
	 * @type {HTMLElement}
	 */
	element: null,

	/**
	 * @type {Rionite.ElementAttributes}
	 */
	get elementAttributes() {
		let attrs = new ElementAttributes(this.element);

		Object.defineProperty(this, 'elementAttributes', {
			configurable: true,
			enumerable: true,
			writable: true,
			value: attrs
		});

		return attrs;
	},

	/**
	 * @type {Rionite.Properties}
	 */
	get props() {
		let props = Object.create(this.elementAttributes);

		props.content = null;
		props.context = null;

		Object.defineProperty(this, 'props', {
			configurable: true,
			enumerable: true,
			writable: true,
			value: props
		});

		return props;
	},

	_elementAttached: null,

	_bindings: null,

	initialized: false,
	isReady: false,

	constructor: function Component(el, props) {
		EventEmitter.call(this);
		DisposableMixin.call(this);

		if (el == null) {
			el = document.createElement(this.constructor.elementIs);
		} else if (typeof el == 'string') {
			let elementIs = this.constructor.elementIs;
			let html = el;

			el = document.createElement(elementIs);
			el.innerHTML = html;

			let firstChild = el.firstChild;

			if (
				firstChild == el.lastChild && firstChild.nodeType == 1 &&
					firstChild.tagName.toLowerCase() == elementIs
			) {
				el = firstChild;
			}
		}

		this.element = el;

		Object.defineProperty(el, '$c', { value: this });

		if (props) {
			let properties = this.props;

			for (let name in props) {
				properties[camelize(name)] = props[name];
			}
		}

		this._elementAttached = new Cell(false, {
			owner: this,
			onChange: this._onElementAttachedChange
		});

		this.created();
	},

	/**
	 * @override
	 */
	_handleEvent(evt) {
		EventEmitter.prototype._handleEvent.call(this, evt);

		if (evt.bubbles !== false && !evt.isPropagationStopped) {
			let parentComponent = this.parentComponent;

			if (parentComponent) {
				parentComponent._handleEvent(evt);
			} else {
				onEvent(evt);
			}
		}
	},

	_onElementAttachedChange(evt) {
		if (evt.value) {
			if (!this.initialized) {
				this.initialize();
				this.initialized = true;
			}

			let constr = this.constructor;
			let rawContent = constr[KEY_RAW_CONTENT];
			let el = this.element;

			if (this.isReady) {
				for (let child; (child = el.firstChild);) {
					el.removeChild(child);
				}
			} else {
				for (let c = constr; ;) {
					el.classList.add(c.elementIs);
					c = Object.getPrototypeOf(c.prototype).constructor;

					if (c == Component) {
						break;
					}
				}

				let attrs = this.elementAttributes;
				let attributesConfig = constr.elementAttributes;

				for (let name in attributesConfig) {
					if (typeof attributesConfig[name] != 'function') {
						let camelizedName = camelize(name);
						attrs[camelizedName] = attrs[camelizedName];
					}
				}

				if (constr.template != null) {
					if (!rawContent) {
						let template = constr.template;

						rawContent = constr[KEY_RAW_CONTENT] = htmlToFragment(
							(typeof template == 'string' ? template : template.render(constr))
								.replace(reClosedCustomElementTag, '<$1$2></$1>')
						);
					}

					let inputContent = this.props.content = document.createDocumentFragment();

					for (let child; (child = el.firstChild);) {
						inputContent.appendChild(child);
					}
				}
			}

			let content = rawContent && rawContent.cloneNode(true);

			if (content) {
				this._bindings = bind(content, this);
				this.element.appendChild(content);
			}

			if (!this.isReady || this.elementAttached !== elementAttached) {
				defer(() => {
					let assetsConfig = this.constructor.assets;

					if (!this.isReady) {
						if (assetsConfig) {
							defineAssets(this, assetsConfig);
						}

						this.ready();

						this.isReady = true;
					}

					if (assetsConfig) {
						listenAssets(this, assetsConfig);
					}

					this.elementAttached();
				});
			}
		} else {
			this.dispose();
			this._destroyBindings();
			this.elementDetached();
		}
	},

	_destroyBindings() {
		let bindings = this._bindings;

		if (bindings) {
			for (let i = bindings.length; i;) {
				bindings[--i].off();
			}

			this._bindings = null;
		}
	},

	created,
	initialize,
	ready,
	elementAttached,
	elementDetached,
	elementMoved,
	elementAttributeChanged,

	/**
	 * @typesign (selector: string) -> ?Rionite.Component|HTMLElement;
	 */
	$(selector) {
		let el = this.element.querySelector(this._prepareSelector(selector));
		return el && (el.$c || el);
	},

	/**
	 * @typesign (selector: string) -> Array<Rionite.Component|HTMLElement>;
	 */
	$$(selector) {
		return map.call(this.element.querySelectorAll(this._prepareSelector(selector)), el => el.$c || el);
	},

	_prepareSelector(selector) {
		selector = selector.split('&');

		if (selector.length == 1) {
			return selector[0];
		}

		let constr = this.constructor;
		let blockName = constr[KEY_BLOCK_NAME_IN_MARKUP];

		if (!blockName) {
			let ctor = constr;

			do {
				if (ctor.template) {
					blockName = ctor.elementIs;
				}

				ctor = Object.getPrototypeOf(ctor.prototype).constructor;
			} while (ctor != Component);

			if (!blockName) {
				blockName = constr.elementIs;
			}

			constr[KEY_BLOCK_NAME_IN_MARKUP] = blockName;
		}

		return selector.join('.' + blockName);
	}
});