##// END OF EJS Templates
Jonathan Frederic -
Show More
@@ -1,455 +1,457
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "jqueryui",
7 7 "base/js/keyboard",
8 8 "bootstrap"
9 9 ], function(widget, $, keyboard){
10 10
11 11 var IntSliderView = widget.DOMWidgetView.extend({
12 12 render : function(){
13 13 // Called when view is rendered.
14 14 this.$el
15 15 .addClass('widget-hbox widget-slider');
16 16 this.$label = $('<div />')
17 17 .appendTo(this.$el)
18 18 .addClass('widget-label')
19 19 .hide();
20 20
21 21 this.$slider = $('<div />')
22 22 .slider({})
23 23 .addClass('slider');
24 24 // Put the slider in a container
25 25 this.$slider_container = $('<div />')
26 26 .addClass('widget-hslider')
27 27 .append(this.$slider);
28 28 this.$el.append(this.$slider_container);
29 29
30 30 this.$readout = $('<div/>')
31 31 .appendTo(this.$el)
32 32 .addClass('widget-readout')
33 33 .attr('contentEditable', true)
34 34 .hide();
35 35
36 36 this.model.on('change:slider_color', function(sender, value) {
37 37 this.$slider.find('a').css('background', value);
38 38 }, this);
39 39 this.$slider.find('a').css('background', this.model.get('slider_color'));
40 40
41 41 // Set defaults.
42 42 this.update();
43 43 },
44 44
45 45 update_attr: function(name, value) {
46 46 // Set a css attr of the widget view.
47 47 if (name == 'color') {
48 48 this.$readout.css(name, value);
49 49 } else if (name.substring(0, 4) == 'font') {
50 50 this.$readout.css(name, value);
51 51 } else if (name.substring(0, 6) == 'border') {
52 52 this.$slider.find('a').css(name, value);
53 53 this.$slider_container.css(name, value);
54 54 } else if (name == 'width' || name == 'height' || name == 'background') {
55 55 this.$slider_container.css(name, value);
56 56 } else {
57 57 this.$slider.css(name, value);
58 58 }
59 59 },
60 60
61 61 update : function(options){
62 62 // Update the contents of this view
63 63 //
64 64 // Called when the model is changed. The model may have been
65 65 // changed by another view or by a state update from the back-end.
66 66 if (options === undefined || options.updated_view != this) {
67 67 // JQuery slider option keys. These keys happen to have a
68 68 // one-to-one mapping with the corrosponding keys of the model.
69 69 var jquery_slider_keys = ['step', 'disabled'];
70 70 var that = this;
71 71 that.$slider.slider({});
72 72 _.each(jquery_slider_keys, function(key, i) {
73 73 var model_value = that.model.get(key);
74 74 if (model_value !== undefined) {
75 75 that.$slider.slider("option", key, model_value);
76 76 }
77 77 });
78 78
79 79 var max = this.model.get('max');
80 80 var min = this.model.get('min');
81 81 if (min <= max) {
82 82 if (max !== undefined) this.$slider.slider('option', 'max', max);
83 83 if (min !== undefined) this.$slider.slider('option', 'min', min);
84 84 }
85 85
86 86 var range_value = this.model.get("_range");
87 87 if (range_value !== undefined) {
88 88 this.$slider.slider("option", "range", range_value);
89 89 }
90 90
91 91 // WORKAROUND FOR JQUERY SLIDER BUG.
92 92 // The horizontal position of the slider handle
93 93 // depends on the value of the slider at the time
94 94 // of orientation change. Before applying the new
95 95 // workaround, we set the value to the minimum to
96 96 // make sure that the horizontal placement of the
97 97 // handle in the vertical slider is always
98 98 // consistent.
99 99 var orientation = this.model.get('orientation');
100 100 var min = this.model.get('min');
101 101 var max = this.model.get('max');
102 102 if (this.model.get('_range')) {
103 103 this.$slider.slider('option', 'values', [min, min]);
104 104 } else {
105 105 this.$slider.slider('option', 'value', min);
106 106 }
107 107 this.$slider.slider('option', 'orientation', orientation);
108 108 var value = this.model.get('value');
109 109 if (this.model.get('_range')) {
110 110 // values for the range case are validated python-side in
111 111 // _Bounded{Int,Float}RangeWidget._validate
112 112 this.$slider.slider('option', 'values', value);
113 113 this.$readout.text(value.join("-"));
114 114 } else {
115 115 if(value > max) {
116 116 value = max;
117 117 }
118 118 else if(value < min){
119 119 value = min;
120 120 }
121 121 this.$slider.slider('option', 'value', value);
122 122 this.$readout.text(value);
123 123 }
124 124
125 125 if(this.model.get('value')!=value) {
126 126 this.model.set('value', value, {updated_view: this});
127 127 this.touch();
128 128 }
129 129
130 130 // Use the right CSS classes for vertical & horizontal sliders
131 131 if (orientation=='vertical') {
132 132 this.$slider_container
133 133 .removeClass('widget-hslider')
134 134 .addClass('widget-vslider');
135 135 this.$el
136 136 .removeClass('widget-hbox')
137 137 .addClass('widget-vbox');
138 138
139 139 } else {
140 140 this.$slider_container
141 141 .removeClass('widget-vslider')
142 142 .addClass('widget-hslider');
143 143 this.$el
144 144 .removeClass('widget-vbox')
145 145 .addClass('widget-hbox');
146 146 }
147 147
148 148 var description = this.model.get('description');
149 149 if (description.length === 0) {
150 150 this.$label.hide();
151 151 } else {
152 152 this.$label.text(description);
153 153 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
154 154 this.$label.show();
155 155 }
156 156
157 157 var readout = this.model.get('readout');
158 158 if (readout) {
159 159 this.$readout.show();
160 160 } else {
161 161 this.$readout.hide();
162 162 }
163 163 }
164 164 return IntSliderView.__super__.update.apply(this);
165 165 },
166 166
167 167 events: {
168 168 // Dictionary of events and their handlers.
169 169 "slide" : "handleSliderChange",
170 170 "blur [contentEditable=true]": "handleTextChange",
171 171 "keydown [contentEditable=true]": "handleKeyDown"
172 172 },
173 173
174 174 handleKeyDown: function(e) {
175 175 if (e.keyCode == keyboard.keycodes.enter) {
176 176 e.preventDefault();
177 177 this.handleTextChange();
178 178 }
179 179 },
180 180
181 181 handleTextChange: function() {
182 182 // this handles the entry of text into the contentEditable label
183 183 // first, the value is checked if it contains a parseable number
184 184 // (or pair of numbers, for the _range case)
185 185 // then it is clamped within the min-max range of the slider
186 186 // finally, the model is updated if the value is to be changed
187 187 //
188 188 // if any of these conditions are not met, the text is reset
189 189 //
190 190 // the step size is not enforced
191 191
192 192 var text = this.$readout.text();
193 193 var vmin = this.model.get('min');
194 194 var vmax = this.model.get('max');
195 195 if (this.model.get("_range")) {
196 196 // range case
197 197 // ranges can be expressed either "val-val" or "val:val" (+spaces)
198 198 var match = this._range_regex.exec(text);
199 199 if (match) {
200 200 var values = [this._parse_value(match[1]),
201 201 this._parse_value(match[2])];
202 202 // reject input where NaN or lower > upper
203 203 if (isNaN(values[0]) ||
204 204 isNaN(values[1]) ||
205 205 (values[0] > values[1])) {
206 206 this.$readout.text(this.model.get('value').join('-'));
207 207 } else {
208 208 // clamp to range
209 209 values = [Math.max(Math.min(values[0], vmax), vmin),
210 210 Math.max(Math.min(values[1], vmax), vmin)];
211 211
212 212 if ((values[0] != this.model.get('value')[0]) ||
213 213 (values[1] != this.model.get('value')[1])) {
214 214 this.$readout.text(values.join('-'));
215 215 this.model.set('value', values, {updated_view: this});
216 216 this.touch();
217 217 } else {
218 218 this.$readout.text(this.model.get('value').join('-'));
219 219 }
220 220 }
221 221 } else {
222 222 this.$readout.text(this.model.get('value').join('-'));
223 223 }
224 224 } else {
225 225 // single value case
226 226 var value = this._parse_value(text);
227 227 if (isNaN(value)) {
228 228 this.$readout.text(this.model.get('value'));
229 229 } else {
230 230 value = Math.max(Math.min(value, vmax), vmin);
231 231
232 232 if (value != this.model.get('value')) {
233 233 this.$readout.text(value);
234 234 this.model.set('value', value, {updated_view: this});
235 235 this.touch();
236 236 } else {
237 237 this.$readout.text(this.model.get('value'));
238 238 }
239 239 }
240 240 }
241 241 },
242 242
243 243 _parse_value: parseInt,
244 244
245 245 _range_regex: /^\s*([+-]?\d+)\s*[-:]\s*([+-]?\d+)/,
246 246
247 247 handleSliderChange: function(e, ui) {
248 248 // Called when the slider value is changed.
249 249
250 250 // Calling model.set will trigger all of the other views of the
251 251 // model to update.
252 252 if (this.model.get("_range")) {
253 253 var actual_value = ui.values.map(this._validate_slide_value);
254 254 this.$readout.text(actual_value.join("-"));
255 255 } else {
256 256 var actual_value = this._validate_slide_value(ui.value);
257 257 this.$readout.text(actual_value);
258 258 }
259 259 this.model.set('value', actual_value, {updated_view: this});
260 260 this.touch();
261 261 },
262 262
263 263 _validate_slide_value: function(x) {
264 264 // Validate the value of the slider before sending it to the back-end
265 265 // and applying it to the other views on the page.
266 266
267 267 // Double bit-wise not truncates the decimel (int cast).
268 268 return ~~x;
269 269 },
270 270 });
271 271
272 272
273 273 var IntTextView = widget.DOMWidgetView.extend({
274 274 render : function(){
275 275 // Called when view is rendered.
276 276 this.$el
277 277 .addClass('widget-hbox widget-text');
278 278 this.$label = $('<div />')
279 279 .appendTo(this.$el)
280 280 .addClass('widget-label')
281 281 .hide();
282 282 this.$textbox = $('<input type="text" />')
283 283 .addClass('form-control')
284 284 .addClass('widget-numeric-text')
285 285 .appendTo(this.$el);
286 286 this.update(); // Set defaults.
287 287 },
288 288
289 289 update : function(options){
290 290 // Update the contents of this view
291 291 //
292 292 // Called when the model is changed. The model may have been
293 293 // changed by another view or by a state update from the back-end.
294 294 if (options === undefined || options.updated_view != this) {
295 295 var value = this.model.get('value');
296 296 if (this._parse_value(this.$textbox.val()) != value) {
297 297 this.$textbox.val(value);
298 298 }
299 299
300 300 if (this.model.get('disabled')) {
301 301 this.$textbox.attr('disabled','disabled');
302 302 } else {
303 303 this.$textbox.removeAttr('disabled');
304 304 }
305 305
306 306 var description = this.model.get('description');
307 307 if (description.length === 0) {
308 308 this.$label.hide();
309 309 } else {
310 310 this.$label.text(description);
311 311 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
312 312 this.$label.show();
313 313 }
314 314 }
315 315 return IntTextView.__super__.update.apply(this);
316 316 },
317 317
318 318 update_attr: function(name, value) {
319 319 // Set a css attr of the widget view.
320 320 this.$textbox.css(name, value);
321 321 },
322 322
323 323 events: {
324 324 // Dictionary of events and their handlers.
325 325 "keyup input" : "handleChanging",
326 326 "paste input" : "handleChanging",
327 327 "cut input" : "handleChanging",
328 328
329 329 // Fires only when control is validated or looses focus.
330 330 "change input" : "handleChanged"
331 331 },
332 332
333 333 handleChanging: function(e) {
334 334 // Handles and validates user input.
335 335
336 336 // Try to parse value as a int.
337 337 var numericalValue = 0;
338 if (e.target.value !== '') {
339 338 var trimmed = e.target.value.trim();
339 if (trimmed === '') {
340 return;
341 } else {
340 342 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
341 343 numericalValue = this._parse_value(e.target.value);
342 344 }
343 345 }
344 346
345 347 // If parse failed, reset value to value stored in model.
346 348 if (isNaN(numericalValue)) {
347 349 e.target.value = this.model.get('value');
348 350 } else if (!isNaN(numericalValue)) {
349 351 if (this.model.get('max') !== undefined) {
350 352 numericalValue = Math.min(this.model.get('max'), numericalValue);
351 353 }
352 354 if (this.model.get('min') !== undefined) {
353 355 numericalValue = Math.max(this.model.get('min'), numericalValue);
354 356 }
355 357
356 358 // Apply the value if it has changed.
357 359 if (numericalValue != this.model.get('value')) {
358 360
359 361 // Calling model.set will trigger all of the other views of the
360 362 // model to update.
361 363 this.model.set('value', numericalValue, {updated_view: this});
362 364 this.touch();
363 365 }
364 366 }
365 367 },
366 368
367 369 handleChanged: function(e) {
368 370 // Applies validated input.
369 if (this.model.get('value') != e.target.value) {
371 if (e.target.value.trim() === '' || e.target.value !== this.model.get('value')) {
370 372 e.target.value = this.model.get('value');
371 373 }
372 374 },
373 375
374 376 _parse_value: parseInt
375 377 });
376 378
377 379
378 380 var ProgressView = widget.DOMWidgetView.extend({
379 381 render : function(){
380 382 // Called when view is rendered.
381 383 this.$el
382 384 .addClass('widget-hbox widget-progress');
383 385 this.$label = $('<div />')
384 386 .appendTo(this.$el)
385 387 .addClass('widget-label')
386 388 .hide();
387 389 this.$progress = $('<div />')
388 390 .addClass('progress')
389 391 .addClass('widget-progress')
390 392 .appendTo(this.$el);
391 393 this.$bar = $('<div />')
392 394 .addClass('progress-bar')
393 395 .css('width', '50%')
394 396 .appendTo(this.$progress);
395 397 this.update(); // Set defaults.
396 398
397 399 this.model.on('change:bar_style', function(model, value) {
398 400 this.update_bar_style();
399 401 }, this);
400 402 this.update_bar_style('');
401 403 },
402 404
403 405 update : function(){
404 406 // Update the contents of this view
405 407 //
406 408 // Called when the model is changed. The model may have been
407 409 // changed by another view or by a state update from the back-end.
408 410 var value = this.model.get('value');
409 411 var max = this.model.get('max');
410 412 var min = this.model.get('min');
411 413 var percent = 100.0 * (value - min) / (max - min);
412 414 this.$bar.css('width', percent + '%');
413 415
414 416 var description = this.model.get('description');
415 417 if (description.length === 0) {
416 418 this.$label.hide();
417 419 } else {
418 420 this.$label.text(description);
419 421 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
420 422 this.$label.show();
421 423 }
422 424 return ProgressView.__super__.update.apply(this);
423 425 },
424 426
425 427 update_bar_style: function(previous_trait_value) {
426 428 var class_map = {
427 429 success: ['progress-bar-success'],
428 430 info: ['progress-bar-info'],
429 431 warning: ['progress-bar-warning'],
430 432 danger: ['progress-bar-danger']
431 433 };
432 434 this.update_mapped_classes(class_map, 'bar_style', previous_trait_value, this.$bar);
433 435 },
434 436
435 437 update_attr: function(name, value) {
436 438 // Set a css attr of the widget view.
437 439 if (name.substring(0, 6) == 'border' || name == 'width' ||
438 440 name == 'height' || name == 'background' || name == 'margin' ||
439 441 name == 'padding') {
440 442
441 443 this.$progress.css(name, value);
442 444 } else if (name == 'color') {
443 445 this.$bar.css('background', value);
444 446 } else {
445 447 this.$bar.css(name, value);
446 448 }
447 449 },
448 450 });
449 451
450 452 return {
451 453 'IntSliderView': IntSliderView,
452 454 'IntTextView': IntTextView,
453 455 'ProgressView': ProgressView,
454 456 };
455 457 });
General Comments 0
You need to be logged in to leave comments. Login now