1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5cr.define('nfcDebug', function() {
6  'use strict';
7
8  function NfcDebugUI() {
9    this.adapterData_ = {};
10    this.peerData_ = {};
11    this.tagData_ = {};
12  }
13
14  NfcDebugUI.prototype = {
15    setAdapterData: function(data) {
16      this.adapterData_ = data;
17    },
18
19    setPeerData: function(data) {
20      this.peerData_ = data;
21    },
22
23    setTagData: function(data) {
24      this.tagData_ = data;
25    },
26
27    /**
28     * Powers the NFC adapter ON or OFF.
29     */
30    toggleAdapterPower: function() {
31      chrome.send('setAdapterPower', [!this.adapterData_.powered]);
32    },
33
34    /**
35     * Tells the NFC adapter to start or stop polling.
36     */
37    toggleAdapterPolling: function() {
38      chrome.send('setAdapterPolling', [!this.adapterData_.polling]);
39    },
40
41    /**
42     * Notifies the UI that the user made an NDEF type selection and the
43     * appropriate form should be displayed.
44     */
45    recordTypeChanged: function() {
46      this.updateRecordFormContents();
47    },
48
49    /**
50     * Creates a table element and populates it for each record contained
51     * in the given list of records and adds them as a child of the given
52     * DOMElement. This method will replace the contents of the given element
53     * with the tables.
54     *
55     * @param {DOMElement} div The container that the records should be rendered
56     *                         to.
57     * @param {Array} records List of NDEF record data.
58     */
59    renderRecords: function(div, records) {
60      div.textContent = '';
61      if (records.length == 0) {
62        return;
63      }
64      var self = this;
65      records.forEach(function(record) {
66        var recordDiv = document.createElement('div');
67        recordDiv.setAttribute('class', 'record-div');
68        for (var key in record) {
69          if (!record.hasOwnProperty(key))
70            continue;
71
72          var rowDiv = document.createElement('div');
73          rowDiv.setAttribute('class', 'record-key-value-div');
74
75          var keyElement, valueElement;
76          if (key == 'titles') {
77            keyElement = document.createElement('div');
78            keyElement.setAttribute('class', 'record-key-div');
79            keyElement.appendChild(document.createTextNode(key));
80            valueElement = document.createElement('div');
81            valueElement.setAttribute('class', 'record-value-div');
82            self.renderRecords(valueElement, record[key]);
83          } else {
84            keyElement = document.createElement('span');
85            keyElement.setAttribute('class', 'record-key-span');
86            keyElement.appendChild(document.createTextNode(key));
87            valueElement = document.createElement('span');
88            valueElement.setAttribute('class', 'record-value-span');
89            valueElement.appendChild(document.createTextNode(record[key]));
90          }
91          rowDiv.appendChild(keyElement);
92          rowDiv.appendChild(valueElement);
93          recordDiv.appendChild(rowDiv);
94        }
95        div.appendChild(recordDiv);
96        if (records[records.length - 1] !== record)
97          div.appendChild(document.createElement('hr'));
98      });
99    },
100
101    /**
102     * Updates which record type form is displayed based on the currently
103     * selected type.
104     */
105    updateRecordFormContents: function() {
106      var recordTypeMenu = $('record-type-menu');
107      var selectedType =
108          recordTypeMenu.options[recordTypeMenu.selectedIndex].value;
109      this.updateRecordFormContentsFromType(selectedType);
110    },
111
112    /**
113     * Updates which record type form is displayed based on the passed in
114     * type string.
115     *
116     * @param {string} type The record type.
117     */
118    updateRecordFormContentsFromType: function(type) {
119      $('text-form').hidden = (type != 'text');
120      $('uri-form').hidden = (type != 'uri');
121      $('smart-poster-form').hidden = (type != 'smart-poster');
122    },
123
124    /**
125     * Tries to push or write the record to the remote tag or device based on
126     * the contents of the record form fields.
127     */
128    submitRecordForm: function() {
129      var recordTypeMenu = $('record-type-menu');
130      var selectedType =
131          recordTypeMenu.options[recordTypeMenu.selectedIndex].value;
132      var recordData = {};
133      if (selectedType == 'text') {
134        recordData.type = 'TEXT';
135        if ($('text-form-text').value)
136          recordData.text = $('text-form-text').value;
137        if ($('text-form-encoding').value)
138          recordData.encoding = $('text-form-encoding').value;
139        if ($('text-form-language-code').value)
140          recordData.languageCode = $('text-form-language-code').value;
141      } else if (selectedType == 'uri') {
142        recordData.type = 'URI';
143        if ($('uri-form-uri').value)
144          recordData.uri = $('uri-form-uri').value;
145        if ($('uri-form-mime-type').value)
146          recordData.mimeType = $('uri-form-mime-type').value;
147        if ($('uri-form-target-size').value) {
148          var targetSize = $('uri-form-target-size').value;
149          targetSize = parseFloat(targetSize);
150          recordData.targetSize = isNaN(targetSize) ? 0.0 : targetSize;
151        }
152      } else if (selectedType == 'smart-poster') {
153        recordData.type = 'SMART_POSTER';
154        if ($('smart-poster-form-uri').value)
155          recordData.uri = $('smart-poster-form-uri').value;
156        if ($('smart-poster-form-mime-type').value)
157          recordData.mimeType = $('smart-poster-form-mime-type').value;
158        if ($('smart-poster-form-target-size').value) {
159          var targetSize = $('smart-poster-form-target-size').value;
160          targetSize = parseFloat(targetSize);
161          recordData.targetSize = isNaN(targetSize) ? 0.0 : targetSize;
162        }
163        var title = {};
164        if ($('smart-poster-form-title-text').value)
165          title.text = $('smart-poster-form-title-text').value;
166        if ($('smart-poster-form-title-encoding').value)
167          title.encoding = $('smart-poster-form-title-encoding').value;
168        if ($('smart-poster-form-title-language-code').value)
169          title.languageCode =
170              $('smart-poster-form-title-language-code').value;
171        if (Object.keys(title).length != 0)
172          recordData.titles = [title];
173      }
174      chrome.send('submitRecordForm', [recordData]);
175    },
176
177    /**
178     * Given a dictionary |data|, builds a table where each row contains the
179     * a key and its value. The resulting table is then added as the sole child
180     * of |div|. |data| contains information about an adapter, tag, or peer and
181     * this method creates a table for display, thus the value of some keys
182     * will be processed.
183     *
184     * @param {DOMElement} div The container that the table should be rendered
185     *                         to.
186     * @param {dictionary} data Data to generate the table from.
187     */
188    createTableFromData: function(div, data) {
189      div.textContent = '';
190      var table = document.createElement('table');
191      table.classList.add('parameters-table');
192      for (var key in data) {
193        var row = document.createElement('tr');
194        var col = document.createElement('td');
195        col.textContent = key;
196        row.appendChild(col);
197
198        col = document.createElement('td');
199        var value = data[key];
200        if (key == 'records')
201          value = value.length;
202        else if (key == 'supportedTechnologies')
203          value = value.join(', ');
204        col.textContent = value;
205        row.appendChild(col);
206        table.appendChild(row);
207      }
208      div.appendChild(table);
209    },
210  };
211
212  cr.addSingletonGetter(NfcDebugUI);
213
214  /**
215   * Initializes the page after the content has loaded.
216   */
217  NfcDebugUI.initialize = function() {
218    $('nfc-adapter-info').hidden = true;
219    $('adapter-toggles').hidden = true;
220    $('nfc-adapter-info').classList.add('transition-out');
221    $('ndef-record-form').classList.add('transition-out');
222    $('nfc-peer-info').classList.add('transition-out');
223    $('nfc-tag-info').classList.add('transition-out');
224    $('power-toggle').onclick = function() {
225      NfcDebugUI.getInstance().toggleAdapterPower();
226    };
227    $('poll-toggle').onclick = function() {
228      NfcDebugUI.getInstance().toggleAdapterPolling();
229    };
230    $('record-type-menu').onchange = function() {
231      NfcDebugUI.getInstance().recordTypeChanged();
232    };
233    $('record-form-submit-button').onclick = function() {
234      NfcDebugUI.getInstance().submitRecordForm();
235    };
236    $('record-form-submit-button').hidden = true;
237    NfcDebugUI.getInstance().updateRecordFormContents();
238    chrome.send('initialize');
239  };
240
241  /**
242   * Updates the UI based on the NFC availability on the current platform.
243   *
244   * @param {bool} available If true, NFC is supported on the current platform.
245   */
246  NfcDebugUI.onNfcAvailabilityDetermined = function(available) {
247    $('nfc-not-supported').hidden = available;
248  };
249
250  /**
251   * Notifies the UI that information about the NFC adapter has been received.
252   *
253   * @param {dictionary} data Properties of the NFC adapter.
254   */
255  NfcDebugUI.onNfcAdapterInfoChanged = function(data) {
256    NfcDebugUI.getInstance().setAdapterData(data);
257
258    $('nfc-adapter-info').hidden = false;
259    NfcDebugUI.getInstance().createTableFromData($('adapter-parameters'), data);
260
261    $('nfc-adapter-info').classList.toggle('transition-out', !data.present);
262    $('nfc-adapter-info').classList.toggle('transition-in', data.present);
263    $('ndef-record-form').classList.toggle('transition-out', !data.present);
264    $('ndef-record-form').classList.toggle('transition-in', data.present);
265
266    $('adapter-toggles').hidden = !data.present;
267    $('ndef-record-form').hidden = !data.present;
268
269    $('power-toggle').textContent = loadTimeData.getString(
270        data.powered ? 'adapterPowerOffText' : 'adapterPowerOnText');
271    $('poll-toggle').textContent = loadTimeData.getString(
272        data.polling ? 'adapterStopPollText' : 'adapterStartPollText');
273  };
274
275  /**
276   * Notifies the UI that information about an NFC peer has been received.
277   *
278   * @param {dictionary} data Properties of the NFC peer device.
279   */
280  NfcDebugUI.onNfcPeerDeviceInfoChanged = function(data) {
281    NfcDebugUI.getInstance().setPeerData(data);
282
283    if (Object.keys(data).length == 0) {
284      $('nfc-peer-info').classList.add('transition-out');
285      $('nfc-peer-info').classList.remove('transition-in');
286      $('record-form-submit-button').hidden = true;
287      return;
288    }
289
290    $('nfc-peer-info').classList.remove('transition-out');
291    $('nfc-peer-info').classList.add('transition-in');
292
293    NfcDebugUI.getInstance().createTableFromData($('peer-parameters'), data);
294
295    $('record-form-submit-button').hidden = false;
296    $('record-form-submit-button').textContent =
297        loadTimeData.getString('ndefFormPushButtonText');
298
299    if (data.records.length == 0) {
300      $('peer-records-entry').hidden = true;
301      return;
302    }
303
304    $('peer-records-entry').hidden = false;
305    NfcDebugUI.getInstance().renderRecords($('peer-records-container'),
306                                           data.records);
307  };
308
309  /**
310   * Notifies the UI that information about an NFC tag has been received.
311   *
312   * @param {dictionary} data Properties of the NFC tag.
313   */
314  NfcDebugUI.onNfcTagInfoChanged = function(data) {
315    NfcDebugUI.getInstance().setTagData(data);
316
317    if (Object.keys(data).length == 0) {
318      $('nfc-tag-info').classList.add('transition-out');
319      $('nfc-tag-info').classList.remove('transition-in');
320      $('record-form-submit-button').hidden = true;
321      return;
322    }
323
324    $('nfc-tag-info').classList.remove('transition-out');
325    $('nfc-tag-info').classList.add('transition-in');
326
327    NfcDebugUI.getInstance().createTableFromData($('tag-parameters'), data);
328
329    $('record-form-submit-button').hidden = false;
330    $('record-form-submit-button').textContent =
331        loadTimeData.getString('ndefFormWriteButtonText');
332
333    if (data.records.length == 0) {
334      $('tag-records-entry').hidden = true;
335      return;
336    }
337
338    $('tag-records-entry').hidden = false;
339    NfcDebugUI.getInstance().renderRecords($('tag-records-container'),
340                                           data.records);
341  };
342
343  /**
344   * Notifies the UI that a call to "setAdapterPower" failed. Displays an
345   * alert.
346   */
347  NfcDebugUI.onSetAdapterPowerFailed = function() {
348    alert(loadTimeData.getString('errorFailedToSetPowerText'));
349  };
350
351  /**
352   * Notifies the UI that a call to "setAdapterPolling" failed. Displays an
353   * alert.
354   */
355  NfcDebugUI.onSetAdapterPollingFailed = function() {
356    alert(loadTimeData.getString('errorFailedToSetPollingText'));
357  };
358
359  /**
360   * Notifies the UI that an error occurred while submitting an NDEF record
361   * form.
362   * @param {string} errorMessage An error message, describing the failure.
363   */
364  NfcDebugUI.onSubmitRecordFormFailed = function(errorMessage) {
365    alert(loadTimeData.getString('errorFailedToSubmitPrefixText') +
366          ' ' + errorMessage);
367  };
368
369  // Export
370  return {
371    NfcDebugUI: NfcDebugUI
372  };
373});
374
375document.addEventListener('DOMContentLoaded', nfcDebug.NfcDebugUI.initialize);
376