jails-js 5.0.0-beta.5 → 5.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +6 -0
- package/README.md +7 -37
- package/dist/jails.js +1 -1
- package/dist/jails.js.LICENSE.txt +4 -0
- package/dist/jails.js.map +1 -1
- package/package.json +40 -39
- package/src/Component.ts +158 -102
- package/src/index.ts +18 -50
- package/src/template-system.ts +87 -0
- package/src/utils/index.js +42 -8
- package/tsconfig.json +11 -3
- package/src/Element.ts +0 -126
- package/src/Scanner.ts +0 -26
- package/src/soda-config.js +0 -87
package/package.json
CHANGED
@@ -1,41 +1,42 @@
|
|
1
1
|
{
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
2
|
+
"name": "jails-js",
|
3
|
+
"version": "5.0.0-beta.8",
|
4
|
+
"description": "A Modern Javascript Library",
|
5
|
+
"main": "dist/jails.js",
|
6
|
+
"scripts": {
|
7
|
+
"start": "webpack --watch --mode=development",
|
8
|
+
"build": "webpack --mode=production",
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
10
|
+
},
|
11
|
+
"repository": {
|
12
|
+
"type": "git",
|
13
|
+
"url": "https://github.com/jails-org/Jails.git"
|
14
|
+
},
|
15
|
+
"keywords": [
|
16
|
+
"Jails",
|
17
|
+
"Javascript",
|
18
|
+
"Component",
|
19
|
+
"Micro-Library"
|
20
|
+
],
|
21
|
+
"author": "javiani",
|
22
|
+
"license": "MIT",
|
23
|
+
"bugs": {
|
24
|
+
"url": "https://github.com/jails-org/Jails/issues"
|
25
|
+
},
|
26
|
+
"homepage": "https://github.com/jails-org/Jails",
|
27
|
+
"devDependencies": {
|
28
|
+
"@babel/core": "^7.2.2",
|
29
|
+
"@babel/preset-env": "^7.2.3",
|
30
|
+
"babel-loader": "^8.0.5",
|
31
|
+
"babel-plugin-transform-custom-element-classes": "^0.1.0",
|
32
|
+
"babel-preset-env": "^1.7.0",
|
33
|
+
"ts-loader": "^9.2.6",
|
34
|
+
"typescript": "^4.5.4",
|
35
|
+
"webpack": "^5.59.1",
|
36
|
+
"webpack-cli": "^3.2.1"
|
37
|
+
},
|
38
|
+
"dependencies": {
|
39
|
+
"morphdom": "^2.6.1",
|
40
|
+
"template7": "^1.4.2"
|
41
|
+
}
|
41
42
|
}
|
package/src/Component.ts
CHANGED
@@ -1,122 +1,178 @@
|
|
1
|
-
import
|
1
|
+
import { rAF, dup, buildtemplates } from './utils'
|
2
2
|
import { on, off, trigger } from './utils/events'
|
3
|
-
import {
|
3
|
+
import { publish, subscribe, unsubscribe } from './utils/pubsub'
|
4
|
+
import morphdom from 'morphdom'
|
4
5
|
|
5
|
-
|
6
|
+
type MainArgs = () => Array<Function>
|
6
7
|
|
7
|
-
|
8
|
-
element,
|
9
|
-
dependencies,
|
10
|
-
ElementInterface
|
8
|
+
export default function WebComponent(module, dependencies, templates, components) {
|
11
9
|
|
12
|
-
|
10
|
+
return class extends HTMLElement {
|
13
11
|
|
14
|
-
|
12
|
+
constructor() {
|
15
13
|
|
16
|
-
|
17
|
-
let resolver
|
18
|
-
let promise = new Promise(resolve => resolver = resolve)
|
14
|
+
super()
|
19
15
|
|
20
|
-
|
16
|
+
let batchUpdates = []
|
21
17
|
|
22
|
-
|
23
|
-
dependencies,
|
24
|
-
elm: element,
|
25
|
-
publish: Pubsub.publish,
|
26
|
-
unsubscribe: Pubsub.unsubscribe,
|
18
|
+
buildtemplates(this, components, templates)
|
27
19
|
|
28
|
-
|
29
|
-
|
30
|
-
},
|
20
|
+
const tplid = this.getAttribute('tplid')
|
21
|
+
const template = templates[tplid]
|
31
22
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
23
|
+
this.__internal__ = {
|
24
|
+
main: () => null,
|
25
|
+
unmount: () => null,
|
26
|
+
onupdate: _ => _,
|
27
|
+
view: module.view ? module.view : _ => _,
|
28
|
+
state: module.model ? dup(module.model) : {}
|
29
|
+
}
|
37
30
|
|
38
|
-
|
39
|
-
|
40
|
-
|
31
|
+
this.base = {
|
32
|
+
template,
|
33
|
+
dependencies,
|
34
|
+
publish,
|
35
|
+
subscribe,
|
36
|
+
unsubscribe,
|
37
|
+
elm: this,
|
41
38
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
subscribe(fn){
|
58
|
-
stateSubscriptions.push(fn)
|
59
|
-
},
|
60
|
-
unsubscribe(fn){
|
61
|
-
stateSubscriptions = stateSubscriptions.filter( item => item !== fn )
|
62
|
-
}
|
63
|
-
},
|
64
|
-
|
65
|
-
destroy(callback) {
|
66
|
-
ElementInterface.destroyers.push(callback)
|
67
|
-
},
|
68
|
-
|
69
|
-
on(name, selectorOrCallback, callback) {
|
70
|
-
on(element, name, selectorOrCallback, callback)
|
71
|
-
},
|
72
|
-
|
73
|
-
off(name, callback) {
|
74
|
-
off(element, name, callback)
|
75
|
-
},
|
76
|
-
|
77
|
-
trigger(ev, target, args) {
|
78
|
-
if (target.constructor === String)
|
79
|
-
trigger(element.querySelector(target), ev, { args: args })
|
80
|
-
else trigger(element, ev, { args: target })
|
81
|
-
},
|
82
|
-
|
83
|
-
emit(n, params) {
|
84
|
-
const args = Array.prototype.slice.call(arguments)
|
85
|
-
trigger(element, args.shift(), { args: args })
|
86
|
-
},
|
87
|
-
|
88
|
-
update(fn) {
|
89
|
-
ElementInterface.parentUpdate = fn
|
90
|
-
},
|
91
|
-
|
92
|
-
get(name, query) {
|
93
|
-
|
94
|
-
return function () {
|
95
|
-
const args = Array.prototype.slice.call(arguments),
|
96
|
-
method = args.shift(),
|
97
|
-
selector = `[data-component*=${name}]`
|
98
|
-
query = query ? selector + query : selector
|
99
|
-
|
100
|
-
Array.from(element.querySelectorAll(query))
|
101
|
-
.forEach(el => {
|
102
|
-
const instance = el.__instance__.instances[name]
|
103
|
-
if (instance && (method in instance.methods))
|
104
|
-
instance.methods[method].apply(null, args)
|
105
|
-
})
|
39
|
+
main: (fn: MainArgs) => { this.__internal__.main = fn },
|
40
|
+
unmount: (fn: () => null) => { this.__internal__.unmount = fn },
|
41
|
+
onupdate: (fn: () => null) => { this.__internal__.onupdate = fn },
|
42
|
+
|
43
|
+
render: (data: any = this.__internal__.state) => {
|
44
|
+
|
45
|
+
if (!document.body.contains(this))
|
46
|
+
return
|
47
|
+
|
48
|
+
|
49
|
+
batchUpdates.push(this.__internal__.view(data))
|
50
|
+
|
51
|
+
rAF(_ => {
|
52
|
+
|
53
|
+
if (batchUpdates.length) {
|
106
54
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
55
|
+
const batchData = {}
|
56
|
+
batchUpdates.forEach(d => Object.assign(batchData, d))
|
57
|
+
batchUpdates = []
|
58
|
+
|
59
|
+
this.__internal__.state = Object.assign(this.__internal__.state, batchData)
|
60
|
+
|
61
|
+
const newdata = dup(this.__internal__.state)
|
62
|
+
const newhtml = this.base.template(newdata)
|
63
|
+
|
64
|
+
morphdom(this, newhtml, morphdomOptions(this, data))
|
65
|
+
|
66
|
+
Array
|
67
|
+
.from(this.querySelectorAll('*'))
|
68
|
+
.filter(el => el.__internal__)
|
69
|
+
.map(el => {
|
70
|
+
rAF(_ => {
|
71
|
+
el.__internal__.onupdate(newdata)
|
72
|
+
el.base.render(newdata)
|
73
|
+
})
|
74
|
+
})
|
75
|
+
}
|
76
|
+
})
|
77
|
+
},
|
78
|
+
|
79
|
+
state: {
|
80
|
+
set: (data: any) => {
|
81
|
+
if (data.constructor === Function) {
|
82
|
+
const newstate = dup(this.__internal__.state)
|
83
|
+
data(newstate)
|
84
|
+
this.base.render(newstate)
|
85
|
+
} else {
|
86
|
+
this.base.render(data)
|
87
|
+
}
|
88
|
+
return new Promise((resolve) => rAF(_ => rAF(resolve)))
|
89
|
+
},
|
90
|
+
|
91
|
+
get: () => {
|
92
|
+
return dup(this.__internal__.state)
|
93
|
+
}
|
94
|
+
},
|
95
|
+
|
96
|
+
on: (eventName: string, selectorOrCallback: object | Function, callback: Function) => {
|
97
|
+
on(this, eventName, selectorOrCallback, callback)
|
98
|
+
},
|
99
|
+
|
100
|
+
off: (eventName: string, callback: Function) => {
|
101
|
+
off(this, eventName, callback)
|
102
|
+
},
|
103
|
+
|
104
|
+
trigger: (eventName: string, target: string, args: any) => {
|
105
|
+
if (target.constructor === String)
|
106
|
+
trigger(this.querySelector(target), eventName, { args: args })
|
107
|
+
else trigger(this, eventName, { args: target })
|
108
|
+
},
|
109
|
+
|
110
|
+
emit: (...args) => {
|
111
|
+
trigger(this, args.shift(), { args: args })
|
111
112
|
}
|
112
113
|
}
|
113
|
-
},
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
module.default(this.base)
|
116
|
+
}
|
117
|
+
|
118
|
+
connectedCallback() {
|
119
|
+
this.base.render()
|
120
|
+
this.__internal__.main().forEach(f => f(this.base))
|
121
|
+
}
|
122
|
+
|
123
|
+
disconnectedCallback() {
|
124
|
+
this.__internal__.unmount(this.base)
|
125
|
+
delete this.__internal__
|
126
|
+
delete this.base
|
127
|
+
delete this.__events
|
128
|
+
}
|
129
|
+
|
130
|
+
attributeChangedCallback() {
|
131
|
+
//TODO
|
132
|
+
}
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
const morphdomOptions = (_parent, data) => {
|
137
|
+
|
138
|
+
return {
|
139
|
+
|
140
|
+
onNodeAdded: onUpdates(_parent),
|
141
|
+
onElUpdated: onUpdates(_parent),
|
142
|
+
onBeforeElChildrenUpdated: checkStatic,
|
143
|
+
onBeforeElUpdated: checkStatic,
|
144
|
+
|
145
|
+
getNodeKey(node) {
|
146
|
+
if (node.nodeType === 1 && node.getAttribute('tplid'))
|
147
|
+
return node.dataset.key || node.getAttribute('tplid')
|
148
|
+
return false
|
118
149
|
}
|
119
150
|
}
|
151
|
+
}
|
120
152
|
|
121
|
-
|
153
|
+
const checkStatic = (node) => {
|
154
|
+
if ('static' in node.dataset) {
|
155
|
+
return false
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
const onUpdates = (_parent) => (node) => {
|
160
|
+
|
161
|
+
if (node.nodeType === 1) {
|
162
|
+
|
163
|
+
if (node.getAttribute && node.getAttribute('scope')) {
|
164
|
+
|
165
|
+
const scope = JSON.parse(node.getAttribute('scope').replace(/\'/g, '\"'))
|
166
|
+
|
167
|
+
Array.from(node.querySelectorAll('*'))
|
168
|
+
.filter(el => el.__internal__)
|
169
|
+
.map(el => {
|
170
|
+
const data = Object.assign(el.__internal__.state, dup(_parent.__internal__.state), scope)
|
171
|
+
el.__internal__.onupdate(data)
|
172
|
+
el.base.render(data)
|
173
|
+
})
|
174
|
+
|
175
|
+
node.removeAttribute('scope')
|
176
|
+
}
|
177
|
+
}
|
122
178
|
}
|
package/src/index.ts
CHANGED
@@ -1,60 +1,28 @@
|
|
1
|
-
import {
|
2
|
-
import
|
3
|
-
import { Component } from './Component'
|
4
|
-
import { stripTemplateTag, dup } from './utils'
|
1
|
+
import { buildtemplates, stripTemplateTag } from './utils'
|
2
|
+
import WebComponent from './component'
|
5
3
|
|
6
|
-
const
|
4
|
+
export const templates = {}
|
5
|
+
export const components = {}
|
7
6
|
|
8
7
|
export default {
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
const body: HTMLElement = document.body
|
13
|
-
|
14
|
-
stripTemplateTag( body )
|
15
|
-
|
16
|
-
Scanner.scan( body, createElement )
|
17
|
-
Scanner.observe( body, createElement, disposeElement )
|
9
|
+
register(name: string, module: any, dependencies: object = {}) {
|
10
|
+
components[name] = { name, module, dependencies }
|
18
11
|
},
|
19
12
|
|
20
|
-
|
21
|
-
|
13
|
+
start() {
|
14
|
+
const body = document.body
|
15
|
+
stripTemplateTag(body)
|
16
|
+
buildtemplates(body, components, templates)
|
17
|
+
registerComponents()
|
22
18
|
}
|
23
19
|
}
|
24
20
|
|
25
|
-
const
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
const C = components[name]
|
33
|
-
|
34
|
-
if( !C ) {
|
35
|
-
console.warn(`Jails - Module ${name} not registered`)
|
36
|
-
return
|
37
|
-
}
|
38
|
-
|
39
|
-
const { module, dependencies } = C
|
40
|
-
ElementInterface.model = Object.assign({}, module.model? dup(module.model) : null, ElementInterface.model )
|
41
|
-
|
42
|
-
const base = Component({ name, element, dependencies, ElementInterface })
|
43
|
-
const promise = module.default(base)
|
44
|
-
|
45
|
-
if( promise && promise.then ) {
|
46
|
-
ElementInterface.promises.push(promise)
|
47
|
-
}
|
48
|
-
|
49
|
-
base.__initialize()
|
50
|
-
ElementInterface.view = module.view || ElementInterface.view
|
51
|
-
ElementInterface.instances[name] = { methods: {} }
|
52
|
-
})
|
53
|
-
|
54
|
-
ElementInterface.update()
|
55
|
-
}
|
56
|
-
|
57
|
-
const disposeElement = ( node ) => {
|
58
|
-
if( node.__instance__)
|
59
|
-
node.__instance__.dispose()
|
21
|
+
const registerComponents = () => {
|
22
|
+
Object
|
23
|
+
.values(components)
|
24
|
+
.forEach(({ name, module, dependencies }) => {
|
25
|
+
const Element = WebComponent(module, dependencies, templates, components)
|
26
|
+
customElements.define(name, Element)
|
27
|
+
})
|
60
28
|
}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import template7 from 'template7'
|
2
|
+
import { stripTemplateTag, decodeHtmlEntities } from './utils'
|
3
|
+
|
4
|
+
export default function templateSystem(element) {
|
5
|
+
|
6
|
+
const directives = [htmlRepeatAndIf]
|
7
|
+
const vdom = element.cloneNode(true)
|
8
|
+
|
9
|
+
stripTemplateTag(vdom)
|
10
|
+
|
11
|
+
const newvdom = directives.reduce((_, directive) => directive(vdom), vdom)
|
12
|
+
const html = newvdom.outerHTML.replace(/html-/g, '')
|
13
|
+
const template = template7.compile(html)
|
14
|
+
|
15
|
+
return (data) => {
|
16
|
+
return template(data)
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
/**@Directives */
|
21
|
+
|
22
|
+
const htmlRepeatAndIf = (vdom) => {
|
23
|
+
|
24
|
+
const nodes = Array
|
25
|
+
.from(vdom.querySelectorAll('[html-repeat],[html-if]'))
|
26
|
+
.reverse()
|
27
|
+
|
28
|
+
if (nodes.length) {
|
29
|
+
|
30
|
+
nodes.forEach(node => {
|
31
|
+
if (node.getAttribute('html-repeat')) {
|
32
|
+
const instruction = node.getAttribute('html-repeat')
|
33
|
+
node.removeAttribute('html-repeat')
|
34
|
+
node.setAttribute('scope', '{{scope this}}')
|
35
|
+
const open = document.createTextNode(`{{#each ${instruction}}}`)
|
36
|
+
const close = document.createTextNode('{{/each}}')
|
37
|
+
node.parentNode.insertBefore(open, node)
|
38
|
+
node.parentNode.insertBefore(close, node.nextSibling)
|
39
|
+
} else if (node.getAttribute('html-if')) {
|
40
|
+
const instruction = node.getAttribute('html-if')
|
41
|
+
node.removeAttribute('html-if')
|
42
|
+
node.setAttribute('scope', '{{scope this}}')
|
43
|
+
const open = document.createTextNode(`{{#ifexp "${instruction}"}}`)
|
44
|
+
const close = document.createTextNode('{{/ifexp}}')
|
45
|
+
node.parentNode.insertBefore(open, node)
|
46
|
+
node.parentNode.insertBefore(close, node.nextSibling)
|
47
|
+
}
|
48
|
+
})
|
49
|
+
}
|
50
|
+
|
51
|
+
return vdom
|
52
|
+
}
|
53
|
+
|
54
|
+
/**@Global Settings */
|
55
|
+
template7.global = {}
|
56
|
+
|
57
|
+
/**@Helpers */
|
58
|
+
template7.registerHelper('scope', (context, options) => {
|
59
|
+
|
60
|
+
const { data } = options
|
61
|
+
const { first, index, last, root } = data
|
62
|
+
|
63
|
+
const scope = {
|
64
|
+
...context,
|
65
|
+
['$first']: first,
|
66
|
+
['$index']: index,
|
67
|
+
['$last']: last,
|
68
|
+
['$parent']: root
|
69
|
+
}
|
70
|
+
|
71
|
+
return JSON.stringify(scope).replace(/\"/g, '\'')
|
72
|
+
})
|
73
|
+
|
74
|
+
template7.registerHelper('ifexp', function (value, options) {
|
75
|
+
|
76
|
+
const condition = decodeHtmlEntities(value)
|
77
|
+
const { root, data } = options
|
78
|
+
|
79
|
+
try {
|
80
|
+
const expression = (new Function('root', `with(root){ return ${condition} }`)).call(root, root)
|
81
|
+
return expression ? options.fn(this, data) : options.inverse(this, data)
|
82
|
+
|
83
|
+
} catch (err) {
|
84
|
+
return options.inverse(this, data)
|
85
|
+
}
|
86
|
+
|
87
|
+
})
|
package/src/utils/index.js
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import templateSystem from '../template-system'
|
1
2
|
|
2
3
|
export const rAF = (fn) => {
|
3
4
|
(requestAnimationFrame || setTimeout)(fn, 1000 / 60)
|
@@ -10,32 +11,65 @@ export const uuid = () => {
|
|
10
11
|
})
|
11
12
|
}
|
12
13
|
|
13
|
-
export const stripTemplateTag = (
|
14
|
+
export const stripTemplateTag = (element) => {
|
14
15
|
const templates = Array.from(element.querySelectorAll('template'))
|
15
16
|
// https://gist.github.com/harmenjanssen/07e425248779c65bc5d11b02fb913274
|
16
|
-
templates.forEach(
|
17
|
-
template.parentNode.replaceChild(template.content, template
|
17
|
+
templates.forEach(template => {
|
18
|
+
template.parentNode.replaceChild(template.content, template)
|
19
|
+
stripTemplateTag(template.content)
|
18
20
|
})
|
19
21
|
}
|
20
22
|
|
21
23
|
export const dup = (o) => {
|
22
|
-
return JSON.parse(
|
24
|
+
return JSON.parse(JSON.stringify(o))
|
23
25
|
}
|
24
26
|
|
25
|
-
export const createTemplate = (
|
27
|
+
export const createTemplate = (html, templates) => {
|
26
28
|
|
27
29
|
const vroot = document.createElement('div')
|
28
30
|
vroot.innerHTML = html
|
29
|
-
stripTemplateTag(
|
31
|
+
stripTemplateTag(vroot)
|
30
32
|
|
31
33
|
Array
|
32
34
|
.from(vroot.querySelectorAll('[data-component]'))
|
33
|
-
.forEach(
|
35
|
+
.forEach(c => {
|
34
36
|
const tplid = c.getAttribute('tplid')
|
35
37
|
const cache = templates[tplid]
|
36
|
-
if(
|
38
|
+
if (cache)
|
37
39
|
c.outerHTML = cache
|
38
40
|
})
|
39
41
|
|
40
42
|
return vroot.innerHTML
|
41
43
|
}
|
44
|
+
|
45
|
+
export const createTemplateId = (element, templates) => {
|
46
|
+
|
47
|
+
const tplid = element.getAttribute('tplid')
|
48
|
+
|
49
|
+
if (!tplid) {
|
50
|
+
const id = uuid()
|
51
|
+
element.setAttribute('tplid', id)
|
52
|
+
templates[id] = templateSystem(element)
|
53
|
+
return templates[id]
|
54
|
+
}
|
55
|
+
|
56
|
+
return templates[tplid]
|
57
|
+
}
|
58
|
+
|
59
|
+
export const buildtemplates = (target, components, templates) => {
|
60
|
+
|
61
|
+
return Array
|
62
|
+
.from(target.querySelectorAll('*'))
|
63
|
+
.filter(node => node.tagName.toLocaleLowerCase() in components)
|
64
|
+
.reverse()
|
65
|
+
.map(node => {
|
66
|
+
createTemplateId(node, templates)
|
67
|
+
return node
|
68
|
+
})
|
69
|
+
}
|
70
|
+
|
71
|
+
export const decodeHtmlEntities = (str) => {
|
72
|
+
const textarea = document.createElement('textarea')
|
73
|
+
textarea.innerHTML = str
|
74
|
+
return textarea.value
|
75
|
+
}
|
package/tsconfig.json
CHANGED