-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathConfig.html
More file actions
199 lines (197 loc) · 26.8 KB
/
Config.html
File metadata and controls
199 lines (197 loc) · 26.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<html><style>body { overflow:hidden;}
h1 { font-family: Arial, sans-serif; font-size: 24px; color: #44a; }
p { margin:0; padding:0; }
br { margin:0; padding:0; }
.line {
white-space: nowrap;
height:16px;
}
.line>span {
display:inline-block; background-color:#eef; height:100%;
margin: 0 5px 0 0; padding-right: 5px; color:#999;
}
comment {color: #080;} module {color: #804;}
quote {color: #008;} comment>quote {color: #080;}
.listing { margin: 10px; border: 1px solid #ccc;
font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 14px;
overflow:scroll;
height:90%;
}
code { padding: 5px 0;}</style></html><body>
<h1>Config.ts</h1>
<div class='listing'><code><p id=1 class="line"><span> 1</span><comment>/**</comment></p>
<p id=2 class="line"><span> 2</span><comment> * ## Config </comment></p>
<p id=3 class="line"><span> 3</span><comment> * Tool to configure a tree of mithril components via an object literal or as a JSON file.</comment></p>
<p id=4 class="line"><span> 4</span><comment> * The object format is `{ <name>: { <Component>: <options>}, ...}`.</comment></p>
<p id=5 class="line"><span> 5</span><comment> * The name `root` is required and defines the top of the tree. Other named sections are allowed and</comment></p>
<p id=6 class="line"><span> 6</span><comment> * can be referenced by name inside the `root` structure, </comment></p>
<p id=7 class="line"><span> 7</span><comment> * This can be used to structure the obect in a more readable and maintainable format.</comment></p>
<p id=8 class="line"><span> 8</span><comment> * </comment></p>
<p id=9 class="line"><span> 9</span><comment> * ### Example:</comment></p>
<p id=10 class="line"><span> 10</span><comment> * In the example below, the comnponents `Layout`, `LeftNav`, `MainNav` will be resolved against modules</comment></p>
<p id=11 class="line"><span> 11</span><comment> * provided in the `context` parameter when creating the `Config` node. </comment></p>
<p id=12 class="line"><span> 12</span><comment> * ```</comment></p>
<p id=13 class="line"><span> 13</span><comment> * {</comment></p>
<p id=14 class="line"><span> 14</span><comment> * root: { Layout: {</comment></p>
<p id=15 class="line"><span> 15</span><comment> * rows: [<quote>"30px"</quote>, <quote>"fill"</quote>, <quote>"10px"</quote>],</comment></p>
<p id=16 class="line"><span> 16</span><comment> * css: <quote>'.my-example'</quote>,</comment></p>
<p id=17 class="line"><span> 17</span><comment> * content: [<quote>'Header'</quote>, <quote>'Body'</quote>, <quote>'Footer]</comment></p>
<p id=18 class="line"><span> 18</span><comment> * }},</comment></p>
<p id=19 class="line"><span> 19</span><comment> * Header: { Layout:{</comment></p>
<p id=20 class="line"><span> 20</span><comment> * columns: [<quote>"200px"</quote>, <quote>"fill"</quote>],</comment></p>
<p id=21 class="line"><span> 21</span><comment> * content: ['</quote>Left Header<quote>', '</quote>Main Header<quote>']</comment></p>
<p id=22 class="line"><span> 22</span><comment> * }},</comment></p>
<p id=23 class="line"><span> 23</span><comment> * Body: { Layout:{</comment></p>
<p id=24 class="line"><span> 24</span><comment> * columns: [<quote>"200px"</quote>, <quote>"fill"</quote>], </comment></p>
<p id=25 class="line"><span> 25</span><comment> * content: ['</quote>LeftNav<quote>', '</quote>MainNav<quote>']</comment></p>
<p id=26 class="line"><span> 26</span><comment> * }},</comment></p>
<p id=27 class="line"><span> 27</span><comment> * Footer: { Layout: {</comment></p>
<p id=28 class="line"><span> 28</span><comment> * css: '</quote>.hsdocs_footer<quote>',</comment></p>
<p id=29 class="line"><span> 29</span><comment> * content: ['</quote>(c) Helpful ; Scripts<quote>']</comment></p>
<p id=30 class="line"><span> 30</span><comment> * }},</comment></p>
<p id=31 class="line"><span> 31</span><comment> * LeftNav: { LeftNav: { lib:<quote>"route.lib"</quote>, field:<quote>"route.field"</quote>}},</comment></p>
<p id=32 class="line"><span> 32</span><comment> * MainNav: { MainNav: { lib:<quote>"route.lib"</quote>, field:<quote>"route.field"</quote>}}</comment></p>
<p id=33 class="line"><span> 33</span><comment> * }</comment></p>
<p id=34 class="line"><span> 34</span><comment> * ```</comment></p>
<p id=35 class="line"><span> 35</span><comment> * <example></comment></p>
<p id=36 class="line"><span> 36</span><comment> * <file name='</quote>script.js<quote>'></comment></p>
<p id=37 class="line"><span> 37</span><comment> * const theContent = {</comment></p>
<p id=38 class="line"><span> 38</span><comment> * root: { Layout: {</comment></p>
<p id=39 class="line"><span> 39</span><comment> * rows: [<quote>"30px"</quote>, <quote>"fill"</quote>, <quote>"30px"</quote>],</comment></p>
<p id=40 class="line"><span> 40</span><comment> * css: '</quote>.my-example<quote>',</comment></p>
<p id=41 class="line"><span> 41</span><comment> * content: ['</quote>Header<quote>', '</quote>Body<quote>', '</quote>Footer<quote>']</comment></p>
<p id=42 class="line"><span> 42</span><comment> * }},</comment></p>
<p id=43 class="line"><span> 43</span><comment> * Header: { Layout:{</comment></p>
<p id=44 class="line"><span> 44</span><comment> * columns: [<quote>"100px"</quote>, <quote>"fill"</quote>],</comment></p>
<p id=45 class="line"><span> 45</span><comment> * content: ['</quote>Left Header<quote>', '</quote>Main Header<quote>']</comment></p>
<p id=46 class="line"><span> 46</span><comment> * }},</comment></p>
<p id=47 class="line"><span> 47</span><comment> * Body: { Layout:{</comment></p>
<p id=48 class="line"><span> 48</span><comment> * columns: [<quote>"100px"</quote>, <quote>"fill"</quote>], </comment></p>
<p id=49 class="line"><span> 49</span><comment> * content: ['</quote>LeftNav<quote>', '</quote>MainNav<quote>']</comment></p>
<p id=50 class="line"><span> 50</span><comment> * }},</comment></p>
<p id=51 class="line"><span> 51</span><comment> * Footer: { Layout: {</comment></p>
<p id=52 class="line"><span> 52</span><comment> * css: '</quote>.my-footer<quote>',</comment></p>
<p id=53 class="line"><span> 53</span><comment> * content: ['</quote>(c) Helpful Scripts<quote>']</comment></p>
<p id=54 class="line"><span> 54</span><comment> * }}</comment></p>
<p id=55 class="line"><span> 55</span><comment> * }</comment></p>
<p id=56 class="line"><span> 56</span><comment> * m.mount(root, {view: () => </comment></p>
<p id=57 class="line"><span> 57</span><comment> * m(hsLayout.Config, { </comment></p>
<p id=58 class="line"><span> 58</span><comment> * source: theContent, <comment>// a file name, or obect literal</comment></comment></p>
<p id=59 class="line"><span> 59</span><comment><comment></comment> * context:[hsLayout] <comment>// a list of es6 modules against which to resolve components at runtime </comment></comment></p>
<p id=60 class="line"><span> 60</span><comment><comment></comment> * })</comment></p>
<p id=61 class="line"><span> 61</span><comment> * });</comment></p>
<p id=62 class="line"><span> 62</span><comment> * </file></comment></p>
<p id=63 class="line"><span> 63</span><comment> * </example></comment></p>
<p id=64 class="line"><span> 64</span><comment> * </comment></p>
<p id=65 class="line"><span> 65</span><comment> * ### Calling pattern</comment></p>
<p id=66 class="line"><span> 66</span><comment> * Instantiate the component via</comment></p>
<p id=67 class="line"><span> 67</span><comment> * ```</comment></p>
<p id=68 class="line"><span> 68</span><comment> * m(Config, { </comment></p>
<p id=69 class="line"><span> 69</span><comment> * source: src, <comment>// a file name, or obect literal</comment></comment></p>
<p id=70 class="line"><span> 70</span><comment><comment></comment> * context:[] <comment>// a list of es6 modules against which to resolve components at runtime </comment></comment></p>
<p id=71 class="line"><span> 71</span><comment><comment></comment> * });</comment></p>
<p id=72 class="line"><span> 72</span><comment> * ```</comment></p>
<p id=73 class="line"><span> 73</span><comment> * Additional parameters can be passed to the root node. The example below establishes routing and passes</comment></p>
<p id=74 class="line"><span> 74</span><comment> * `field` and `lib` to the root node:</comment></p>
<p id=75 class="line"><span> 75</span><comment> * ```</comment></p>
<p id=76 class="line"><span> 76</span><comment> * class Router {</comment></p>
<p id=77 class="line"><span> 77</span><comment> * view(node:Vnode) { </comment></p>
<p id=78 class="line"><span> 78</span><comment> * return m(Config, Object.assign({source: src, context: [hsLayout]}, node.attrs));</comment></p>
<p id=79 class="line"><span> 79</span><comment> * }</comment></p>
<p id=80 class="line"><span> 80</span><comment> * }</comment></p>
<p id=81 class="line"><span> 81</span><comment> * m.route(document.body, '</quote>/api<quote>', { </comment></p>
<p id=82 class="line"><span> 82</span><comment> * '</quote>/api<quote>': Router,</comment></p>
<p id=83 class="line"><span> 83</span><comment> * '</quote>/api/:lib<quote>': Router,</comment></p>
<p id=84 class="line"><span> 84</span><comment> * '</quote>/api/:lib/:field<quote>': Router</comment></p>
<p id=85 class="line"><span> 85</span><comment> * });</comment></p>
<p id=86 class="line"><span> 86</span><comment> * ```</comment></p>
<p id=87 class="line"><span> 87</span><comment> */</comment></p>
<p id=88 class="line"><span> 88</span></p>
<p id=89 class="line"><span> 89</span> <comment>/** */</comment></p>
<p id=90 class="line"><span> 90</span> import m from <quote>"mithril"</quote>;</p>
<p id=91 class="line"><span> 91</span> type Vnode = m.Vnode<any, any>;</p>
<p id=92 class="line"><span> 92</span> import { Log } from '</quote>hsutil<quote>'; const log = new Log('</quote>Config<quote>'); </p>
<p id=93 class="line"><span> 93</span></p>
<p id=94 class="line"><span> 94</span><comment>/**</comment></p>
<p id=95 class="line"><span> 95</span><comment> * `Component` class that creates a tree of mithril components out of a configuration obect or file.</comment></p>
<p id=96 class="line"><span> 96</span><comment> */</comment></p>
<p id=97 class="line"><span> 97</span>export class Config {</p>
<p id=98 class="line"><span> 98</span> async oninit(node:Vnode) {</p>
<p id=99 class="line"><span> 99</span> const context:any[] = node.attrs.context;</p>
<p id=100 class="line"><span> 100</span> if (!node.state.cfg) {</p>
<p id=101 class="line"><span> 101</span> const s = (typeof node.attrs.source === '</quote>string<quote>')?</p>
<p id=102 class="line"><span> 102</span> await m.request({ method: <quote>"GET"</quote>, url: node.attrs.source})</p>
<p id=103 class="line"><span> 103</span> : node.attrs.source;</p>
<p id=104 class="line"><span> 104</span> node.state.cfg = translate(s, s.root, context);</p>
<p id=105 class="line"><span> 105</span> }</p>
<p id=106 class="line"><span> 106</span> }</p>
<p id=107 class="line"><span> 107</span> view(node:Vnode) { </p>
<p id=108 class="line"><span> 108</span> const cfg = node.state.cfg;</p>
<p id=109 class="line"><span> 109</span> return (cfg && cfg.compClass)? m(cfg.compClass, Object.assign(Object.assign({}, cfg.attrs), node.attrs)) : m('</quote>div<quote>', '</quote>waiting<quote>');</p>
<p id=110 class="line"><span> 110</span> }</p>
<p id=111 class="line"><span> 111</span>}</p>
<p id=112 class="line"><span> 112</span></p>
<p id=113 class="line"><span> 113</span><comment>/**</comment></p>
<p id=114 class="line"><span> 114</span><comment> * recursively translates a configuration, trying to fetch the class definition for each element (key) in `config`. </comment></p>
<p id=115 class="line"><span> 115</span><comment> * If successful, it creates an object literal containing the component class and its attributes.</comment></p>
<p id=116 class="line"><span> 116</span><comment> * If unsuccessful, the element'</quote>s value is returned unaltered so that it can be consumed </comment></p>
<p id=117 class="line"><span> 117</span><comment> * by an instance further up in the recursion tree.</comment></p>
<p id=118 class="line"><span> 118</span><comment> * @param config an object literal containing the entire configuration tree</comment></p>
<p id=119 class="line"><span> 119</span><comment> * @param subcfg an object literal containing a configuration subtree</comment></p>
<p id=120 class="line"><span> 120</span><comment> * @param context an array of objects against which to instantiate elements of `config`.</comment></p>
<p id=121 class="line"><span> 121</span><comment> * @return an object literal representing the configuration, with Class names resolved </comment></p>
<p id=122 class="line"><span> 122</span><comment> * against the provided `context`.</comment></p>
<p id=123 class="line"><span> 123</span><comment> */</comment></p>
<p id=124 class="line"><span> 124</span>function translate(config:any, subcfg:any, context:any[]) {</p>
<p id=125 class="line"><span> 125</span> <comment>// resolve if synonym</comment></p>
<p id=126 class="line"><span> 126</span><comment></comment> if (isSynonym(config, subcfg)) { subcfg = config[subcfg]; }</p>
<p id=127 class="line"><span> 127</span> <comment>// return if primitive</comment></p>
<p id=128 class="line"><span> 128</span><comment></comment> if ([<quote>'string'</quote>, <quote>'number'</quote>, <quote>'boolean'</quote>, <quote>'function'</quote>].indexOf(typeof subcfg)>=0) { return subcfg; }</p>
<p id=129 class="line"><span> 129</span> let result = subcfg.length? [] : {};</p>
<p id=130 class="line"><span> 130</span> <comment>// step through options </comment></p>
<p id=131 class="line"><span> 131</span><comment></comment> const options = Object.keys(subcfg);</p>
<p id=132 class="line"><span> 132</span> options.map((opt:string) => {</p>
<p id=133 class="line"><span> 133</span> const cl:any = resolve(opt, context);</p>
<p id=134 class="line"><span> 134</span> const content = translate(config, subcfg[opt], context); </p>
<p id=135 class="line"><span> 135</span> <comment>// if a class resolution exists:</comment></p>
<p id=136 class="line"><span> 136</span><comment></comment> if (cl) { </p>
<p id=137 class="line"><span> 137</span> log.debug(()=>`resolved class <quote>'${opt}'</quote> to ${log.inspect(cl, {depth:1})}`);</p>
<p id=138 class="line"><span> 138</span> const r = {</p>
<p id=139 class="line"><span> 139</span> compClass:cl, <comment>// Component class</comment></p>
<p id=140 class="line"><span> 140</span><comment></comment> attrs:content <comment>// attributes passed to the Component class</comment></p>
<p id=141 class="line"><span> 141</span><comment></comment> };</p>
<p id=142 class="line"><span> 142</span> (!Array.isArray(subcfg) && options.length === 1)? </p>
<p id=143 class="line"><span> 143</span> result = r : </p>
<p id=144 class="line"><span> 144</span> result[opt] = r; </p>
<p id=145 class="line"><span> 145</span> }</p>
<p id=146 class="line"><span> 146</span> <comment>// otherwise, if no class resolution exists:</comment></p>
<p id=147 class="line"><span> 147</span><comment></comment> else { </p>
<p id=148 class="line"><span> 148</span> if (isNaN(parseInt(opt))) {</p>
<p id=149 class="line"><span> 149</span> log.debug(()=>`resolved direct <quote>'${opt}'</quote> to ${log.inspect(content)}`);</p>
<p id=150 class="line"><span> 150</span> }</p>
<p id=151 class="line"><span> 151</span> result[opt] = content; </p>
<p id=152 class="line"><span> 152</span> }</p>
<p id=153 class="line"><span> 153</span> }); </p>
<p id=154 class="line"><span> 154</span> return result; </p>
<p id=155 class="line"><span> 155</span>}</p>
<p id=156 class="line"><span> 156</span></p>
<p id=157 class="line"><span> 157</span><comment>/**</comment></p>
<p id=158 class="line"><span> 158</span><comment> * resolves the symbol `sym` against the provided `context`.</comment></p>
<p id=159 class="line"><span> 159</span><comment> * If `context` has a key `sym`, returns `context[sym]` as the literal definition for `sym`. </comment></p>
<p id=160 class="line"><span> 160</span><comment> * @param sym the symbol to resolve</comment></p>
<p id=161 class="line"><span> 161</span><comment> * @param context the context to resolve against; `mithril` and `hsLayout` </comment></p>
<p id=162 class="line"><span> 162</span><comment> * are implicitely part of the context and need not be specified.</comment></p>
<p id=163 class="line"><span> 163</span><comment> * @return the resolved Class, or `undefined`.</comment></p>
<p id=164 class="line"><span> 164</span><comment> */</comment></p>
<p id=165 class="line"><span> 165</span>function resolve(sym:string, context:any[]) {</p>
<p id=166 class="line"><span> 166</span> log.debug(()=>`resolving ${sym} in context <quote>'${log.inspect(context)}'</quote>`);</p>
<p id=167 class="line"><span> 167</span> let cl:any;</p>
<p id=168 class="line"><span> 168</span> context.some((c:any) => cl = c[sym]);</p>
<p id=169 class="line"><span> 169</span> log.debug(()=>`resolving ${sym} => ${log.inspect(cl)}`);</p>
<p id=170 class="line"><span> 170</span> return cl;</p>
<p id=171 class="line"><span> 171</span>}</p>
<p id=172 class="line"><span> 172</span></p>
<p id=173 class="line"><span> 173</span>function isSynonym(config:any, key:any) { return typeof key === <quote>'string'</quote> && config[key]; }</p>
<p id=174 class="line"><span> 174</span></p></code></div>
</body>