git @ Cat's Eye Technologies DAM / 9f46694
Initial import of files for DAM. Chris Pressey 3 years ago
10 changed file(s) with 619 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 DAM
1 ===
2
3 You've tried the Document Object Model, now try the Document *Awesome* Model!
4
5 (FIXME TODO put twee picture of cartoon beaver mascot here)
6
7 What is this
8 ------------
9
10 **DAM** is a tiny library for creating bits of an HTML5 document.
11 (I'd say "for creating UIs" but that may be overstating it a tad.)
12 It's written in ES5 Javascript (so it can be used directly by most
13 modern web browsers) and it's about 1K in size (uncompressed).
14
15 *NOTE: this is version 0.0: everything is subject to change*
16
17 Basic usage
18 -----------
19
20 Basically you can use it like this:
21
22 <script src="dam.js"></script>
23 <script>
24 var div=DAM.maker('div'), p=DAM.maker('p'), span=DAM.maker('span'), button=DAM.maker('button');
25 var d = div(
26 p(
27 "Hello, ", span("world"), "."
28 ),
29 button({ onclick: function(e) { alert(e); } }, "Alert!")
30 );
31 document.getElementById('container').appendChild(d);
32 </script>
33
34 `DAM.maker` is a function that takes a tag string and returns a function that
35 creates and returns a DOM Element with that tag.
36
37 The returned function also takes any number of arguments. Each argument
38 influences the DOM Element that is created:
39
40 * A string argument will create a child text node inside the Element.
41 * A DOM Element argument will insert that Element as a child of the Element.
42 * A `null` or `undefined` argument will be ignored.
43 * A plain Javascript object argument will configure the created Element;
44 each key in the object sets one attribute of the created Element.
45 In this case, some keys and values are treated specially:
46 * Keys must always be strings.
47 * A key that starts with `on` will set up an event handler.
48 * A `null` value will unset the attribute.
49
50 Widgets
51 -------
52
53 If you have a pattern of elements that you create over and over, you can
54 package that up in a function that creates those elements. DAM calls this
55 package a _widget_, and the function that creates it a _widget maker_.
56 The names of DAM widget makers usually begin with `make`.
57
58 A simple example based on the code above:
59
60 <script src="dam.js"></script>
61 <script>
62 var div=DAM.maker('div'), p=DAM.maker('p'), span=DAM.maker('span'), button=DAM.maker('button');
63 function makeGreeting(config) {
64 return div(
65 p(
66 "Hello, ", span(config.who), "."
67 ),
68 button({ onclick: function(e) { alert(e); } }, "Alert!")
69 );
70 )
71 var greets = div(
72 makeGreeting({ who: "world" }),
73 makeGreeting({ who: "Earthlings" }),
74 makeGreeting({ who: "there" })
75 );
76 document.getElementById('container').appendChild(greets);
77 </script>
78
79 By convention, the first argument of a widget maker is a configuration
80 object which configures the widget; it is completely widget-specific.
81
82 If the widget supports it, the remaining arguments will then be applied to
83 the widget, in the same manner as the arguments passed to the function
84 returned by `DAM.maker` are applied to the new element it creates.
85
86 If the widget is a "container widget" then it should definitely support this,
87 as a means of letting the caller add children to the widget.
88
89 "Applied to the widget" might mean "applied to one of the elements of the
90 widget", if the widget is composed of several elements.
91
92 ### Supplied widget library
93
94 `dam-widgets.js` defines a number of widgets built on top of DAM. Having
95 these widgets readily available so that I could re-use them across my projects
96 is one of the main reasons I bothered to put this DAM thing together. All
97 the same, you can just treat these as examples or starting points for new widgets.
98
99 * [Checkbox widget](demo/checkbox.html)
100 * [Panel widget](demo/panel.html)
101 * [Select widget](demo/select.html)
102 * [Range widget](demo/range.html)
103
104 ### Advanced widget creation
105
106 The function returned by `DAM.maker` is simply `DAM.makeElem` with some
107 arguments pre-set and transformed. The tag passed to `DAM.maker` is passed
108 as the first argument to `DAM.makeElem`, and the argument list received by
109 the function returned by `DAM.maker` is passed as the second argument to
110 `DAM.makeElem`, as a plain Javascript array.
111
112 But `DAM.makeElem` can be called directly, and calling it directly allows
113 more explicit access to its arguments. Thus it is often called directly in
114 widget makers.
115
116 Related work
117 ------------
118
119 You can think of DAM as something like [hyperscript][] except:
120
121 * It has [hyperscript-helpers][] built-in (sort of)
122 * It has no dependencies
123 * It doesn't try to parse what you want (you have spell out what you want)
124 * It doesn't make as many promises about what it can do
125 * It doesn't have an ecosystem, only a convention for widget makers
126 * It's in the public domain
127
128 I was only peripherally aware of hyperscript when I wrote this; any
129 similarities are probably because certain solutions (such as setting
130 attributes from an object) are "obvious". (But I saw no need for e.g.
131 `htmlFor` when you can just put `'for'` in single quotes...)
132
133 [hyperscript]: https://github.com/hyperhype/hyperscript
134 [hyperscript-helpers]: https://github.com/ohanhi/hyperscript-helpers
135
136 TODO
137 ----
138
139 Classes on widgets for styling (DAM_checkbox, DAM_panel, etc)
140
141 Function to set the value of a DAM range control in an orderly fashion
0 This is free and unencumbered software released into the public domain.
1
2 Anyone is free to copy, modify, publish, use, compile, sell, or
3 distribute this software, either in source code form or as a compiled
4 binary, for any purpose, commercial or non-commercial, and by any
5 means.
6
7 In jurisdictions that recognize copyright laws, the author or authors
8 of this software dedicate any and all copyright interest in the
9 software to the public domain. We make this dedication for the benefit
10 of the public at large and to the detriment of our heirs and
11 successors. We intend this dedication to be an overt act of
12 relinquishment in perpetuity of all present and future rights to this
13 software under copyright law.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21 OTHER DEALINGS IN THE SOFTWARE.
22
23 For more information, please refer to <http://unlicense.org/>
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>DAM checkbox demo</title>
4 </head>
5 <body>
6
7 <h1>DAM checkbox demo</h1>
8
9 <p>DAM comes with a checkbox widget. This widget simply bundles together a checkbox input
10 (on the left) with a label (on the right).</p>
11
12 <div id="installation"></div>
13
14 <script src="../src/dam.js"></script>
15 <script src="../src/dam-widgets.js"></script>
16 <script>
17 var div=DAM.maker('div'), span=DAM.maker('span');
18 var understood = false;
19 var great = false;
20 var statusBar = span();
21 function updateStatus() {
22 statusBar.innerHTML = (understood ? "understood " : "") + (great ? "great " : "");
23 }
24 var d = div(
25 div(
26 DAM.makeCheckbox({ onchange: function(b) { understood = b; updateStatus(); } }, "I understand"),
27 DAM.makeCheckbox({ onchange: function(b) { great = b; updateStatus(); } }, "It's great")
28 ),
29 statusBar
30 );
31 document.getElementById('installation').appendChild(d);
32 </script>
33
34 </body>
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>DAM</title>
4 <style>
5 #installation canvas {
6 border: 1px solid blue
7 }
8 </style>
9 </head>
10 <body>
11
12 <h1>DAM</h1>
13
14 <div id="installation"></div>
15
16 <script src="../src/dam.js"></script>
17 <script src="../src/dam-widgets.js"></script>
18 <script>
19 var div=DAM.maker("div"), p=DAM.maker("p"), span=DAM.maker("span"), button=DAM.maker("button"),
20 canvas=DAM.maker("canvas"), label=DAM.maker("label"), br=DAM.maker("br"), input=DAM.maker("input"),
21 textarea=DAM.maker("textarea");
22
23 var d = div(
24 p(
25 "Hello, ", span("world"), "."
26 ),
27 div(
28 button({ onclick: function(e) { alert(e); } }, "Alert!")
29 ),
30 div(
31 canvas({ width: 100, height: 100 })
32 ),
33 div(
34 label("Phone number:",
35 input({ type: "text", size: 12, value: "KL5-1214" })
36 ),
37 br(),
38 input({ type: "range", min: 0, max: 100, value: 75, onchange: function(e) { console.log(e.target.value); } }),
39 br(),
40 textarea({ rows: 10, cols: 80 }, "MAX\nQUEUE"),
41 )
42 );
43 document.getElementById('installation').appendChild(d);
44 </script>
45
46 </body>
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>DAM panel demo</title>
4 </head>
5 <body>
6
7 <h1>DAM panel demo</h1>
8
9 <p>DAM comes with a panel widget. This widget provides a button by which it can
10 be collapsed or expanded.</p>
11
12 <div id="installation"></div>
13
14 <script src="../src/dam.js"></script>
15 <script src="../src/dam-widgets.js"></script>
16 <script>
17 var div=DAM.maker("div"), p=DAM.maker("p"), span=DAM.maker("span"), button=DAM.maker("button");
18 var statusBar = span();
19 function updateStatus(msg) {
20 statusBar.innerHTML += "<p>clicked " + msg + "</p>";
21 }
22 document.getElementById('installation').appendChild(
23 div(
24 DAM.makePanel(
25 { title: "Knobs", isOpen: false },
26 button("Knob 1", { onclick: function(e) { updateStatus("Knob 1"); }}),
27 button("Knob 2", { onclick: function(e) { updateStatus("Knob 2"); }})
28 ),
29 DAM.makePanel(
30 { title: "Doohickeys", isOpen: true },
31 button("Doohickey 1", { onclick: function(e) { updateStatus("Doohickey 1"); }}),
32 button("Doohickey 2", { onclick: function(e) { updateStatus("Doohickey 2"); }})
33 ),
34 statusBar
35 )
36 );
37 </script>
38
39 </body>
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>DAM range demo</title>
4 </head>
5 <body>
6
7 <h1>DAM range demo</h1>
8
9 <p>DAM comes with a range widget. This widget provides a slider, a textbox, and two buttons,
10 any of which can be used to adjust a numeric value within a range.</p>
11
12 <div id="installation"></div>
13
14 <script src="../src/dam.js"></script>
15 <script src="../src/dam-widgets.js"></script>
16 <script>
17 var div=DAM.maker("div"), p=DAM.maker("p"), span=DAM.maker("span"), button=DAM.maker("button");
18 document.getElementById('installation').appendChild(
19 div(
20 DAM.makeRange({
21 title: "Fudge factor",
22 min: 0,
23 max: 100,
24 value: 50,
25 onchange: function(v) {
26 console.log(v);
27 }
28 })
29 )
30 );
31 </script>
32
33 </body>
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>DAM select demo</title>
4 </head>
5 <body>
6
7 <h1>DAM select demo</h1>
8
9 <p>DAM comes with a select widget. This widget provides a dropdown control where each
10 selection is associated with some data.</p>
11
12 <div id="installation"></div>
13
14 <script src="../src/dam.js"></script>
15 <script src="../src/dam-widgets.js"></script>
16 <script>
17 var div=DAM.maker("div"), p=DAM.maker("p"), span=DAM.maker("span"), button=DAM.maker("button");
18 document.getElementById('installation').appendChild(
19 div(
20 DAM.makeSelect({
21 options: [
22 {
23 text: "First",
24 value: 1,
25 },
26 {
27 text: "Second",
28 value: 2,
29 selected: true
30 },
31 {
32 text: "Third",
33 value: 3,
34 }
35 ],
36 onchange: function(option) {
37 alert(option.value);
38 }
39 })
40 )
41 );
42 </script>
43
44 </body>
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>DAM + Storeon demo</title>
4 </head>
5 <body>
6
7 <h1>DAM + Storeon demo</h1>
8
9 <p>DAM doesn't come with any state management capabilities, but if you're looking
10 for a state management library that's as obscenely small as DAM (and,
11 like DAM, written in ES5 Javascript -- no transpiling necessary), have you considered
12 <a href="https://evilmartians.com/chronicles/storeon-redux-in-173-bytes">Storeon</a>?</p>
13
14 <div id="installation"></div>
15
16 <script src="../src/dam.js"></script>
17 <script src="../src/dam-widgets.js"></script>
18
19 <script>var module = {};</script>
20 <script src="https://unpkg.com/storeon@0.8.2/index.js"></script>
21 <script>var createStore = module.exports;</script>
22
23 <script>
24 function initCounter(store) {
25 store.on('@init', function() { return {count: 0}; });
26 store.on('inc', function(state) { return {count: state.count + 1}; });
27 store.on('dec', function(state) { return {count: state.count - 1}; });
28 }
29
30 var store = createStore([initCounter]);
31
32 var div=DAM.maker('div'), span=DAM.maker('span'), button=DAM.maker('button');
33 var statusBar = span();
34 function updateStatusBar(state) {
35 statusBar.innerHTML = "Count is " + state.count + ".";
36 }
37 store.on('@changed', updateStatusBar);
38 updateStatusBar(store.get());
39
40 var d = div(
41 div(
42 button('Increment', { onclick: function(e) { store.dispatch('inc'); } }),
43 button('Decrement', { onclick: function(e) { store.dispatch('dec'); } })
44 ),
45 statusBar
46 );
47 document.getElementById('installation').appendChild(d);
48 </script>
49
50 </body>
0 /* dam-widgets.js version 0.0. This file is in the public domain. */
1
2 /* dam.js should be included before this source. */
3
4 /*
5 * A labelled checkbox, where the checkbox appears to the left of the label.
6 * Arguments after the first (config) argument will be applied to the label element.
7 */
8 DAM.makeCheckbox = function(config) {
9 if (typeof DAM.makeCheckboxCounter === 'undefined') DAM.makeCheckboxCounter = 0;
10 var checkboxId = 'cfzzzb_' + (DAM.makeCheckboxCounter++);
11
12 var onchange = config.onchange || function(b) {};
13
14 // config label: make copy of arguments, replace first with a bespoke config
15 var args = new Array(arguments.length);
16 for(var i = 0; i < args.length; ++i) {
17 args[i] = arguments[i];
18 }
19 args[0] = { 'for': checkboxId }
20
21 return DAM.makeElem('span', [
22 DAM.makeElem('input', [
23 {
24 type: 'checkbox',
25 id: checkboxId,
26 onchange: function(e) {
27 onchange(e.target.checked);
28 }
29 },
30 config.checkboxAttrs || {}
31 ]),
32 DAM.makeElem('label', args)
33 ]);
34 };
35
36 /*
37 * A collapsible panel.
38 * Arguments after the first (config) argument will be applied to the inner container div element.
39 */
40 DAM.makePanel = function(config) {
41 var isOpen = !!(config.isOpen);
42 var title = config.title || "";
43
44 function getLabel() {
45 return (isOpen ? "∇" : "⊳") + " " + title;
46 }
47
48 // config inner container
49 var args = new Array(arguments.length);
50 for(var i = 0; i < args.length; ++i) {
51 args[i] = arguments[i];
52 }
53 args[0] = {}
54
55 var innerContainer = DAM.makeElem('div', args);
56 innerContainer.style.display = isOpen ? "block" : "none";
57
58 var button = DAM.makeElem('button', [
59 getLabel(),
60 {
61 onclick: function(e) {
62 isOpen = !isOpen;
63 button.innerHTML = getLabel();
64 innerContainer.style.display = isOpen ? "block" : "none";
65 }
66 }
67 ]);
68
69 return DAM.makeElem("div", [
70 button,
71 innerContainer
72 ]);
73 };
74
75 /*
76 * A select dropdown.
77 */
78 DAM.makeSelect = function(config) {
79 var title = config.title || "";
80 var options = config.options || [];
81 var onchange = config.onchange || function(v) {};
82
83 var select = DAM.makeElem('select');
84 for (var i = 0; i < options.length; i++) {
85 var op = DAM.makeElem('option');
86 op.value = options[i].value;
87 op.text = options[i].text;
88 op.selected = !!(options[i].selected);
89 select.options.add(op);
90 }
91 select.addEventListener('change', function(e) {
92 onchange(options[select.selectedIndex]);
93 });
94 return DAM.makeElem('label', [title, select]);
95 };
96
97 /*
98 * A range control.
99 */
100 DAM.makeRange = function(config) {
101 var title = config.title || "";
102 var min_ = config['min'];
103 var max_ = config['max'];
104 var value = config.value || min_;
105 var onchange = config.onchange || function(v) {};
106 var textInputSize = config.textInputSize || 5;
107
108 var textInput; var slider;
109
110 slider = DAM.makeElem('input', [
111 {
112 type: "range", min: min_, max: max_, value: value,
113 onchange: function(e) {
114 var v = parseInt(slider.value, 10);
115 if (!isNaN(v)) {
116 textInput.value = "" + v;
117 onchange(v);
118 }
119 }
120 }
121 ]);
122
123 textInput = DAM.makeElem('input', [
124 {
125 size: "" + textInputSize,
126 value: "" + value,
127 onchange: function(e) {
128 var v = parseInt(textInput.value, 10);
129 if (!isNaN(v) && v >= min_ && v <= max_) {
130 slider.value = "" + v;
131 onchange(v);
132 }
133 }
134 }
135 ]);
136
137 var incButton = DAM.makeElem('button', ['+',
138 {
139 onclick: function(e) {
140 var v = parseInt(textInput.value, 10);
141 if ((!isNaN(v)) && v < max_) {
142 v++;
143 textInput.value = "" + v;
144 slider.value = "" + v;
145 onchange(v);
146 }
147 }
148 }
149 ]);
150
151 var decButton = DAM.makeElem('button', ['-',
152 {
153 onclick: function(e) {
154 var v = parseInt(textInput.value, 10);
155 if ((!isNaN(v)) && v > min_) {
156 v--;
157 textInput.value = "" + v;
158 slider.value = "" + v;
159 onchange(v);
160 }
161 }
162 }
163 ]);
164
165 return DAM.makeElem('span', [DAM.makeElem('label', [title, slider]), textInput, decButton, incButton]);
166 };
0 /* dam.js version 0.0. This file is in the public domain. */
1
2 if (typeof window === 'undefined' || window.DAM === undefined) DAM = {};
3
4 DAM.makeElem = function(tag, args) {
5 args = args || [];
6 var elem = document.createElement(tag);
7 for (var i = 0; i < args.length; i++) {
8 var arg = args[i];
9 if (arg instanceof Element) {
10 elem.appendChild(arg);
11 } else if (typeof arg === 'string' || arg instanceof String) {
12 elem.appendChild(document.createTextNode(arg));
13 } else if (typeof arg === 'object' && arg !== null) {
14 Object.keys(arg).forEach(function(key) {
15 if (key.substring(0, 2) === 'on') {
16 elem.addEventListener(key.substring(2), arg[key]);
17 } else if (arg[key] === null) {
18 elem.removeAttribute(key);
19 } else {
20 elem.setAttribute(key, arg[key]);
21 }
22 });
23 } else {
24 console.log(arg);
25 }
26 }
27 return elem;
28 };
29 DAM.maker = function(tag) {
30 return function() {
31 return DAM.makeElem(tag, arguments);
32 };
33 };