www_app 1.3.0 → 2.0.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/README.md +42 -24
  4. data/VERSION +1 -1
  5. data/bin/www_app +15 -4
  6. data/lib/public/vendor/hogan-3.0.2.min.js +5 -0
  7. data/lib/public/vendor/instruct_instruct_instruct.js +247 -0
  8. data/lib/public/vendor/jquery-2.1.3.min.js +4 -0
  9. data/lib/public/vendor/lodash.min.js +89 -0
  10. data/lib/public/www_app.js +885 -625
  11. data/lib/www_app/CSS.rb +310 -0
  12. data/lib/www_app/HTML.rb +219 -0
  13. data/lib/www_app/JavaScript.rb +51 -0
  14. data/lib/www_app/TO.rb +897 -0
  15. data/lib/www_app.rb +324 -945
  16. data/playground/config.ru +102 -0
  17. data/specs/client-side/index.html +1 -1
  18. data/specs/client-side/index.js +31 -379
  19. data/specs/lib/config.ru +60 -31
  20. data/specs/lib/helpers.rb +24 -2
  21. data/specs/server-side/0000-new.rb +1 -1
  22. data/specs/server-side/0001-underscore-double.rb +38 -0
  23. data/specs/server-side/0001-underscore.rb +73 -0
  24. data/specs/server-side/0010-attrs.rb +3 -4
  25. data/specs/server-side/0011-id.rb +5 -5
  26. data/specs/server-side/0020-tag.rb +2 -2
  27. data/specs/server-side/0020-tag_content.rb +1 -1
  28. data/specs/server-side/0021-body.rb +1 -1
  29. data/specs/server-side/0021-script.rb +66 -20
  30. data/specs/server-side/{0021-page_title.rb → 0021-title.rb} +5 -3
  31. data/specs/server-side/0030-mustache.rb +27 -20
  32. data/specs/server-side/0030-style.rb +64 -21
  33. data/specs/server-side/0040-css.rb +4 -4
  34. data/specs/server-side/0041-pseudo.rb +55 -0
  35. data/specs/server-side/0042-slash.rb +20 -0
  36. data/specs/server-side/0060-text.rb +26 -0
  37. metadata +18 -13
  38. data/lib/public/jquery-2.1.1.js +0 -4
  39. data/lib/public/underscore-1.7.0.js +0 -6
  40. data/lib/public/underscore-min.map +0 -1
  41. data/lib/public/underscore.string-2.3.0.js +0 -1
  42. data/lib/www_app/Clean.rb +0 -169
  43. data/lib/www_app/dsl.rb +0 -86
  44. data/lib/www_app/source.rb +0 -53
  45. data/specs/server-side/0050-on.rb +0 -64
  46. data/specs/server-side/0060-string.rb +0 -32
  47. /data/lib/public/{jquery.serialize-object.min.js → vendor/jquery.serialize-object.min.js} +0 -0
data/lib/www_app/TO.rb ADDED
@@ -0,0 +1,897 @@
1
+ require 'mustache'
2
+ require 'escape_escape_escape'
3
+
4
+ # ===================================================================
5
+ # === Symbol customizations: ========================================
6
+ # ===================================================================
7
+ class Symbol
8
+
9
+ def to_mustache *args
10
+ WWW_App::Clean.mustache *args, self
11
+ end
12
+
13
+ end # === class Symbol
14
+ # ===================================================================
15
+
16
+ # ===================================================================
17
+ # === Mustache customizations: ======================================
18
+ # ===================================================================
19
+ Mustache.raise_on_context_miss = true
20
+
21
+ class Mustache
22
+
23
+ def render(data = template, ctx = {})
24
+ ctx = data
25
+ tpl = templateify(template)
26
+
27
+ begin
28
+ context.push(ctx)
29
+ tpl.render(context)
30
+ ensure
31
+ context.pop
32
+ end
33
+ end # === def render
34
+
35
+ class Generator
36
+
37
+ alias_method :w_syms_on_fetch, :on_fetch
38
+
39
+ def on_fetch(names)
40
+ if names.length == 2
41
+ "ctx[#{names.first.to_sym.inspect}, #{names.last.to_sym.inspect}]"
42
+ else
43
+ w_syms_on_fetch(names)
44
+ end
45
+ end
46
+
47
+ end # === class Generator
48
+
49
+ class Context
50
+
51
+ def fetch *args
52
+ raise ContextMiss.new("Can't find: #{args.inspect}") if args.size != 2
53
+
54
+ meth, key = args
55
+
56
+ @stack.each { |frame|
57
+ case
58
+ when frame.is_a?(Hash) && meth == :coll && !frame.has_key?(key)
59
+ return false
60
+
61
+ when frame.is_a?(Hash) && meth == :coll && frame.has_key?(key)
62
+ target = frame[key]
63
+ if target == true || target == false || target == nil || target.is_a?(Array) || target.is_a?(Hash)
64
+ return target
65
+ end
66
+ fail "Invalid value: #{key.inspect} (#{key.class})"
67
+
68
+ when frame.is_a?(Hash) && frame.has_key?(key)
69
+ return ::Escape_Escape_Escape.send(meth, frame[key])
70
+
71
+ end
72
+ }
73
+
74
+ raise ContextMiss.new("Can't find .#{meth}(#{key.inspect})")
75
+ end
76
+
77
+ # NOTE: :alias_method has to go after the re-definition of
78
+ # :fetch or else it uses the original :fetch method/definition.
79
+ alias_method :[], :fetch
80
+
81
+
82
+ end # === class Context
83
+
84
+ end # === class Mustache
85
+ # ===================================================================
86
+
87
+ class WWW_App
88
+
89
+ class Clean
90
+
91
+ MUSTACHE_Regex = /\A[a-z0-9\_\.]+\z/i
92
+ PERIOD = '.'.freeze
93
+
94
+ class << self
95
+
96
+ def mustache *args
97
+ case args.size
98
+ when 2
99
+ meth, val = args
100
+ escape_it = false
101
+ when 3
102
+ escape_it, meth, val = args
103
+ else
104
+ fail ::ArgumentError, "Unknown args: #{args}"
105
+ end
106
+
107
+ v = meth.to_s + PERIOD + val.to_s
108
+ fail "Unknown chars: #{args.inspect}" unless v[MUSTACHE_Regex]
109
+
110
+ if escape_it
111
+ "!{ #{v} }!"
112
+ else
113
+ "{{{ #{v} }}}"
114
+ end
115
+ end
116
+
117
+ def method_missing name, *args
118
+ ::Escape_Escape_Escape.send(name, *args)
119
+ end
120
+
121
+ end # === class << self
122
+ end # === class Clean
123
+
124
+ module TO
125
+ COMMA = ", ".freeze
126
+ SPACE = " ".freeze
127
+ NOTHING = "".freeze
128
+ GEM_PATH = File.dirname(__FILE__).sub('lib/www_app'.freeze, NOTHING)
129
+ VERSION = File.read(GEM_PATH + '/VERSION').strip
130
+ JS_FILE_PATHS = begin
131
+ public = "#{GEM_PATH}/lib/public"
132
+ all = Dir.glob("#{public}/**/*.{map,js}").map { |path|
133
+ "/www_app-#{VERSION}/#{path.gsub("#{public}/", NOTHING)}"
134
+ }
135
+ special = all.select { |f| f[/(instruct.js|www_app.js)$/] }
136
+ filtered = all.reject { |f| special.include?(f) }
137
+ filtered + special
138
+ end
139
+
140
+ INVALID_SCRIPT_TYPE_CHARS = /[^a-z0-9\-\/\_]+/
141
+
142
+ KEY_REQUIRED = proc { |hash, k|
143
+ fail "Key not set: #{k.inspect}"
144
+ }
145
+
146
+ def to_raw_text
147
+ str = ""
148
+ indent = 0
149
+ print_tag = lambda { |t|
150
+ info = t.select { |n| [:id, :class, :closed, :pseudo].include?( n ) }
151
+ info[:parent] = t[:parent] && t[:parent][:tag_name]
152
+
153
+ str += "#{" " * indent}#{t[:tag_name].inspect} -- #{info.inspect.gsub(/\A\{|\}\Z/, '')}\n"
154
+ indent += 1
155
+ if t[:children]
156
+ t[:children].each { |c|
157
+ str << print_tag.call(c)
158
+ }
159
+ end
160
+ indent -= 1
161
+ }
162
+
163
+ @tags.each { |t| print_tag.call(t) }
164
+ str
165
+ end
166
+
167
+ def to_html *args
168
+ return @mustache.render(*args) if instance_variable_defined?(:@mustache)
169
+
170
+ final = ""
171
+ indent = 0
172
+ todo = @tags.dup
173
+ last = nil
174
+ stacks = {:js=>[], :script_tags=>[]}
175
+ last_open = nil
176
+
177
+ script_libs_added = false
178
+
179
+ doc = [
180
+ doc_type = {:tag_name=>:doc_type , :text => "<!DOCTYPE html>"},
181
+ html = {:tag_name=>:html , :children=>[
182
+ head = {:tag_name=>:head , :lang=>'en', :children=>[]},
183
+ body = {:tag_name=>:body , :children=>[]}
184
+ ]}
185
+ ]
186
+ style_tags = {:tag_name => :style_tags, :children => []}
187
+
188
+ tags = @tags.dup
189
+ while (t = tags.shift)
190
+ t_name = t[:tag_name]
191
+ parent = t[:parent]
192
+
193
+ case # ==============
194
+ when t_name == :title && !parent
195
+ fail "Title already set." if head[:children].detect { |c| c[:tag_name] == :title }
196
+ head[:children] << t
197
+
198
+ when t_name == :meta
199
+ head[:children] << t
200
+
201
+ when t_name == :style
202
+ style_tags[:children] << t
203
+
204
+ when t_name == :_ && !parent
205
+ body[:css] = (body[:css] || {}).merge(t[:css]) if t[:css]
206
+ body[:class] = (body[:class] || []).concat(t[:class]) if t[:class]
207
+
208
+ if t[:id]
209
+ fail ":body already has id: #{body[:id].inspect}, #{t[:id]}" if body[:id]
210
+ body[:id] = t[:id]
211
+ end
212
+
213
+ if t[:children]
214
+ body[:children].concat t[:children]
215
+ tags = t[:children].dup.concat(tags)
216
+ end
217
+
218
+ else # ==============
219
+ if !parent
220
+ body[:children] << t
221
+ end
222
+
223
+ if t[:css]
224
+ style_tags[:children] << t
225
+ end
226
+
227
+ if t[:children]
228
+ tags = t[:children].dup.concat(tags)
229
+ end
230
+
231
+ if t_name == :script
232
+ stacks[:script_tags] << t
233
+ end
234
+
235
+ if t_name == :js
236
+ stacks[:js].concat [css_selector(t[:parent])].concat(t[:value])
237
+ end
238
+
239
+ end # === case ========
240
+ end # === while
241
+
242
+ if body[:css] && !body[:css].empty?
243
+ style_tags[:children] << body
244
+ end
245
+
246
+ is_fragment = stacks[:script_tags].empty? && stacks[:js].empty? && style_tags[:children].empty? && head[:children].empty? && body.values_at(:css, :id, :class).compact.empty?
247
+
248
+ if is_fragment
249
+ doc = body[:children]
250
+
251
+ else # is doc
252
+ head[:children] << style_tags
253
+ content_type = head[:children].detect { |t| t[:tag_name] == :meta && t[:http_equiv] && t[:http_equiv].downcase=='Content-Type'.downcase }
254
+ if !content_type
255
+ head[:children].unshift(
256
+ {:tag_name=>:meta, :http_equiv=>'Content-Type', :content=>"text/html; charset=UTF-8"}
257
+ )
258
+ end
259
+
260
+ end # if is_fragment
261
+
262
+ todo = doc.dup
263
+ while (tag = todo.shift)
264
+ t_name = tag.is_a?(Hash) && tag[:tag_name]
265
+
266
+ case
267
+
268
+ when tag == :new_line
269
+ final << NEW_LINE
270
+
271
+ when tag == :open
272
+ attributes = stacks.delete :attributes
273
+
274
+ tag_sym = todo.shift
275
+
276
+ if [:script].include?(tag_sym) || (todo.first != :close && !indent.zero? && !HTML::NO_NEW_LINES.include?(last_open))
277
+ final << NEW_LINE << SPACES(indent)
278
+ end
279
+
280
+ if HTML::SELF_CLOSING_TAGS.include?(tag_sym)
281
+ final << (
282
+ attributes ?
283
+ "<#{tag_sym} #{attributes} />\n" :
284
+ "<#{tag_sym} />\n"
285
+ )
286
+ if todo.first == :close && todo[1] == tag_sym
287
+ todo.shift
288
+ todo.shift
289
+ end
290
+
291
+ else # === has closing tag
292
+ if attributes
293
+ final << "<#{tag_sym} #{attributes}>"
294
+ else
295
+ final << "<#{tag_sym}>"
296
+ end
297
+ end # === if HTML
298
+
299
+ last = indent
300
+ indent += 2
301
+ last_open = tag_sym
302
+
303
+
304
+ when tag == :close
305
+ indent -= 2
306
+ if last != indent
307
+ final << SPACES(indent)
308
+ end
309
+ last = indent
310
+ final << "</#{todo.shift}>"
311
+
312
+ when tag == :clean_attrs
313
+ attributes = todo.shift
314
+ target = todo.shift
315
+ tag_name = target[:tag_name]
316
+
317
+ attributes.each { |attr, val|
318
+ attributes[attr] = case
319
+
320
+ when attr == :src && tag_name == :script
321
+ fail ::ArgumentError, "Invalid type: #{val.inspect}" unless val.is_a?(String)
322
+ Clean.relative_href val
323
+
324
+ when attr == :type && tag_name == :script
325
+ clean = val.gsub(INVALID_SCRIPT_TYPE_CHARS, '')
326
+ clean = 'text/unknown' if clean.empty?
327
+ clean
328
+
329
+ when attr == :type && val == :hidden
330
+ 'hidden'
331
+
332
+ when attr == :href && tag_name == :a
333
+ if val.is_a? Symbol
334
+ Clean.mustache :href, val
335
+ else
336
+ Clean.href val
337
+ end
338
+
339
+ when [:action, :src, :href].include?(attr)
340
+ Clean.relative_href(val)
341
+
342
+ when attr == :id
343
+ Clean.html_id(val.to_s)
344
+
345
+ when attr == :class
346
+ val.map { |name|
347
+ Clean.css_class_name(name.to_s)
348
+ }.join(" ".freeze)
349
+
350
+ when tag_name == :style && attr == :type
351
+ 'text/css'
352
+
353
+ when ::WWW_App::HTML::TAGS_TO_ATTRIBUTES[tag_name].include?(attr)
354
+ Clean.html(val)
355
+
356
+ else
357
+ fail "Invalid attr: #{attr.inspect}"
358
+
359
+ end # case attr
360
+
361
+ } # === each attr
362
+
363
+ stacks[:attributes] = attributes.inject([]) { |memo, (k,v)|
364
+ memo << "#{k}=\"#{v}\""
365
+ memo
366
+ }.join " ".freeze
367
+
368
+ when t_name == :doc_type
369
+ if tag[:text] == "<!DOCTYPE html>"
370
+ final << tag[:text]
371
+ final << NEW_LINE
372
+ else
373
+ fail "Unknown doc type: #{tag[:text].inspect}"
374
+ end
375
+
376
+ when t_name == :text
377
+ final.<<(
378
+ tag[:skip_escape] ?
379
+ tag[:value] :
380
+ Clean.html(tag[:value])
381
+ )
382
+
383
+
384
+ when t_name == :meta
385
+ case
386
+ when tag[:http_equiv]
387
+ key_name = "http-equiv"
388
+ key_content = tag[:http_equiv].gsub(/[^a-zA-Z\/\;\ 0-9\=\-]+/, '')
389
+ content = tag[:content].gsub(/[^a-zA-Z\/\;\ 0-9\=\-]+/, '')
390
+ else
391
+ fail ArgumentError, tag.keys.inspect
392
+ end
393
+
394
+ final << (
395
+ %^#{SPACES(indent)}<meta #{key_name}="#{key_content}" content="#{content}" />^
396
+ )
397
+
398
+ when t_name == :html # === :html tag ================
399
+ todo = [
400
+ :clean_attrs, {:lang=>(tag[:lang] || 'en')}, tag,
401
+ :open, :html
402
+ ].concat(tag[:children]).concat([:new_line, :close, :html]).concat(todo)
403
+
404
+ when t_name == :head # === :head tag =================
405
+ todo = [ :open, :head, :new_line ].
406
+ concat(tag[:children] || []).
407
+ concat([:new_line, :close, :head]).
408
+ concat(todo)
409
+
410
+ when t_name == :title && !parent(tag)
411
+ todo = [
412
+ :open, :title
413
+ ].concat(tag[:children]).concat([:close, :title]).concat(todo)
414
+
415
+ when t_name == :_ # =============== :_ tag ========
416
+ nil # do nothing
417
+
418
+ when t_name == :js
419
+ next
420
+
421
+ when t_name == :script # =============== :script tag ===
422
+
423
+ attrs = tag.select { |k, v|
424
+ k == :src || k == :type || k == :class
425
+ }
426
+
427
+ new_todo = []
428
+
429
+ if attrs[:src] && !script_libs_added
430
+ new_todo << {:tag_name=>:js_to_script_tag}
431
+ script_libs_added = true
432
+ end
433
+
434
+ new_todo.concat [
435
+ :clean_attrs, attrs, tag,
436
+ :open, :script,
437
+ ]
438
+
439
+ new_todo.concat(tag[:children]) if tag[:children]
440
+
441
+ if tag[:children] && !tag[:children].empty? && tag[:children].first[:tag_name] != :text && tag[:children].last[:tag_name] != :text
442
+ new_todo << :new_line
443
+ end
444
+
445
+ new_todo.concat [
446
+ :close, :script
447
+ ].concat(todo)
448
+
449
+ todo = new_todo
450
+
451
+ when t_name == :js_to_script_tag
452
+ next if stacks[:js].empty? && stacks[:script_tags].empty?
453
+ stacks[:clean_text] ||= lambda { |raw_x|
454
+ x = case raw_x
455
+ when ::Symbol, ::String
456
+ Clean.html(raw_x.to_s)
457
+ when ::Array
458
+ raw_x.map { |x| stacks[:clean_text].call x }
459
+ when ::Numeric
460
+ x
461
+ else
462
+ fail "Unknown type for json: #{raw_x.inspect}"
463
+ end
464
+ }
465
+
466
+ script_tag = {:tag_name=>:script}.freeze
467
+
468
+ new_todo = []
469
+ JS_FILE_PATHS.each { |path|
470
+ new_todo.concat [
471
+ :clean_attrs, {:src=>path}, script_tag,
472
+ :open, :script,
473
+ :close, :script
474
+ ]
475
+ }
476
+
477
+ clean_vals = stacks[:js].map { |raw_x| stacks[:clean_text].call(raw_x) }
478
+ content = <<-EOF
479
+ \n#{SPACES(indent)}WWW_App.run( #{::Escape_Escape_Escape.json_encode(clean_vals)} );
480
+ EOF
481
+
482
+ new_todo.concat [
483
+ :clean_attrs, {:type=>'application/javascript'}, script_tag,
484
+ :open, :script,
485
+ {:tag_name=>:text, :skip_escape=>true, :value=> content },
486
+ :close, :script
487
+ ]
488
+
489
+ todo = new_todo.concat(todo)
490
+
491
+ when tag == :javascript
492
+ vals = todo.shift
493
+
494
+ when tag == :to_json
495
+ vals = todo.shift
496
+ ::Escape_Escape_Escape.json_encode(to_clean_text(:javascript, vals))
497
+
498
+
499
+ when t_name == :style
500
+ next
501
+
502
+ when t_name == :style_tags # =============== <style ..> TAG =================
503
+ next if tag[:children].empty?
504
+
505
+ style_and_other_tags = tag[:children].dup
506
+ flatten = []
507
+
508
+ # === flatten groups
509
+ # style
510
+ # div, span {
511
+ # a:link, a:visited {
512
+ # --->
513
+ # style
514
+ # div a:link, div a:visited, span a:link, span a:visited {
515
+ #
516
+ prev = nil
517
+
518
+ while e = style_and_other_tags.shift
519
+ case
520
+ when e[:tag_name] == :style
521
+ style_and_other_tags = e[:children].dup.concat(style_and_other_tags)
522
+
523
+ when e[:tag_name] == :group
524
+ style_and_other_tags = e[:children].dup.concat(style_and_other_tags)
525
+ prev = nil
526
+ flatten << e
527
+
528
+ when parent?(e, :group)
529
+ if e[:__]
530
+ e[:__children] = []
531
+ end
532
+
533
+ if prev && prev[:__]
534
+ prev[:__children] << e
535
+ e[:__parent] = prev
536
+ end
537
+ prev = e
538
+
539
+ else
540
+ flatten << e
541
+
542
+ end # === case
543
+ end # === while
544
+
545
+ todo = [
546
+ :clean_attrs, {:type=>'text/css'}, {:tag_name=>:style},
547
+ :open, :style,
548
+ :flat_style_groups, flatten,
549
+ :close, :style
550
+ ].concat(todo)
551
+
552
+ when tag == :flat_style_groups
553
+
554
+ indent += 2
555
+ css_final = ""
556
+ flatten = todo.shift
557
+
558
+ #
559
+ # Each produces:
560
+ #
561
+ # selectors {
562
+ # escaped/sanitized css;
563
+ # }
564
+ #
565
+ flatten.each { |style|
566
+ next if !style[:css] || style[:css].empty?
567
+
568
+ css_final << "\n" << SPACES(indent) << css_selector(style, :full) << " {\n".freeze
569
+
570
+ the_css = style[:css] || (parent?(style, :group) && style[:parent][:css])
571
+ if the_css
572
+ indent += 2
573
+ the_css.each { |raw_k, raw_val|
574
+ name = begin
575
+ clean_k = ::WWW_App::Clean.css_attr(raw_k.to_s.gsub('_','-'))
576
+ fail("Invalid name for css property name: #{raw_k.inspect}") if !clean_k || clean_k.empty?
577
+ clean_k
578
+ end
579
+
580
+ raw_val = raw_val.is_a?(Array) ? raw_val.join(COMMA) : raw_val.to_s
581
+
582
+ v = case
583
+
584
+ when name[IMAGE_AT_END]
585
+ case raw_val
586
+ when 'inherit', 'none'
587
+ raw_val
588
+ else
589
+ "url(#{Clean.href(raw_val)})"
590
+ end
591
+
592
+ when ::WWW_App::CSS::PROPERTIES.include?(raw_k)
593
+ Clean.css_value raw_val
594
+
595
+ else
596
+ fail "Invalid css attr: #{name.inspect}"
597
+
598
+ end # === case
599
+
600
+ css_final << SPACES(indent) << "#{name}: #{v};\n"
601
+ } # === each style
602
+ indent -= 2
603
+ end # === if style[:css]
604
+
605
+ css_final << SPACES(indent) << "}\n".freeze << SPACES(indent - 2)
606
+ }
607
+
608
+ indent -= 2
609
+ todo = [
610
+ {:tag_name=>:text, :skip_escape=>true, :value=>css_final}
611
+ ].concat(todo)
612
+
613
+ when tag == :script # ============
614
+ h = vals
615
+
616
+ if h[:tag] == :script && h[:content] && !h[:content].empty?
617
+ return <<-EOF
618
+ <script type="text/css">
619
+ WWW_App.run(
620
+ #{to_clean_text :to_json, h[:content]}
621
+ );
622
+ </script>
623
+ EOF
624
+ end
625
+
626
+ html = h[:childs].map { |tag_index|
627
+ to_clean_text(:html, @tag_arr[tag_index])
628
+ }.join(NEW_LINE).strip
629
+
630
+ return unless h[:render?]
631
+
632
+ if html.empty? && h[:text]
633
+ html = if h[:text].is_a?(::Symbol)
634
+ h[:text].to_mustache(:html)
635
+ else
636
+ if h[:text].is_a?(::Hash)
637
+ if h[:text][:escape] == false
638
+ h[:text][:value]
639
+ else
640
+ Clean.html(h[:text][:value].strip)
641
+ end
642
+ else
643
+ Clean.html(h[:text].strip)
644
+ end
645
+ end
646
+ end # === if html.empty?
647
+
648
+ (html = nil) if html.empty?
649
+
650
+ case
651
+ when h[:tag] == :render_if
652
+ key = h[:attrs][:key]
653
+ open = "{{# coll.#{key} }}"
654
+ close = "{{/ coll.#{key} }}"
655
+
656
+ when h[:tag] == :render_unless
657
+ key = h[:attrs][:key]
658
+ open = "{{^ coll.#{key} }}"
659
+ close = "{{/ coll.#{key} }}"
660
+
661
+ when Methods[:elements].include?(h[:tag])
662
+ open = "<#{h[:tag]}#{to_clean_text(:attrs, h)}"
663
+ if NO_END_TAGS.include?(h[:tag])
664
+ open += ' />'
665
+ close = nil
666
+ else
667
+ open += '>'
668
+ close = "</#{h[:tag]}>"
669
+ end
670
+
671
+ else
672
+ fail "Unknown html tag: #{h[:tag].inspect}"
673
+
674
+ end # === case h[:tag]
675
+
676
+ if h[:tag]
677
+ [open, html, close].compact.join
678
+ else
679
+ html
680
+ end # === if
681
+
682
+ when t_name && ::WWW_App::HTML::TAGS.include?(t_name) # === HTML tags =====
683
+
684
+ # ================================
685
+ # === Save this for last to allow
686
+ # certain tags
687
+ # to be over-riddent,
688
+ # like :script
689
+ # ================================
690
+
691
+ attrs = {}
692
+ attrs.default KEY_REQUIRED
693
+
694
+ new_todo = []
695
+ t2a = ::WWW_App::HTML::TAGS_TO_ATTRIBUTES
696
+
697
+ tag.each { |attr_name, v|
698
+ if t2a[:all].include?(attr_name) || (t2a[tag[:tag_name]] && t2a[tag[:tag_name]].include?(attr_name))
699
+ attrs[attr_name] = v
700
+ end
701
+ }
702
+
703
+ if !attrs.empty?
704
+ new_todo.concat [:clean_attrs, attrs, tag]
705
+ end
706
+
707
+ new_todo.concat [:open, tag[:tag_name]]
708
+
709
+ if tag[:children] && !tag[:children].empty?
710
+ new_todo.concat tag[:children]
711
+ if tag[:children].detect { |t| HTML::TAGS.include?(t[:tag_name]) }
712
+ new_todo << :new_line
713
+ end
714
+ end
715
+ new_todo.concat [:close, tag[:tag_name]]
716
+ todo = new_todo.concat(todo)
717
+
718
+ else
719
+ fail "Unknown: #{tag.inspect[0,30]}"
720
+ end # === case
721
+ end # === while
722
+
723
+ final
724
+
725
+ @mustache ||= begin
726
+ mustache = ::Mustache.new
727
+ mustache.template = Clean.clean_utf8(final)
728
+ mustache
729
+ end
730
+
731
+ to_html(*args)
732
+ end # === to_html
733
+
734
+ module OLD
735
+ #
736
+ # Examples
737
+ # dom_id -> the current dom id of the current element
738
+ # dom_id :default -> if no dom it, set/get default of current element
739
+ # dom_id {:element:} -> dom id of element: {:type=>:html, :tag=>...}
740
+ #
741
+ def dom_id *args
742
+
743
+ use_default = false
744
+
745
+ case
746
+ when args.empty?
747
+ e = tag!
748
+ # do nothing else
749
+
750
+ when args.size == 1 && args.first == :default
751
+ e = tag!
752
+ use_default = true
753
+
754
+ when args.size == 1 && args.first.is_a?(::Hash) && args.first[:type]==:html
755
+ e = args.first
756
+
757
+ else
758
+ fail "Unknown args: #{args.inspect}"
759
+ end
760
+
761
+ id = e[:attrs][:id]
762
+ return id if id
763
+ return nil unless use_default
764
+
765
+ e[:default_id] ||= begin
766
+ key = e[:tag]
767
+ @default_ids[key] ||= -1
768
+ @default_ids[key] += 1
769
+ end
770
+ end # === def dom_id
771
+
772
+ #
773
+ # Examples
774
+ # selector_id -> a series of ids and tags to be used as a JS selector
775
+ # Example:
776
+ # #id tag tag
777
+ # tag tag
778
+ #
779
+ #
780
+ def selector_id
781
+ i = tag![:tag_index]
782
+ id_given = false
783
+ classes = []
784
+
785
+ while !id_given && i && i > -1
786
+ e = @tag_arr[i]
787
+ id = dom_id e
788
+ (id_given = true) if id
789
+
790
+ if e[:tag] == :body && !classes.empty?
791
+ # do nothing because
792
+ # we do not want 'body tag.class tag.class'
793
+ else
794
+ case
795
+ when id
796
+ classes << "##{id}"
797
+ else
798
+ classes << e[:tag]
799
+ end # === case
800
+ end # === if
801
+
802
+ i = e[:parent_index]
803
+ end
804
+
805
+ return 'body' if classes.empty?
806
+ classes.join SPACE
807
+ end
808
+
809
+ #
810
+ # Examples
811
+ # css_id -> current css id of element.
812
+ # It uses the first class, if any, found.
813
+ # #id.class -> if #id and first class found.
814
+ # #id -> if class is missing and id given.
815
+ # #id tag.class -> if class given and ancestor has id.
816
+ # #id tag tag -> if no class given and ancestor has id.
817
+ # tag tag tag -> if no ancestor has class.
818
+ #
819
+ # css_id :my_class -> same as 'css_id()' except
820
+ # 'my_class' overrides :class attribute of current
821
+ # element.
822
+ #
823
+ #
824
+ def css_id *args
825
+
826
+ str_class = nil
827
+
828
+ case args.size
829
+ when 0
830
+ fail "Not in a tag." unless tag!
831
+ str_class = @css_id_override
832
+ when 1
833
+ str_class = args.first
834
+ else
835
+ fail "Unknown args: #{args.inspect}"
836
+ end
837
+
838
+ i = tag![:tag_index]
839
+ id_given = false
840
+ classes = []
841
+
842
+ while !id_given && i && i > -1
843
+ e = @tag_arr[i]
844
+ id = dom_id e
845
+ first_class = e[:attrs][:class].first
846
+
847
+ if id
848
+ id_given = true
849
+ if str_class
850
+ classes.unshift(
851
+ str_class.is_a?(::Symbol) ?
852
+ "##{id}.#{str_class}" :
853
+ "##{id}#{str_class}"
854
+ )
855
+ else
856
+ classes.unshift "##{id}"
857
+ end
858
+
859
+ else # no id given
860
+ if str_class
861
+ classes.unshift(
862
+ str_class.is_a?(::Symbol) ?
863
+ "#{e[:tag]}.#{str_class}" :
864
+ "#{e[:tag]}#{str_class}"
865
+ )
866
+ elsif first_class
867
+ classes.unshift "#{e[:tag]}.#{first_class}"
868
+ else
869
+ if e[:tag] != :body || (classes.empty?)
870
+ classes.unshift "#{e[:tag]}"
871
+ end
872
+ end # if first_class
873
+
874
+ end # if id
875
+
876
+ i = e[:parent_index]
877
+ break if i == @body[:tag_index] && !classes.empty?
878
+ end
879
+
880
+ classes.join SPACE
881
+ end
882
+
883
+ end # === module OLD
884
+ end # === module TO
885
+ end # === class WWW_App
886
+
887
+
888
+
889
+ __END__
890
+ <!DOCTYPE html>
891
+ <html lang="en">
892
+ <head>
893
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
894
+ :HEAD
895
+ </head>
896
+ :BODY
897
+ </html>