9e4139f9225f428a2bf1b732b903273e3bc8ffe9
[dygraphs.git] / experimental / palette / palette.js
1 // Copyright (c) 2011 Google, Inc.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to deal
5 // in the Software without restriction, including without limitation the rights
6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 // copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
9 //
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
12 //
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 // THE SOFTWARE.
20
21 /**
22 * @fileoverview Dygraphs options palette.
23 *
24 * @author konigsberg@google.com (Robert Konigsberg)
25 */
26 "use strict";
27
28 /**
29 * scope is either "global", "series", "x", "y" or "y2".
30 */
31 function Palette(scope) {
32 // Contains pair of "input" (the input object) and "row" (the parent row)
33 // Also contains functionString.
34 this.model = {};
35 // This is meant to be overridden by a palette host.
36 this.onchange = function() {};
37 this.scope = scope;
38 this.root = null;
39 }
40
41 Palette.createChild = function(type, parentElement, className) {
42 var element = document.createElement(type);
43 parentElement.appendChild(element);
44 if (className) {
45 element.className = className;
46 }
47 return element;
48 };
49
50 Palette.prototype.create = function(parentElement) {
51 var palette = this;
52
53 var table = Palette.createChild("div", parentElement[0], "palette");
54 this.root = table;
55 table.width="300px";
56
57 this.tooltip = new Tooltip();
58
59 // Build the header
60 var header = Palette.createChild("div", table, "header");
61 header.style.visibility = "visible";
62
63 // CURRENTLY HIDDEN.
64 var tmp = Palette.createChild("button", Palette.createChild("span", header));
65 tmp.textContent = "Copy"
66 tmp.onclick = function() {
67 var textarea = new TextArea();
68 textarea.show("header", "Now is the time for all good men\nto come to the aid of their country");
69 };
70 tmp.style.display = "none";
71
72 // One row per option.
73 for (var opt in opts) {
74 try {
75 if (opts.hasOwnProperty(opt)) {
76 var type = opts[opt].type;
77
78 var scope = opts[opt].scope || [ "global" ]; // Scope can be empty, infer "global" only.
79 var valid = scope[0] == "*" || $.inArray(this.scope, scope) >= 0;
80 if (!valid) {
81 continue;
82 }
83
84 var isFunction = type.indexOf("function(") == 0;
85 var row = Palette.createChild("div", table);
86 row.onmouseover = function(source, title, type, body) {
87 return function() {
88 palette.tooltip.show(source, title, type, body);
89 };
90 } (row, opt, type, Dygraph.OPTIONS_REFERENCE[opt].description);
91 row.onmouseout = function() { palette.tooltip.hide(); };
92
93 var div = Palette.createChild("span", row, "name");
94 div.textContent = opt;
95
96 var value = Palette.createChild("span", row, "option");
97
98 if (isFunction) {
99 var input = Palette.createChild("button", value);
100 input.onclick = function(opt, palette) {
101 return function(event) {
102 var entry = palette.model[opt];
103 var inputValue = entry.functionString;
104 if (inputValue == null || inputValue.length == 0) {
105 inputValue = opts[opt].type + "{\n\n}";
106 }
107 var textarea = new TextArea();
108 textarea.show(opt, inputValue);
109 textarea.okCallback = function(value) {
110 if (value != inputValue) {
111 entry.functionString = value;
112 entry.input.textContent = value ? "defined" : "not defined";
113 palette.onchange();
114 }
115 }
116 }
117 }(opt, this);
118 } else if (type == "boolean") {
119 var input = Palette.createChild("button", value);
120 input.onclick = function(e) {
121 var btn = e.target;
122 if (btn.value == "none") {
123 Palette.populateBooleanButton(btn, "true");
124 } else if (btn.value == "true") {
125 Palette.populateBooleanButton(btn, "false");
126 } else {
127 Palette.populateBooleanButton(btn, "none");
128 }
129 palette.onchange();
130 };
131 } else {
132 var input = Palette.createChild("input", value, "textInput");
133 input.type="text";
134 input.onkeypress = function(event) {
135 var keycode = event.which;
136 if (keycode == 13 || keycode == 8) {
137 palette.onchange();
138 }
139 }
140 }
141 this.model[opt] = { input: input, row: row };
142 }
143 } catch(err) {
144 throw "For option " + opt + ":" + err;
145 }
146 }
147 this.filter("");
148 }
149
150 // TODO: replace semicolon parsing with comma parsing, and supporting quotes.
151 Palette.parseStringArray = function(value) {
152 if (value == null || value.length == 0) {
153 return null;
154 }
155 return value.split(";");
156 }
157
158 Palette.parseBooleanArray = function(value) {
159 if (value == null || value.length == 0) {
160 return null;
161 }
162 return value.split(',').map(function(x) { return x.trim() == "true"; });
163 }
164
165 Palette.parseFloatArray = function(value) {
166 if (value == null || value.length == 0) {
167 return null;
168 }
169 return value.split(',').map(function(x) { return parseFloat(x); });
170 }
171
172 Palette.parseIntArray = function(value) {
173 if (value == null || value.length == 0) {
174 return null;
175 }
176 return value.split(',').map(function(x) { return parseInt(x); });
177 }
178
179 Palette.prototype.read = function() {
180 var results = {};
181 for (var opt in this.model) {
182 if (this.model.hasOwnProperty(opt)) {
183 var type = opts[opt].type;
184 var isFunction = type.indexOf("function(") == 0;
185 var input = this.model[opt].input;
186 var value = isFunction ? this.model[opt].functionString : input.value;
187 if (value && value.length != 0) {
188 if (type == "boolean") {
189 if (value == "false") {
190 results[opt] = false;
191 }
192 if (value == "true") {
193 results[opt] = true;
194 }
195 // Ignore value == "none"
196 } else if (type == "int") {
197 results[opt] = parseInt(value);
198 } else if (type == "float") {
199 results[opt] = parseFloat(value);
200 } else if (type == "array<string>") {
201 results[opt] = Palette.parseStringArray(value);
202 } else if (type == "array<float>") {
203 results[opt] = Palette.parseFloatArray(value);
204 } else if (type == "array<boolean>") {
205 results[opt] = Palette.parseBooleanArray(value);
206 } else if (type == "array<int>") {
207 results[opt] = Palette.parseIntArray(value);
208 } else if (type == "array<Date>") {
209 results[opt] = Palette.parseIntArray(value);
210 } else if (isFunction) {
211 var localVariable = null;
212 eval("localVariable = " + value);
213 results[opt] = localVariable;
214 } else {
215 results[opt] = value;
216 }
217 }
218 }
219 }
220 return results;
221 }
222
223 /**
224 * Write to input elements.
225 */
226 Palette.prototype.write = function(hash) {
227 var results = {};
228 for (var opt in this.model) {
229 if (this.model.hasOwnProperty(opt)) {
230 var input = this.model[opt].input;
231 var type = opts[opt].type;
232 var value = hash[opt];
233 if (type == "boolean") {
234 var text = value == true ? "true" : (value == false ? "false" : "none");
235 Palette.populateBooleanButton(input, text);
236 } else if (type == "array<string>") {
237 if (value) {
238 input.value = value.join("; ");
239 }
240 } else if (type.indexOf("array") == 0) {
241 if (value) {
242 input.value = value.join(", ");
243 }
244 } else if (type.indexOf("function(") == 0) {
245 input.textContent = value ? "defined" : "not defined";
246 this.model[opt].functionString = value ? value.toString() : null;
247 } else {
248 if (value != undefined) {
249 input.value = value;
250 }
251 }
252 }
253 }
254 }
255
256 Palette.populateBooleanButton = function(button, value) {
257 button.innerHTML = value;
258 button.value = value;
259 }
260
261 Palette.prototype.filter = function(pattern) {
262 pattern = pattern.toLowerCase();
263 var even = true;
264 for (var opt in this.model) {
265 if (this.model.hasOwnProperty(opt)) {
266 var row = this.model[opt].row;
267 var matches = opt.toLowerCase().indexOf(pattern) >= 0;
268 row.style.display = matches ? "block" : "none";
269 if (matches) {
270 row.className = even ? "even" : "odd";
271 even = !even;
272 }
273 }
274 }
275 }