jails-js 5.0.0-beta.7 → 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.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-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,123 +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'
4
- import { Instances } from './Instances'
5
-
6
- export const Component = ({
7
-
8
- name,
9
- element,
10
- dependencies,
11
- ElementInterface
12
-
13
- }) => {
14
-
15
- const subscriptions = []
16
-
17
- let stateSubscriptions = []
18
- let resolver
19
- let promise = new Promise(resolve => resolver = resolve)
20
-
21
- const base = {
22
-
23
- name,
24
- dependencies,
25
- elm: element,
26
- publish: Pubsub.publish,
27
- unsubscribe: Pubsub.unsubscribe,
28
-
29
- __initialize() {
30
- resolver(base)
31
- },
32
-
33
- main(fn) {
34
- promise
35
- .then( _ => fn().forEach(lambda => lambda(base)))
36
- .catch( err => console.error( err) )
37
- },
38
-
39
- expose(methods) {
40
- ElementInterface.instances[name].methods = methods
41
- },
42
-
43
- state: {
44
- set( state ) {
45
- if( state.constructor === Function ){
46
- const model = ElementInterface.model
47
- state(model)
48
- ElementInterface.update(model)
49
- } else {
50
- ElementInterface.update(state)
51
- }
52
- stateSubscriptions.forEach( fn => fn(ElementInterface.model) )
53
- return new Promise((resolve) => rAF(_ => rAF(resolve) ))
54
- },
55
- get() {
56
- return ElementInterface.model
57
- },
58
- subscribe(fn){
59
- stateSubscriptions.push(fn)
60
- },
61
- unsubscribe(fn){
62
- stateSubscriptions = stateSubscriptions.filter( item => item !== fn )
3
+ import { publish, subscribe, unsubscribe } from './utils/pubsub'
4
+ import morphdom from 'morphdom'
5
+
6
+ type MainArgs = () => Array<Function>
7
+
8
+ export default function WebComponent(module, dependencies, templates, components) {
9
+
10
+ return class extends HTMLElement {
11
+
12
+ constructor() {
13
+
14
+ super()
15
+
16
+ let batchUpdates = []
17
+
18
+ buildtemplates(this, components, templates)
19
+
20
+ const tplid = this.getAttribute('tplid')
21
+ const template = templates[tplid]
22
+
23
+ this.__internal__ = {
24
+ main: () => null,
25
+ unmount: () => null,
26
+ onupdate: _ => _,
27
+ view: module.view ? module.view : _ => _,
28
+ state: module.model ? dup(module.model) : {}
63
29
  }
64
- },
65
-
66
- destroy(callback) {
67
- ElementInterface.destroyers.push(callback)
68
- },
69
-
70
- on(name, selectorOrCallback, callback) {
71
- on(element, name, selectorOrCallback, callback)
72
- },
73
-
74
- off(name, callback) {
75
- off(element, name, callback)
76
- },
77
-
78
- trigger(ev, target, args) {
79
- if (target.constructor === String)
80
- trigger(element.querySelector(target), ev, { args: args })
81
- else trigger(element, ev, { args: target })
82
- },
83
-
84
- emit(n, params) {
85
- const args = Array.prototype.slice.call(arguments)
86
- trigger(element, args.shift(), { args: args })
87
- },
88
-
89
- update(fn) {
90
- ElementInterface.parentUpdate = fn
91
- },
92
-
93
- get(name, query) {
94
-
95
- return function () {
96
- const args = Array.prototype.slice.call(arguments),
97
- method = args.shift(),
98
- selector = `[data-component*=${name}]`
99
- query = query ? selector + query : selector
100
-
101
- Array.from(element.querySelectorAll(query))
102
- .forEach(el => {
103
- const instance = Instances.get(el).instances[name]
104
- if (instance && (method in instance.methods))
105
- instance.methods[method].apply(null, args)
106
- })
107
30
 
108
- if (element.matches(query)) {
109
- const instance = Instances.get(element).instances[name]
110
- if (instance && method in instance.methods)
111
- instance.methods[method].apply(null, args)
31
+ this.base = {
32
+ template,
33
+ dependencies,
34
+ publish,
35
+ subscribe,
36
+ unsubscribe,
37
+ elm: this,
38
+
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) {
54
+
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 })
112
112
  }
113
113
  }
114
- },
115
114
 
116
- subscribe(name, method) {
117
- subscriptions.push({ name, method })
118
- Pubsub.subscribe(name, method)
115
+ module.default(this.base)
119
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
149
+ }
150
+ }
151
+ }
152
+
153
+ const checkStatic = (node) => {
154
+ if ('static' in node.dataset) {
155
+ return false
120
156
  }
157
+ }
158
+
159
+ const onUpdates = (_parent) => (node) => {
121
160
 
122
- return base
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
+ }
123
178
  }
package/src/index.ts CHANGED
@@ -1,62 +1,28 @@
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'
1
+ import { buildtemplates, stripTemplateTag } from './utils'
2
+ import WebComponent from './component'
6
3
 
7
- const components = {}
4
+ export const templates = {}
5
+ export const components = {}
8
6
 
9
7
  export default {
10
8
 
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 )
9
+ register(name: string, module: any, dependencies: object = {}) {
10
+ components[name] = { name, module, dependencies }
19
11
  },
20
12
 
21
- register( name, module, dependencies = {} ) {
22
- components[name] = { name, module, dependencies }
13
+ start() {
14
+ const body = document.body
15
+ stripTemplateTag(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(({ name, module, dependencies }) => {
25
+ const Element = WebComponent(module, dependencies, templates, components)
26
+ customElements.define(name, Element)
27
+ })
62
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
  }