salix/front/core/lib/component.js

204 lines
5.9 KiB
JavaScript
Raw Normal View History

import ngModule from '../module';
2018-10-18 07:24:20 +00:00
import EventEmitter from './event-emitter';
2018-10-22 15:54:04 +00:00
import {kebabToCamel} from './string';
2018-10-18 07:24:20 +00:00
/**
* Base class for component controllers.
*/
2018-10-18 07:24:20 +00:00
export default class Component extends EventEmitter {
/**
* Contructor.
*
* @param {HTMLElement} $element The main component element
* @param {$rootScope.Scope} $scope The element scope
2020-04-25 09:50:04 +00:00
* @param {Function} $transclude The transclusion function
*/
2020-04-25 09:50:04 +00:00
constructor($element, $scope, $transclude) {
2018-10-22 15:54:04 +00:00
super();
2020-04-25 09:50:04 +00:00
this.$ = $scope;
2018-10-22 15:54:04 +00:00
if (!$element) return;
this.element = $element[0];
this.element.$ctrl = this;
this.$element = $element;
2020-04-25 09:50:04 +00:00
this.$transclude = $transclude;
this.classList = this.element.classList;
const constructor = this.constructor;
const $options = constructor.$options;
if ($options && $options.installClasses)
this.classList.add(...this.constructor.$classNames);
if ($transclude && constructor.slotTemplates) {
for (let slotTemplate of constructor.slotTemplates)
this.fillSlots(slotTemplate);
}
}
2018-10-22 15:54:04 +00:00
$postLink() {
if (!this.$element) return;
let attrs = this.$element[0].attributes;
let $scope = this.$;
for (let attr of attrs) {
2020-04-25 09:50:04 +00:00
if (!attr.name.startsWith('on-')) continue;
2018-10-22 15:54:04 +00:00
let eventName = kebabToCamel(attr.name.substr(3));
let callback = locals => $scope.$parent.$eval(attr.nodeValue, locals);
this.on(eventName, callback);
}
}
2018-10-18 07:24:20 +00:00
/**
* The component owner window.
*/
get window() {
return this.document.defaultView;
}
2018-10-18 07:24:20 +00:00
/**
* The component owner document.
*/
get document() {
return this.element.ownerDocument;
}
/**
* Translates an string.
*
* @param {String} string String to translate
* @param {Array} params Translate parameters
* @return {String} The translated string
*/
$t(string, params) {
return this.$translate.instant(string, params);
}
2019-10-26 23:30:01 +00:00
2020-04-25 09:50:04 +00:00
/**
* Fills the default transclude slot.
*
* @param {JQElement|String} template The slot template
*/
fillDefaultSlot(template) {
const linkFn = this.$compile(template);
this.$transclude.$$boundTransclude = this.createBoundTranscludeFn(linkFn);
}
/**
* Fills a named transclude slot.
*
* @param {String} slot The trasnclude slot name
2020-05-06 12:38:09 +00:00
* @param {JQElement|String} template The slot template
2020-04-25 09:50:04 +00:00
*/
fillSlot(slot, template) {
const linkFn = this.$compile(template);
const slots = this.$transclude.$$boundTransclude.$$slots;
slots[slot] = this.createBoundTranscludeFn(linkFn);
}
/**
* Fills component transclude slots using the passed HTML template string
* as source.
*
* @param {String} template The HTML template string
*/
fillSlots(template) {
const name = this.constructor.$options.name;
const transclude = this.constructor.$options.transclude;
if (!transclude)
throw new Error(`No transclusion option defined in '${name}'`);
if (!this.$transclude)
throw new Error(`No $transclude injected in '${name}'`);
let slotMap = {};
for (let slotName in transclude) {
let slotTag = transclude[slotName].match(/\w+$/)[0];
slotMap[slotTag] = slotName;
}
const $template = angular.element(template);
for (let i = 0; i < $template.length; i++) {
let slotElement = $template[i];
if (slotElement.nodeType != Node.ELEMENT_NODE) continue;
let tagName = kebabToCamel(slotElement.tagName.toLowerCase());
if (tagName == 'default')
this.fillDefaultSlot(slotElement.childNodes);
else {
let slotName = slotMap[tagName];
if (!slotName)
throw new Error(`No slot found for '${tagName}' in '${name}'`);
this.fillSlot(slotName, slotElement);
}
}
}
/**
* Creates a bounded transclude function from a linking function.
*
* @param {Function} linkFn The linking function
* @return {Function} The bounded transclude function
*/
createBoundTranscludeFn(linkFn) {
2019-10-26 23:30:01 +00:00
let scope = this.$;
let previousBoundTranscludeFn = this.$transclude.$$boundTransclude;
function vnBoundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
2019-10-26 23:30:01 +00:00
if (!transcludedScope) {
transcludedScope = scope.$new(false, containingScope);
transcludedScope.$$transcluded = true;
}
return linkFn(transcludedScope, cloneFn, {
2019-10-26 23:30:01 +00:00
parentBoundTranscludeFn: previousBoundTranscludeFn,
transcludeControllers: controllers,
futureParentElement: futureParentElement
});
}
vnBoundTranscludeFn.$$slots = previousBoundTranscludeFn.$$slots;
return vnBoundTranscludeFn;
}
copySlot(slot, $transclude) {
this.$transclude.$$boundTransclude.$$slots[slot] =
$transclude.$$boundTransclude.$$slots[slot];
}
}
Component.$inject = ['$element', '$scope'];
2020-04-25 09:50:04 +00:00
/*
* Automatically adds the most used services to the prototype, so they are
* available as component properties.
*/
function runFn(...args) {
const proto = Component.prototype;
for (let i = 0; i < runFn.$inject.length; i++)
proto[runFn.$inject[i]] = args[i];
Object.assign(proto, {
$params: proto.$stateParams
});
}
2019-10-26 10:04:48 +00:00
runFn.$inject = [
'$translate',
'$q',
'$http',
'$httpParamSerializer',
2019-10-26 10:04:48 +00:00
'$state',
'$stateParams',
'$timeout',
'$transitions',
'$compile',
'$filter',
'$interpolate',
2019-11-10 10:08:44 +00:00
'$window',
'vnApp',
'vnToken',
'vnConfig',
2020-05-06 12:38:09 +00:00
'vnModules',
'aclService'
2019-10-26 10:04:48 +00:00
];
ngModule.run(runFn);