1/**
2 * Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 **/
6
7function View(window) {
8  this.display = window.document.querySelector('#calculator-display');
9  this.buttons = window.document.querySelectorAll('#calculator-buttons button');
10  window.addEventListener('keydown', this.handleKey_.bind(this));
11  Array.prototype.forEach.call(this.buttons, function(button) {
12    button.addEventListener('click', this.handleClick_.bind(this));
13    button.addEventListener('mousedown', this.handleMouse_.bind(this));
14    button.addEventListener('touchstart', this.handleTouch_.bind(this));
15    button.addEventListener('touchmove', this.handleTouch_.bind(this));
16    button.addEventListener('touchend', this.handleTouchEnd_.bind(this));
17    button.addEventListener('touchcancel', this.handleTouchEnd_.bind(this));
18  }, this);
19}
20
21View.prototype.clearDisplay = function(values) {
22  this.display.innerHTML = '';
23  this.addValues(values);
24};
25
26View.prototype.addResults = function(values) {
27  this.appendChild_(this.display, null, 'div', 'hr');
28  this.addValues(values);
29};
30
31View.prototype.addValues = function(values) {
32  var equation = this.makeElement_('div', 'equation');
33  this.appendChild_(equation, null, 'span', 'accumulator', values.accumulator);
34  this.appendChild_(equation, null, 'span', 'operation');
35  this.appendChild_(equation, '.operation', 'span', 'operator');
36  this.appendChild_(equation, '.operation', 'span', 'operand', values.operand);
37  this.appendChild_(equation, '.operator', 'div', 'spacer');
38  this.appendChild_(equation, '.operator', 'div', 'value', values.operator);
39  this.setAttribute_(equation, '.accumulator', 'aria-hidden', 'true');
40  this.display.appendChild(equation).scrollIntoView();
41};
42
43View.prototype.setValues = function(values) {
44  var equation = this.display.lastElementChild;
45  this.setContent_(equation, '.accumulator', values.accumulator || '');
46  this.setContent_(equation, '.operator .value', values.operator || '');
47  this.setContent_(equation, '.operand', values.operand || '');
48};
49
50View.prototype.getValues = function() {
51  var equation = this.display.lastElementChild;
52  return {
53    accumulator: this.getContent_(equation, '.accumulator') || null,
54    operator: this.getContent_(equation, '.operator .value') || null,
55    operand: this.getContent_(equation, '.operand') || null,
56  };
57};
58
59/** @private */
60View.prototype.handleKey_ = function(event) {
61  this.onKey.call(this, event.shiftKey ? ('^' + event.which) : event.which);
62}
63
64/** @private */
65View.prototype.handleClick_ = function(event) {
66  this.onButton.call(this, event.target.dataset.button)
67}
68
69/** @private */
70View.prototype.handleMouse_ = function(event) {
71  event.target.setAttribute('data-active', 'mouse');
72}
73
74/** @private */
75View.prototype.handleTouch_ = function(event) {
76  event.preventDefault();
77  this.handleTouchChange_(event.touches[0]);
78}
79
80/** @private */
81View.prototype.handleTouchEnd_ = function(event) {
82  this.handleTouchChange_(null);
83}
84
85/** @private */
86View.prototype.handleTouchChange_ = function(location) {
87  var previous = this.touched;
88  if (!this.isInButton_(previous, location)) {
89    this.touched = this.findButtonContaining_(location);
90    if (previous)
91      previous.removeAttribute('data-active');
92    if (this.touched) {
93      this.touched.setAttribute('data-active', 'touch');
94      this.onButton.call(this, this.touched.dataset.button);
95    }
96  }
97}
98
99/** @private */
100View.prototype.findButtonContaining_ = function(location) {
101  var found;
102  for (var i = 0; location && i < this.buttons.length && !found; ++i) {
103    if (this.isInButton_(this.buttons[i], location))
104      found = this.buttons[i];
105  }
106  return found;
107}
108
109/** @private */
110View.prototype.isInButton_ = function(button, location) {
111  var bounds = location && button && button.getClientRects()[0];
112  var x = bounds && location.clientX;
113  var y = bounds && location.clientY;
114  var x1 = bounds && bounds.left;
115  var x2 = bounds && bounds.right;
116  var y1 = bounds && bounds.top;
117  var y2 = bounds && bounds.bottom;
118  return (bounds && x >= x1 && x < x2 && y >= y1 && y < y2);
119}
120
121/** @private */
122View.prototype.makeElement_ = function(tag, classes, content) {
123  var element = this.display.ownerDocument.createElement(tag);
124  element.setAttribute('class', classes);
125  element.textContent = content || '';
126  return element;
127};
128
129/** @private */
130View.prototype.appendChild_ = function(root, selector, tag, classes, content) {
131  var parent = (root && selector) ? root.querySelector(selector) : root;
132  parent.appendChild(this.makeElement_(tag, classes, content));
133};
134
135/** @private */
136View.prototype.setAttribute_ = function(root, selector, name, value) {
137  var element = root && root.querySelector(selector);
138  if (element)
139    element.setAttribute(name, value);
140};
141
142/** @private */
143View.prototype.setContent_ = function(root, selector, content) {
144  var element = root && root.querySelector(selector);
145  if (element)
146    element.textContent = content || '';
147};
148
149/** @private */
150View.prototype.getContent_ = function(root, selector) {
151  var element = root && root.querySelector(selector);
152  return element ? element.textContent : null;
153};
154