2e9acb2479c9e7eb3c1af9081e0384390bffadc9
[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 this.model = {};
34 // This is meant to be overridden by a palette host.
35 this.onchange = function() {};
36 this.scope = scope;
37 this.root = null;
38 }
39
40 Palette.createChild = function(type, parentElement, className) {
41 var element = document.createElement(type);
42 parentElement.appendChild(element);
43 if (className) {
44 element.className = className;
45 }
46 return element;
47 };
48
49 Palette.prototype.create = function(parentElement) {
50 var palette = this;
51
52 var table = Palette.createChild("div", parentElement[0], "palette");
53 this.root = table;
54 table.width="300px";
55
56 this.tooltip = new Tooltip();
57
58 // Build the header
59 var header = Palette.createChild("div", table, "header");
60 header.style.visibility = "visible";
61
62 // CURRENTLY HIDDEN.
63 var tmp = Palette.createChild("button", Palette.createChild("span", header));
64 tmp.textContent = "Copy"
65 tmp.onclick = function() {
66 var textarea = new TextArea();
67 textarea.show("header", "Now is the time for all good men\nto come to the aid of their country");
68 };
69 tmp.style.display = "none";
70
71 // One row per option.
72 for (var opt in opts) {
73 try {
74 if (opts.hasOwnProperty(opt)) {
75 var type = opts[opt].type;
76
77 var scope = opts[opt].scope || [ "global" ]; // Scope can be empty, infer "global" only.
78 var valid = scope[0] == "*" || $.inArray(this.scope, scope) >= 0;
79 if (!valid) {
80 continue;
81 }
82
83 var isFunction = type.indexOf("function(") == 0;
84 var row = Palette.createChild("div", table);
85 row.onmouseover = function(source, title, type, body) {
86 return function() {
87 palette.tooltip.show(source, title, type, body);
88 };
89 } (row, opt, type, Dygraph.OPTIONS_REFERENCE[opt].description);
90 row.onmouseout = function() { palette.tooltip.hide(); };
91
92 var div = Palette.createChild("span", row, "name");
93 div.textContent = opt;
94
95 var value = Palette.createChild("span", row, "option");
96
97 if (isFunction) {
98 var input = Palette.createChild("button", value);
99 input.onclick = function(opt, palette) {
100 return function(event) {
101 var entry = palette.model[opt];
102 var inputValue = entry.functionString;
103 if (inputValue == null || inputValue.length == 0) {
104 inputValue = opts[opt].type + "{\n\n}";
105 }
106 var textarea = new TextArea();
107 textarea.show(opt, inputValue);
108 textarea.okCallback = function(value) {
109 if (value != inputValue) {
110 entry.functionString = value;
111 entry.input.textContent = value ? "defined" : "undefined";
112 palette.onchange();
113 }
114 }
115 }
116 }(opt, this);
117 } else {
118 var input = Palette.createChild("input", value, "textInput");
119 if (type == "boolean") {
120 input.size = "5";
121 input.maxlength = "5";
122 }
123 input.onkeypress = function(event) {
124 var keycode = event.which;
125 if (keycode == 13 || keycode == 8) {
126 palette.onchange();
127 }
128 }
129
130 input.type="text";
131 }
132 this.model[opt] = { input: input, row: row };
133 }
134 } catch(err) {
135 throw "For option " + opt + ":" + err;
136 }
137 }
138 this.filter("");
139 }
140
141 // TODO: replace semicolon parsing with comma parsing, and supporting quotes.
142 Palette.parseStringArray = function(value) {
143 if (value == null || value.length == 0) {
144 return null;
145 }
146 return value.split(";");
147 }
148
149 Palette.parseBooleanArray = function(value) {
150 if (value == null || value.length == 0) {
151 return null;
152 }
153 return value.split(',').map(function(x) { return x.trim() == "true"; });
154 }
155
156 Palette.parseFloatArray = function(value) {
157 if (value == null || value.length == 0) {
158 return null;
159 }
160 return value.split(',').map(function(x) { return parseFloat(x); });
161 }
162
163 Palette.parseIntArray = function(value) {
164 if (value == null || value.length == 0) {
165 return null;
166 }
167 return value.split(',').map(function(x) { return parseInt(x); });
168 }
169
170 Palette.prototype.read = function() {
171 var results = {};
172 for (var opt in this.model) {
173 if (this.model.hasOwnProperty(opt)) {
174 var type = opts[opt].type;
175 var isFunction = type.indexOf("function(") == 0;
176 var input = this.model[opt].input;
177 var value = isFunction ? this.model[opt].functionString : input.value;
178 if (value && value.length != 0) {
179 if (type == "boolean") {
180 results[opt] = value == "true";
181 } else if (type == "int") {
182 results[opt] = parseInt(value);
183 } else if (type == "float") {
184 results[opt] = parseFloat(value);
185 } else if (type == "array<string>") {
186 results[opt] = Palette.parseStringArray(value);
187 } else if (type == "array<float>") {
188 results[opt] = Palette.parseFloatArray(value);
189 } else if (type == "array<boolean>") {
190 results[opt] = Palette.parseBooleanArray(value);
191 } else if (type == "array<Date>") {
192 results[opt] = Palette.parseIntArray(value);
193 } else if (isFunction) {
194 var localVariable = null;
195 eval("localVariable = " + value);
196 results[opt] = localVariable;
197 } else {
198 results[opt] = value;
199 }
200 }
201 }
202 }
203 return results;
204 }
205
206 /**
207 * Write to input elements.
208 */
209 Palette.prototype.write = function(hash) {
210 var results = {};
211 for (var opt in this.model) {
212 if (this.model.hasOwnProperty(opt)) {
213 var input = this.model[opt].input;
214 var type = opts[opt].type;
215 var value = hash[opt];
216 if (type == "array<string>") {
217 if (value) {
218 input.value = value.join("; ");
219 }
220 } else if (type.indexOf("array") == 0) {
221 if (value) {
222 input.value = value.join(", ");
223 }
224 } else if (type.indexOf("function(") == 0) {
225 input.textContent = value ? "defined" : "not defined";
226 this.model[opt].functionString = value ? value.toString() : null;
227 } else {
228 if (value) {
229 input.value = value;
230 }
231 }
232 }
233 }
234 }
235
236 Palette.prototype.filter = function(pattern) {
237 pattern = pattern.toLowerCase();
238 var even = true;
239 for (var opt in this.model) {
240 if (this.model.hasOwnProperty(opt)) {
241 var row = this.model[opt].row;
242 var matches = opt.toLowerCase().indexOf(pattern) >= 0;
243 row.style.display = matches ? "block" : "none";
244 if (matches) {
245 row.className = even ? "even" : "odd";
246 even = !even;
247 }
248 }
249 }
250 }