Remove the "Copy" code, it's been replaced by "to hash".
[dygraphs.git] / experimental / palette / palette.js
CommitLineData
20590000
RK
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
58a18b02
RK
28/**
29 * scope is either "global", "series", "x", "y" or "y2".
30 */
31function Palette(scope) {
32 // Contains pair of "input" (the input object) and "row" (the parent row)
7a608878 33 // Also contains functionString.
20590000 34 this.model = {};
58a18b02 35 // This is meant to be overridden by a palette host.
20590000 36 this.onchange = function() {};
58a18b02
RK
37 this.scope = scope;
38 this.root = null;
20590000
RK
39}
40
909bae9b 41Palette.createChild = function(type, parentElement, className) {
59a80f4a
RK
42 var element = document.createElement(type);
43 parentElement.appendChild(element);
909bae9b
RK
44 if (className) {
45 element.className = className;
46 }
59a80f4a
RK
47 return element;
48};
49
58a18b02 50Palette.prototype.create = function(parentElement) {
20590000 51 var palette = this;
20590000 52
58a18b02
RK
53 var table = Palette.createChild("div", parentElement[0], "palette");
54 this.root = table;
59a80f4a 55 table.width="300px";
20590000 56
59a80f4a 57 this.tooltip = new Tooltip();
20590000 58
58a18b02
RK
59 // Build the header
60 var header = Palette.createChild("div", table, "header");
61 header.style.visibility = "visible";
20590000 62
58a18b02 63 // One row per option.
20590000
RK
64 for (var opt in opts) {
65 try {
66 if (opts.hasOwnProperty(opt)) {
67 var type = opts[opt].type;
58a18b02
RK
68
69 var scope = opts[opt].scope || [ "global" ]; // Scope can be empty, infer "global" only.
70 var valid = scope[0] == "*" || $.inArray(this.scope, scope) >= 0;
71 if (!valid) {
72 continue;
73 }
74
20590000 75 var isFunction = type.indexOf("function(") == 0;
59a80f4a 76 var row = Palette.createChild("div", table);
58a18b02
RK
77 row.onmouseover = function(source, title, type, body) {
78 return function() {
79 palette.tooltip.show(source, title, type, body);
59a80f4a
RK
80 };
81 } (row, opt, type, Dygraph.OPTIONS_REFERENCE[opt].description);
82 row.onmouseout = function() { palette.tooltip.hide(); };
83
909bae9b 84 var div = Palette.createChild("span", row, "name");
21285cf9 85 div.textContent = opt;
59a80f4a 86
909bae9b 87 var value = Palette.createChild("span", row, "option");
20590000
RK
88
89 if (isFunction) {
59a80f4a 90 var input = Palette.createChild("button", value);
20590000
RK
91 input.onclick = function(opt, palette) {
92 return function(event) {
93 var entry = palette.model[opt];
94 var inputValue = entry.functionString;
95 if (inputValue == null || inputValue.length == 0) {
d95dc014 96 inputValue = opts[opt].type + "{\n\n}";
20590000 97 }
24cfd518
RK
98 var textarea = new TextArea();
99 textarea.show(opt, inputValue);
100 textarea.okCallback = function(value) {
20590000
RK
101 if (value != inputValue) {
102 entry.functionString = value;
7a608878 103 entry.input.textContent = value ? "defined" : "not defined";
20590000
RK
104 palette.onchange();
105 }
106 }
107 }
108 }(opt, this);
7a608878
RK
109 } else if (type == "boolean") {
110 var input = Palette.createChild("button", value);
111 input.onclick = function(e) {
112 var btn = e.target;
113 if (btn.value == "none") {
114 Palette.populateBooleanButton(btn, "true");
115 } else if (btn.value == "true") {
116 Palette.populateBooleanButton(btn, "false");
117 } else {
118 Palette.populateBooleanButton(btn, "none");
119 }
120 palette.onchange();
121 };
20590000 122 } else {
21285cf9 123 var input = Palette.createChild("input", value, "textInput");
7a608878 124 input.type="text";
20590000
RK
125 input.onkeypress = function(event) {
126 var keycode = event.which;
127 if (keycode == 13 || keycode == 8) {
128 palette.onchange();
129 }
130 }
20590000
RK
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.
142Palette.parseStringArray = function(value) {
143 if (value == null || value.length == 0) {
144 return null;
145 }
146 return value.split(";");
147}
148
149Palette.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
156Palette.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
163Palette.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
170Palette.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") {
7a608878
RK
180 if (value == "false") {
181 results[opt] = false;
182 }
183 if (value == "true") {
184 results[opt] = true;
185 }
186 // Ignore value == "none"
20590000
RK
187 } else if (type == "int") {
188 results[opt] = parseInt(value);
189 } else if (type == "float") {
190 results[opt] = parseFloat(value);
191 } else if (type == "array<string>") {
192 results[opt] = Palette.parseStringArray(value);
193 } else if (type == "array<float>") {
194 results[opt] = Palette.parseFloatArray(value);
195 } else if (type == "array<boolean>") {
196 results[opt] = Palette.parseBooleanArray(value);
9ccb0d6e
RK
197 } else if (type == "array<int>") {
198 results[opt] = Palette.parseIntArray(value);
20590000
RK
199 } else if (type == "array<Date>") {
200 results[opt] = Palette.parseIntArray(value);
201 } else if (isFunction) {
202 var localVariable = null;
203 eval("localVariable = " + value);
204 results[opt] = localVariable;
205 } else {
206 results[opt] = value;
207 }
208 }
209 }
210 }
211 return results;
212}
213
214/**
215 * Write to input elements.
216 */
217Palette.prototype.write = function(hash) {
218 var results = {};
219 for (var opt in this.model) {
20590000
RK
220 if (this.model.hasOwnProperty(opt)) {
221 var input = this.model[opt].input;
222 var type = opts[opt].type;
223 var value = hash[opt];
7a608878
RK
224 if (type == "boolean") {
225 var text = value == true ? "true" : (value == false ? "false" : "none");
226 Palette.populateBooleanButton(input, text);
227 } else if (type == "array<string>") {
20590000
RK
228 if (value) {
229 input.value = value.join("; ");
230 }
231 } else if (type.indexOf("array") == 0) {
232 if (value) {
233 input.value = value.join(", ");
234 }
235 } else if (type.indexOf("function(") == 0) {
21285cf9 236 input.textContent = value ? "defined" : "not defined";
20590000
RK
237 this.model[opt].functionString = value ? value.toString() : null;
238 } else {
9ccb0d6e 239 if (value != undefined) {
20590000
RK
240 input.value = value;
241 }
242 }
243 }
244 }
245}
246
7a608878
RK
247Palette.populateBooleanButton = function(button, value) {
248 button.innerHTML = value;
249 button.value = value;
250}
251
20590000
RK
252Palette.prototype.filter = function(pattern) {
253 pattern = pattern.toLowerCase();
59a80f4a 254 var even = true;
20590000
RK
255 for (var opt in this.model) {
256 if (this.model.hasOwnProperty(opt)) {
257 var row = this.model[opt].row;
59a80f4a
RK
258 var matches = opt.toLowerCase().indexOf(pattern) >= 0;
259 row.style.display = matches ? "block" : "none";
260 if (matches) {
261 row.className = even ? "even" : "odd";
262 even = !even;
263 }
20590000
RK
264 }
265 }
266}