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 }