aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2018-11-01 09:01:49 +0000
committers-ol <s-ol@users.noreply.github.com>2018-11-01 09:01:49 +0000
commitfc181b154fb3f5cdefcf72c2a496b5b14f274e79 (patch)
tree5b4cfbebb4760fba68161265f07826b55206c311 /lib
parentREHYDRATION (diff)
parentfaster time to first render (diff)
downloadmmm-fc181b154fb3f5cdefcf72c2a496b5b14f274e79.tar.gz
mmm-fc181b154fb3f5cdefcf72c2a496b5b14f274e79.zip
Merge branch 'root-mmmfs'
Diffstat (limited to 'lib')
-rw-r--r--lib/component.client.moon7
-rw-r--r--lib/component.server.moon20
-rw-r--r--lib/dom.client.moon6
-rw-r--r--lib/dom.server.moon13
-rw-r--r--lib/init.client.moon6
-rw-r--r--lib/init.server.moon9
-rw-r--r--lib/mmmfs/browser.moon153
-rw-r--r--lib/mmmfs/fileder.moon75
-rw-r--r--lib/mmmfs/init.moon27
9 files changed, 208 insertions, 108 deletions
diff --git a/lib/component.client.moon b/lib/component.client.moon
index 6e0e8d8..ffeb084 100644
--- a/lib/component.client.moon
+++ b/lib/component.client.moon
@@ -136,6 +136,12 @@ class ReactiveElement
if 'table' == (type child) and child.destroy
child\destroy!
+get_or_create = (elem, id, ...) ->
+ elem = (document\getElementById id) or elem
+
+ with ReactiveElement elem, ...
+ \set 'id', id
+
elements = setmetatable {}, __index: (name) =>
with val = (...) -> ReactiveElement name, ...
@[name] = val
@@ -143,6 +149,7 @@ elements = setmetatable {}, __index: (name) =>
{
:ReactiveVar,
:ReactiveElement,
+ :get_or_create,
-- :join,
:tohtml,
:append,
diff --git a/lib/component.server.moon b/lib/component.server.moon
index 85d97eb..0026d32 100644
--- a/lib/component.server.moon
+++ b/lib/component.server.moon
@@ -1,5 +1,8 @@
import opairs from require 'lib.ordered'
+void_tags = { 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr' }
+void_tags = { t,t for t in *void_tags }
+
-- convert anything to HTML string
-- val must be one of:
-- * MMMElement (have a .render method that returns a string)
@@ -97,17 +100,28 @@ class ReactiveElement
tmp ..= "#{kk}: #{vv}; "
v = tmp
b ..= " #{k}=\"#{v}\""
- b ..= ">" .. table.concat @children, ''
- b ..= "</#{@element}>"
- b
+
+ if void_tags[@element]
+ assert #@children == 0, "void tag #{element} cannot have children!"
+ b .. ">"
+ else
+ b .. ">"
+ b ..= ">" .. table.concat @children, ''
+ b ..= "</#{@element}>"
+ b
elements = setmetatable {}, __index: (name) =>
with val = (...) -> ReactiveElement name, ...
@[name] = val
+get_or_create = (elem, id, ...) ->
+ with ReactiveElement elem, ...
+ \set 'id', id
+
{
:ReactiveVar,
:ReactiveElement,
+ :get_or_create,
:tohtml,
:flush,
:append,
diff --git a/lib/dom.client.moon b/lib/dom.client.moon
index a5a319b..acdbfa4 100644
--- a/lib/dom.client.moon
+++ b/lib/dom.client.moon
@@ -24,9 +24,9 @@ element = (element) -> (...) ->
for child in *children
if 'string' == type child
- e.innerHTML ..= child
- else
- e\appendChild child
+ child = document\createTextNode child
+
+ e\appendChild child
setmetatable {}, __index: (name) =>
with val = element name
diff --git a/lib/dom.server.moon b/lib/dom.server.moon
index 3a7b537..fe6b60c 100644
--- a/lib/dom.server.moon
+++ b/lib/dom.server.moon
@@ -1,5 +1,8 @@
import opairs from require 'lib.ordered'
+void_tags = { 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr' }
+void_tags = { t,t for t in *void_tags }
+
element = (element) -> (...) ->
children = { ... }
@@ -24,9 +27,13 @@ element = (element) -> (...) ->
if #children == 0
children = attributes
- b ..= ">" .. table.concat children, ''
- b ..= "</#{element}>"
- b
+ if void_tags[element]
+ assert #children == 0, "void tag #{element} cannot have children!"
+ b .. ">"
+ else
+ b ..= ">" .. table.concat children, ''
+ b ..= "</#{element}>"
+ b
setmetatable {}, __index: (name) =>
with val = element name
diff --git a/lib/init.client.moon b/lib/init.client.moon
index 981cd25..77ff0ee 100644
--- a/lib/init.client.moon
+++ b/lib/init.client.moon
@@ -1,4 +1,4 @@
-export MODE, print, warn, relative, append, on_client
+export MODE, print, warn, relative, on_client
export window, document
window = js.global
@@ -7,8 +7,9 @@ window = js.global
MODE = 'CLIENT'
deep_tostring = (tbl, space='') ->
- buf = space .. tostring tbl
+ return tbl if 'userdata' == type tbl
+ buf = space .. tostring tbl
return buf unless 'table' == type tbl
buf = buf .. ' {\n'
@@ -42,5 +43,4 @@ relative = do
name = base .. name if '.' == name\sub 1, 1
_require name
-append = document.body\appendChild
on_client = (f, ...) -> f ...
diff --git a/lib/init.server.moon b/lib/init.server.moon
index f81a2dc..198c64e 100644
--- a/lib/init.server.moon
+++ b/lib/init.server.moon
@@ -1,4 +1,4 @@
-export MODE, print, warn, relative, append, on_client
+export MODE, print, warn, relative, on_client
MODE = 'SERVER'
deep_tostring = (tbl, space='') ->
@@ -38,18 +38,13 @@ relative = do
name = base .. name if '.' == name\sub 1, 1
_require name
--- shorthand to append elements to body
-buffer = ''
-append = (val) ->
- buffer ..= val
-
import compile, insert_loader from require 'lib.duct_tape'
insert_loader!
on_client = (fn, ...) ->
args = {...}
-- warn code
- append "<script type=\"application/lua\">
+ "<script type=\"application/lua\">
local fn = #{compile fn}
fn(#{table.concat [string.format '%q', v for v in *args ], ', '})
</script>"
diff --git a/lib/mmmfs/browser.moon b/lib/mmmfs/browser.moon
index a4080c2..ce7709f 100644
--- a/lib/mmmfs/browser.moon
+++ b/lib/mmmfs/browser.moon
@@ -1,79 +1,70 @@
require = relative ..., 1
import Key from require '.fileder'
import get_conversions from require '.conversion'
-import ReactiveVar, ReactiveElement, text, elements from require 'lib.component'
+import ReactiveVar, get_or_create, text, elements from require 'lib.component'
import div, span, a, select, option from elements
-limit = (list, num) -> [v for i,v in ipairs list when i <= num]
-
class Browser
- new: (@root, @path={}, rehydrate=false) =>
- @path = ReactiveVar @path
- -- @path\subscribe (path) -> window.location.hash = '/' .. table.concat path, '/'
- @prop = ReactiveVar (@root\find 'mmm/dom') or next @root.props
- @active = @path\map (path) ->
- fileder = @root
- for name in *path
- local next
- for child in *fileder.children
- if name == child\get 'name', 'alpha'
- next = child
- break
+ new: (@root, path='/', rehydrate=false) =>
+ -- root fileder
+ assert @root, 'root fileder is nil'
- if not next
- return
+ -- active path
+ @path = ReactiveVar path
- fileder = next
+ if MODE == 'CLIENT'
+ -- update URL bar
+ @path\subscribe (path) ->
+ path ..= '/' unless path\match '/$'
+ window.history\pushState nil, '', path
- fileder
+ -- active fileder
+ -- (re)set every time @path changes
+ @active = @path\map @root\walk
- @active\subscribe (fileder) -> @prop\set (fileder\find 'mmm/dom') or next fileder.props
+ -- currently active property
+ -- (re)set to default every time @active changes
+ @prop = @active\map (fileder) ->
+ return unless fileder
+ (fileder\find 'mmm/dom') or next fileder.props
-- retrieve or create the root
- root = 'div'
- root = document\getElementById 'browser-root' if rehydrate
- @dom = ReactiveElement root, {
- id: 'browser-root'
- style: {
- position: 'absolute',
- top: 0,
- bottom: 0,
- left: 0,
- right: 0,
- display: 'flex',
- overflow: 'hidden',
- 'flex-direction': 'column',
- 'justify-content': 'space-between',
- }
- }
+ @dom = get_or_create 'div', 'browser-root'
- -- add or prepend the navbar
- if MODE == 'CLIENT'
- @dom\prepend div {
- style: {
- padding: '1em',
- flex: '0 0 auto',
- display: 'flex',
- 'justify-content': 'space-between',
- background: '#eeeeee',
- },
+ -- prepend the navbar
+ if MODE == 'SERVER'
+ @dom\append div id: 'browser-navbar'
+ else
+ @dom\prepend get_or_create 'div', 'browser-navbar', {
span 'path: ', @path\map (path) -> with div style: { display: 'inline-block' }
- \append a 'root', href: '#', onclick: (_, e) ->
- e\preventDefault!
- @navigate {}
+ path_segment = (name, href) ->
+ a name, :href, onclick: (_, e) ->
+ e\preventDefault!
+ @navigate href
+
+ href = ''
+ path = path\match '^/(.*)'
+
+ \append path_segment 'root', '/'
+
+ while path
+ name, rest = path\match '^(%w+)/(.*)'
+ if not name
+ name = path
+
+ path = rest
+ href = "#{href}/#{name}"
- for i,name in ipairs path
\append '/'
- \append a name, href: '#', onclick: (_, e) ->
- e\preventDefault!
- @navigate limit path, i
+ \append path_segment name, href
span 'view property: ', @active\map (fileder) ->
onchange = (_, e) ->
@prop\set Key e.target.value
- current = @prop\get!\tostring!
- with select :onchange
+ current = @prop\get!
+ current = current and current\tostring!
+ with select :onchange, disabled: not fileder
if fileder
for key, _ in pairs fileder.props
value = key\tostring!
@@ -81,49 +72,51 @@ class Browser
}
-- append or patch #browser-content
- node = 'div'
- node = document\getElementById 'browser-content' if rehydrate
- @dom\append ReactiveElement node, {
- id: 'browser-content',
- style: {
- flex: '1 0 0',
- overflow: 'auto',
- padding: '1em 2em',
- },
- @get_content rehydrate and node
- }
+ @dom\append with get_or_create 'div', 'browser-content'
+ \append @get_content!, (rehydrate and .node.lastChild)
+
+ if rehydrate
+ -- force one rerender to set onclick handlers etc
+ @prop\set @prop\get!
+ -- export mmm/component interface
@node = @dom.node
@render = @dom\render
- get_content: (wrapper) =>
- var = @prop\map (prop) ->
+ -- render #browser-content
+ get_content: () =>
+ disp_error = (msg) -> span msg, style: { color: '#f00' }
+ @prop\map (prop) ->
active = @active\get!
- ok, res = pcall ->
- conversions = assert (get_conversions 'mmm/dom', prop.type), "no conversion path"
+ return disp_error "fileder not found!" unless active
+ return disp_error "property not found!" unless prop
+
+ convert = ->
+ conversions = get_conversions 'mmm/dom', prop.type
value = assert (active\get prop), "value went missing?"
+ return unless conversions
+
for i=#conversions,1,-1
{ :inp, :out, :transform } = conversions[i]
value = transform value, active
value
- if ok and res
- res
+ ok, res = if MODE == 'CLIENT'
+ pcall convert
else
- warn "error: ", res unless ok
- span "cannot display!", style: { color: '#f00' }
-
- -- wrapper was built already so take over the old value
- if wrapper
- var\set wrapper.lastElementChild
+ true, convert!
- var
+ if ok
+ res or disp_error "[no conversion path to mmm/dom]"
+ else
+ warn "error: ", res unless ok
+ disp_error "[unknown error displaying]"
navigate: (new) => @path\set new
{
- :Browser,
+ :Browser
}
diff --git a/lib/mmmfs/fileder.moon b/lib/mmmfs/fileder.moon
index 75659d4..c3e40f7 100644
--- a/lib/mmmfs/fileder.moon
+++ b/lib/mmmfs/fileder.moon
@@ -37,17 +37,75 @@ class Fileder
-- instantiate from props and children tables
-- or mix in one table (numeric keys are children, remainder props)
-- prop-keys are passed to Key constructor
- new: (props, @children) =>
- if not @children
- @children = for i, child in ipairs props
+ new: (props, children) =>
+ if not children
+ children = for i, child in ipairs props
props[i] = nil
child
- @props = { (Key k), v for k, v in pairs props }
+ -- automatically mount children on insert
+ @children = setmetatable {}, __newindex: (t, k, child) ->
+ rawset t, k, child
+ if @path == '/'
+ child\mount '/'
+ elseif @path
+ child\mount @path .. '/'
+
+ -- copy children
+ for i, child in ipairs children
+ @children[i] = child
+
+ -- automatically reify string keys on insert
+ @props = setmetatable {}, __newindex: (t, key, v) ->
+ rawset t, key, nil -- fix for fengari.io
+ rawset t, (Key key), v
+
+ -- copy props
+ for k, v in pairs props
+ @props[k] = v
+
+ -- recursively walk to and return the fileder with @path == path
+ -- * path - the path to walk to
+ walk: (path) =>
+ -- early-out if we are outside of the path already
+ return unless path\match '^' .. @path
+
+ -- gotcha
+ return @ if path == @path
+
+ for child in *@children
+ result = child\walk path
+ return result if result
+
+ -- recursively mount fileder and children at path
+ -- * path - the path to mount at (default: '/')
+ -- * mount_as - dont append own name to path
+ mount: (path='/', mount_as=false) =>
+ assert not @path, "mounted twice: #{@path} and now #{path}"
+
+ if mount_as
+ @path = path
+ else
+ @path = path .. @gett 'name: alpha'
+
+ if @path == '/'
+ for child in *@children
+ child\mount '/'
+ else
+ for child in *@children
+ child\mount @path .. '/'
+
+ -- recursively iterate all children (coroutine)
+ -- * depth - depth to stop after; 1 = yield only self (default: infinite)
+ iterate: (depth=0) =>
+ coroutine.yield @
+ return if depth == 1
+
+ for child in *@children
+ child\iterate depth - 1
-- find property key according to criteria, nil if no value or conversion path
- -- * name - property name (optional: defaults to main content)
- -- * type - wanted result type
+ -- * ... - arguments like Key
find: (...) =>
want = Key ...
@@ -66,8 +124,7 @@ class Fileder
error "couldn't find key after resolution?"
-- get property according to criteria, nil if no value or conversion path
- -- * name - property name (optional: defaults to main content)
- -- * type - wanted result type
+ -- * ... - arguments like Key
get: (...) =>
want = Key ...
@@ -89,7 +146,7 @@ class Fileder
want = Key ...
value, key = @get want
- assert value, "node doesn't have value for #{want\tostring!}"
+ assert value, "node doesn't have value for '#{want\tostring!}'"
value, key
{
diff --git a/lib/mmmfs/init.moon b/lib/mmmfs/init.moon
index bb26505..d0de45b 100644
--- a/lib/mmmfs/init.moon
+++ b/lib/mmmfs/init.moon
@@ -1,7 +1,34 @@
require = relative ...
import Key, Fileder from require '.fileder'
+import Browser from require '.browser'
+import tohtml from require 'lib.component'
+
+define_fileders = (...) ->
+ source_module = ...
+
+ (...) ->
+ with fileder = Fileder ...
+ .source_module = source_module
+
+rehydrate = (path) ->
+ import Browser from require 'lib.mmmfs.browser'
+ root = require 'root'
+ root\mount!
+
+ export BROWSER
+ BROWSER = Browser root, path, true
+
+render = (root, path) ->
+ export BROWSER
+ BROWSER = Browser root, path
+
+ content = tohtml BROWSER
+ content, on_client rehydrate, path
{
:Key
:Fileder
+ :render
+ :define_fileders
+ :module_roots
}