1 module awebview.jsvariable;
2 
3 import std.algorithm;
4 import std.conv;
5 import std.string;
6 import std.typecons;
7 
8 import awebview.wrapper;
9 import awebview.gui.activity;
10 
11 
12 
13 enum bool isJSExpression(T) = is(typeof((T a){
14     string str = a.jsExpr;
15     Activity act = a.activity;
16 }));
17 
18 
19 mixin template JSExpressionOperators()
20 {
21     import std.format : format;
22     import std.range : iota;
23 
24     void opAssign(JSExpr)(JSExpr expr)
25     if(isJSExpression!JSExpr)
26     in{
27         assert(expr.activity is this.activity);
28     }
29     body{
30         this.activity.runJS(this.jsExpr ~ '=' ~ expr.jsExpr ~ ';');
31     }
32 
33 
34     auto opIndex()(string name)
35     {
36         return this.jsUnaryExpression("%s." ~ name);
37     }
38 
39 
40     void opIndexAssign(JSExpr)(JSExpr expr, string name)
41     if(isJSExpression!JSExpr)
42     in{
43         assert(expr.activity is this.activity);
44     }
45     body {
46         this.activity.runJS(this.jsExpr ~ '.' ~ name ~ '=' ~ expr.jsExpr ~ ';');
47     }
48 
49 
50     void opIndexAssign(T)(T expr, string name)
51     if(is(typeof(JSValue(expr))))
52     {
53         this.opIndexAssign(JSValue(expr), name);
54     }
55 
56 
57     void opIndexAssign(JSValue value, string name)
58     {
59         if(value.isBoolean){
60             if(value.get!bool)
61                 this.activity.runJS(this.jsExpr ~ '.' ~ name ~ "= true;");
62             else
63                 this.activity.runJS(this.jsExpr ~ '.' ~ name ~ "= false;");
64         }else{
65             auto c = this.activity.carrierObject;
66             c.setProperty("value", value);
67             this.activity.runJS(this.jsExpr ~ '.' ~ name ~ "=_carrierObject_.value;");
68         }
69     }
70 
71 
72     auto invoke(T...)(string name, auto ref T args) @system // dmd bug
73     {
74       static if(T.length == 0)
75         return this.jsUnaryExpression("%s." ~ name ~ "()");
76       else{
77         auto v = activity.newJSVariable();
78         foreach(i, ref e; args)
79             v[format("value%s", i)] = e;
80 
81         auto ret = this.jsBinaryExpression(v, "%1$s." ~ name ~ format("(%(%%2$s.value%s, %))", iota(args.length)));
82         return ret;
83       }
84     }
85 }
86 
87 
88 mixin template DOMOperators()
89 {
90     /**
91     .innerHTML
92     */
93     void innerHTML(string html) @property
94     {
95         this["innerHTML"] = html;
96     }
97 
98 
99     string innerHTML() @property
100     {
101         return this["innerHTML"].eval().to!string;
102     }
103 
104 
105     /**
106     See also: HTML.insertAdjacentHTML
107     */
108     void insertAdjacentHTML(string pos, string html)
109     {
110         this.invoke("insertAdjacentHTML", pos, html).run();
111     }
112 
113 
114     /**
115     append html to this, which is block element.
116     See also: jQuery.append
117     */
118     void append(string html)
119     {
120         this.insertAdjacentHTML("beforeend", html);
121     }
122 
123 
124     /**
125     prepend html to this which is block element.
126     See also: jQuery.prepend
127     */
128     void prepend(string html)
129     {
130         this.insertAdjacentHTML("afterbegin", html);
131     }
132 
133 
134     /**
135     insert html after this.
136     See also: jQuery.insertAfter
137     */
138     void insertAfter(string html)
139     {
140         this.insertAdjacentHTML("afterend", html);
141     }
142 
143 
144     /**
145     insert html before this.
146     See also: jQuery.insertBefore
147     */
148     void insertBefore(string html)
149     {
150         this.insertAdjacentHTML("beforebegin", html);
151     }
152 }
153 
154 
155 
156 mixin template JSExprDOMEagerOperators()
157 {
158     auto _jsExprObj() @property { return this.activity.jsExpression(this.jsExpr); }
159 
160     JSValue opIndex()(string name)
161     {
162         return this._jsExprObj[name].eval;
163     }
164 
165 
166     void opIndexAssign(JSExpr)(JSExpr expr, string name)
167     if(isJSExpression!JSExpr)
168     in{
169         assert(expr.activity is this.activity);
170     }
171     body {
172         this._jsExprObj[name] = expr;
173     }
174 
175 
176     void opIndexAssign(T)(T expr, string name)
177     if(is(typeof(JSValue(expr))))
178     {
179         this.opIndexAssign(JSValue(expr), name);
180     }
181 
182 
183     void opIndexAssign(JSValue value, string name)
184     {
185         this._jsExprObj[name] = value;
186     }
187 
188 
189     auto invoke(T...)(string name, auto ref T args)
190     {
191         return this._jsExprObj.invoke(name, forward!args).eval;
192     }
193 
194 
195         /**
196     .innerHTML
197     */
198     void innerHTML(string html) @property
199     {
200         this["innerHTML"] = html;
201     }
202 
203 
204     string innerHTML() @property
205     {
206         return this["innerHTML"].to!string;
207     }
208 
209 
210     /**
211     See also: HTML.insertAdjacentHTML
212     */
213     void insertAdjacentHTML(string pos, string html)
214     {
215         this.invoke("insertAdjacentHTML", pos, html);
216     }
217 
218 
219     /**
220     append html to this, which is block element.
221     See also: jQuery.append
222     */
223     void append(string html)
224     {
225         this.insertAdjacentHTML("beforeend", html);
226     }
227 
228 
229     /**
230     prepend html to this which is block element.
231     See also: jQuery.prepend
232     */
233     void prepend(string html)
234     {
235         this.insertAdjacentHTML("afterbegin", html);
236     }
237 
238 
239     /**
240     insert html after this.
241     See also: jQuery.insertAfter
242     */
243     void insertAfter(string html)
244     {
245         this.insertAdjacentHTML("afterend", html);
246     }
247 
248 
249     /**
250     insert html before this.
251     See also: jQuery.insertBefore
252     */
253     void insertBefore(string html)
254     {
255         this.insertAdjacentHTML("beforebegin", html);
256     }
257 }
258 
259 
260 
261 auto newJSVariable(Activity activity)
262 {
263     static ulong number;
264 
265     if(number == 0){
266         import std.random : rndGen;
267 
268         number = rndGen.front;
269         rndGen.popFront();
270     }else
271         ++number;
272 
273     immutable name = "jsv" ~ to!string(number);
274 
275 
276     static struct JSVariablePayload
277     {
278         ~this()
279         {
280             activity.runJS("delete " ~ _name ~ ";");
281         }
282 
283 
284         string jsExpr() const @property { return _name; }
285         Activity activity() @property { return _activity; }
286 
287       private:
288         Activity _activity;
289         string _name;
290     }
291 
292 
293     struct RefCountedJSVariable
294     {
295         RefCounted!JSVariablePayload impl;
296 
297         alias impl this;
298         mixin JSExpressionOperators!();
299     }
300 
301     activity.runJS(name ~ ` = {};`);
302     return RefCountedJSVariable(RefCounted!JSVariablePayload(activity, name));
303 }
304 
305 
306 struct JSExpressionCode
307 {
308     string jsExpr() const @property { return _code; }
309     Activity activity() @property { return _activity; }
310 
311     mixin JSExpressionOperators!();
312 
313   private:
314     Activity _activity;
315     string _code;
316 }
317 
318 
319 JSExpressionCode jsExpression(Activity activity, string jscode)
320 {
321     return JSExpressionCode(activity, jscode);
322 }
323 
324 
325 auto jsExpression(Activity activity, JSValue value)
326 {
327     auto v = activity.newJSVariable;
328     auto c = activity.carrierObject;
329 
330     c.setProperty("value", value);
331     v = activity.jsExpression("_carrierObject_.value");
332 
333     return v;
334 }
335 
336 
337 JSValue eval(JSExpr)(JSExpr expr)
338 if(isJSExpression!JSExpr)
339 {
340     return expr.activity.evalJS(expr.jsExpr);
341 }
342 
343 
344 void run(JSExpr)(JSExpr expr)
345 if(isJSExpression!JSExpr)
346 {
347     expr.activity.runJS(expr.jsExpr);
348 }
349 
350 
351 auto bindToJSVariable(JSExpr)(JSExpr expr)
352 if(isJSExpression!JSExpr)
353 {
354     auto v = expr.activity.newJSVariable;
355     v = activity.jsExpression(expr.jsExpr);
356     return v;
357 }
358 
359 
360 auto jsBinaryExpression(A, B)(A a, B b, string fmt)
361 if(isJSExpression!A && isJSExpression!B)
362 in{
363     assert(a.activity == b.activity);
364 }
365 body{
366     static struct JSBinaryExpression
367     {
368         Activity activity() @property { return _a.activity; }
369         string jsExpr() const @property { return _jsExpr; }
370 
371         mixin JSExpressionOperators!();
372 
373       private:
374         A _a;
375         B _b;
376         string _jsExpr;
377     }
378 
379 
380     return JSBinaryExpression(a, b, format(fmt, a.jsExpr, b.jsExpr));
381 }
382 
383 
384 auto jsUnaryExpression(A)(A a, string fmt)
385 if(isJSExpression!A)
386 {
387     static struct JSUnaryExpression
388     {
389         Activity activity() @property { return _a.activity; }
390         string jsExpr() const @property { return _jsExpr; }
391 
392         mixin JSExpressionOperators!();
393 
394       private:
395         A _a;
396         string _jsExpr;
397     }
398 
399     return JSUnaryExpression(a, format(fmt, a.jsExpr));
400 }