1 module uim.vue.base.component;
2 
3 import uim.vue;
4 
5 @safe:
6 
7 @safe:
8 
9 class DVUEComponent : DVUEObj {
10 	this() { super(); }
11 	this(DVUEApp anApp) { this().app(anApp); }
12 	this(string aName) { this().name(aName); }
13 	this(DVUEApp anApp, string aName) { this(anApp).name(aName); }
14 
15 	mixin(TProperty!("DVUEApp", "app"));
16 
17 	string[] _classes;
18 	auto classes() { return _classes; }
19 	O classes(this O)(string[] entries...) { _classes ~= entries; return cast(O)this; }
20 	O classes(this O)(string[] entries) { _classes ~= entries; return cast(O)this; }
21 	unittest {
22 	}
23 
24 	/**
25 	* filters - allows to define filters that can be used to apply common text formatting
26 	* Example (pretty version, default is minimized)
27 	* VUEComponent.filters("capitalize", "if (!value) return '';value = value.toString();return value.charAt(0).toUpperCase() + value.slice(1);")
28 	* filters: {
29   *		capitalize: function (value) {
30   *  		if (!value) return '';
31   *  		value = value.toString();
32   *  		return value.charAt(0).toUpperCase() + value.slice(1);
33   *		}
34 	*	}
35 	*/
36 	mixin(XStringAA!"filters"); 
37 	unittest {
38 		// assert(VUEComponent.filters("capitalize", "if (!value) return '';value = value.toString();return value.charAt(0).toUpperCase() + value.slice(1);").filters("capitalize").length > 0);
39 	}
40 
41 	mixin(XStringAA!"props");
42 	O props(this O)(string name, string datatype, string defaultValue, bool required = false) {
43 		string[] results;
44 		results ~= (datatype ? "type:%s".format(datatype) : "type:String");
45 		if (defaultValue) results ~= (defaultValue.indexOf("return") >= 0 ? "default:function(){%s}".format(defaultValue) : "default:%s".format(defaultValue)); 
46 		if (required) results ~= "required:true";
47 		_props[name] = results.join(",");
48 		return cast(O)this; }
49 	O props(this O)(string name, string datatype, string defaultValue, string validate, bool required = false) {
50 		string[] results;
51 		results ~= (datatype ? "type:%s".format(datatype) : "type:String");
52 		if (defaultValue) results ~= (defaultValue.indexOf("return") >= 0 ? "default:function(){%s}".format(defaultValue) : "default:%s".format(defaultValue)); 
53 		if (validate) results ~=  "validator:function(value){"~validate~"}";
54 		if (required) results ~= "required:true";
55 		_props[name] = results.join(",");
56 		return cast(O)this; }
57 	unittest {
58 		assert(VUEComponent.props("a", "x").props == ["a":"x"]);
59 		assert(VUEComponent.props(["a":"b"]).props == ["a":"b"]);
60 	}
61 
62 	mixin(XString!"render");
63 	unittest {
64 		assert(VUEComponent.render("a").render == "a");
65 		assert(VUEComponent.render("a").render("x").render == "ax");
66 		assert(VUEComponent.render("a").clearRender.render == null);
67 	}
68 
69 	/**
70 	* extends - Allows declaratively extending another component without having to use Vue.extend. 
71 	* This is primarily intended to make it easier to extend between single file components.
72 	*/
73 	mixin(XStringArray!"extends");
74 	unittest {
75 		assert(VUEComponent.extends("a").extends == ["a"]);
76 		assert(VUEComponent.extends(["a","b"]).extends == ["a","b"]);
77 		assert(VUEComponent.extends("a").extends("x").extends == ["a", "x"]);
78 		// TODO: assert(VUEComponent.extends("a").extends("x").removeExtends("a").extends == ["x"]);
79 		assert(VUEComponent.extends(["a","b"]).clearExtends.extends == null);
80 	}
81 
82 	mixin(XString!"script"); 
83 	unittest {
84 		assert(VUEComponent.script("a").script == "a");
85 		assert(VUEComponent.script("a").script("x").script == "ax");
86 		assert(VUEComponent.script("a").clearScript.script == null);
87 	}
88 
89 	mixin(XString!"style"); 
90 	unittest {
91 		assert(VUEComponent.style("a").style == "a");
92 		assert(VUEComponent.style("a").style("x").style == "ax");
93 		assert(VUEComponent.style("a").clearStyle.style == null);
94 	}
95 
96 	// imports
97 	O imports(this O)(DVUEComponent[] someComponents...) { this.components(someComponents); return cast(O)this; }
98 	O imports(this O)(DVUEComponent[] someComponents) { this.components(someComponents); return cast(O)this; }
99 	
100 	O imports(this O)(DVUEModule aModule) { this.imports(aModule.name, "../module/"~aModule.name~".js"); return cast(O)this; }
101 	O imports(this O)(DVUEMixin aMixin) { this.imports(aMixin.name, "../mixin/"~aMixin.name~".js"); return cast(O)this; }
102 	O imports(this O)(string name, string path) { this.imports(name~" from '"~path~"'"); return cast(O)this; }
103 	O imports(this O)(string text) { _imports ~= "import "~text~";"; return cast(O)this; }
104 
105 	/**
106 	 * components - Local registration of components
107 	 * For each property in the components object, 
108 	 * the key will be the name of the custom element, while the value will contain the options object for the component.
109 	 * Key and name could be the same. Example:
110 	 * components: {
111    * 	ComponentA: ComponentA,
112    * 	'component-b': ComponentB
113    * }
114 	 	*/
115 	protected string[string] _components;
116 	auto components() { return _components; }
117 	O clearComponents(this O)() { _components = null; return cast(O)this; }
118 	O components(this O)(string[] someComponents...) { foreach(name; someComponents) this.components(name, name); return cast(O)this; }
119 	O components(this O)(string[string] someComponents) { foreach(key, name; someComponents) component(key, name); return cast(O)this; }
120 	O components(this O)(string name, string aComponent) { _components[name] = aComponent; return cast(O)this;  }
121 	unittest {
122 		assert(VUEComponent("test").components("componentA") == `Vue.component('test',{components:{componentA:componentA}});`); 
123 		writeln(VUEComponent("test").components("component-a", "componentA"));
124 		assert(VUEComponent("test").components("component-a", "componentA") == `Vue.component('test',{components:{'component-a':componentA}});`);
125 	}
126 /* ERROR
127 	// Local registration using imports
128 	O components(this O)(DVUEComponent[] someComponents...) { foreach(c; someComponents) this.component(c); return cast(O)this; }
129 	O components(this O)(DVUEComponent[] someComponents) { foreach(c; someComponents) component(c); return cast(O)this; }
130 	O component(this O)(DVUEComponent aComponent) { 
131 		this.imports(aComponent.name, "./"~aComponent.name~".js").component(aComponent.name); 
132 		if (app) app.components(aComponent); 
133 		return cast(O)this; }
134 	unittest {
135 		assert(VUEComponent.component("componentA").components == ["componentA":"componentA"]);
136 		assert(VUEComponent.component("'component-a'", "componentA").components == ["'component-a'":"componentA"]);
137 	}
138 */
139 	mixin(TProperty!("string", "content"));
140 	/**
141 	* Mixins are a flexible way to distribute reusable functionalities for Vue components. 
142 	* A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.
143 	**/
144 	mixin(XPropertyAA!("string", "DVUEMixin", "mixins"));
145 	unittest {
146 		auto mixin1 = VUEMixin;
147 		auto mixin2 = VUEMixin;
148 		assert(VUEComponent.mixins(["test": mixin1]).mixins.length == 1);
149 /* 		assert(VUEComponent.mixins(["test": mixin1]).mixinsOne("test") == mixin1);
150 		assert(VUEComponent.mixins("test", mixin1).mixinsOne("test") == mixin1);
151 		assert(VUEComponent.mixins(["test": mixin1, "test2": mixin2]).mixinsOne("test") == mixin1);
152 		assert(VUEComponent.mixins(["test": mixin1, "test2": mixin2]).mixinsAll("test") == [mixin1, mixin2]);
153 		assert(VUEComponent.mixins("test", mixin1).mixins("test2", mixin2).mixinsOne("test") == mixin1);
154  */	}
155 
156 
157 	string globalRegistration() {/*
158 		// debug writeln("Name = ",_name);
159 		// debug writeln("Template = ",_template_);
160 		// debug writeln("Props = ", _props);
161 		// debug writeln("Data = ", _data);
162 */
163 		string result;
164 		if (_name) result ~= "'"~_name~"'";
165 
166 		auto mySettings = settings;
167 		if (_template_) mySettings["template"] = "`%s`".format(_template_);	
168 
169 		if (mySettings) result ~= (result ? ",":"")~mySettings.toJS(true);
170 		return "Vue.component("~result~");";
171 	}
172 	unittest {
173 		assert(VUEComponent("test") == `Vue.component('test');`);
174 		assert(VUEComponent("test").template_("xyz") == "Vue.component('test',{template:`xyz`});",
175 		"Wrong? -> "~VUEComponent("test").template_("xyz").toString);
176 	}
177 
178 	void request(HTTPServerRequest req, HTTPServerResponse res) {
179 		res.writeBody(toString, "text/javascript");
180 	}
181 
182 	string toVue() {
183 		string result;
184 		if (_template_) result ~= "<template>"~_template_~"</template>";
185 		if (_script) result ~= "<script>"~_script~"</script>";
186 		if (_style) result ~= "<style scoped>"~_style~"</style>";
187 		return result;
188 	}
189 	unittest{
190 		/// TODO
191 	}
192 
193 	override string[string] settings() {
194 		string[string] results = super.settings;
195 		if (_classes) this.computed("classes", `return`~this.classes.toJS~`;`);
196 
197 		if (_components) {
198 			string[] componentsForVue;
199 			foreach(kv; _components.byKeyValue) {
200 				if (kv.key.indexOf("-") >= 0) componentsForVue ~= "'%s':%s".format(kv.key, kv.value); 
201 				else componentsForVue ~= "%s:%s".format(kv.key, kv.value);
202 			}
203 			results["components"] = "{"~componentsForVue.join(",")~"}";
204 		}
205 		if (_data) results["data"] = "function(){return"~_data.toJS(true)~"}";
206 		if (_filters) {
207 			string[string] filtersForVue;
208 			foreach(kv; _filters.byKeyValue) filtersForVue[kv.key] = "function(value){"~kv.value~"}";
209 			results["filters"] = filtersForVue.toJS;
210 		}
211 		if (_mixins) results["mixins"] = _mixins.toJS;
212 		if (_props) {
213 			string[string] propsForVue;
214 			foreach(kv; props.byKeyValue) { propsForVue[kv.key] = "{"~kv.value~"}"; }
215 			results["props"] = propsForVue.toJS;
216 		}
217 		if (_watch) results["watch"] = _watch.toJS;
218 
219 		return results;
220 	}
221 	unittest{
222 		/// TODO
223 	}
224 
225 	string toVue3(string target) {
226 		string result;
227 		result ~= target~`.component('%s',%s)`.format(_name, settings.toJS(true));
228 		return result;
229 	}
230 	unittest {
231 		writeln(VUEComponent.toVue3("test"));
232 		assert(VUEComponent.toVue3("test") == "test.component('my-component-name', {`~settings.toJS(true)~`})`");
233 	}
234 
235 	override bool opEquals(string txt) { return toString == txt; }
236 	override string toString() { return globalRegistration; }
237 	unittest{
238 		/// TODO
239 	}
240 }
241 auto VUEComponent() { return new DVUEComponent(); }
242 auto VUEComponent(string aName) { return new DVUEComponent(aName); }
243 auto VUEComponent(DVUEApp anApp) { return new DVUEComponent(anApp); }
244 auto VUEComponent(DVUEApp anApp, string aName) { return new DVUEComponent(anApp, aName); }
245 unittest {
246 	assert(VUEComponent("xxx") == "Vue.component('xxx');"); 
247 	assert(VUEComponent.name("xxx") == "Vue.component('xxx');"); 
248 	assert(VUEComponent.name("xxx").template_("<h1>hello</h1>") == "Vue.component('xxx',{template:`<h1>hello</h1>`});"); 
249 	assert(VUEComponent.name("xxx").template_("<h1>hello</h1>").data(["x":"xx", "y":"yy"]) == "Vue.component('xxx',{data:function(){return{x:xx,y:yy}},template:`<h1>hello</h1>`});"); 
250 // test computed
251 	assert(VUEComponent.computed("a","return b;") == "Vue.component({computed:{a:function(){return b;}}});", 
252 	"Wrong? -> "~VUEComponent.computed("a","return b;").toString);
253 	assert(VUEComponent.computed("a","return b;").computed("x","return z;") == "Vue.component({computed:{a:function(){return b;},x:function(){return z;}}});", 
254 	"Wrong? -> "~VUEComponent.computed("a","return b;").computed("x","return z;").toString);
255 	assert(VUEComponent.computed(["a":"return b;", "x":"return z;"]) == "Vue.component({computed:{a:function(){return b;},x:function(){return z;}}});", 
256 	"Wrong? -> "~VUEComponent.computed(["a":"return b;", "x":"return z;"]).toString);
257 	// test methods
258 	assert(VUEComponent.methods("a()","return b;") == "Vue.component({methods:{a(){return b;}}});", 
259 	"Wrong? -> "~VUEComponent.methods("a()","return b;").toString);
260 	assert(VUEComponent.methods("a()","return b;").methods("x()","return z;") == "Vue.component({methods:{a(){return b;},x(){return z;}}});", 
261 	"Wrong? -> "~VUEComponent.methods("a()","return b;").methods("x()","return z;").toString);
262 	assert(VUEComponent.methods(["a()":"return b;", "x()":"return z;"]) == "Vue.component({methods:{a(){return b;},x(){return z;}}});", 
263 	"Wrong? -> "~VUEComponent.methods(["a()":"return b;", "x()":"return z;"]).toString);
264 }