summaryrefslogtreecommitdiff
path: root/modules/DateTimePickerHelper.jsm
blob: b509742b0d179ecf8325b1c7bc772bbefb3eee89 (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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/* 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";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

const DEBUG = false;
function debug(aStr) {
  if (DEBUG) {
    dump("-*- DateTimePickerHelper: " + aStr + "\n");
  }
}

this.EXPORTED_SYMBOLS = [
  "DateTimePickerHelper"
];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");

/*
 * DateTimePickerHelper receives message from content side (input box) and
 * is reposible for opening, closing and updating the picker. Similary,
 * DateTimePickerHelper listens for picker's events and notifies the content
 * side (input box) about them.
 */
this.DateTimePickerHelper = {
  picker: null,
  weakBrowser: null,

  MESSAGES: [
    "FormDateTime:OpenPicker",
    "FormDateTime:ClosePicker",
    "FormDateTime:UpdatePicker"
  ],

  init: function() {
    for (let msg of this.MESSAGES) {
      Services.mm.addMessageListener(msg, this);
    }
  },

  uninit: function() {
    for (let msg of this.MESSAGES) {
      Services.mm.removeMessageListener(msg, this);
    }
  },

  // nsIMessageListener
  receiveMessage: function(aMessage) {
    debug("receiveMessage: " + aMessage.name);
    switch (aMessage.name) {
      case "FormDateTime:OpenPicker": {
        this.showPicker(aMessage.target, aMessage.data);
        break;
      }
      case "FormDateTime:ClosePicker": {
        if (!this.picker) {
          return;
        }
        this.picker.closePicker();
        this.close();
        break;
      }
      case "FormDateTime:UpdatePicker": {
        if (!this.picker) {
          return;
        }
        this.picker.setPopupValue(aMessage.data);
        break;
      }
      default:
        break;
    }
  },

  // nsIDOMEventListener
  handleEvent: function(aEvent) {
    debug("handleEvent: " + aEvent.type);
    switch (aEvent.type) {
      case "DateTimePickerValueChanged": {
        this.updateInputBoxValue(aEvent);
        break;
      }
      case "popuphidden": {
        let browser = this.weakBrowser ? this.weakBrowser.get() : null;
        if (browser) {
          browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
        }
        this.picker.closePicker();
        this.close();
        break;
      }
      default:
        break;
    }
  },

  // Called when picker value has changed, notify input box about it.
  updateInputBoxValue: function(aEvent) {
    let browser = this.weakBrowser ? this.weakBrowser.get() : null;
    if (browser) {
      browser.messageManager.sendAsyncMessage(
        "FormDateTime:PickerValueChanged", aEvent.detail);
    }
  },

  // Get picker from browser and show it anchored to the input box.
  showPicker: Task.async(function* (aBrowser, aData) {
    let rect = aData.rect;
    let dir = aData.dir;
    let type = aData.type;
    let detail = aData.detail;

    this._anchor = aBrowser.ownerGlobal.gBrowser.popupAnchor;
    this._anchor.left = rect.left;
    this._anchor.top = rect.top;
    this._anchor.width = rect.width;
    this._anchor.height = rect.height;
    this._anchor.hidden = false;

    debug("Opening picker with details: " + JSON.stringify(detail));

    let window = aBrowser.ownerDocument.defaultView;
    let tabbrowser = window.gBrowser;
    if (Services.focus.activeWindow != window ||
        tabbrowser.selectedBrowser != aBrowser) {
      // We were sent a message from a window or tab that went into the
      // background, so we'll ignore it for now.
      return;
    }

    this.weakBrowser = Cu.getWeakReference(aBrowser);
    this.picker = aBrowser.dateTimePicker;
    if (!this.picker) {
      debug("aBrowser.dateTimePicker not found, exiting now.");
      return;
    }
    // The datetimepopup binding is only attached when it is needed.
    // Check if openPicker method is present to determine if binding has
    // been attached. If not, attach the binding first before calling it.
    if (!this.picker.openPicker) {
      let bindingPromise = new Promise(resolve => {
        this.picker.addEventListener("DateTimePickerBindingReady",
                                     resolve, {once: true});
      });
      this.picker.setAttribute("active", true);
      yield bindingPromise;
    }
    // The arrow panel needs an anchor to work. The popupAnchor (this._anchor)
    // is a transparent div that the arrow can point to.
    this.picker.openPicker(type, this._anchor, detail);

    this.addPickerListeners();
  }),

  // Picker is closed, do some cleanup.
  close: function() {
    this.removePickerListeners();
    this.picker = null;
    this.weakBrowser = null;
    this._anchor.hidden = true;
  },

  // Listen to picker's event.
  addPickerListeners: function() {
    if (!this.picker) {
      return;
    }
    this.picker.addEventListener("popuphidden", this);
    this.picker.addEventListener("DateTimePickerValueChanged", this);
  },

  // Stop listening to picker's event.
  removePickerListeners: function() {
    if (!this.picker) {
      return;
    }
    this.picker.removeEventListener("popuphidden", this);
    this.picker.removeEventListener("DateTimePickerValueChanged", this);
  },
};