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/package.json CHANGED
@@ -1,41 +1,42 @@
1
1
  {
2
- "name": "jails-js",
3
- "version": "5.0.0-beta.5",
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-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 * as Pubsub from './utils/pubsub'
1
+ import { rAF, dup, buildtemplates } from './utils'
2
2
  import { on, off, trigger } from './utils/events'
3
- import { rAF } from './utils'
3
+ import { publish, subscribe, unsubscribe } from './utils/pubsub'
4
+ import morphdom from 'morphdom'
4
5
 
5
- export const Component = ({
6
+ type MainArgs = () => Array<Function>
6
7
 
7
- name,
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
- const subscriptions = []
12
+ constructor() {
15
13
 
16
- let stateSubscriptions = []
17
- let resolver
18
- let promise = new Promise(resolve => resolver = resolve)
14
+ super()
19
15
 
20
- const base = {
16
+ let batchUpdates = []
21
17
 
22
- name,
23
- dependencies,
24
- elm: element,
25
- publish: Pubsub.publish,
26
- unsubscribe: Pubsub.unsubscribe,
18
+ buildtemplates(this, components, templates)
27
19
 
28
- __initialize() {
29
- resolver(base)
30
- },
20
+ const tplid = this.getAttribute('tplid')
21
+ const template = templates[tplid]
31
22
 
32
- main(fn) {
33
- promise
34
- .then( _ => fn().forEach(lambda => lambda(base)))
35
- .catch( err => console.error( err) )
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
- expose(methods) {
39
- ElementInterface.instances[name].methods = methods
40
- },
31
+ this.base = {
32
+ template,
33
+ dependencies,
34
+ publish,
35
+ subscribe,
36
+ unsubscribe,
37
+ elm: this,
41
38
 
42
- state: {
43
- set( state ) {
44
- if( state.constructor === Function ){
45
- const model = ElementInterface.model
46
- state(model)
47
- ElementInterface.update(model)
48
- } else {
49
- ElementInterface.update(state)
50
- }
51
- stateSubscriptions.forEach( fn => fn(ElementInterface.model) )
52
- return new Promise((resolve) => rAF(_ => rAF(resolve) ))
53
- },
54
- get() {
55
- return ElementInterface.model
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
- if (element.matches(query)) {
108
- const instance = element.__instance__.instances[name]
109
- if (instance && method in instance.methods)
110
- instance.methods[method].apply(null, args)
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
- subscribe(name, method) {
116
- subscriptions.push({ name, method })
117
- Pubsub.subscribe(name, method)
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
- return base
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 { Element } from './Element'
2
- import { Scanner } from './Scanner'
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 components = {}
4
+ export const templates = {}
5
+ export const components = {}
7
6
 
8
7
  export default {
9
8
 
10
- start() {
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
- register( name, module, dependencies = {} ) {
21
- components[name] = { name, module, dependencies }
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 createElement = ( element: HTMLElement ) => {
26
-
27
- const ElementInterface = Element( element )
28
- const names = element.dataset.component.split(/\s/)
29
-
30
- names.forEach( name => {
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
+ })
@@ -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 = ( element ) => {
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( template => {
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( JSON.stringify(o) )
24
+ return JSON.parse(JSON.stringify(o))
23
25
  }
24
26
 
25
- export const createTemplate = ( html, templates ) => {
27
+ export const createTemplate = (html, templates) => {
26
28
 
27
29
  const vroot = document.createElement('div')
28
30
  vroot.innerHTML = html
29
- stripTemplateTag( vroot )
31
+ stripTemplateTag(vroot)
30
32
 
31
33
  Array
32
34
  .from(vroot.querySelectorAll('[data-component]'))
33
- .forEach( c => {
35
+ .forEach(c => {
34
36
  const tplid = c.getAttribute('tplid')
35
37
  const cache = templates[tplid]
36
- if( cache )
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
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "lib": [ "dom" ],
4
- "sourceMap": true
5
- }
3
+ "baseUrl": "./src",
4
+ "outDir": "./dist/",
5
+ "target": "es6",
6
+ "allowJs": true,
7
+ "lib": [
8
+ "dom"
9
+ ],
10
+ },
11
+ "exclude": [
12
+ "./node_modules"
13
+ ]
6
14
  }