summaryrefslogtreecommitdiffstats
path: root/mobile/android/chrome/content/SelectHelper.js
blob: 41d0193d4c69ec554cafc2b678f5c77ee7c476e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

var SelectHelper = {
  _uiBusy: false,

  handleEvent: function(event) {
    this.handleClick(event.target);
  },

  handleClick: function(target) {
    // if we're busy looking at a select we want to eat any clicks that
    // come to us, but not to process them
    if (this._uiBusy || !this._isMenu(target) || this._isDisabledElement(target)) {
      return;
    }

    this._uiBusy = true;
    this.show(target);
    this._uiBusy = false;
  },

  // This is a callback function to be provided to prompt.show(callBack).
  // It will update which Option elements in a Select have been selected
  // or unselected and fire the onChange event.
  _promptCallBack: function(data, element) {
    let selected = data.list;

    if (element instanceof Ci.nsIDOMXULMenuListElement) {
      if (element.selectedIndex != selected[0]) {
        element.selectedIndex = selected[0];
        this.fireOnCommand(element);
      }
    } else if (element instanceof HTMLSelectElement) {
      let changed = false;
      let i = 0; // The index for the element from `data.list` that we are currently examining.
      this.forVisibleOptions(element, function(node) {
        if (node.selected && selected.indexOf(i) == -1) {
          changed = true;
          node.selected = false;
        } else if (!node.selected && selected.indexOf(i) != -1) {
          changed = true;
          node.selected = true;
        }
        i++;
      });

      if (changed) {
        this.fireOnChange(element);
      }
    }
  },

  show: function(element) {
    let list = this.getListForElement(element);
    let p = new Prompt({
      window: element.ownerDocument.defaultView
    });

    if (element.multiple) {
      p.addButton({
        label: Strings.browser.GetStringFromName("selectHelper.closeMultipleSelectDialog")
      }).setMultiChoiceItems(list);
    } else {
      p.setSingleChoiceItems(list);
    }

    p.show((data) => {
      this._promptCallBack(data,element)
    });
  },

  _isMenu: function(element) {
    return (element instanceof HTMLSelectElement || element instanceof Ci.nsIDOMXULMenuListElement);
  },

  // Return a list of Option elements within a Select excluding
  // any that were not visible.
  getListForElement: function(element) {
    let index = 0;
    let items = [];
    this.forVisibleOptions(element, function(node, options,parent) {
      let item = {
        label: node.text || node.label,
        header: options.isGroup,
        disabled: node.disabled,
        id: index,
        selected: node.selected,
      };

      if (parent) {
        item.child = true;
        item.disabled = item.disabled || parent.disabled;
      }
      items.push(item);
      index++;
    });
    return items;
  },

  // Apply a function to all visible Option elements in a Select
  forVisibleOptions: function(element, aFunction, parent = null) {
    if (element instanceof Ci.nsIDOMXULMenuListElement) {
      element = element.menupopup;
    }
    let children = element.children;
    let numChildren = children.length;


    // if there are no children in this select, we add a dummy row so that at least something appears
    if (numChildren == 0) {
      aFunction.call(this, {label: ""}, {isGroup: false}, parent);
    }

    for (let i = 0; i < numChildren; i++) {
      let child = children[i];
      let style = window.getComputedStyle(child, null);
      if (style.display !== "none") {
        if (child instanceof HTMLOptionElement ||
            child instanceof Ci.nsIDOMXULSelectControlItemElement) {
          aFunction.call(this, child, {isGroup: false}, parent);
        } else if (child instanceof HTMLOptGroupElement) {
          aFunction.call(this, child, {isGroup: true});
          this.forVisibleOptions(child, aFunction, child);
        }
      }
    }
  },

  fireOnChange: function(element) {
    let event = element.ownerDocument.createEvent("Events");
    event.initEvent("change", true, true, element.defaultView, 0,
        false, false, false, false, null);
    setTimeout(function() {
      element.dispatchEvent(event);
    }, 0);
  },

  fireOnCommand: function(element) {
    let event = element.ownerDocument.createEvent("XULCommandEvent");
    event.initCommandEvent("command", true, true, element.defaultView, 0,
        false, false, false, false, null);
    setTimeout(function() {
      element.dispatchEvent(event);
    }, 0);
  },

  _isDisabledElement : function(element) {
    let currentElement = element;
    while (currentElement) {
      // Must test with === in case a form has a field named "disabled". See bug 1263589.
      if (currentElement.disabled === true) {
        return true;
      }
      currentElement = currentElement.parentElement;
    }
    return false;
  }
};