1<!DOCTYPE html>
2<!--
3Copyright (c) 2014 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/settings.html">
9<link rel="import" href="/tracing/ui/base/ui.html">
10
11<style>
12* /deep/ .labeled-checkbox {
13  display: flex;
14  white-space: nowrap;
15}
16</style>
17
18<script>
19'use strict';
20
21tr.exportTo('tr.ui.b', function() {
22
23  function createSpan(opt_dictionary) {
24    var ownerDocument = document;
25    if (opt_dictionary && opt_dictionary.ownerDocument)
26      ownerDocument = opt_dictionary.ownerDocument;
27    var spanEl = ownerDocument.createElement('span');
28    if (opt_dictionary) {
29      if (opt_dictionary.className)
30        spanEl.className = opt_dictionary.className;
31      if (opt_dictionary.textContent)
32        spanEl.textContent = opt_dictionary.textContent;
33      if (opt_dictionary.tooltip)
34        spanEl.title = opt_dictionary.tooltip;
35      if (opt_dictionary.parent)
36        opt_dictionary.parent.appendChild(spanEl);
37      if (opt_dictionary.bold)
38        spanEl.style.fontWeight = 'bold';
39      if (opt_dictionary.italic)
40        spanEl.style.fontStyle = 'italic';
41      if (opt_dictionary.marginLeft)
42        spanEl.style.marginLeft = opt_dictionary.marginLeft;
43      if (opt_dictionary.marginRight)
44        spanEl.style.marginRight = opt_dictionary.marginRight;
45      if (opt_dictionary.backgroundColor)
46        spanEl.style.backgroundColor = opt_dictionary.backgroundColor;
47      if (opt_dictionary.color)
48        spanEl.style.color = opt_dictionary.color;
49    }
50    return spanEl;
51  };
52
53  function createDiv(opt_dictionary) {
54    var divEl = document.createElement('div');
55    if (opt_dictionary) {
56      if (opt_dictionary.className)
57        divEl.className = opt_dictionary.className;
58      if (opt_dictionary.parent)
59        opt_dictionary.parent.appendChild(divEl);
60      if (opt_dictionary.textContent)
61        divEl.textContent = opt_dictionary.textContent;
62      if (opt_dictionary.maxWidth)
63        divEl.style.maxWidth = opt_dictionary.maxWidth;
64    }
65    return divEl;
66  };
67
68  function createScopedStyle(styleContent) {
69    var styleEl = document.createElement('style');
70    styleEl.scoped = true;
71    styleEl.innerHTML = styleContent;
72    return styleEl;
73  }
74
75  function valuesEqual(a, b) {
76    if (a instanceof Array && b instanceof Array)
77      return a.length === b.length && JSON.stringify(a) === JSON.stringify(b);
78    return a === b;
79  }
80
81  function createSelector(
82      targetEl, targetElProperty,
83      settingsKey, defaultValue,
84      items, opt_namespace) {
85    var defaultValueIndex;
86    for (var i = 0; i < items.length; i++) {
87      var item = items[i];
88      if (valuesEqual(item.value, defaultValue)) {
89        defaultValueIndex = i;
90        break;
91      }
92    }
93    if (defaultValueIndex === undefined)
94      throw new Error('defaultValue must be in the items list');
95
96    var selectorEl = document.createElement('select');
97    selectorEl.addEventListener('change', onChange);
98    for (var i = 0; i < items.length; i++) {
99      var item = items[i];
100      var optionEl = document.createElement('option');
101      optionEl.textContent = item.label;
102      optionEl.targetPropertyValue = item.value;
103      optionEl.item = item;
104      selectorEl.appendChild(optionEl);
105    }
106    function onChange(e) {
107      var value = selectorEl.selectedOptions[0].targetPropertyValue;
108      tr.b.Settings.set(settingsKey, value, opt_namespace);
109      targetEl[targetElProperty] = value;
110    }
111    var oldSetter = targetEl.__lookupSetter__('selectedIndex');
112    selectorEl.__defineGetter__('selectedValue', function(v) {
113      return selectorEl.children[selectorEl.selectedIndex].targetPropertyValue;
114    });
115    selectorEl.__defineGetter__('selectedItem', function(v) {
116      return selectorEl.children[selectorEl.selectedIndex].item;
117    });
118    selectorEl.__defineSetter__('selectedValue', function(v) {
119      for (var i = 0; i < selectorEl.children.length; i++) {
120        var value = selectorEl.children[i].targetPropertyValue;
121        if (valuesEqual(value, v)) {
122          var changed = selectorEl.selectedIndex != i;
123          if (changed) {
124            selectorEl.selectedIndex = i;
125            onChange();
126          }
127          return;
128        }
129      }
130      throw new Error('Not a valid value');
131    });
132
133    var initialValue = tr.b.Settings.get(
134        settingsKey, defaultValue, opt_namespace);
135    var didSet = false;
136    for (var i = 0; i < selectorEl.children.length; i++) {
137      if (valuesEqual(selectorEl.children[i].targetPropertyValue,
138          initialValue)) {
139        didSet = true;
140        targetEl[targetElProperty] = initialValue;
141        selectorEl.selectedIndex = i;
142        break;
143      }
144    }
145    if (!didSet) {
146      selectorEl.selectedIndex = defaultValueIndex;
147      targetEl[targetElProperty] = defaultValue;
148    }
149
150    return selectorEl;
151  }
152
153  function createEditCategorySpan(optionGroupEl, targetEl) {
154    var spanEl = createSpan({className: 'edit-categories'});
155    spanEl.textContent = 'Edit categories';
156    spanEl.classList.add('labeled-option');
157
158    spanEl.addEventListener('click', function() {
159      targetEl.onClickEditCategories();
160    });
161    return spanEl;
162  }
163
164  function createOptionGroup(targetEl, targetElProperty,
165                             settingsKey, defaultValue,
166                             items) {
167    function onChange() {
168      var value = [];
169      if (this.value.length)
170        value = this.value.split(',');
171      tr.b.Settings.set(settingsKey, value);
172      targetEl[targetElProperty] = value;
173    }
174
175    var optionGroupEl = createSpan({className: 'labeled-option-group'});
176    var initialValue = tr.b.Settings.get(settingsKey, defaultValue);
177    for (var i = 0; i < items.length; ++i) {
178      var item = items[i];
179      var id = 'category-preset-' + item.label.replace(/ /g, '-');
180
181      var radioEl = document.createElement('input');
182      radioEl.type = 'radio';
183      radioEl.setAttribute('id', id);
184      radioEl.setAttribute('name', 'category-presets-group');
185      radioEl.setAttribute('value', item.value);
186      radioEl.addEventListener('change', onChange.bind(radioEl, targetEl,
187                                                       targetElProperty,
188                                                       settingsKey));
189      if (valuesEqual(initialValue, item.value))
190        radioEl.checked = true;
191
192      var labelEl = document.createElement('label');
193      labelEl.textContent = item.label;
194      labelEl.setAttribute('for', id);
195
196      var spanEl = createSpan({className: 'labeled-option'});
197      spanEl.appendChild(radioEl);
198      spanEl.appendChild(labelEl);
199
200      spanEl.__defineSetter__('checked', function(opt_bool) {
201        var changed = radioEl.checked !== (!!opt_bool);
202        if (!changed)
203          return;
204
205        radioEl.checked = !!opt_bool;
206        onChange();
207      });
208      spanEl.__defineGetter__('checked', function() {
209        return radioEl.checked;
210      });
211
212      optionGroupEl.appendChild(spanEl);
213    }
214    optionGroupEl.appendChild(createEditCategorySpan(optionGroupEl, targetEl));
215    // Since this option group element is not yet added to the tree,
216    // querySelector will fail during updateEditCategoriesStatus_ call.
217    // Hence, creating the element with the 'expanded' classlist category
218    // added, if last selected value was 'Manual' selection.
219    if (!initialValue.length)
220      optionGroupEl.classList.add('categories-expanded');
221    targetEl[targetElProperty] = initialValue;
222
223    return optionGroupEl;
224  }
225
226  var nextCheckboxId = 1;
227  function createCheckBox(targetEl, targetElProperty,
228                          settingsKey, defaultValue,
229                          label, opt_changeCb) {
230    var buttonEl = document.createElement('input');
231    buttonEl.type = 'checkbox';
232
233    var initialValue = tr.b.Settings.get(settingsKey, defaultValue);
234    buttonEl.checked = !!initialValue;
235    if (targetEl)
236      targetEl[targetElProperty] = initialValue;
237
238    function onChange() {
239      tr.b.Settings.set(settingsKey, buttonEl.checked);
240      if (targetEl)
241        targetEl[targetElProperty] = buttonEl.checked;
242      if (opt_changeCb)
243        opt_changeCb.call();
244    }
245
246    buttonEl.addEventListener('change', onChange);
247
248    var id = '#checkbox-' + nextCheckboxId++;
249
250    var spanEl = createSpan({className: 'labeled-checkbox'});
251    buttonEl.setAttribute('id', id);
252
253    var labelEl = document.createElement('label');
254    labelEl.textContent = label;
255    labelEl.setAttribute('for', id);
256    spanEl.appendChild(buttonEl);
257    spanEl.appendChild(labelEl);
258
259    spanEl.__defineSetter__('checked', function(opt_bool) {
260      var changed = buttonEl.checked !== (!!opt_bool);
261      if (!changed)
262        return;
263
264      buttonEl.checked = !!opt_bool;
265      onChange();
266    });
267    spanEl.__defineGetter__('checked', function() {
268      return buttonEl.checked;
269    });
270
271    return spanEl;
272  }
273
274  function createButton(targetEl, targetElProperty, label, opt_changeCb) {
275    var buttonEl = document.createElement('input');
276    buttonEl.type = 'button';
277
278    function onClick() {
279      if (opt_changeCb)
280        opt_changeCb.call();
281    }
282
283    buttonEl.addEventListener('click', onClick);
284    buttonEl.value = label;
285
286    return buttonEl;
287  }
288
289  function createTextInput(
290      targetEl, targetElProperty, settingsKey, defaultValue) {
291    var initialValue = tr.b.Settings.get(settingsKey, defaultValue);
292    var el = document.createElement('input');
293    el.type = 'text';
294    function onChange(e) {
295      tr.b.Settings.set(settingsKey, el.value);
296      targetEl[targetElProperty] = el.value;
297    }
298    el.addEventListener('input', onChange);
299    el.value = initialValue;
300    targetEl[targetElProperty] = initialValue;
301
302    return el;
303  }
304
305  function isElementAttachedToDocument(el) {
306    var cur = el;
307    while (cur.parentNode)
308      cur = cur.parentNode;
309    return (cur === el.ownerDocument || cur.nodeName === '#document-fragment');
310  }
311
312  function asHTMLOrTextNode(value, opt_ownerDocument) {
313    if (value instanceof Node)
314      return value;
315    var ownerDocument = opt_ownerDocument || document;
316    return ownerDocument.createTextNode(value);
317  }
318
319  return {
320    createSpan: createSpan,
321    createDiv: createDiv,
322    createScopedStyle: createScopedStyle,
323    createSelector: createSelector,
324    createOptionGroup: createOptionGroup,
325    createCheckBox: createCheckBox,
326    createButton: createButton,
327    createTextInput: createTextInput,
328    isElementAttachedToDocument: isElementAttachedToDocument,
329    asHTMLOrTextNode: asHTMLOrTextNode
330  };
331});
332</script>
333