1 module awebview.gui.html;
2 
3 import carbon.utils;
4 import awebview.gui.activity;
5 import awebview.gui.methodhandler;
6 import awebview.gui.application;
7 
8 import awebview.wrapper;
9 
10 import awebview.jsbuilder;
11 import awebview.cssgrammar;
12 
13 import std.variant;
14 import std.typecons;
15 
16 import std.array : appender;
17 import std.format : formattedWrite;
18 import std.functional : forward;
19 import std.conv : to;
20 import std.datetime;
21 public import core.time : Duration;
22 
23 public import carbon.event : FiredContext;
24 import carbon.templates : Lstr;
25 import carbon.utils : toLiteral;
26 
27 
28 string buildHTMLTagAttr(in string[string] attrs)
29 {
30     auto app = appender!string();
31     foreach(k, v; attrs)
32         app.formattedWrite("%s=%s ", k, toLiteral(v));
33 
34     return app.data;
35 }
36 
37 
38 string buildHTMLTagAttr(string tag, string value)
39 {
40     import std.string : format;
41     return format("%s=%s ", tag, toLiteral(value));
42 }
43 
44 unittest
45 {
46     assert(buildHTMLTagAttr("a", "b") == "a=b ");
47     assert(buildHTMLTagAttr(["a": "b"]) == "a=b ");
48 }
49 
50 
51 abstract class HTMLPage
52 {
53     this(string id)
54     {
55         _id =  id;
56     }
57 
58 
59     final
60     @property
61     inout(Activity) activity() inout pure nothrow @safe @nogc
62     {
63         return _activity;
64     }
65 
66 
67     final
68     @property
69     inout(Application) application() inout pure nothrow @safe @nogc
70     {
71         if(_activity is null)
72             return null;
73         else
74             return _activity.application;
75     }
76 
77 
78     final
79     string id() const pure nothrow @safe @nogc @property { return _id; }
80 
81 
82     string html() const @property;
83 
84 
85     inout(HTMLElement[string]) elements() inout @property;
86 
87 
88     final
89     inout(HTMLElement) opIndex(string id) inout
90     {
91         return this.elements[id];
92     }
93 
94 
95     void onStart(Activity activity)
96     {
97         _activity = activity;
98 
99         foreach(key, elem; elements.maybeModified){
100             elem.onStart(this);
101         }
102     }
103 
104 
105     void onAttach(bool isInitPhase)
106     {
107         _activity = activity;
108         foreach(key, elem; elements.maybeModified)
109             elem.onAttach(isInitPhase);
110     }
111 
112 
113     void onLoad(bool isInit)
114     {
115         foreach(key, elem; elements.maybeModified)
116             elem.onLoad(isInit);
117 
118         _clientWidth = activity.evalJS(`document.documentElement.clientWidth`).get!uint;
119         _clientHeight = activity.evalJS(`document.documentElement.clientHeight`).get!uint;
120         _resizeStatements = generateResizeStatements(this.activity);
121         onResize(activity.width, activity.height);
122     }
123 
124 
125     void onUpdate()
126     {
127         auto newW = activity.evalJS(`document.documentElement.clientWidth`).get!uint;
128         auto newH = activity.evalJS(`document.documentElement.clientHeight`).get!uint;
129 
130         if(newW != _clientWidth || newH != _clientHeight)
131             onResize(activity.width, activity.height);
132 
133         _clientWidth = newW;
134         _clientHeight = newH;
135 
136         foreach(k, elem; elements.maybeModified)
137             elem.onUpdate();
138     }
139 
140 
141     void onDetach()
142     {
143         foreach(key, elem; elements.maybeModified)
144             elem.onDetach();
145     }
146 
147 
148     void onDestroy()
149     {
150         foreach(k, elem; elements.maybeModified)
151             elem.onDestroy();
152     }
153 
154 
155     void onResize(size_t w, size_t h)
156     {
157         this.activity.runJS(_resizeStatements);
158     }
159 
160 
161   private:
162     Activity _activity;
163     string _id;
164 
165     size_t _clientWidth;
166     size_t _clientHeight;
167 
168     string _resizeStatements;
169 }
170 
171 
172 class WebPage : HTMLPage
173 {
174     this(string id, string url)
175     {
176         super(id);
177         _url = url;
178     }
179 
180 
181     @property
182     void location(string str)
183     {
184         if(activity){
185             _url = str;
186             activity.view.loadURL(WebURL(str));
187         }
188         else
189             _url = str;
190     }
191 
192 
193     @property
194     string location()
195     {
196         if(activity)
197             return activity.evalJS(q{document.location}).to!string;
198         else
199             return _url;
200     }
201 
202 
203     override
204     void onAttach(bool isInitPhase)
205     {
206         _bLoaded = false;
207     }
208 
209 
210     override
211     void onUpdate()
212     {
213         if(!_bLoaded)
214             this.location = _url;
215 
216         _bLoaded = true;
217     }
218 
219 
220     override
221     inout(HTMLElement[string]) elements() inout @property { return null; }
222 
223 
224     override
225     string html() const
226     {
227         return `<html><head><title>Jump</title></head><body>Now loading...</body></html>`;
228     }
229 
230   private:
231     string _url;
232     bool _bLoaded;
233 }
234 
235 
236 class TemplateHTMLPage(string form) : HTMLPage
237 {
238     this(string id, Variant[string] exts = null)
239     {
240         super(id);
241         _exts = exts;
242     }
243 
244 
245     final
246     auto js(this This)() @property pure nothrow @safe @nogc inout
247     {
248         static struct Result
249         {
250             string html() const
251             {
252                 auto app = appender!string();
253                 foreach(_, e; _this._js)
254                     app.formattedWrite(`<script src="%s"></script>`);
255                 return app.data;
256             }
257 
258 
259             alias html this;
260 
261 
262             void opOpAssign(string op : "~")(string src)
263             {
264                 import std.path : baseName;
265 
266                 auto bn = src.baseName;
267                 _this._js[bn] = src;
268             }
269 
270 
271           private:
272             This _this;
273         }
274 
275 
276         return Result(this);
277     }
278 
279 
280     final
281     auto css(this This)() @property pure nothrow @safe @nogc inout
282     {
283         static struct Result
284         {
285             string html() const
286             {
287                 auto app = appender!string();
288                 foreach(_, e; _this._css)
289                     app.formattedWrite(`<link rel="stylesheet" href="%s">`);
290                 return app.data;
291             }
292 
293 
294             alias html this;
295 
296 
297             void opOpAssign(string op : "~")(string src)
298             {
299                 import std.path : baseName;
300 
301                 auto bn = src.baseName;
302                 _this._css[bn] = src;
303             }
304 
305 
306           private:
307             This _this;
308         }
309 
310 
311         return Result(this);
312     }
313 
314 
315     override
316     @property
317     string html() const
318     {
319         return mixin(Lstr!(form));
320     }
321 
322 
323     override
324     @property
325     inout(HTMLElement[string]) elements() inout { return _elems; }
326 
327 
328     void opOpAssign(string op : "~")(HTMLElement element)
329     {
330         addElement(element);
331     }
332 
333 
334     void addElement(HTMLElement element)
335     {
336         _elems[element.id] = element;
337     }
338 
339 
340     @property
341     inout(Variant[string]) exts() inout { return _exts; }
342 
343 
344   private:
345     HTMLElement[string] _elems;
346     Variant[string] _exts;
347     string[string] _js;
348     string[string] _css;
349 }
350 
351 
352 class HTMLElement
353 {
354     this(string id, bool doCreateObject)
355     in{
356         if(id is null)
357             assert(!doCreateObject);
358     }
359     body{
360         _id = id;
361         _hasObj = doCreateObject;
362     }
363 
364 
365     final
366     @property
367     inout(HTMLPage) page() inout pure nothrow @safe @nogc { return _page; }
368 
369 
370     final
371     @property
372     inout(Activity) activity() inout pure nothrow @safe @nogc
373     {
374         if(_page is null)
375             return null;
376         else
377             return _page.activity;
378     }
379 
380 
381     final
382     @property
383     inout(Application) application() inout pure nothrow @safe @nogc
384     {
385         if(this.activity is null)
386             return null;
387         else
388             return this.activity.application;
389     }
390 
391 
392     final
393     @property
394     bool hasObject() const pure nothrow @safe @nogc
395     {
396         return _hasObj;
397     }
398 
399 
400     @property
401     WeakRef!JSObject jsobject()
402     in {
403         assert(_v.isObject);
404     }
405     body {
406         return _v.get!(WeakRef!JSObject);
407     }
408 
409 
410     @property
411     JSExpression domObject()
412     in {
413         assert(hasObject || hasId);
414     }
415     body {
416         if(this.hasObject)
417             return JSExpression(mixin(Lstr!q{_tmp_%[_id%].domObject}), activity);
418         else
419             return JSExpression(`document.getElementById("` ~ id ~ `")`, activity);
420     }
421 
422 
423     final @property bool hasId() const pure nothrow @safe @nogc { return _id !is null; }
424 
425 
426     final @property string id() const pure nothrow @safe @nogc { return _id; }
427 
428 
429     @property string html() const { return ""; }
430     @property string mime() const { return "text/html"; }
431 
432 
433     void onStart(HTMLPage page)
434     {
435         _page = page;
436 
437         if(this.hasObject && !_v.isObject){
438             _v = activity.createObject(_id);
439         }
440     }
441 
442 
443     void onDestroy()
444     {
445         _page = null;
446         _v = JSValue.null_;
447     }
448 
449 
450     void onAttach(bool isInit)
451     {
452         if(isInit && this.hasObject && !_v.isObject){
453             _v = activity.createObject(_id);
454         }
455     }
456 
457 
458     void onUpdate() {}
459 
460 
461     void onDetach() {}
462 
463 
464     void onLoad(bool isInit)
465     {
466         if(this.hasObject){
467             activity.runJS(mixin(Lstr!q{
468                 _tmp_%[_id%] = {};
469                 _tmp_%[_id%].domObject = document.getElementById("%[_id%]");
470             }));
471         }
472 
473         if(this.hasId || this.hasObject){
474             foreach(key, ref v; _staticProperties)
475                 this.opIndexAssign(v, key);
476         }
477     }
478 
479 
480     final
481     @property
482     auto staticProps()
483     {
484         static struct Result
485         {
486             void opIndexAssign(T)(T value, string name)
487             if(is(typeof(JSValue(value)) : JSValue))
488             {
489                 _elem.staticPropsSet(name, value);
490             }
491 
492 
493             void remove(string name)
494             {
495                 _elem.staticPropsRemove(name);
496             }
497 
498 
499             bool opBinaryRight(string op : "in")(string name) inout
500             {
501                 return _elem.inStaticProps(name);
502             }
503 
504           private:
505             HTMLElement _elem;
506         }
507 
508         return Result(this);
509     }
510 
511 
512     void staticPropsSet(T)(string name, T value)
513     if(is(typeof(JSValue(value)) : JSValue))
514     in{
515         assert(this.hasId);
516     }
517     body{
518         JSValue jv = JSValue(value);
519         _staticProperties[name] = jv;
520         if(this.activity){
521             this.opIndexAssign(jv, name);
522         }
523     }
524 
525 
526     final
527     void staticPropsRemove(string name)
528     {
529         _staticProperties.remove(name);
530     }
531 
532 
533     final
534     bool inStaticProps(string name) inout
535     {
536         return !(name !in _staticProperties);
537     }
538 
539 
540     void opIndexAssign(T)(T value, string name)
541     if(is(typeof(JSValue(value)) : JSValue))
542     in{
543         assert(this.hasId);
544     }
545     body{
546         this.opIndexAssign(JSValue(value), name);
547     }
548 
549 
550     final
551     void opIndexAssign(JSValue value, string name)
552     in{
553         assert(this.hasId);
554     }
555     body{
556         if(value.isBoolean){
557             if(value.get!bool)
558                 activity.runJS(mixin(Lstr!
559                     q{%[domObject.jsExpr%].%[name%] = true;}
560                 ));
561             else
562                 activity.runJS(mixin(Lstr!
563                     q{%[domObject.jsExpr%].%[name%] = false;}
564                 ));
565         }else{
566             auto carrier = activity.carrierObject;
567             carrier.setProperty("value", value);
568 
569             activity.runJS(mixin(Lstr!
570                 q{%[domObject.jsExpr%].%[name%] = _carrierObject_.value;}
571             ));
572         }
573     }
574 
575 
576     final
577     JSValue opIndex(string name)
578     in{
579         assert(this.hasId);
580     }
581     body{
582         JSValue jv = activity.evalJS(mixin(Lstr!
583             q{%[domObject.jsExpr%].%[name%];}
584         ));
585 
586         return jv;
587     }
588 
589 
590     final
591     Tuple!(uint, "x", uint, "y", uint, "width", uint, "height")
592      boundingClientRect() @property
593     {
594         this.activity.runJS(mixin(Lstr!q{
595             var e = %[domObject.jsExpr%].getBoundingClientRect();
596             _carrierObject_.x = e.left;
597             _carrierObject_.y = e.top;
598             _carrierObject_.w = e.width;
599             _carrierObject_.h = e.height;
600         }));
601 
602         auto co = activity.carrierObject;
603         typeof(return) res;
604         res.x = co["x"].get!uint;
605         res.y = co["y"].get!uint;
606         res.width = co["w"].get!uint;
607         res.height = co["h"].get!uint;
608 
609         return res;
610     }
611 
612 
613     final
614     uint posY() @property
615     {
616         return domObject.invoke("getBoundingClientRect")["top"].eval().get!uint;
617     }
618 
619 
620     final
621     uint posX() @property
622     {
623         return domObject.invoke("getBoundingClientRect")["left"].eval().get!uint;
624     }
625 
626 
627     final
628     uint[2] pos() @property
629     {
630         auto rec = boundingClientRect();
631         return [rec.x, rec.y];
632     }
633 
634 
635     final
636     uint width() @property
637     {
638         return domObject.invoke("getBoundingClientRect")["width"].eval().get!uint;
639     }
640 
641 
642     final
643     uint height() @property
644     {
645         return domObject.invoke("getBoundingClientRect")["width"].eval().get!uint;
646     }
647 
648 
649     /**
650     .innerHTML
651     */
652     void innerHTML(string html)
653     in{
654         assert(this.hasId);
655     }
656     body{
657         (domObject["innerHTML"] = html).run();
658     }
659 
660 
661     /**
662     See also: HTML.insertAdjacentHTML
663     */
664     void insertAdjacentHTML(string pos, string html)
665     in{
666 
667     }
668     body{
669         domObject.invoke!"insertAdjacentHTML"(pos, html).run();
670     }
671 
672 
673     /**
674     append html to this, which is block element.
675     See also: jQuery.append
676     */
677     void append(string html)
678     in{
679         assert(this.hasId);
680     }
681     body{
682         this.insertAdjacentHTML("beforeend", html);
683     }
684 
685 
686     /**
687     prepend html to this which is block element.
688     See also: jQuery.prepend
689     */
690     void prepend(string html)
691     in{
692         assert(this.hasId);
693     }
694     body{
695         this.insertAdjacentHTML("afterbegin", html);
696     }
697 
698 
699     /**
700     insert html after this.
701     See also: jQuery.insertAfter
702     */
703     void insertAfter(string html)
704     in{
705         assert(this.hasId);
706     }
707     body{
708         this.insertAdjacentHTML("afterend", html);
709     }
710 
711 
712     /**
713     insert html before this.
714     See also: jQuery.insertBefore
715     */
716     void insertBefore(string html)
717     in{
718         assert(this.hasId);
719     }
720     body{
721         this.insertAdjacentHTML("beforebegin", html);
722     }
723 
724 
725     void invoke(T...)(string name, auto ref T args)
726     in{
727         assert(this.hasId);
728     }
729     body{
730       static if(T.length == 0)
731         activity.evalJS(mixin(Lstr!
732             q{%[domObject.jsExpr%].%[name%]();}
733         ));
734       else{
735         auto carrier = activity.carrierObject;
736         foreach(i, ref e; args)
737             carrier.setProperty(format("value%s", i), JSValue(e));
738 
739         activity.evalJS(mixin(Lstr!
740             q{%[domObject.jsExpr%].%[name%](%[format("%(_carrierObject_.value%s,%)", iota(args.length))%]);}
741         ));
742       }
743     }
744 
745 
746   private:
747     string _id;
748     bool _hasObj;
749     JSValue _v;
750     HTMLPage _page;
751     JSValue[string] _staticProperties;
752 }
753 
754 
755 class IDOnlyElement : HTMLElement
756 {
757     this(string id)
758     in{
759         assert(id !is null);
760     }
761     body{
762         super(id, false);
763     }
764 }
765 
766 
767 class TagOnlyElement : HTMLElement
768 {
769     this(string id)
770     in {
771         assert(id !is null);
772     }
773     body {
774         super(id, true);
775     }
776 }
777 
778 
779 /**
780 Example:
781 ----------------------
782 class MyButton : TemplateHTMLElement!(HTMLElement,
783     q{<input type="button" id="%[id%]" value="Click me!">})
784 {
785     this(string id)
786     {
787         super(id, null, false);
788     }
789 }
790 
791 auto btn1 = new MyButton("btn1");
792 assert(btn1.html == q{<input type="button" id="btn1" value="Click me!">});
793 ----------------------
794 */
795 abstract class TemplateHTMLElement(Element, string form) : Element
796 if(is(Element : HTMLElement))
797 {
798     this(T...)(auto ref T args)
799     {
800       static if(is(typeof(super(forward!args[0 .. $-1]))) &&
801                 is(typeof(args[$-1]) : Variant[string]))
802       {
803         super(forward!args[0 .. $-1]);
804         _exts = args[$-1];
805       }
806       else
807         super(forward!args);
808     }
809 
810 
811     @property
812     inout(Variant[string]) exts() inout { return _exts; }
813 
814 
815     override
816     @property
817     string html() const
818     {
819         import carbon.templates : Lstr;
820         return mixin(Lstr!(form));
821     }
822 
823 
824   private:
825     Variant[string] _exts;
826 }
827 
828 
829 /// ditto
830 alias TemplateHTMLElement(string form) = TemplateHTMLElement!(HTMLElement, form);
831 
832 
833 /**
834 Example:
835 ----------------
836 class MyButton : DefineSignals!(DeclareSignals!(HTMLElement, "onClick"), "onClick")
837 {
838     this(string id)
839     {
840         super(id, true);
841     }
842 
843     string html() const { ... }
844 }
845 
846 
847 MyButton btn1 = new MyButton("btn1");
848 btn1.onClick.strongConnect(delegate(FiredContext ctx, WeakRef!(const(JSArrayCpp)) arr){
849     assert(ctx.sender == btn1);
850 
851     writeln("fired a signal by ", ctx);
852 });
853 ----------------
854 */
855 abstract class DefineSignals(Element, names...) : Element
856 if(is(Element : HTMLElement) && names.length >= 1)
857 {
858     import carbon.event;
859 
860     this(T...)(auto ref T args)
861     {
862         super(forward!args);
863     }
864 
865 
866     mixin(genMethod());
867 
868 
869   private:
870     mixin(genField());
871 
872     static
873     {
874         string genField()
875         {
876             auto app = appender!string;
877             foreach(s; names)
878                 app.formattedWrite("EventManager!(WeakRef!(const(JSArrayCpp))) _%sEvent;\n", s);
879 
880             return app.data;
881         }
882 
883 
884         string genMethod()
885         {
886             auto app = appender!string;
887             foreach(s; names){
888                 app.formattedWrite("ref RestrictedSignal!(FiredContext, WeakRef!(const(JSArrayCpp))) %1$s() { return _%1$sEvent; }\n", s);
889                 app.formattedWrite("override void %1$s(WeakRef!(const(JSArrayCpp)) arr) { _%1$sEvent.emit(this, arr); }\n", s);
890             }
891 
892             return app.data;
893         }
894     }
895 }
896 
897 
898 /**
899 Example:
900 -------------------
901 class MyButton : DeclareSignals!(HTMLElement, "onClick")
902 {
903     this(string id)
904     {
905         super(id, true);
906     }
907 
908 
909     override
910     void onClick(WeakRef!(JSArrayCpp) args)
911     {
912         writeln("OK");
913     }
914 }
915 -------------------
916 */
917 abstract class DeclareSignals(Element, names...) : Element
918 if(is(Element : HTMLElement) && names.length >= 1)
919 {
920     this(T...)(auto ref T args)
921     {
922         super(forward!args);
923     }
924 
925 
926     final
927     void doJSInitialize(bool b) @property
928     {
929         _doInit = b;
930     }
931 
932 
933     final
934     void stopPropergation(bool b) @property
935     {
936         _stopProp = b;
937     }
938 
939 
940     mixin(genDeclMethods);
941 
942     override
943     void onStart(HTMLPage page)
944     {
945         super.onStart(page);
946         this.activity.methodHandler.set(this);
947     }
948 
949 
950     override
951     void onLoad(bool init)
952     {
953         super.onLoad(init);
954 
955         if(_doInit){
956             this.activity.runJS(genSettingEventHandlers(this.id, this.domObject.jsExpr, _stopProp));
957         }
958     }
959 
960 
961   private:
962     bool _doInit = true;
963     bool _stopProp = false;
964 
965     static
966     {
967         string genDeclMethods()
968         {
969             auto app = appender!string();
970 
971             foreach(s; names)
972                 app.formattedWrite(`@JSMethodTag("%1$s"w) `"void %1$s(WeakRef!(const(JSArrayCpp)));\n", s);
973 
974             return app.data;
975         }
976 
977 
978         string genSettingEventHandlers(string id, string domExpr, bool stopProp)
979         {
980             import std.string : toLower;
981 
982             auto app = appender!string();
983             app.formattedWrite("var e = %s;", domExpr);
984 
985             foreach(s; names){
986                 if(!stopProp)
987                     app.formattedWrite(q{e.%3$s = function() { %1$s.%2$s(); };}, id, s, toLower(s));
988                 else
989                     app.formattedWrite(q{e.%3$s = function(ev) { ev.stopPropergation(); %1$s.%2$s(); };}, id, s, toLower(s));
990             }
991 
992             return app.data;
993         }
994     }
995 }
996 
997 
998 alias DeclDefSignals(Element, names...) = DefineSignals!(DeclareSignals!(Element, names), names);
999 
1000 
1001 /**
1002 Open context menu when user click right button.
1003 */
1004 abstract class DeclareContextMenu(Element, setting...) : DeclareSignals!(Element, "onContextMenu", setting)
1005 {
1006     this(T...)(auto ref T args)
1007     {
1008         super(forward!args);
1009     }
1010 
1011 
1012     HTMLPage menuPage() @property;
1013 
1014 
1015     override
1016     void onContextMenu(WeakRef!(const(JSArrayCpp)))
1017     {
1018         this.activity.popup(this.menuPage);
1019     }
1020 }
1021 
1022 
1023 /**
1024 Mouse hover event
1025 */
1026 abstract class DeclareHoverSignal(Element) : DeclareSignals!(Element, "onMouseOver", "onMouseOut")
1027 {
1028     this(T...)(auto ref T args)
1029     {
1030         super(forward!args);
1031     }
1032 
1033 
1034     /**
1035     */
1036     final
1037     bool hover() @property { return _hovered; }
1038 
1039 
1040     final
1041     void onHoverImpl()
1042     {
1043         if(_isStarted)
1044             onHover(_hovered, Clock.currTime - _startTime);
1045     }
1046 
1047 
1048     /**
1049     */
1050     void onHover(bool bOver, Duration dur);
1051 
1052 
1053     override
1054     void onUpdate()
1055     {
1056         super.onUpdate();
1057 
1058         onHoverImpl();
1059     }
1060 
1061 
1062     override
1063     void onMouseOver(WeakRef!(const(JSArrayCpp)))
1064     {
1065         _isStarted = true;
1066         _hovered = true;
1067         _startTime = Clock.currTime;
1068         onHoverImpl();
1069     }
1070 
1071 
1072     override
1073     void onMouseOut(WeakRef!(const(JSArrayCpp)))
1074     {
1075         _hovered = false;
1076         _startTime = Clock.currTime;
1077         onHoverImpl();
1078     }
1079 
1080 
1081   private:
1082     bool _isStarted;
1083     bool _hovered;
1084     SysTime _startTime;
1085 }
1086 
1087 
1088 /**
1089 Selectors API
1090 */
1091 alias querySelector = querySelectorImpl!false;
1092 
1093 
1094 /// ditto
1095 alias querySelectorAll = querySelectorImpl!true;
1096 
1097 
1098 auto querySelectorImpl(bool isAll)(Activity activity, string cssSelector)
1099 {
1100     static struct Result
1101     {
1102         void opIndexAssign(T)(T value, string name)
1103         if(is(typeof(JSValue(value)) : JSValue))
1104         {
1105             this.opIndexAssign(JSValue(value), name);
1106         }
1107 
1108 
1109         void opIndexAssign(JSValue value, string name)
1110         {
1111             if(value.isBoolean){
1112                 if(value.get!bool)
1113                     (_expr[name] = true).run();
1114                 else
1115                     (_expr[name] = false).run();
1116             }else{
1117                 auto carrier = _expr.activity.carrierObject;
1118                 carrier.setProperty("value", value);
1119 
1120                 _expr.activity.runJS(mixin(Lstr!
1121                     q{%[_expr.jsExpr%].%[name%] = _carrierObject_.value;}
1122                 ));
1123             }
1124         }
1125 
1126 
1127         JSValue opIndex(string name)
1128         {
1129             return _expr[name].eval();
1130         }
1131 
1132 
1133         /**
1134         .innerHTML
1135         */
1136         void innerHTML(string html)
1137         {
1138             (_expr["innerHTML"] = html).run();
1139         }
1140 
1141 
1142         /**
1143         See also: HTML.insertAdjacentHTML
1144         */
1145         void insertAdjacentHTML(string pos, string html)
1146         {
1147             _expr.invoke!"insertAdjacentHTML"(pos, html).run();
1148         }
1149 
1150 
1151         /**
1152         append html to this, which is block element.
1153         See also: jQuery.append
1154         */
1155         void append(string html)
1156         {
1157             this.insertAdjacentHTML("beforeend", html);
1158         }
1159 
1160 
1161         /**
1162         prepend html to this which is block element.
1163         See also: jQuery.prepend
1164         */
1165         void prepend(string html)
1166         {
1167             this.insertAdjacentHTML("afterbegin", html);
1168         }
1169 
1170 
1171         /**
1172         insert html after this.
1173         See also: jQuery.insertAfter
1174         */
1175         void insertAfter(string html)
1176         {
1177             this.insertAdjacentHTML("afterend", html);
1178         }
1179 
1180 
1181         /**
1182         insert html before this.
1183         See also: jQuery.insertBefore
1184         */
1185         void insertBefore(string html)
1186         {
1187             this.insertAdjacentHTML("beforebegin", html);
1188         }
1189 
1190 
1191         void invoke(T...)(string name, auto ref T args)
1192         {
1193           static if(T.length == 0)
1194             _expr.activity.evalJS(mixin(Lstr!
1195                 q{%[_expr.jsExpr%].%[name%]();}
1196             ));
1197           else{
1198             auto carrier = _expr.activity.carrierObject;
1199             foreach(i, ref e; args)
1200                 carrier.setProperty(format("value%s", i), JSValue(e));
1201 
1202             _expr.activity.evalJS(mixin(Lstr!
1203                 q{%[_expr.jsExpr%].%[name%](%[format("%(_carrierObject_.value%s,%)", iota(args.length))%]);}
1204             ));
1205           }
1206         }
1207 
1208 
1209       private:
1210         JSExpression _expr;
1211     }
1212 
1213     Result res;
1214     res._expr = JSExpression(mixin(Lstr!q{document.%[isAll ? "querySelectorAll" : "querySelector"%](%[toLiteral(cssSelector)%])}),
1215                              activity);
1216     return res;
1217 }