www_app 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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>