@jeefo/component
Advanced tools
Comparing version 0.0.9 to 0.0.10
252
compiler.js
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : compiler.js | ||
* Created at : 2019-06-23 | ||
* Updated at : 2019-12-07 | ||
* Updated at : 2020-06-23 | ||
* Author : jeefo | ||
@@ -18,26 +18,29 @@ * Purpose : | ||
const jqlite = require("@jeefo/jqlite"); | ||
const definitions_table = require("./definitions_table"); | ||
const StructureComponent = require("./structure_component"); | ||
const DirectiveComponent = require("./directive_component"); | ||
const JeefoDOMParser = require("./dom_parser"); | ||
const definitions_table = require("./definitions_table"); | ||
const Directive = require("./components/directive"); | ||
const StructureComponent = require("./components/structure_component"); | ||
const RenderableComponent = require("./components/renderable_component"); | ||
const { MARKER } = StructureComponent; | ||
const BINDER = /{{\s*\S+.*}}/m; | ||
const single_tag_elements = ["img"]; | ||
const new_binding_component = (element, parent) => { | ||
return new RenderableComponent("binding--component", element, {}, parent); | ||
}; | ||
const is_element = n => n instanceof Element; | ||
// Higher order structure diretive like: forEach="item in items" | ||
async function find_structure_directive (node, parent) { | ||
async function find_structure (element, parent) { | ||
let name, definition; | ||
for (const [attr_name] of node.attrs) { | ||
const _definition = await definitions_table.get_directive(attr_name); | ||
if (_definition && _definition.is_structure) { | ||
if (definition) { | ||
if (_definition.priority > definition.priority) { | ||
name = attr_name; | ||
definition = _definition; | ||
if (element.hasAttributes()) { | ||
for (const attr of element.attributes) { | ||
const def = await definitions_table.get_directive(attr.name); | ||
if (def && def.is_structure) { | ||
if (definition && def.priority < definition.priority) { | ||
continue; | ||
} | ||
} else { | ||
name = attr_name; | ||
definition = _definition; | ||
name = attr.name; | ||
definition = def; | ||
} | ||
@@ -47,54 +50,49 @@ } | ||
if (! definition) { return; } | ||
if (definition) { | ||
const component = new StructureComponent( | ||
name, element, definition, parent | ||
); | ||
component.expression = element.getAttribute(name); | ||
element.removeAttribute(name); | ||
const component = new StructureComponent(null, definition, parent); | ||
component.node = node; | ||
component.expression = node.attrs.get(name); | ||
node.attrs.remove(name); | ||
return component; | ||
} | ||
return component; | ||
name = element.tagName.toLowerCase(); | ||
definition = await definitions_table.get_component(name); | ||
if (definition && definition.is_structure) { | ||
return new StructureComponent(name, element, definition, parent); | ||
} | ||
} | ||
const fake_definition = { | ||
binders : [], | ||
dependencies : [], | ||
Controller : class Controller {}, | ||
controller_name : null, | ||
is_self_required : false, | ||
}; | ||
async function find_component (node, parent) { | ||
async function find_component (element, parent) { | ||
const name = element.tagName.toLowerCase(); | ||
let component = null; | ||
let definition = await definitions_table.get_component(node.name); | ||
let definition = await definitions_table.get_component(name); | ||
if (definition) { | ||
component = new StructureComponent(node.name, definition, parent); | ||
if (definition.is_structure) { | ||
component.node = node; | ||
return component; | ||
} | ||
// TODO: think about better way, maybe return jeefo template or | ||
// something... | ||
if (definition.template_handler) { | ||
definition.template_handler(node); | ||
const new_element = definition.template_handler(element); | ||
if (is_element(new_element)) { | ||
if (element.parentNode) { | ||
element.parentNode.replaceChild(new_element, element); | ||
} | ||
element = new_element; | ||
} | ||
} else { | ||
node.children = definition.transclude(node.children); | ||
definition.transclude(element); | ||
} | ||
} | ||
// Content binding | ||
if (node.children.length === 0 && | ||
node.content && node.content.includes("${")) { | ||
if (! node.attrs.has("jf-bind")) { | ||
node.attrs.set("jf-bind", node.content); | ||
} | ||
node.content = null; | ||
component = new RenderableComponent(name, element, definition, parent); | ||
} | ||
if (! component) { | ||
// Attribute binding or has directive | ||
for (const [attr_name, value] of node.attrs) { | ||
const def = await definitions_table.get_directive(attr_name); | ||
if (def || (value && value.includes("${"))) { | ||
component = new StructureComponent( | ||
null, fake_definition, parent | ||
); | ||
// Attribute binding or has directive | ||
if (! component && element.hasAttributes()) { | ||
for (const {name, value} of element.attributes) { | ||
const def = await definitions_table.get_directive(name); | ||
if (def || BINDER.test(value) || name.startsWith("on--")) { | ||
component = new_binding_component(element, parent); | ||
break; | ||
@@ -105,106 +103,66 @@ } | ||
if (node.events.length) { | ||
// Content binding | ||
if (element.children.length === 0 && BINDER.test(element.textContent)) { | ||
if (element.hasAttribute("js-bind")) { | ||
throw new Error("Ambiguous binding"); | ||
} | ||
element.setAttribute("jf-bind", element.textContent); | ||
if (! component) { | ||
component = new StructureComponent(null, fake_definition, parent); | ||
component = new_binding_component(element, parent); | ||
} | ||
component.binding_events = node.events; | ||
} | ||
return component; | ||
} | ||
async function resolve_template (nodes, parent_component) { | ||
const results = []; | ||
async function resolve_components (elements, parent) { | ||
const components = []; | ||
for (const node of nodes) { | ||
for (let elem of elements) { | ||
// Structure directive is higher order | ||
let component = await find_structure_directive(node, parent_component); | ||
let component = await find_structure(elem, parent); | ||
if (component) { | ||
parent_component.children.push(component); | ||
results.push( | ||
`<${node.name} ${ component.get_marker() }></${node.name}>` | ||
); | ||
components.push(component); | ||
continue; | ||
} | ||
let attrs = ''; | ||
let content = ''; | ||
component = await find_component(node, parent_component); | ||
component = await find_component(elem, parent); | ||
if (component) { | ||
attrs += ` ${ component.get_marker() }`; | ||
parent_component.children.push(component); | ||
components.push(component); | ||
const {$element} = component; | ||
elem = $element.DOM_element; | ||
if (component.is_self_required) { | ||
results.push( | ||
`<${node.name}${attrs}></${node.name}>` | ||
); | ||
continue; | ||
// Find directives | ||
if (elem.hasAttributes()) { | ||
let i = elem.attributes.length; | ||
while (i--) { | ||
const {name, value} = elem.attributes[i]; | ||
const def = await definitions_table.get_directive(name); | ||
if (def) { | ||
const directive = new Directive(name, $element, def); | ||
component.directives.push(directive); | ||
} else if (name.startsWith("on--")) { | ||
component.binding_events.push({ | ||
event_name : name.substring(4), | ||
expression : value, | ||
}); | ||
elem.removeAttribute(name); | ||
} | ||
} | ||
} | ||
} | ||
if (node.children.length) { | ||
const parent = component || parent_component; | ||
content = await resolve_template(node.children, parent); | ||
} else if (node.content) { | ||
content = node.content; | ||
} | ||
// Find directives | ||
if (node.id) { | ||
attrs += ` id="${ node.id }"`; | ||
} | ||
if (node.class_list.length) { | ||
attrs += ` class="${ node.class_list.join(' ') }"`; | ||
} | ||
for (const [name, value] of node.attrs) { | ||
attrs += ` ${ name }`; | ||
if (value) { | ||
attrs += `="${ value }"`; | ||
} | ||
const definition = await definitions_table.get_directive(name); | ||
if (definition) { | ||
const directive = new DirectiveComponent(name, definition); | ||
component.directives.push(directive); | ||
} | ||
} | ||
if (single_tag_elements.includes(node.name)) { | ||
results.push(`<${node.name}${attrs}>`); | ||
} else { | ||
results.push(`<${node.name}${attrs}>${content}</${node.name}>`); | ||
} | ||
await resolve_components(elem.children, component || parent); | ||
} | ||
return results.join(''); | ||
return components; | ||
} | ||
function set_elements (components, $wrapper) { | ||
components.forEach(component => { | ||
if (! component.is_initialized) { | ||
component.$element = $wrapper.first(component.selector); | ||
component.$element.DOM_element.removeAttribute(MARKER); | ||
} | ||
set_elements(component.children, $wrapper); | ||
}); | ||
} | ||
async function compile_from_elements (elements, parent, to_initialize = true) { | ||
const components = await resolve_components(elements, parent); | ||
async function compile (nodes, parent_component, to_initialize = true) { | ||
const template = await resolve_template(nodes, parent_component); | ||
const $wrapper = jqlite("<div></div>"); | ||
const $elements = jqlite(template); | ||
if ($elements.DOM_element) { | ||
$wrapper.append($elements); | ||
} else { | ||
for (let i = 0; i < $elements.length; i+= 1) { | ||
$wrapper.append($elements[i]); | ||
} | ||
} | ||
set_elements(parent_component.children, $wrapper); | ||
if (to_initialize) { | ||
for (const component of parent_component.children) { | ||
if (! component.is_initialized) { | ||
await component.init(); | ||
for (const component of components) { | ||
if (! component.is_initialized && ! component.is_destroyed) { | ||
await component.initialize(); | ||
} | ||
@@ -214,13 +172,17 @@ } | ||
// Much faster way remove all child nodes | ||
// ref: https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript?answertab=votes#tab-top | ||
const wrapper = $wrapper.DOM_element; | ||
const elements = []; | ||
while (wrapper.firstChild) { | ||
elements.push(wrapper.firstChild); | ||
wrapper.removeChild(wrapper.firstChild); | ||
} | ||
return elements; | ||
} | ||
async function compile (template, parent_component, to_initialize = true) { | ||
const dom_parser = new JeefoDOMParser(template); | ||
await compile_from_elements( | ||
dom_parser.elements, parent_component, to_initialize | ||
); | ||
return dom_parser.detach(); | ||
} | ||
compile.from_elements = compile_from_elements; | ||
module.exports = compile; |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : component_definition.js | ||
* Created at : 2019-06-24 | ||
* Updated at : 2019-12-29 | ||
* Updated at : 2020-06-08 | ||
* Author : jeefo | ||
@@ -19,10 +19,7 @@ * Purpose : | ||
const extend_member = require("@jeefo/utils/class/extend_member"); | ||
const jeefo_template = require("@jeefo/template"); | ||
const object_for_each = require("@jeefo/utils/object/for_each"); | ||
const styles = require("./styles"); | ||
const IDefinition = require("./i_definition"); | ||
const IDefinition = require("./interfaces/i_definition"); | ||
const TranscludeController = require("./transclude_controller"); | ||
const STRING_TEMPLATE = /{{([^}]+)}}/g; | ||
const is_class = value => value.toString().startsWith("class"); | ||
@@ -46,4 +43,3 @@ | ||
if (type.toLowerCase() === "structure") { | ||
this.is_structure = true; | ||
this.is_self_required = true; | ||
this.is_structure = true; | ||
} else { | ||
@@ -56,3 +52,3 @@ throw new SyntaxError("Invalid definition type"); | ||
if (style) { | ||
const selectors = this.selectors.map(s => `"${s}"`); | ||
const selectors = this.selectors.map(s => `"${s.toLowerCase()}"`); | ||
styles.add_style(style, { | ||
@@ -65,11 +61,7 @@ "component-selectors" : `[${ selectors.join(", ") }]` | ||
if (typeof template === "string") { | ||
const _template = template.replace(STRING_TEMPLATE, (_, expr) => { | ||
return `\${${ expr }}`; | ||
}); | ||
const nodes = jeefo_template.parse(_template); | ||
this.transclude_controller = new TranscludeController(nodes); | ||
this.transclude_controller = new TranscludeController(template); | ||
} else if (typeof template === "function") { | ||
this.template_handler = template; | ||
} else { | ||
this.transclude_controller = new TranscludeController([]); | ||
this.transclude_controller = new TranscludeController(); | ||
} | ||
@@ -106,4 +98,4 @@ | ||
transclude (child_nodes) { | ||
return this.transclude_controller.transclude(child_nodes); | ||
transclude (element) { | ||
this.transclude_controller.transclude(element); | ||
} | ||
@@ -110,0 +102,0 @@ } |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : directive_definition.js | ||
* Created at : 2017-08-07 | ||
* Updated at : 2019-12-29 | ||
* Updated at : 2020-06-08 | ||
* Author : jeefo | ||
@@ -21,3 +21,3 @@ * Purpose : | ||
const styles = require("./styles"); | ||
const IDefinition = require("./i_definition"); | ||
const IDefinition = require("./interfaces/i_definition"); | ||
@@ -41,4 +41,3 @@ const is_class = value => value.toString().startsWith("class"); | ||
if (type.toLowerCase() === "structure") { | ||
this.is_structure = true; | ||
this.is_self_required = true; | ||
this.is_structure = true; | ||
} else { | ||
@@ -45,0 +44,0 @@ throw new SyntaxError("Invalid directive type"); |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : for_each.js | ||
* Created at : 2017-07-25 | ||
* Updated at : 2020-06-01 | ||
* Updated at : 2020-06-22 | ||
* Author : jeefo | ||
@@ -16,9 +16,37 @@ * Purpose : | ||
// jshint curly: false | ||
/* maybe useful some time | ||
const filter_children = async from => { | ||
const seen = []; | ||
const filtered_children = []; | ||
NEXT: | ||
for (let i = children.length - 1; i >= from; i -= 1) { | ||
const child = children[i]; | ||
let j = values.length; | ||
while (j--) { | ||
if (values[j] === child.value && ! seen.includes(j)) { | ||
seen.push(j); | ||
continue NEXT; | ||
} | ||
} | ||
filtered_children.push(child); | ||
children.splice(i, 1); | ||
} | ||
if (filtered_children.length) { | ||
return Promise.all(filtered_children.map(c => c.destroy())); | ||
} | ||
}; | ||
await filter_children(0); | ||
*/ | ||
// ignore:end | ||
const jqlite = require("@jeefo/jqlite"); | ||
const parser = require("../input/parser"); | ||
const compile = require("../compiler"); | ||
const Interpreter = require("../interpreter"); | ||
const StructureComponent = require("../structure_component"); | ||
const parser = require("../input/parser"); | ||
const Interpreter = require("../interpreter"); | ||
const ConditionalComponent = require("../components/conditional_component"); | ||
@@ -28,23 +56,27 @@ const comp_prop = Symbol("component"); | ||
const definition = { | ||
binders : [], | ||
dependencies : [], | ||
Controller : class ForEachDirectiveController {}, | ||
controller_name : null, | ||
is_self_required : false, | ||
}; | ||
class ForEachElement {} | ||
const CODE_CANCEL = Math.random(); | ||
async function create_new_child (value, index, component) { | ||
const { variable_name, index_name } = component; | ||
const new_child = new StructureComponent(null, definition, component); | ||
const { element, variable_name, index_name } = component; | ||
const wrapper = await new ConditionalComponent("foreach--wrapper", element); | ||
new_child.index = new_child.controller[index_name] = index; | ||
new_child.value = new_child.controller[variable_name] = value; | ||
wrapper.controller = new ForEachElement(); | ||
wrapper.index = wrapper.controller[index_name] = index; | ||
wrapper.value = wrapper.controller[variable_name] = value; | ||
const elements = await compile([component.node.clone(true)], new_child); | ||
new_child.$element = jqlite(elements[0]); | ||
return new_child; | ||
return wrapper; | ||
} | ||
const is_synced = (values, children) => { | ||
if (values.length === children.length) { | ||
let i = values.length; | ||
while (i--) { | ||
if (values[i] !== children[i].value) return false; | ||
} | ||
return true; | ||
} | ||
}; | ||
async function sync_children (instance) { | ||
@@ -55,27 +87,4 @@ const values = instance[values_prop]; | ||
const is_synced = () => { | ||
return ( | ||
values.length === children.length && | ||
values.every((v, i) => v === children[i].value) | ||
); | ||
}; | ||
let is_dirty = false; | ||
let i = children.length; | ||
while (i--) { | ||
if (! values.includes(children[i].value)) { | ||
await children[i].destroy(); | ||
} | ||
} | ||
const destroy_from = from => { | ||
for (let i = children.length - 1; i >= from; i -= 1) { | ||
const index = values.indexOf(children[i].value, from); | ||
if (index === -1) { | ||
children[i].destroy(); | ||
} | ||
} | ||
}; | ||
destroy_from(0); | ||
let is_canceled = false, cancel_resolver; | ||
@@ -86,6 +95,11 @@ component.cancel_syncing = () => { | ||
}; | ||
component.is_syncing = true; | ||
LOOP: | ||
while (! is_synced()) { | ||
for (let [i, value] of values.entries()) { | ||
while (! is_synced(values, children) && ! is_canceled) { | ||
is_dirty = true; | ||
LOOP: | ||
for (let i = 0; i < values.length; i+= 1) { | ||
const value = values[i]; | ||
if (i < children.length) { | ||
@@ -95,3 +109,3 @@ if (children[i].value === value) { continue; } | ||
for (let j = i + 1; j < children.length; j += 1) { | ||
if (children[j] === value) { | ||
if (children[j].value === value) { | ||
children.splice(i, 0, children.splice(j, 1)[0]); | ||
@@ -105,12 +119,25 @@ continue LOOP; | ||
children.splice(i, 0, new_child); | ||
if (is_canceled) { break; } | ||
destroy_from(i + 1); | ||
continue LOOP; | ||
new_child.parent = component; | ||
} | ||
if (children.length > values.length) { | ||
const dead_children = children.splice(values.length); | ||
await Promise.all(dead_children.map(c => c.destroy())); | ||
} | ||
if (component.is_initialized) { | ||
for (const child of children) { | ||
if (! child.is_initialized) await child.initialize(); | ||
if (is_canceled) break; | ||
} | ||
} | ||
} | ||
component.is_syncing = false; | ||
if (is_canceled) { | ||
component.cancel_syncing = null; | ||
cancel_resolver(); | ||
throw CODE_CANCEL; | ||
} | ||
return is_dirty; | ||
} | ||
@@ -125,2 +152,3 @@ | ||
this[comp_prop] = component; | ||
component.element.removeAttribute("for-each"); | ||
@@ -152,5 +180,8 @@ try { | ||
$element.replace(document.createComment(comment)); | ||
component.$placeholder = $element; | ||
component.is_initialized = true; | ||
await this.on_digest(); | ||
} catch (e) { | ||
// WTF ??? | ||
throw e; | ||
@@ -163,7 +194,5 @@ } | ||
const { | ||
$element : $comment, | ||
children, | ||
index_name, | ||
interpreter, | ||
is_attached, | ||
$placeholder, | ||
} = component; | ||
@@ -177,28 +206,44 @@ this[values_prop] = interpreter.get_value(); | ||
} | ||
await sync_children(this); | ||
if ($comment.DOM_element.parentNode === null) { | ||
const frag = document.createDocumentFragment(); | ||
frag.appendChild($comment.DOM_element); | ||
} | ||
try { | ||
const is_changed = await sync_children(this); | ||
if (! is_changed) return; | ||
children.forEach((child, index) => { | ||
if (! child.is_attached || child.index !== index) { | ||
const {index_name} = component; | ||
const move = (index, child) => { | ||
if (index === 0) { | ||
$comment.after(child.$element); | ||
$placeholder.after(child.$element); | ||
} else { | ||
const prev = component.children[index - 1]; | ||
const prev = children[index - 1]; | ||
prev.$element.after(child.$element); | ||
} | ||
}; | ||
child.index = index; | ||
child.controller[index_name] = index; | ||
if (is_attached && ! child.is_attached) { | ||
child.trigger_renderable(); | ||
for (const [index, child] of children.entries()) { | ||
if (! child.is_attached) { | ||
move(index, child); | ||
child.is_attached = true; | ||
} else if (child.index !== index) { | ||
const data = { | ||
old_index : child.index, | ||
new_index : index | ||
}; | ||
move(index, child); | ||
child.index = child.controller[index_name] = index; | ||
if (child.is_rendered) { | ||
child.$element.trigger("foreach:move", { data }); | ||
} | ||
} | ||
} | ||
}); | ||
if (component.is_rendered) | ||
for (const child of children) | ||
if (! child.is_rendered) child.trigger_render(); | ||
} catch (e) { | ||
if (e !== CODE_CANCEL) throw e; | ||
} | ||
}, | ||
} | ||
}; |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : if.js | ||
* Created at : 2017-09-17 | ||
* Updated at : 2019-12-03 | ||
* Updated at : 2020-06-12 | ||
* Author : jeefo | ||
@@ -18,6 +18,5 @@ * Purpose : | ||
const jqlite = require("@jeefo/jqlite"); | ||
const compile = require("../compiler"); | ||
const Interpreter = require("../interpreter"); | ||
const StructureComponent = require("../structure_component"); | ||
const jqlite = require("@jeefo/jqlite"); | ||
const Interpreter = require("../interpreter"); | ||
const ConditionalComponent = require("../components/conditional_component"); | ||
@@ -27,24 +26,13 @@ const prop_comment = Symbol("$comment"); | ||
const definition = { | ||
binders : [], | ||
Controller : class Controller {}, | ||
dependencies : [], | ||
}; | ||
const compile_component = async (component, $comment) => { | ||
const child = new StructureComponent(null, definition, component); | ||
component.children.push(child); | ||
const wrapper = await new ConditionalComponent( | ||
"if--rendered", component.element, component | ||
); | ||
const elements = await compile([component.node.clone(true)], child, false); | ||
child.$element = jqlite(elements[0]); | ||
if (! wrapper.is_destroyed) { | ||
await wrapper.initialize(); | ||
if (! child.is_destroyed) { | ||
$comment.after(child.$element); | ||
if (component.is_initialized) { | ||
await child.init(); | ||
if (! child.is_destroyed && component.is_attached) { | ||
child.trigger_renderable(); | ||
} | ||
if (! wrapper.is_destroyed) { | ||
$comment.after(wrapper.$element); | ||
if (component.is_rendered) { wrapper.trigger_render(); } | ||
} | ||
@@ -65,2 +53,3 @@ } | ||
component.interpreter = new Interpreter(expression, component); | ||
component.element.removeAttribute("if"); | ||
@@ -67,0 +56,0 @@ this[prop_comment] = jqlite(comment); |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : jf_bind_directive.js | ||
* File Name : jf_bind.js | ||
* Created at : 2017-07-26 | ||
* Updated at : 2019-07-21 | ||
* Updated at : 2020-06-11 | ||
* Author : jeefo | ||
@@ -25,3 +25,3 @@ * Purpose : | ||
break; | ||
case null : | ||
case "null" : | ||
$element.text = "null"; | ||
@@ -39,4 +39,4 @@ break; | ||
}, | ||
controller : { | ||
on_init : function ($element) { | ||
controller : class Binder { | ||
on_init ($element) { | ||
bind($element, this["(bind)"]); | ||
@@ -43,0 +43,0 @@ const observer = new Observer(this); |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : switch.js | ||
* Created at : 2019-07-12 | ||
* Updated at : 2020-01-02 | ||
* Updated at : 2020-06-12 | ||
* Author : jeefo | ||
@@ -18,42 +18,30 @@ * Purpose : | ||
const jqlite = require("@jeefo/jqlite"); | ||
const Events = require("@jeefo/template/tokens/events"); | ||
const Attributes = require("@jeefo/template/tokens/attributes"); | ||
const NodeElement = require("@jeefo/template/node_element"); | ||
const compile = require("../compiler"); | ||
const Interpreter = require("../interpreter"); | ||
const StructureComponent = require("../structure_component"); | ||
const jqlite = require("@jeefo/jqlite"); | ||
const compile = require("../compiler"); | ||
const is_element = require("../is_element"); | ||
const Interpreter = require("../interpreter"); | ||
const ConditionalComponent = require("../components/conditional_component"); | ||
const prop_component = Symbol("component"); | ||
const definition = { | ||
binders : [], | ||
Controller : class Controller {}, | ||
dependencies : [], | ||
}; | ||
const compile_child = async (component, {element, $placeholder}) => { | ||
const wrapper = await new ConditionalComponent( | ||
"switch-wrapper", element, component | ||
); | ||
const find_case = (cases, value) => { | ||
return cases.find(node => { | ||
if (node.interpreter) { | ||
return node.interpreter.get_value() === value; | ||
if (! wrapper.is_destroyed) { | ||
await wrapper.initialize(); | ||
if (! wrapper.is_destroyed) { | ||
$placeholder.after(wrapper.$element); | ||
if (component.is_rendered) { wrapper.trigger_render(); } | ||
} | ||
return node; | ||
}); | ||
} | ||
return wrapper; | ||
}; | ||
const compile_component = async (node, component) => { | ||
const child = new StructureComponent(null, definition, component); | ||
const elements = await compile([node.clone(true)], child, false); | ||
child.$element = jqlite(elements[0]); | ||
return child; | ||
const compile_self = async instance => { | ||
await compile.from_elements([instance.element], instance); | ||
}; | ||
const placeholder_node = new NodeElement(null, { | ||
name : "switch-placeholder", | ||
class_list : [], | ||
content : null, | ||
attrs : new Attributes(), | ||
events : new Events(), | ||
}); | ||
module.exports = { | ||
@@ -66,38 +54,42 @@ type : "structure", | ||
async on_init ($element, component) { | ||
const { node, expression } = component; | ||
const {element, expression} = component; | ||
element.removeAttribute("switch"); | ||
const cases = []; | ||
const placeholder_clone = placeholder_node.clone(); | ||
placeholder_clone.attrs.set("switch-id", component.id); | ||
node.children = node.children.map(child => { | ||
if (child.attrs.has("case")) { | ||
const expr = child.attrs.get("case"); | ||
child.interpreter = new Interpreter(expr, component); | ||
component.cases = []; | ||
component.interpreter = new Interpreter(expression, component); | ||
cases.push(child); | ||
return placeholder_clone; | ||
} else if (child.attrs.has("default")) { | ||
cases.push(child); | ||
return placeholder_clone; | ||
} else { | ||
return child; | ||
let i = element.childNodes.length; | ||
while (i--) { | ||
const node = element.childNodes[i]; | ||
if (! is_element(node)) { continue; } | ||
if (node.hasAttribute("case")) { | ||
const expr = node.getAttribute("case"); | ||
const comment = `switch-case: ${expr}`; | ||
const comment_el = document.createComment(comment); | ||
component.cases.push({ | ||
element : node, | ||
interpreter : new Interpreter(expr, component), | ||
$placeholder : jqlite(comment_el), | ||
}); | ||
node.removeAttribute("case"); | ||
element.replaceChild(comment_el, node); | ||
} else if (node.hasAttribute("default")) { | ||
if (component.default_case) { | ||
throw new Error("Multiple default case found in switch."); | ||
} | ||
const comment = document.createComment("switch-default"); | ||
component.default_case = { | ||
element : node, | ||
$placeholder : jqlite(comment), | ||
}; | ||
node.removeAttribute("default"); | ||
element.replaceChild(comment, node); | ||
} | ||
}); | ||
component.cases = cases; | ||
component.interpreter = new Interpreter(expression, component); | ||
component.placeholders = []; | ||
const elements = await compile([node], component); | ||
$element.replace(elements[0]); | ||
if (component.children[0].$element.DOM_element === elements[0]) { | ||
component.$element = null; | ||
} | ||
component.placeholders.forEach((p, i) => { | ||
p.node = cases[i]; | ||
}); | ||
component.is_initialized = true; | ||
compile_self(component); | ||
this[prop_component] = component; | ||
await this.on_digest(); | ||
@@ -107,41 +99,20 @@ } | ||
async on_digest () { | ||
const component = this[prop_component]; | ||
const value = component.interpreter.get_value(); | ||
let matched_node = find_case(component.cases, value); | ||
const component = this[prop_component]; | ||
const value = component.interpreter.get_value(); | ||
// Cleaning old switch-case | ||
if (component.matched_node) { | ||
if (component.matched_node === matched_node) { | ||
matched_node = null; | ||
} else { | ||
if (component.active_child) { | ||
component.active_child.destroy(); | ||
} | ||
component.matched_node = component.active_child = null; | ||
let matched_case = component.cases.find(node => { | ||
return node.interpreter.get_value() === value; | ||
}) || component.default_case; | ||
if (component.matched_case !== matched_case) { | ||
// Cleaning old switch-case | ||
if (component.instance) { | ||
component.instance.destroy(); | ||
component.instance = null; | ||
} | ||
} | ||
if (matched_node) { | ||
component.matched_node = matched_node; | ||
const child = await compile_component(matched_node, component); | ||
if (! child.is_destroyed) { | ||
const placeholder = component.placeholders.find(p => { | ||
return p.node === matched_node; | ||
}); | ||
const child_index = component.children.findIndex(child => { | ||
return child === placeholder; | ||
}); | ||
component.children.splice(child_index + 1, 0, child); | ||
placeholder.$element.after(child.$element); | ||
component.active_child = child; | ||
if (component.is_initialized) { | ||
await child.init(); | ||
if (! child.is_destroyed && component.is_attached) { | ||
child.trigger_renderable(); | ||
} | ||
} | ||
} | ||
component.matched_case = matched_case; | ||
component.instance = await compile_child( | ||
component, matched_case | ||
); | ||
} | ||
@@ -148,0 +119,0 @@ } |
17
index.js
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : index.js | ||
* Created at : 2017-08-08 | ||
* Updated at : 2019-11-29 | ||
* Updated at : 2020-06-09 | ||
* Author : jeefo | ||
@@ -39,2 +39,15 @@ * Purpose : | ||
module.exports = definitions_table; | ||
module.exports = { | ||
IComponent : require("./interfaces/i_component"), | ||
IRenderable : require("./interfaces/i_renderable"), | ||
IDefinition : require("./interfaces/i_definition"), | ||
Directive : require("./components/directive"), | ||
InvisibleComponent : require("./components/invisible_component"), | ||
StructureComponent : require("./components/structure_component"), | ||
RenderableComponent : require("./components/renderable_component"), | ||
compile : require("./compiler"), | ||
JeefoDOMParser : require("./dom_parser"), | ||
definitions_table | ||
}; |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : ast_node_table.js | ||
* Created at : 2017-09-18 | ||
* Updated at : 2019-10-31 | ||
* Updated at : 2020-06-11 | ||
* Author : jeefo | ||
@@ -17,3 +17,14 @@ * Purpose : | ||
const { AST_Node_Table } = require("@jeefo/parser"); | ||
const { | ||
AST_Node_Table, | ||
AST_Node_Definition | ||
} = require("@jeefo/parser"); | ||
const { | ||
PRIMITIVE, | ||
TERMINATION | ||
} = require("@jeefo/ecma_parser/es6/enums/precedence_enum"); | ||
const { | ||
expression, | ||
primary_expression | ||
} = require("@jeefo/ecma_parser/es6/enums/states_enum"); | ||
@@ -67,4 +78,7 @@ const ast_node_table = new AST_Node_Table(); | ||
"es6/expressions/property_definition", | ||
// 12.2.9 - Template literal | ||
"es6/literals/template_literal", | ||
// replaced by jeefo binder | ||
//"es6/literals/template_literal", | ||
// 12.2.10 - The grouping operator | ||
@@ -129,2 +143,7 @@ "es8/expressions/cover_parenthesized_expression_and_arrow_parameters", | ||
// DEBUG_START | ||
const debugger_stmt = require(`${proj_dir}/es5/statements/debugger_statement`); | ||
ast_node_table.register_reserved_word("debugger", debugger_stmt); | ||
// DEBUG_END | ||
const _this = require(`${ proj_dir }/es8/expressions/this_keyword`); | ||
@@ -137,2 +156,193 @@ ast_node_table.register_reserved_word("this", _this); | ||
// Jeefo binder | ||
const template_string = new AST_Node_Definition({ | ||
id : "Template literal string", | ||
type : "Primitive", | ||
precedence : -1, | ||
is : () => {}, | ||
initialize : (node, current_token, parser) => { | ||
const { streamer } = parser.tokenizer; | ||
let length = 0; | ||
let current_index = streamer.cursor.position.index; | ||
let virtual_length = 0; | ||
let next_character = streamer.get_current_character(); | ||
let start, end, last_pos; | ||
if (parser.last_position) { | ||
start = parser.last_position.clone(); | ||
start.index += 1; | ||
start.column += 1; | ||
start.virtual_column += 1; | ||
} else { | ||
start = streamer.clone_cursor_position(); | ||
} | ||
const get_next_character = (next_length = 1) => { | ||
return streamer.at(current_index + length + next_length); | ||
}; | ||
LOOP: | ||
while (true) { | ||
switch (next_character) { | ||
case '\t' : | ||
virtual_length += streamer.tab_size - 1; | ||
break; | ||
case '\n' : | ||
next_character = get_next_character(); | ||
if (next_character === '`') { | ||
if (parser.last_position) { | ||
end = start.clone(); | ||
streamer.cursor.position.line -= 1; | ||
} else { | ||
streamer.cursor.move(length, virtual_length); | ||
end = streamer.clone_cursor_position(); | ||
} | ||
streamer.cursor.position.line += 1; | ||
streamer.cursor.position.column = 0; | ||
streamer.cursor.position.virtual_column = 0; | ||
break LOOP; | ||
} else { | ||
if (parser.last_position) { | ||
last_pos = start.clone(); | ||
parser.last_position = null; | ||
} else { | ||
if (length) { | ||
streamer.cursor.move(length, virtual_length); | ||
last_pos = streamer.clone_cursor_position(); | ||
} | ||
streamer.cursor.position.line += 1; | ||
} | ||
streamer.cursor.position.column = 0; | ||
streamer.cursor.position.virtual_column = 0; | ||
current_index = streamer.cursor.position.index; | ||
length = virtual_length = 0; | ||
} | ||
break; | ||
case '{': | ||
if (get_next_character() === '{') { | ||
const is_new_line = ( | ||
length === 1 && | ||
streamer.cursor.position.column === 0 && | ||
last_pos | ||
); | ||
streamer.cursor.move(length-1, virtual_length - 1); | ||
if (is_new_line) { | ||
end = last_pos; | ||
} else { | ||
end = streamer.clone_cursor_position(); | ||
} | ||
break LOOP; | ||
} | ||
break; | ||
case '`': | ||
streamer.cursor.move(length - 1, virtual_length - 1); | ||
end = streamer.clone_cursor_position(); | ||
break LOOP; | ||
case '\\': | ||
length += 1; | ||
virtual_length += 1; | ||
break; | ||
case null: parser.throw_unexpected_end_of_stream(); | ||
} | ||
next_character = get_next_character(); | ||
length += 1; | ||
virtual_length += 1; | ||
} | ||
node.value = streamer.substring_from_offset(start.index); | ||
node.start = start; | ||
node.end = end; | ||
} | ||
}); | ||
const jeefo_binder_expression = new AST_Node_Definition({ | ||
id : "Jeefo binder expression", | ||
type : "Primitive", | ||
precedence : -1, | ||
is : () => {}, | ||
initialize : (node, current_token, parser) => { | ||
const start = current_token.start; | ||
const {streamer} = parser.tokenizer; | ||
streamer.cursor.move(1); | ||
parser.prepare_next_state("expression", true); | ||
const expression = parser.parse_next_node(TERMINATION); | ||
parser.expect("}}", () => { | ||
return parser.next_token.id === "Delimiter" && | ||
parser.next_token.value === '}' && | ||
streamer.is_next_character('}'); | ||
}); | ||
streamer.cursor.move(1); | ||
node.expression = expression; | ||
node.start = start; | ||
node.end = streamer.clone_cursor_position(); | ||
} | ||
}); | ||
ast_node_table.register_node_definition({ | ||
id : "Template literal", | ||
type : "Primitive", | ||
precedence : PRIMITIVE, | ||
is : (token, { current_state }) => { | ||
return current_state === expression && token.id === "Backtick"; | ||
}, | ||
initialize : (node, current_token, parser) => { | ||
const body = []; | ||
const { streamer } = parser.tokenizer; | ||
const start_position = streamer.clone_cursor_position(); | ||
if (streamer.get_next_character() === '\n') { | ||
parser.last_position = streamer.clone_cursor_position(); | ||
} | ||
let next_character = streamer.next(); | ||
LOOP: | ||
while (true) { | ||
switch (next_character) { | ||
case '`' : break LOOP; | ||
case null: | ||
parser.throw_unexpected_end_of_stream(); | ||
break; | ||
} | ||
if (next_character === '{' && streamer.is_next_character('{')) { | ||
body.push(jeefo_binder_expression.generate_new_node(parser)); | ||
} else { | ||
body.push(template_string.generate_new_node(parser)); | ||
} | ||
next_character = streamer.get_next_character(); | ||
if (next_character === '\n') { | ||
parser.last_position = streamer.clone_cursor_position(); | ||
} else { | ||
parser.last_position = null; | ||
} | ||
if (streamer.get_current_character() === '\n') { | ||
streamer.cursor.position.line -= 1; | ||
} | ||
streamer.next(); | ||
} | ||
delete parser.last_position; | ||
node.body = body; | ||
node.start = start_position; | ||
node.end = streamer.clone_cursor_position(); | ||
// It's important, since there is no real next token | ||
parser.next_token = node; | ||
parser.current_state = primary_expression; | ||
} | ||
}); | ||
module.exports = ast_node_table; |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : tokenizer.js | ||
* Created at : 2019-07-09 | ||
* Updated at : 2019-10-31 | ||
* Updated at : 2020-06-06 | ||
* Author : jeefo | ||
@@ -24,2 +24,20 @@ * Purpose : | ||
tokenizer.register({ | ||
id : "JeefoBinder", | ||
priority : 41, | ||
is : (current_character, streamer) => { | ||
return current_character === '{' && streamer.is_next_character('{'); | ||
}, | ||
initialize : (token, character, streamer) => { | ||
const start = streamer.clone_cursor_position(); | ||
streamer.cursor.move(1); | ||
token.value = "{{"; | ||
token.start = start; | ||
token.end = streamer.clone_cursor_position(); | ||
} | ||
}); | ||
module.exports = tokenizer; |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : interpreter.js | ||
* Created at : 2019-06-30 | ||
* Updated at : 2020-03-08 | ||
* Updated at : 2020-06-11 | ||
* Author : jeefo | ||
@@ -22,24 +22,32 @@ * Purpose : | ||
const is_event_binder = ({event_binder}, prop) => { | ||
return event_binder && prop in event_binder; | ||
}; | ||
function find_controller (property, controllers, input_component) { | ||
const build_script = ({ id, controller }) => { | ||
const ctrl_name = `ctrl_${ id }`; | ||
controllers[ctrl_name] = controller; | ||
return `$ctrls.${ ctrl_name }.${ property }`; | ||
}; | ||
if (is_event_binder(input_component, property)) { | ||
controllers.event_binder = input_component.event_binder; | ||
return `$ctrls.event_binder.${property}`; | ||
} | ||
const is_matched = ({ controller, controller_name }) => { | ||
return ( | ||
property === controller_name || | ||
(controller && property in controller) | ||
); | ||
const build_script = ({ id, controller, controller_name }) => { | ||
if (controller_name) { | ||
if (controller_name === property) { | ||
controllers[controller_name] = controller; | ||
return `$ctrls.${controller_name}`; | ||
} | ||
} else if (controller && property in controller) { | ||
const ctrl_name = `ctrl_${ id }`; | ||
controllers[ctrl_name] = controller; | ||
return `$ctrls.${ctrl_name}.${property}`; | ||
} | ||
}; | ||
const _find_controller = component => { | ||
if (is_matched(component)) { | ||
return build_script(component); | ||
} | ||
const script = build_script(component); | ||
if (script) { return script; } | ||
const directive = component.directives.find(is_matched); | ||
if (directive) { | ||
return build_script(directive); | ||
for (const directive of component.directives) { | ||
const script = build_script(directive); | ||
if (script) { return script; } | ||
} | ||
@@ -58,6 +66,4 @@ }; | ||
if (window[property]) { | ||
return build_script({ | ||
id : "global", | ||
controller : window | ||
}); | ||
controllers.__global = window; | ||
return `$ctrls.__global.${property}`; | ||
} | ||
@@ -110,2 +116,6 @@ } | ||
return `${ prop } : ${ value }`; | ||
case "Positive plus operator" : | ||
case "Negation minus operator" : | ||
const { operator: {value: op}, expression } = node; | ||
return `${op}${compile(expression, controllers, component)}`; | ||
case "Logical not operator" : | ||
@@ -166,2 +176,8 @@ return `! ${ compile(node.expression, controllers, component) }`; | ||
}).join(" + "); | ||
// DEBUG_START | ||
case "Debugger statement" : | ||
return node.keyword.value; | ||
// DEBUG_END | ||
default: | ||
@@ -239,3 +255,5 @@ throw new Error(`Invalid AST_Node: '${ node.id }'`); | ||
const last_stmt = compiled_stmts[last_index]; | ||
compiled_stmts[last_index] = `result = ${ last_stmt }`; | ||
if (statements[statements.length - 1].id !== "Debugger statement") { | ||
compiled_stmts[last_index] = `result = ${ last_stmt }`; | ||
} | ||
@@ -242,0 +260,0 @@ const code = build_fn_body(compiled_stmts); |
{ | ||
"name": "@jeefo/component", | ||
"version": "0.0.9", | ||
"version": "0.0.10", | ||
"homepage": "https://github.com/je3f0o/jeefo_component", | ||
@@ -5,0 +5,0 @@ "copyright": "2019", |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : transclude_controller.js | ||
* Created at : 2019-06-26 | ||
* Updated at : 2019-07-21 | ||
* Updated at : 2020-06-06 | ||
* Author : jeefo | ||
@@ -18,29 +18,28 @@ * Purpose : | ||
const Transcluder = require("./transcluder"); | ||
const Transcluder = require("./transcluder"); | ||
const JeefoDOMParser = require("./dom_parser"); | ||
function find_transcluders (nodes, parent_indices, transclude_controller) { | ||
nodes.forEach((node, index) => { | ||
const parent_position = parent_indices.concat(); | ||
parent_position.push(index); | ||
function find_transcluders (elements, indices, transclude_controller) { | ||
for (let i = 0; i < elements.length; i += 1) { | ||
const element = elements[i]; | ||
indices.push(i); | ||
if (node.name === "jf-content") { | ||
const selector_name = node.attrs.get("select") || null; | ||
transclude_controller.add(selector_name, parent_position); | ||
} else if (node.children.length) { | ||
find_transcluders( | ||
node.children, parent_position, transclude_controller | ||
); | ||
if (element.tagName.toLowerCase() === "jf-content") { | ||
const selector_name = element.getAttribute("select"); | ||
transclude_controller.add(selector_name, indices); | ||
} else { | ||
find_transcluders(element.children, indices, transclude_controller); | ||
} | ||
}); | ||
indices.pop(); | ||
} | ||
} | ||
class TranscludeController { | ||
constructor (structure_nodes) { | ||
this.structure_nodes = structure_nodes; | ||
this.default_transcluder = null; | ||
this.named_transcluders = []; | ||
this.named_transcluders_map = Object.create(null); | ||
constructor (markup = '') { | ||
this.dom_parser = new JeefoDOMParser(markup); | ||
this.named_transcluders = []; | ||
this.default_transcluder = null; | ||
if (structure_nodes.length) { | ||
find_transcluders(structure_nodes, [], this); | ||
if (this.dom_parser.elements.length) { | ||
find_transcluders(this.dom_parser.elements, [], this); | ||
} else { | ||
@@ -53,19 +52,17 @@ this.default_transcluder = new Transcluder(null, []); | ||
get (selector_name) { | ||
return this.named_transcluders_map[selector_name] || null; | ||
} | ||
add (selector_name, indices) { | ||
const transcluder = new Transcluder(selector_name, indices); | ||
add (selector_name, parent_position) { | ||
const transcluder = new Transcluder(selector_name, parent_position); | ||
if (selector_name) { | ||
if (this.named_transcluders_map[selector_name]) { | ||
throw new Error(`Duplicated transcluder detected.`); | ||
const named_transcluder = this.named_transcluders.find(t => { | ||
return t.selector_name === selector_name; | ||
}); | ||
if (named_transcluder) { | ||
throw new Error("Duplicated transcluder detected."); | ||
} | ||
this.named_transcluders.push(transcluder); | ||
this.named_transcluders_map[selector_name] = transcluder; | ||
} else if (! this.default_transcluder) { | ||
this.default_transcluder = transcluder; | ||
} else { | ||
throw new Error(`Ambigious transcluder detected.`); | ||
throw new Error("Ambigious transcluder detected."); | ||
} | ||
@@ -75,29 +72,43 @@ } | ||
// TODO: sort transcluders and transclude from behind | ||
transclude (child_nodes) { | ||
if (this.structure_nodes.length === 0) { return child_nodes; } | ||
const nodes = this.structure_nodes.map(node => node.clone(true)); | ||
transclude (element) { | ||
if (this.dom_parser.elements.length === 0) { return; } | ||
if (child_nodes.length) { | ||
child_nodes.forEach(child_node => { | ||
const selector_name = child_node.name; | ||
const named_transcluder = this.get(selector_name); | ||
if (named_transcluder) { | ||
named_transcluder.add_node(child_node); | ||
} else if (this.default_transcluder) { | ||
this.default_transcluder.add_node(child_node); | ||
} else { | ||
throw new Error("Transcluder is not found"); | ||
} | ||
}); | ||
const body = this.dom_parser.document.body.cloneNode(true); | ||
let i = this.named_transcluders.length; | ||
while (i--) { | ||
this.named_transcluders[i].transclude(nodes); | ||
while (element.firstChild) { | ||
const child = element.removeChild(element.firstChild); | ||
let named_transcluder; | ||
if (child.nodeType === Node.ELEMENT_NODE) { | ||
named_transcluder = this.named_transcluders.find(t => { | ||
return t.selector_name === child.tagName.toLowerCase(); | ||
}); | ||
} | ||
if (this.default_transcluder) { | ||
this.default_transcluder.transclude(nodes); | ||
if (named_transcluder) { | ||
named_transcluder.elements.push(child); | ||
} else if (this.default_transcluder) { | ||
this.default_transcluder.elements.push(child); | ||
} else { | ||
throw new Error("Transcluder is not found"); | ||
} | ||
} | ||
return nodes; | ||
let i = this.named_transcluders.length; | ||
while (i--) { | ||
this.named_transcluders[i].set_placeholder(body); | ||
} | ||
if (this.default_transcluder) { | ||
this.default_transcluder.set_placeholder(body); | ||
} | ||
i = this.named_transcluders.length; | ||
while (i--) { | ||
this.named_transcluders[i].transclude(); | ||
} | ||
if (this.default_transcluder) { | ||
this.default_transcluder.transclude(); | ||
} | ||
while (body.firstChild) { | ||
element.appendChild(body.firstChild); | ||
} | ||
} | ||
@@ -104,0 +115,0 @@ } |
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. | ||
* File Name : transcluder.js | ||
* Created at : 2017-08-26 | ||
* Updated at : 2019-07-21 | ||
* Updated at : 2020-06-05 | ||
* Author : jeefo | ||
@@ -17,30 +17,33 @@ * Purpose : | ||
const splice = Array.prototype.splice; | ||
class Transcluder { | ||
constructor (selector_name, parent_position) { | ||
this.nodes = []; | ||
this.selector_name = selector_name; | ||
this.parent_position = parent_position; | ||
constructor (selector_name, indices) { | ||
this.indices = indices.concat(); | ||
this.elements = []; | ||
this.placeholder = null; | ||
this.selector_name = selector_name; | ||
} | ||
add_node (node) { | ||
this.nodes.push(node); | ||
} | ||
transclude (structure_nodes) { | ||
// TODO: replace this temporary solution | ||
let parent_container = structure_nodes; | ||
const length = this.parent_position.length - 1; | ||
set_placeholder (parent_element) { | ||
const length = this.indices.length - 1; | ||
for (let i = 0; i < length; ++i) { | ||
const index = this.parent_position[i]; | ||
parent_container = parent_container[index].children; | ||
const index = this.indices[i]; | ||
parent_element = parent_element.children[index]; | ||
} | ||
const args = [this.parent_position.last(), 1].concat(this.nodes); | ||
splice.apply(parent_container, args); | ||
this.nodes = []; | ||
const index = this.indices[this.indices.length - 1]; | ||
this.placeholder = parent_element.children[index]; | ||
} | ||
transclude () { | ||
const parent_element = this.placeholder.parentNode; | ||
while (this.elements.length) { | ||
parent_element.insertBefore( | ||
this.elements.pop(), this.placeholder.nextSibling | ||
); | ||
} | ||
parent_element.removeChild(this.placeholder); | ||
this.placeholder = null; | ||
} | ||
} | ||
module.exports = Transcluder; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
98653
35
2787
4
8