jquery-ify the palette object.
[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
58a18b02 41Palette.prototype.create = function(parentElement) {
20590000 42 var palette = this;
20590000 43
9390f624
RK
44 var table = $("<div>")
45 .addClass("palette")
46 .width(300)
47 .appendTo(parentElement);
20590000 48
9390f624 49 this.root = table;
59a80f4a 50 this.tooltip = new Tooltip();
20590000 51
58a18b02 52 // One row per option.
9390f624 53 $.each(opts, function(opt, optEntry) {
20590000 54 try {
9390f624
RK
55 var scope = optEntry.scope || [ "global" ]; // Scope can be empty, infer "global" only.
56 var valid = scope[0] == "*" || $.inArray(palette.scope, scope) >= 0;
57 if (!valid) {
58 return;
59 }
58a18b02 60
9390f624
RK
61 var type = optEntry.type;
62 var isFunction = type.indexOf("function(") == 0;
63
64 var input;
65 if (isFunction) {
66 input = $("<button>")
67 .click(function(opt, palette) {
68 return function(event) {
69 var entry = palette.model[opt];
70 var inputValue = entry.functionString;
71 var type = opts[opt].type;
72 if (inputValue == null || inputValue.length == 0) {
73 inputValue = type + "{\n\n}";
74 }
75 var textarea = new TextArea();
76 textarea.show(opt, inputValue);
77 textarea.okCallback = function(value) {
78 if (value != inputValue) {
79 entry.functionString = value;
80 entry.input.textContent = value ? "defined" : "not defined";
81 palette.onchange();
82 }
83 };
84 };
85 } (opt, palette) // Instantiating this inner function.
86 );
87 } else if (type == "boolean") {
88 input = $("<button>")
89 .click(function(event) {
90 var btn = event.target;
91 if (btn.value == "none") {
92 Palette.populateBooleanButton(btn, "true");
93 } else if (btn.value == "true") {
94 Palette.populateBooleanButton(btn, "false");
95 } else {
96 Palette.populateBooleanButton(btn, "none");
97 }
98 palette.onchange();
99 });
100 } else {
101 input = $("<input>", { type: "text" })
102 .addClass("textInput")
103 .keypress(function(event) {
104 var keycode = event.which;
105 if (keycode == 13 || keycode == 8) {
106 palette.onchange();
107 }
108 });
109 }
110
111 var row = $("<div>")
112 .append($("<span>").addClass("name").text(opt))
113 .append($("<span>").addClass("option")
114 .append(input));
58a18b02 115
9390f624 116 row.mouseover(function(source, title, type, body) {
58a18b02 117 return function() {
9390f624
RK
118 // source[0] is un-jquerying.
119 // TODO(konigsberg): when tooltip is jquery, dump this.
120 palette.tooltip.show(source[0], title, type, body);
59a80f4a 121 };
9390f624
RK
122 } (row, opt, type, Dygraph.OPTIONS_REFERENCE[opt].description))
123 .mouseout(function() { palette.tooltip.hide(); })
59a80f4a 124
9390f624 125 row.appendTo(table);
20590000 126
9390f624 127 palette.model[opt] = { input: input, row: row };
20590000
RK
128 } catch(err) {
129 throw "For option " + opt + ":" + err;
130 }
9390f624
RK
131 });
132
20590000
RK
133 this.filter("");
134}
135
136// TODO: replace semicolon parsing with comma parsing, and supporting quotes.
137Palette.parseStringArray = function(value) {
138 if (value == null || value.length == 0) {
139 return null;
140 }
141 return value.split(";");
142}
143
144Palette.parseBooleanArray = function(value) {
145 if (value == null || value.length == 0) {
146 return null;
147 }
9390f624
RK
148 return value.split(',').map(function(x) {
149 return x.trim() == "true";
150 });
20590000
RK
151}
152
153Palette.parseFloatArray = function(value) {
154 if (value == null || value.length == 0) {
155 return null;
156 }
9390f624
RK
157 return value.split(',').map(function(x) {
158 return parseFloat(x);
159 });
20590000
RK
160}
161
162Palette.parseIntArray = function(value) {
163 if (value == null || value.length == 0) {
164 return null;
165 }
9390f624
RK
166
167 return value.split(',').map(function(x) {
168 return parseInt(x);
169 });
20590000
RK
170}
171
172Palette.prototype.read = function() {
173 var results = {};
174 for (var opt in this.model) {
175 if (this.model.hasOwnProperty(opt)) {
176 var type = opts[opt].type;
177 var isFunction = type.indexOf("function(") == 0;
9390f624 178 var input = this.model[opt].input[0]; // jquery dereference.
20590000
RK
179 var value = isFunction ? this.model[opt].functionString : input.value;
180 if (value && value.length != 0) {
181 if (type == "boolean") {
7a608878
RK
182 if (value == "false") {
183 results[opt] = false;
184 }
185 if (value == "true") {
186 results[opt] = true;
187 }
188 // Ignore value == "none"
20590000
RK
189 } else if (type == "int") {
190 results[opt] = parseInt(value);
191 } else if (type == "float") {
192 results[opt] = parseFloat(value);
193 } else if (type == "array<string>") {
194 results[opt] = Palette.parseStringArray(value);
195 } else if (type == "array<float>") {
196 results[opt] = Palette.parseFloatArray(value);
197 } else if (type == "array<boolean>") {
198 results[opt] = Palette.parseBooleanArray(value);
9ccb0d6e
RK
199 } else if (type == "array<int>") {
200 results[opt] = Palette.parseIntArray(value);
20590000
RK
201 } else if (type == "array<Date>") {
202 results[opt] = Palette.parseIntArray(value);
203 } else if (isFunction) {
204 var localVariable = null;
205 eval("localVariable = " + value);
206 results[opt] = localVariable;
207 } else {
208 results[opt] = value;
209 }
210 }
211 }
212 }
213 return results;
214}
215
216/**
217 * Write to input elements.
218 */
219Palette.prototype.write = function(hash) {
220 var results = {};
221 for (var opt in this.model) {
20590000 222 if (this.model.hasOwnProperty(opt)) {
9390f624 223 var input = this.model[opt].input[0]; // jquery dereference
20590000
RK
224 var type = opts[opt].type;
225 var value = hash[opt];
7a608878
RK
226 if (type == "boolean") {
227 var text = value == true ? "true" : (value == false ? "false" : "none");
228 Palette.populateBooleanButton(input, text);
229 } else if (type == "array<string>") {
20590000
RK
230 if (value) {
231 input.value = value.join("; ");
232 }
233 } else if (type.indexOf("array") == 0) {
234 if (value) {
235 input.value = value.join(", ");
236 }
237 } else if (type.indexOf("function(") == 0) {
21285cf9 238 input.textContent = value ? "defined" : "not defined";
20590000
RK
239 this.model[opt].functionString = value ? value.toString() : null;
240 } else {
9ccb0d6e 241 if (value != undefined) {
20590000
RK
242 input.value = value;
243 }
244 }
245 }
246 }
247}
248
7a608878
RK
249Palette.populateBooleanButton = function(button, value) {
250 button.innerHTML = value;
251 button.value = value;
252}
253
20590000
RK
254Palette.prototype.filter = function(pattern) {
255 pattern = pattern.toLowerCase();
59a80f4a 256 var even = true;
20590000
RK
257 for (var opt in this.model) {
258 if (this.model.hasOwnProperty(opt)) {
259 var row = this.model[opt].row;
59a80f4a 260 var matches = opt.toLowerCase().indexOf(pattern) >= 0;
9390f624 261 row.toggle(matches);
59a80f4a 262 if (matches) {
9390f624 263 row.attr("class", even ? "even" : "odd");
59a80f4a
RK
264 even = !even;
265 }
20590000
RK
266 }
267 }
268}