nornj
Version:
A powerful template engine that can works with React, JSX enhancement or alternative tools.
394 lines (344 loc) • 10.2 kB
text/typescript
import nj from '../core';
import * as tools from '../utils/tools';
import * as tranData from '../transforms/transformData';
import { ExtensionDelegate, ExtensionDelegateMultiParams, ExtensionPrefixConfig, ExtensionOption } from '../interface';
//Global extension list
export const extensions: { [name: string]: ExtensionDelegate | ExtensionDelegateMultiParams } = {
if: (value, options) => {
if (value && value._njOpts) {
options = value;
value = options.props.condition;
}
if (value === 'false') {
value = false;
}
let ret;
const props = options.props;
if (value) {
ret = ((props && props.then) || options.children)();
} else if (props) {
const elseFn = props['else'];
if (props.elseifs) {
const l = props.elseifs.length;
tools.each(
props.elseifs,
(elseif, i) => {
if (elseif.value) {
ret = elseif.fn();
return false;
} else if (i === l - 1) {
if (elseFn) {
ret = elseFn();
}
}
},
true
);
} else {
if (elseFn) {
ret = elseFn();
}
}
}
if (options.useString && ret == null) {
return '';
}
return ret;
},
then: options => (options.tagProps.then = options.children),
else: options => (options.tagProps['else'] = options.children),
elseif: (value, options) => {
if (value && value._njOpts) {
options = value;
value = options.props.condition || options.props.value;
}
const { tagProps } = options;
if (!tagProps.elseifs) {
tagProps.elseifs = [];
}
tagProps.elseifs.push({
value,
fn: options.children
});
},
switch: (value, options) => {
if (value && value._njOpts) {
options = value;
value = options.props.value;
}
let ret;
const props = options.props,
elseifs = props.elseifs || [{}],
l = elseifs.length;
tools.each(
elseifs,
(elseif, i) => {
if (value === elseif.value) {
ret = elseif.fn();
return false;
} else if (i === l - 1) {
if (props['else']) {
ret = props['else']();
}
}
},
true
);
return ret;
},
each: (list, options) => {
if (list && list._njOpts) {
options = list;
list = options.props.of;
}
const useString = options.useString,
props = options.props;
let ret;
if (list) {
if (useString) {
ret = '';
} else {
ret = [];
}
const isArrayLike = tools.isArrayLike(list);
const isArrayLoop = isArrayLike || tools.isSet(list) || tools.isWeakSet(list);
tools.each(
list,
(item, index, len, lenObj) => {
const param = {
data: [item],
index: isArrayLoop ? index : len,
item,
newParent: true
};
const _len = isArrayLoop ? len : lenObj;
const extra = {
'@first': param.index === 0,
'@last': param.index === _len - 1
};
if (!isArrayLoop) {
extra['@key'] = index;
}
param.data.push(extra);
const retI = options.children(param);
if (useString) {
ret += retI;
} else {
ret.push(retI);
}
},
isArrayLike
);
//Return null when not use string and result is empty.
if (!useString && !ret.length) {
ret = null;
}
if ((!ret || !ret.length) && props && props['else']) {
ret = props['else']();
}
} else {
if (props && props['else']) {
ret = props['else']();
}
if (useString && ret == null) {
ret = '';
}
}
return ret;
},
//Parameter
prop: (name, options) => {
const ret = options.value(); //Get parameter value
let value;
if (ret !== undefined) {
value = ret;
} else {
//Match to Similar to "checked" or "disabled" attribute.
value = !options.useString ? true : name;
}
options.tagProps[options.outputH ? tranData.fixPropName(name) : name] = value;
},
//Spread parameters
spread: (props, options) => {
const { tagProps } = options;
tools.each(
props,
(v, k) => {
tagProps[k] === undefined && (options.tagProps[k] = v);
},
false
);
},
show: options => {
if (!options.value()) {
const { tagProps, useString } = options;
if (!tagProps.style) {
tagProps.style = useString ? '' : {};
}
if (useString) {
tagProps.style += (tagProps.style ? ';' : '') + 'display:none';
} else if (tools.isArray(tagProps.style)) {
tagProps.style.push({ display: 'none' });
} else {
tagProps.style.display = 'none';
}
}
},
obj: options => options.props,
block: options => options.children(),
with: function(originalData, options) {
if (originalData && originalData._njOpts) {
options = originalData;
return options.children({
data: [options.props]
});
} else {
const { props } = options;
return options.children({
data: [
props && props.as
? {
[props.as]: originalData
}
: originalData
]
});
}
},
arg: options => {
const { tagProps } = options;
if (!tagProps.args) {
tagProps.args = [];
}
tagProps.args.push(options.value());
},
css: options => options.props.style
};
function _config(params?: ExtensionOption, extra?: ExtensionOption): ExtensionOption {
let ret: ExtensionOption = {
onlyGlobal: false,
useString: false,
newContext: true,
isSubTag: false,
isDirective: false,
isBindable: false,
useExpressionInProps: false,
hasName: true,
noTagName: false,
hasTagProps: true,
hasTmplCtx: true,
hasOutputH: false
};
if (params) {
ret = tools.assign(ret, params);
}
if (extra) {
ret = tools.assign(ret, extra);
}
return ret;
}
const _defaultCfg: ExtensionOption = {
onlyGlobal: true,
newContext: false,
hasName: false,
hasTagProps: false,
hasTmplCtx: false
};
const _defaultCfgExpInProps = _config(_defaultCfg, { useExpressionInProps: true });
//Extension default config
export const extensionConfig: { [name: string]: ExtensionOption } = {
if: _config(_defaultCfgExpInProps),
else: _config(_defaultCfgExpInProps, { isSubTag: true, hasTagProps: true }),
switch: _config(_defaultCfgExpInProps, { needPrefix: ExtensionPrefixConfig.onlyLowerCase }),
each: _config(_defaultCfgExpInProps, {
newContext: {
item: 'item',
index: 'index',
data: {
first: ['@first', 'first'],
last: ['@last', 'last'],
$key: ['@key', 'key']
}
}
}),
prop: _config(_defaultCfg, { isDirective: true, needPrefix: true, hasTagProps: true }),
obj: _config(_defaultCfg, { needPrefix: true }),
with: _config(_defaultCfg, { newContext: { getDataFromProps: true } }),
style: { needPrefix: true }
};
extensionConfig.then = _config(extensionConfig['else']);
extensionConfig.elseif = _config(extensionConfig['else']);
extensionConfig.spread = _config(extensionConfig.prop);
extensionConfig.block = _config(extensionConfig.obj);
extensionConfig.arg = _config(extensionConfig.prop);
extensionConfig.show = _config(extensionConfig.prop, { useExpressionInProps: true, noTagName: true, hasOutputH: true });
extensionConfig.css = _config(extensionConfig.obj);
//Extension alias
extensions['for'] = extensions.each;
extensionConfig['for'] = _config(extensionConfig.each);
extensions['case'] = extensions.elseif;
extensionConfig['case'] = _config(extensionConfig.elseif);
extensions.empty = extensions['default'] = extensions['else'];
extensionConfig.empty = _config(extensionConfig['else']);
extensionConfig['default'] = _config(extensionConfig['else']);
extensions.strProp = extensions.prop;
extensionConfig.strProp = _config(extensionConfig.prop, { useString: true });
extensions.strArg = extensions.arg;
extensionConfig.strArg = _config(extensionConfig.strProp);
extensions.pre = extensions.block;
extensionConfig.pre = _config(extensionConfig.block);
export function registerExtension<T extends ExtensionDelegate>(options: {
[name: string]: T | { extension?: T; options?: ExtensionOption };
}): void;
export function registerExtension<T extends ExtensionDelegate>(
name: string,
extension: T,
options?: ExtensionOption,
mergeConfig?: boolean
): void;
export function registerExtension<T extends ExtensionDelegate>(
name,
extension?: T,
options?: ExtensionOption,
mergeConfig?: boolean
): void {
let params = name;
if (!tools.isObject(name)) {
params = {};
params[name] = {
extension,
options
};
}
tools.each(
params,
function(v, name) {
if (v) {
const { extension, options }: { extension: T; options: ExtensionOption } = v;
if (extension) {
extensions[name] = extension;
} else if (!mergeConfig) {
extensions[name] = v;
}
if (mergeConfig) {
if (!extensionConfig[name]) {
extensionConfig[name] = _config();
}
if (tools.isObject(options)) {
tools.assign(extensionConfig[name], options);
} else {
extensionConfig[name] = _config();
}
} else {
extensionConfig[name] = _config(options);
}
}
},
false
);
}
tools.assign(nj, {
extensions,
extensionConfig,
registerExtension
});