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 auto jsExpression(Activity activity, string jscode)
307 {
308     static struct JSExpressionCode
309     {
310         string jsExpr() const @property { return _code; }
311         Activity activity() @property { return _activity; }
312 
313         mixin JSExpressionOperators!();
314 
315       private:
316         Activity _activity;
317         string _code;
318     }
319 
320     return JSExpressionCode(activity, jscode);
321 }
322 
323 
324 auto jsExpression(Activity activity, JSValue value)
325 {
326     auto v = activity.newJSVariable;
327     auto c = activity.carrierObject;
328 
329     c.setProperty("value", value);
330     v = activity.jsExpression("_carrierObject_.value");
331 
332     return v;
333 }
334 
335 
336 JSValue eval(JSExpr)(JSExpr expr)
337 if(isJSExpression!JSExpr)
338 {
339     return expr.activity.evalJS(expr.jsExpr);
340 }
341 
342 
343 void run(JSExpr)(JSExpr expr)
344 if(isJSExpression!JSExpr)
345 {
346     expr.activity.runJS(expr.jsExpr);
347 }
348 
349 
350 auto bindToJSVariable(JSExpr)(JSExpr expr)
351 if(isJSExpression!JSExpr)
352 {
353     auto v = expr.activity.newJSVariable;
354     v = activity.jsExpression(expr.jsExpr);
355     return v;
356 }
357 
358 
359 auto jsBinaryExpression(A, B)(A a, B b, string fmt)
360 if(isJSExpression!A && isJSExpression!B)
361 in{
362     assert(a.activity == b.activity);
363 }
364 body{
365     static struct JSBinaryExpression
366     {
367         Activity activity() @property { return _a.activity; }
368         string jsExpr() const @property { return _jsExpr; }
369 
370         mixin JSExpressionOperators!();
371 
372       private:
373         A _a;
374         B _b;
375         string _jsExpr;
376     }
377 
378 
379     return JSBinaryExpression(a, b, format(fmt, a.jsExpr, b.jsExpr));
380 }
381 
382 
383 auto jsUnaryExpression(A)(A a, string fmt)
384 if(isJSExpression!A)
385 {
386     static struct JSUnaryExpression
387     {
388         Activity activity() @property { return _a.activity; }
389         string jsExpr() const @property { return _jsExpr; }
390 
391         mixin JSExpressionOperators!();
392 
393       private:
394         A _a;
395         string _jsExpr;
396     }
397 
398     return JSUnaryExpression(a, format(fmt, a.jsExpr));
399 }