aboutsummaryrefslogtreecommitdiffstats
path: root/root/meta/mmm.component
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2018-11-10 09:14:44 +0000
committers-ol <s-ol@users.noreply.github.com>2018-11-10 09:14:44 +0000
commit5efdd65677f1cb94d7f617accbb0bb798e50eddc (patch)
treee62445326374937c0f9b2562c5f7f68acc35213c /root/meta/mmm.component
parentcleanup a bit (diff)
downloadmmm-5efdd65677f1cb94d7f617accbb0bb798e50eddc.tar.gz
mmm-5efdd65677f1cb94d7f617accbb0bb798e50eddc.zip
begin documenting mmm.component
Diffstat (limited to 'root/meta/mmm.component')
-rw-r--r--root/meta/mmm.component/description: text$plain1
-rw-r--r--root/meta/mmm.component/tests: text$moonscript -> mmm$dom.moon246
-rw-r--r--root/meta/mmm.component/text$moonscript -> fn -> mmm$dom.moon169
-rw-r--r--root/meta/mmm.component/todoMVC/description: text$plain1
-rw-r--r--root/meta/mmm.component/todoMVC/text$moonscript -> mmm$component.moon34
5 files changed, 451 insertions, 0 deletions
diff --git a/root/meta/mmm.component/description: text$plain b/root/meta/mmm.component/description: text$plain
new file mode 100644
index 0000000..e55fcd6
--- /dev/null
+++ b/root/meta/mmm.component/description: text$plain
@@ -0,0 +1 @@
+a small and DOM-centric framework for reactive web interfaces
diff --git a/root/meta/mmm.component/tests: text$moonscript -> mmm$dom.moon b/root/meta/mmm.component/tests: text$moonscript -> mmm$dom.moon
new file mode 100644
index 0000000..547b030
--- /dev/null
+++ b/root/meta/mmm.component/tests: text$moonscript -> mmm$dom.moon
@@ -0,0 +1,246 @@
+import article, div, h1, ul, li, pre from require 'mmm.dom'
+
+_content = {}
+append = (stuff) -> table.insert _content, stuff
+
+last = nil
+test_group = (name) ->
+ if last
+ append div (h1 last.name), ul last
+
+ last = { :name }
+ (name, test) ->
+ ok, err = pcall test
+ table.insert last, li if ok
+ "passed '#{name}'"
+ else
+ "failed '#{name}'", pre err
+
+expect = (expected, note, ...) ->
+ ok, msg = pcall ...
+ if ok or not msg\find expected
+ error note
+
+run_test = test_group 'component.moon'
+
+local ReactiveVar, ReactiveElement
+run_test "exports ReactiveVar, ReactiveElement", ->
+ import ReactiveVar, ReactiveElement from require 'mmm.component'
+ assert ReactiveVar, "ReactiveVar not exported"
+ assert ReactiveElement, "ReactiveElement not exported"
+
+run_test "exports tohtml helper", ->
+ import tohtml from require 'mmm.component'
+ assert 'function' == (type tohtml), "tohtml not exported"
+
+-- @TODO: get_or_create
+
+run_test "exports text helper", ->
+ import text from require 'mmm.component'
+ assert 'function' == (type text), "text not exported"
+
+ node = text 'a test string'
+
+ data = if MODE == 'CLIENT'
+ assert (js.instanceof node, js.global.Node), "expected text to generate a Node"
+ node.data
+ else
+ node
+
+ assert data == 'a test string', "expected text to store the string"
+
+run_test "text joins multiple arguments", ->
+ import text from require 'mmm.component'
+
+ node = text 'a', 'test', 'string'
+
+ data = if MODE == 'CLIENT' then node.data else node
+ assert data == 'a test string', "expected text to join arguments with spaces"
+
+run_test "exports elements table", ->
+ import elements from require 'mmm.component'
+
+ assert (type elements.div!) == 'table', "expected to build element with elements.div!"
+ assert (type elements.madeup!) == 'table', "expected to build element with elements.madeup!"
+
+run_test = test_group 'ReactiveVar'
+
+run_test "stores a value", ->
+ reactive = ReactiveVar 'test'
+ assert 'test' == reactive\get!, "expected x to be 'test'"
+
+run_test "provides #map shorthand", ->
+ local done
+
+ original = ReactiveVar 1
+ mapped = original\map (a) -> a + 10
+
+ assert mapped\get! == 11, "expected mapped to be 11"
+ return if MODE == 'SERVER'
+
+ original\set 4
+ assert mapped\get! == 14, "expected mapped to update"
+
+ mapped\subscribe coroutine.wrap (next, last) ->
+ assert next == 26, "expected next to be 26"
+ assert last == 14, "expected last to be 14"
+ done = true
+
+ original\set 16
+ assert done, "expected to reach the end"
+
+if MODE == 'CLIENT'
+ run_test "propagates updates", ->
+ local done
+
+ reactive = ReactiveVar 'test'
+ reactive\subscribe coroutine.wrap (next) ->
+ assert next == 'toast', "expected next to be 'toast'"
+ assert coroutine.yield! == 'cheese', "expected next to be 'cheese'"
+ done = true
+
+ reactive\set 'toast'
+ assert 'toast' == reactive\get!, "expected #get to return 'toast'"
+ reactive\set 'cheese'
+ assert done, "expected to reach the end"
+
+ run_test "passes old value as well", ->
+ local done
+
+ reactive = ReactiveVar 1
+ reactive\subscribe coroutine.wrap (next, last) ->
+ assert last == 1, "expected last:1 to be 1"
+ next, last = coroutine.yield!
+ assert last == 2, "expected last:2 to be 2"
+ done = true
+
+ reactive\set 2
+ reactive\set 3
+ assert done, "expected to reach the end"
+
+ run_test "provides #transform shorthand", ->
+ local done
+
+ reactive = ReactiveVar 1
+ reactive\subscribe coroutine.wrap (next, last) ->
+ assert last == 1, "expected last:1 to be 1"
+ next, last = coroutine.yield!
+ assert last == 2, "expected last:2 to be 2"
+ done = true
+
+ add_one = (a) -> a + 1
+ reactive\transform add_one
+ reactive\transform add_one
+ assert done, "expected to reach the end"
+
+ run_test "#subscribe returns function to unsubscribe", ->
+ calls = 0
+
+ reactive = ReactiveVar 1
+ unsub = reactive\subscribe coroutine.wrap () ->
+ calls += 1
+ coroutine.yield!
+ calls += 1
+ coroutine.yield!
+ calls += 1
+
+ assert 'function' == (type unsub), "expected to receive a function"
+
+ reactive\set 2
+ reactive\set 3
+ assert calls == 2, "wat"
+
+ unsub!
+ reactive\set 4
+ assert calls == 2, "expected to stop receiving updates"
+
+ run_test "tracks multiple subscriptions at once", ->
+ reactive = ReactiveVar 'test'
+ unsub = reactive\subscribe coroutine.wrap (next) ->
+ assert next == 'toast', "expected next to be toast"
+ next = coroutine.yield!
+ assert next == 'cheese', "expected next to be cheese"
+ coroutine.yield!
+ error "expected not to get here"
+
+ reactive\set 'toast'
+
+ local done
+ reactive\subscribe coroutine.wrap (next) ->
+ assert next == 'cheese', "expected next to be cheese"
+ next = coroutine.yield!
+ assert next == 'test', "expected next to be test"
+ done = true
+
+ reactive\set 'cheese'
+ unsub!
+ reactive\set 'test'
+ assert done, "expected to reach the end"
+
+run_test = test_group 'ReactiveElement'
+
+run_test "creates a HTML element", ->
+ elem = ReactiveElement 'span'
+ assert elem.node and elem.node.localName == 'span', "expected Node to be a <span>"
+
+-- @TODO: can take over a DOM Node
+
+run_test "sets attributes from a table arg", ->
+ elem = ReactiveElement 'span', class: 'never'
+ assert elem.node.className == 'never', "expected class to be 'never'"
+
+run_test "appends Nodes from arguments", ->
+ e_div, e_pre = div!, pre!
+ elem = ReactiveElement 'span', e_div, e_pre
+ assert elem.node.firstElementChild == e_div, "expected div to be the first child of elem"
+ assert elem.node.lastElementChild == e_pre, "expected pre to be the last child of elem"
+
+run_test "can append ReactiveElements and text", ->
+ e_div = ReactiveElement 'div'
+ elem = ReactiveElement 'div', e_div, 'testtext'
+ assert elem.node.firstElementChild == e_div.node, "expected div to be the first child of elem"
+ assert elem.node.lastChild.data == 'testtext', "expected last child of elem to be 'testtext'"
+
+run_test "accepts attributes after children", ->
+ e_div = div!
+ elem = ReactiveElement 'div', e_div, class: 'test'
+ assert elem.node.firstElementChild == e_div, "expected div to be the first child of elem"
+ assert elem.node.className == 'test', "expected class to be 'test'"
+
+run_test "allows mixing attributes and children in a single table", ->
+ e_div, e_pre = div!, pre!
+ elem = ReactiveElement 'div', { class: 'test', e_div, e_pre }
+ assert elem.node.firstElementChild == e_div, "expected div to be the first child of elem"
+ assert elem.node.lastElementChild == e_pre, "expected pre to be the last child of elem"
+ assert elem.node.className == 'test', "expected class to be 'test'"
+
+run_test "can unwrap and track attributes from ReactiveVars", ->
+ klass = ReactiveVar 'test'
+ elem = ReactiveElement 'div', class: klass
+ assert elem.node.className == 'test', "expected class to be 'test'"
+ klass\set 'toast'
+ assert elem.node.className == 'toast', "expected class to be 'toast'"
+
+run_test "can unwrap and track children from ReactiveVars", ->
+ child = ReactiveVar h1 'test'
+ elem = ReactiveElement 'div', child, pre 'fixed'
+ assert elem.node.firstElementChild.localName == 'h1', "expected first child to be h1"
+ assert elem.node.childElementCount == 2, "expected node to have two children"
+ child\set div 'toast'
+ assert elem.node.firstElementChild.localName == 'div', "expected first child to be div"
+ assert elem.node.childElementCount == 2, "expected node to have two children"
+
+run_test "warns when appending a string from a ReactiveVar", ->
+ import text from require 'mmm.component'
+
+ str = ReactiveVar 'test'
+ elem = ReactiveElement 'div', str
+ expect 'cannot replace string node', 'expected error', str\set, 'string too'
+ elem\destroy!
+
+ elem = ReactiveElement 'div', str\map text
+ str\set 'this is text'
+
+test_group!
+
+article _content
diff --git a/root/meta/mmm.component/text$moonscript -> fn -> mmm$dom.moon b/root/meta/mmm.component/text$moonscript -> fn -> mmm$dom.moon
new file mode 100644
index 0000000..d5acc1e
--- /dev/null
+++ b/root/meta/mmm.component/text$moonscript -> fn -> mmm$dom.moon
@@ -0,0 +1,169 @@
+import article, section, h1, h2, h3, p, a, div, ul, li, pre, code from require 'mmm.dom'
+import lua, moonscript from (require 'mmm.highlighting').languages
+
+mmmcomp = -> code 'mmm.component'
+
+source = do
+ (moon_src, lua_src, demo=true) ->
+ the_code = pre (moonscript moon_src), (lua lua_src), class: 'dual-code'
+
+ return the_code unless demo
+
+ example = assert load lua_src
+ div the_code, div example!, class: 'example'
+
+=> article {
+ h1 mmmcomp!
+ p mmmcomp!, " is a small and DOM-centric framework for reactive web interfaces."
+
+ p do
+ fengari = a "fengari.io", href: '//fengari.io'
+
+ "Built for reactive UI, ", mmmcomp!, " is meant to run on the client using ", fengari, ".
+ However, like ", (code 'mmm.dom'), ", the API is supported both on the client as well as
+ on the server (were most reactive features have been omitted) so that static pre-rendered
+ content can still be generated from the same code that powers the reactive interface."
+
+ h2 "Examples"
+ p "Feel free to read the API documentation below, or take a look at the following examples using the ",
+ (code 'mmmfs'), " inspect mode:"
+
+ ul for child in *@children
+ li a (child\gett 'name: alpha'), {
+ href: child.path,
+ onclick: (e) =>
+ e\preventDefault!
+ BROWSER\navigate child.path
+ }
+
+ h2 "API"
+ p "Begin by requiring ", mmmcomp!, ". The module returns a table containing the following:"
+
+ ul {
+ li (code 'ReactiveVar'), ": class/constructor for reactive state variables.",
+ li (code 'ReactiveElement'), ": class/constructor for reactive DOM elements. Rarely used directly."
+ li (code 'elements'), ": 'magic table' containing constructors for ReactiveElements by tag name."
+ li (code 'tohtml'), ": helper to convert from ReactiveElements to mmm/dom (DOM nodes / HTML strings)"
+ li (code 'text'), ": helper to convert Lua strings to DOM Text Nodes."
+ li (code 'get_or_create'), ": helper for rehydratable views."
+ }
+
+ section do
+ rvar = -> code 'ReactiveVar'
+
+ {
+ id: 'ReactiveVar'
+
+ h3 "Reactive Variables"
+ p mmmcomp!, " is centered around the concept of Reactive Variables (", rvar!, "s).
+ A ", rvar!, " is a container for a piece of application state that other pieces of code can
+ subscribe to. These attached callbacks are invoked whenever the value changes."
+
+ p "You can instantiate a ", rvar!, " via the constructor at any time. The constructor takes
+ the initial variable as an argument, but if you omit it ", (code 'nil'), " will work fine as well.
+ After instantiation, ", (code ':get()'), " and ", (code ':set(val)'), " will give access to the value:"
+
+ source [[
+import ReactiveVar from require 'mmm.component'
+
+test = ReactiveVar 3
+print test\get! -- prints '3'
+test\set 4
+print test\get! -- prints '4'
+ ]], [[
+local ReactiveVar = require 'mmm.component'.ReactiveVar
+
+local test = ReactiveVar(3)
+print(test:get()) -- prints '3'
+test:set(4)
+print(test:get()) -- prints '4'
+ ]], false
+
+ p "The value can also be changed using ", (code ':transform(fn)'), ", which is simply a shorthand for ",
+ (code 'var:set(fn(var:get()))'), ":"
+
+ source [[
+import ReactiveVar from require 'mmm.component'
+
+add_one = (n) -> n + 1
+count = ReactiveVar 1
+
+count\transform add_one
+print test\get! -- prints '2'
+
+count\transform add_one
+print test\get! -- prints '3'
+ ]], [[
+local ReactiveVar = require 'mmm.component'.ReactiveVar
+
+local function add_one(x) return x + 1 end
+local count = ReactiveVar(1)
+
+count:transform(add_one)
+print(test:get()) -- prints '2'
+
+count:transform(add_one)
+print(test:get()) -- prints '3'
+ ]], false
+
+ p "Now, so far we haven't really seen anything useful - this is all just behaving like a normal variable.
+ The ", (code ':subscribe(callback)'), " method is what makes ", rvar!, "s interesting: Whenever the value
+ changes, the ", rvar!, " calls each of the registered handlers, passing the new as well as the previous value:"
+
+ source [[
+import ReactiveVar from require 'mmm.component'
+
+add_one = (n) -> n + 1
+count = ReactiveVar 1
+count\subscribe (new, old) ->
+ print "changing from #{old} to #{new}!"
+
+
+count\transform add_one -- changing from 1 to 2
+count\set "a string" -- changing from 2 to a string
+ ]], [[
+local ReactiveVar = require 'mmm.component'.ReactiveVar
+
+local function add_one(x) return x + 1 end
+local count = ReactiveVar(1)
+cout:subscribe(function(new old)
+ print("changing from " .. old .. " to " .. new)
+end)
+
+count:transform(add_one) -- changing from 1 to 2
+count:set("a string") -- changing from 2 to a string
+ ]], false
+
+ p "This allows other code (such as ", (code 'ReactiveElement'), "s) to react to value changes and update
+ themselves, as we will see in a minute. Often you will want to derive state from other state. To make this
+ easy while keeping everything reactive, ", mmmcomp!, " includes the ", (code ':map(fn)'), " method."
+
+ p (code ':map(fn)'), " applies the function ", (code 'fn'), " to the current value, just as ",
+ (code ':transform(fn)'), " would, but it doesn't update the value itself - it rather returns a new ", rvar!,
+ " instance that is already set up to update whenever the original one changes."
+
+ source [[
+import ReactiveVar from require 'mmm.component'
+
+fruit = ReactiveVar "apple"
+loud_fruit = fruit\map string.upper
+
+print fruit\get! -- prints 'apple'
+print loud_fruit\get! -- prints 'APPLE'
+
+fruit\set "orange"
+print loud_fruit\get! -- prints 'ORANGE'
+ ]], [[
+local ReactiveVar = require 'mmm.component'.ReactiveVar
+
+local fruit = ReactiveVar("apple")
+local loud_fruit = fruit:map(string.upper)
+
+print(fruit:get()) -- prints 'apple'
+print(loud_fruit:get()) -- prints 'APPLE'
+
+fruit:set("orange")
+print(loud_fruit:get()) -- prints 'ORANGE'
+ ]], false
+ }
+}
diff --git a/root/meta/mmm.component/todoMVC/description: text$plain b/root/meta/mmm.component/todoMVC/description: text$plain
new file mode 100644
index 0000000..baaf6fc
--- /dev/null
+++ b/root/meta/mmm.component/todoMVC/description: text$plain
@@ -0,0 +1 @@
+TodoMVC using mmm.component
diff --git a/root/meta/mmm.component/todoMVC/text$moonscript -> mmm$component.moon b/root/meta/mmm.component/todoMVC/text$moonscript -> mmm$component.moon
new file mode 100644
index 0000000..dc4e5e1
--- /dev/null
+++ b/root/meta/mmm.component/todoMVC/text$moonscript -> mmm$component.moon
@@ -0,0 +1,34 @@
+import ReactiveVar, text, elements from require 'mmm.component'
+import article, div, form, span, h3, a, input from elements
+
+parent = div!
+todoItem = (desc, done) ->
+ -- convert into reactive data sources
+ desc, done = (ReactiveVar desc), ReactiveVar done
+ with me = div style:
+ margin: '8px'
+ padding: '8px'
+ background: '#eeeeee'
+ \append h3 (desc\map text), style: 'margin: 0;'
+ \append span done\map (done) -> text if done then 'done' else 'not done yet'
+ \append input type: 'checkbox', checked: done, onchange: (e) => done\set e.target.checked
+ \append a (text 'delete'), href: '#', onclick: (e) => parent\remove me
+
+parent\append todoItem 'write a Component System', true
+parent\append todoItem 'eat Lasagna', true
+parent\append todoItem 'do other things'
+
+desc = ReactiveVar 'start'
+form = with form {
+ action: ''
+ style:
+ margin: '2px'
+ onsubmit: (e) =>
+ e\preventDefault!
+ parent\append todoItem desc\get!
+ desc\set ''
+ }
+ \append input type: 'text', value: desc, onchange: (e) => desc\set e.target.value
+ \append input type: 'submit', value: 'add'
+
+article parent, form