jails-js 5.0.0-beta.7 → 5.0.0

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/package.json CHANGED
@@ -1,41 +1,44 @@
1
1
  {
2
- "name": "jails-js",
3
- "version": "5.0.0-beta.7",
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-preset-env": "^1.7.0",
32
- "ts-loader": "^9.2.6",
33
- "typescript": "^4.5.4",
34
- "webpack": "^5.59.1",
35
- "webpack-cli": "^3.2.1"
36
- },
37
- "dependencies": {
38
- "morphdom": "^2.6.1",
39
- "sodajs": "^0.4.10"
40
- }
2
+ "name": "jails-js",
3
+ "version": "5.0.0",
4
+ "description": "Jails | A Functional Component Library",
5
+ "main": "dist/index.js",
6
+ "types": "types/index.d.ts",
7
+ "scripts": {
8
+ "start": "webpack --watch --mode=development",
9
+ "build": "webpack --mode=production",
10
+ "publish-beta": "yarn build && npm version prerelease --preid=beta && npm publish --tag beta",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/jails-org/Jails.git"
16
+ },
17
+ "keywords": [
18
+ "Jails",
19
+ "Javascript",
20
+ "Component",
21
+ "Micro-Library"
22
+ ],
23
+ "author": "javiani",
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://github.com/jails-org/Jails/issues"
27
+ },
28
+ "homepage": "https://github.com/jails-org/Jails",
29
+ "devDependencies": {
30
+ "@babel/core": "^7.2.2",
31
+ "@babel/preset-env": "^7.2.3",
32
+ "babel-loader": "^8.0.5",
33
+ "babel-plugin-transform-custom-element-classes": "^0.1.0",
34
+ "babel-preset-env": "^1.7.0",
35
+ "ts-loader": "^9.2.6",
36
+ "typescript": "^4.5.4",
37
+ "webpack": "^5.59.1",
38
+ "webpack-cli": "^3.2.1"
39
+ },
40
+ "dependencies": {
41
+ "morphdom": "^2.6.1",
42
+ "squirrelly": "^8.0.8"
43
+ }
41
44
  }
@@ -0,0 +1,146 @@
1
+ import morphdom from 'morphdom'
2
+
3
+ import { rAF, dup, buildtemplates } from './utils'
4
+ import { on, off, trigger } from './utils/events'
5
+ import { publish, subscribe } from './utils/pubsub'
6
+
7
+ export default function Component( elm, { module, dependencies, templates, components }) {
8
+
9
+ const options = getOptions( module )
10
+ buildtemplates( elm, components, templates )
11
+
12
+ const tplid = elm.getAttribute('tplid')
13
+ const template = tplid ? templates[tplid] : null
14
+ const state = { data: module.model ? dup(module.model) : {} }
15
+
16
+ const base = {
17
+ template,
18
+ elm,
19
+ dependencies,
20
+ publish,
21
+ subscribe,
22
+
23
+ main(fn) {
24
+ options.main = fn
25
+ },
26
+
27
+ unmount(fn) {
28
+ options.unmount = fn
29
+ },
30
+
31
+ onupdate(fn) {
32
+ options.onupdate = fn
33
+ },
34
+
35
+ on(eventName, selectorOrCallback, callback) {
36
+ on(elm, eventName, selectorOrCallback, callback)
37
+ },
38
+
39
+ off(eventName, callback) {
40
+ off(elm, eventName, callback)
41
+ },
42
+
43
+ trigger(eventName, target, args) {
44
+ if (target.constructor === String) {
45
+ Array
46
+ .from(elm.querySelectorAll(target))
47
+ .forEach( children => trigger(children, eventName, { args: args }) )
48
+ }
49
+ else trigger(elm, eventName, { args: target })
50
+ },
51
+
52
+ emit: ( ...args ) => {
53
+ trigger(elm, args.shift(), { args: args })
54
+ },
55
+
56
+ state: {
57
+ set( data ) {
58
+ if (data.constructor === Function) {
59
+ const newstate = dup(state.data)
60
+ data(newstate)
61
+ base.render(newstate)
62
+ } else {
63
+ base.render(data)
64
+ }
65
+ return new Promise((resolve) => rAF(_ => rAF(resolve)))
66
+ },
67
+ get() {
68
+ return dup(state.data)
69
+ }
70
+ },
71
+
72
+ render(data = state.data) {
73
+
74
+ if (!document.body.contains(elm)) {
75
+ return
76
+ }
77
+
78
+ state.data = Object.assign(state.data, data)
79
+
80
+ const newdata = dup(state.data)
81
+ const newhtml = base.template(options.view(newdata))
82
+
83
+ morphdom(elm, newhtml, morphdomOptions(elm, options))
84
+
85
+ rAF(_ => {
86
+ Array
87
+ .from(elm.querySelectorAll('[tplid]'))
88
+ .forEach((child: any) => {
89
+ child.options.onupdate(newdata)
90
+ child.base.render(newdata)
91
+ })
92
+ })
93
+ }
94
+ }
95
+
96
+ return { base, options }
97
+ }
98
+
99
+ const getOptions = (module) => ({
100
+ main: (a) => a,
101
+ unmount: (a) => a,
102
+ onupdate: (a) => a,
103
+ view: module.view ? module.view : (a) => a
104
+ })
105
+
106
+ const morphdomOptions = (_parent, options ) => ({
107
+
108
+ onNodeAdded: onUpdates(_parent, options),
109
+ onElUpdated: onUpdates(_parent, options),
110
+ onBeforeElChildrenUpdated: checkStatic,
111
+ onBeforeElUpdated: checkStatic,
112
+
113
+ getNodeKey(node) {
114
+ if (node.nodeType === 1 && node.getAttribute('tplid')){
115
+ return 'key' in node.attributes? node.attributes.key.value : node.getAttribute('tplid')
116
+ }
117
+ return false
118
+ }
119
+ })
120
+
121
+ const checkStatic = (node) => {
122
+ if ('html-static' in node.attributes) {
123
+ return false
124
+ }
125
+ }
126
+
127
+ const onUpdates = (_parent, options) => (node) => {
128
+
129
+ if (node.nodeType === 1) {
130
+
131
+ if (node.getAttribute && node.getAttribute('scope')) {
132
+ const json = node.getAttribute('scope')
133
+ const scope = (new Function(`return ${json}`))()
134
+ Array.from(node.querySelectorAll('[tplid]'))
135
+ .map((el) => {
136
+ const data = Object.assign({}, _parent.base.state.get(), scope)
137
+ options.onupdate(data)
138
+ el.base.render(data)
139
+ })
140
+
141
+ node.removeAttribute('scope')
142
+ }
143
+ }
144
+
145
+ return node
146
+ }
package/src/element.ts ADDED
@@ -0,0 +1,52 @@
1
+ import Component from './component'
2
+ import { purge } from './utils'
3
+
4
+ export default function Element(module, dependencies, templates, components) {
5
+
6
+ return class extends HTMLElement {
7
+
8
+ base: any
9
+ options: any
10
+ returns : any
11
+ __events: any
12
+
13
+ constructor() {
14
+
15
+ super()
16
+
17
+ const { base, options } = Component(this, { module, dependencies, templates, components })
18
+
19
+ this.base = base
20
+ this.options = options
21
+ this.returns = module.default(base)
22
+ }
23
+
24
+ connectedCallback() {
25
+ this.base.render()
26
+
27
+ if( this.returns && this.returns.constructor === Promise ) {
28
+ this.returns.then( _ => {
29
+ if( this.base ) {
30
+ this.options.main().forEach(f => f(this.base))
31
+ }
32
+ })
33
+ }else {
34
+ this.options.main().forEach(f => f(this.base))
35
+ }
36
+ }
37
+
38
+ disconnectedCallback() {
39
+ this.options.unmount(this.base)
40
+ if(!document.body.contains(this) ) {
41
+ this.__events = null
42
+ this.base.elm = null
43
+ this.base = null
44
+ purge(this)
45
+ }
46
+ }
47
+
48
+ attributeChangedCallback() {
49
+ //TODO
50
+ }
51
+ }
52
+ }
package/src/index.ts CHANGED
@@ -1,62 +1,29 @@
1
- import { Element } from './Element'
2
- import { Scanner } from './Scanner'
3
- import { Component } from './Component'
4
- import { Instances } from './Instances'
5
- import { stripTemplateTag, dup } from './utils'
6
1
 
2
+ import { buildtemplates } from './utils'
3
+ import Element from './element'
4
+
5
+ const templates = {}
7
6
  const components = {}
8
7
 
9
8
  export default {
10
9
 
11
- start() {
12
-
13
- const body: HTMLElement = document.body
14
-
15
- stripTemplateTag( body )
16
-
17
- Scanner.observe( body, createElement, disposeElement )
18
- Scanner.scan( body, createElement )
10
+ register( name:string, module:any, dependencies: object ) {
11
+ components[name] = { name, module, dependencies }
19
12
  },
20
13
 
21
- register( name, module, dependencies = {} ) {
22
- components[name] = { name, module, dependencies }
14
+ start() {
15
+ const body = document.body
16
+ buildtemplates( body, components, templates )
17
+ registerComponents()
23
18
  }
24
19
  }
25
20
 
26
- const createElement = ( element: HTMLElement ) => {
27
-
28
- const ElementInterface = Element( element )
29
- const names = element.dataset.component.split(/\s/)
30
-
31
- names.forEach( name => {
32
-
33
- const C = components[name]
34
-
35
- if( !C ) {
36
- console.warn(`Jails - Module ${name} not registered`)
37
- return
38
- }
39
-
40
- const { module, dependencies } = C
41
- ElementInterface.model = Object.assign({}, module.model? dup(module.model) : null, ElementInterface.model )
42
-
43
- const base = Component({ name, element, dependencies, ElementInterface })
44
- const promise = module.default(base)
45
-
46
- if( promise && promise.then ) {
47
- ElementInterface.promises.push(promise)
48
- }
49
-
50
- base.__initialize()
51
- ElementInterface.view = module.view || ElementInterface.view
52
- ElementInterface.instances[name] = { methods: {} }
53
- })
54
-
55
- ElementInterface.update()
56
- }
57
-
58
- const disposeElement = ( node ) => {
59
- const instance = Instances.get(node)
60
- if( Instances.get(node) )
61
- instance.dispose()
21
+ const registerComponents = () => {
22
+ Object
23
+ .values( components )
24
+ .forEach( (component) => {
25
+ const { name, module, dependencies } = component
26
+ const Base = Element(module, dependencies, templates, components)
27
+ customElements.define(name, Base)
28
+ })
62
29
  }
@@ -0,0 +1,86 @@
1
+ import { compile, defaultConfig, filters } from 'squirrelly'
2
+ import { decodeHtmlEntities } from './utils'
3
+
4
+ defaultConfig.tags = ['{', '}']
5
+ defaultConfig.useWith = true
6
+
7
+ export default function templateSystem( element ) {
8
+
9
+ const tree = document.createElement('template')
10
+
11
+ tree.innerHTML = element.outerHTML.replace(/<\/?template[^>]*>/g, '')
12
+
13
+ directives(tree.content)
14
+
15
+ const html = decodeHtmlEntities(
16
+ tree.innerHTML
17
+ .replace(/html-(selected|checked|readonly|disabled|autoplay)=\"(.*)\"/g, `{@if ($2) }$1{/if}`)
18
+ .replace(/html-/g, '')
19
+ )
20
+
21
+ const template = compile(html, defaultConfig)
22
+
23
+ return ( data ) => {
24
+ return template(data, defaultConfig)
25
+ }
26
+ }
27
+
28
+ /**@Directives */
29
+
30
+ const directives = (vdom) => {
31
+
32
+ const nodes = Array
33
+ .from(vdom.querySelectorAll('[html-for],[html-if],[html-foreach]'))
34
+ .reverse()
35
+
36
+ if (nodes.length) {
37
+
38
+ nodes.forEach(( node ) => {
39
+ if (node.getAttribute('html-foreach')) {
40
+ const instruction = node.getAttribute('html-foreach') || ''
41
+ const split = instruction.match(/(.*)\sin\s(.*)/) || ''
42
+ const varname = split[1]
43
+ const object = split[2]
44
+ node.removeAttribute('html-foreach')
45
+ node.setAttribute('scope', `{${varname} | JSON($key, '${varname}')}`)
46
+ const open = document.createTextNode(`{@foreach(${object}) => $key, ${varname}}`)
47
+ const close = document.createTextNode('{/foreach}')
48
+ wrap(open, node, close)
49
+ } else if (node.getAttribute('html-for')) {
50
+ const instruction = node.getAttribute('html-for') || ''
51
+ const split = instruction.match(/(.*)\sin\s(.*)/) || ''
52
+ const varname = split[1]
53
+ const object = split[2]
54
+ node.removeAttribute('html-for')
55
+ node.setAttribute('scope', `{${varname} | JSON($index, '${varname}')}`)
56
+ const open = document.createTextNode(`{@each(${object}) => ${varname}, $index}`)
57
+ const close = document.createTextNode('{/each}')
58
+ wrap(open, node, close)
59
+ } else if (node.getAttribute('html-if')) {
60
+ const instruction = node.getAttribute('html-if')
61
+ node.removeAttribute('html-if')
62
+ const open = document.createTextNode(`{@if (${instruction}) }`)
63
+ const close = document.createTextNode('{/if}')
64
+ wrap(open, node, close)
65
+ }
66
+ })
67
+ }
68
+
69
+ return vdom
70
+ }
71
+
72
+ filters.define('JSON', (scope, index, varname) => {
73
+
74
+ const key = index.constructor == String ? '$key' : '$index'
75
+ const newobject = { $index: index } as any
76
+
77
+ newobject[varname] = scope
78
+ newobject[key] = index
79
+
80
+ return JSON.stringify(newobject)
81
+ })
82
+
83
+ const wrap = (open, node, close) => {
84
+ node.parentNode?.insertBefore(open, node)
85
+ node.parentNode?.insertBefore(close, node.nextSibling)
86
+ }
@@ -41,7 +41,7 @@ const delegate = (node, selector, callback) => {
41
41
  e.delegateTarget = parent
42
42
  callback.apply(element, [e].concat(detail.args))
43
43
  }
44
- if( parent === node ) break
44
+ if (parent === node) break
45
45
  parent = parent.parentNode
46
46
  }
47
47
  }
@@ -0,0 +1,80 @@
1
+ import templateSystem from '../template-system'
2
+
3
+ const textarea = document.createElement('textarea')
4
+
5
+ export const rAF = (fn) => {
6
+ if (requestAnimationFrame)
7
+ return requestAnimationFrame(fn)
8
+ else
9
+ return setTimeout(fn, 1000 / 60)
10
+ }
11
+
12
+ export const uuid = () => {
13
+ return 'xxxxxxxx'.replace(/[xy]/g, (c) => {
14
+ const r = Math.random() * 8 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
15
+ return v.toString(8)
16
+ })
17
+ }
18
+
19
+ export const stripTemplateTag = ( element ) => {
20
+ const templates = Array.from(element.querySelectorAll('template'))
21
+ // https://gist.github.com/harmenjanssen/07e425248779c65bc5d11b02fb913274
22
+ templates.forEach((template) => {
23
+ template.parentNode?.replaceChild(template.content, template)
24
+ stripTemplateTag(template.content)
25
+ })
26
+ }
27
+
28
+ export const dup = (o) => {
29
+ return JSON.parse(JSON.stringify(o))
30
+ }
31
+
32
+ export const createTemplateId = (element, templates ) => {
33
+
34
+ const tplid = element.getAttribute('tplid')
35
+
36
+ if (!tplid) {
37
+ const id = uuid()
38
+ element.setAttribute('tplid', id)
39
+ templates[id] = templateSystem(element)
40
+ }
41
+ }
42
+
43
+ export const buildtemplates = ( target, components, templates ) => {
44
+
45
+ return Array
46
+ .from(target.querySelectorAll('*'))
47
+ .filter((node) => node.tagName.toLowerCase() in components)
48
+ .reverse()
49
+ .map((node) => {
50
+ Array.from(node.querySelectorAll('template'))
51
+ .map((template) => buildtemplates(template.content, components, templates))
52
+ createTemplateId(node, templates)
53
+ return node
54
+ })
55
+ }
56
+
57
+ export const decodeHtmlEntities = ( str ) => {
58
+ textarea.innerHTML = str
59
+ return textarea.value
60
+ }
61
+
62
+ // http://crockford.com/javascript/memory/leak.html
63
+ export const purge = (d) => {
64
+ var a = d.attributes, i, l, n
65
+ if (a) {
66
+ for (i = a.length - 1; i >= 0; i -= 1) {
67
+ n = a[i].name
68
+ if (typeof d[n] === 'function') {
69
+ d[n] = null
70
+ }
71
+ }
72
+ }
73
+ a = d.childNodes
74
+ if (a) {
75
+ l = a.length
76
+ for (i = 0; i < l; i += 1) {
77
+ purge(d.childNodes[i])
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,20 @@
1
+ const topics: any = {}
2
+ const _async: any = {}
3
+
4
+ export const publish = (name, params) => {
5
+ _async[name] = Object.assign({}, _async[name], params)
6
+ if (topics[name])
7
+ topics[name].forEach(topic => topic(params))
8
+ }
9
+
10
+ export const subscribe = (name, method) => {
11
+ topics[name] = topics[name] || []
12
+ topics[name].push(method)
13
+ if (name in _async) {
14
+ method(_async[name])
15
+ }
16
+ return () => {
17
+ topics[name] = topics[name].filter( fn => fn != method )
18
+ }
19
+ }
20
+