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 7 function 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 21 View.prototype.clearDisplay = function(values) { 22 this.display.innerHTML = ''; 23 this.addValues(values); 24 }; 25 26 View.prototype.addResults = function(values) { 27 this.appendChild_(this.display, null, 'div', 'hr'); 28 this.addValues(values); 29 }; 30 31 View.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 43 View.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 50 View.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 */ 60 View.prototype.handleKey_ = function(event) { 61 this.onKey.call(this, event.shiftKey ? ('^' + event.which) : event.which); 62 } 63 64 /** @private */ 65 View.prototype.handleClick_ = function(event) { 66 this.onButton.call(this, event.target.dataset.button) 67 } 68 69 /** @private */ 70 View.prototype.handleMouse_ = function(event) { 71 event.target.setAttribute('data-active', 'mouse'); 72 } 73 74 /** @private */ 75 View.prototype.handleTouch_ = function(event) { 76 event.preventDefault(); 77 this.handleTouchChange_(event.touches[0]); 78 } 79 80 /** @private */ 81 View.prototype.handleTouchEnd_ = function(event) { 82 this.handleTouchChange_(null); 83 } 84 85 /** @private */ 86 View.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 */ 100 View.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 */ 110 View.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 */ 122 View.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 */ 130 View.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 */ 136 View.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 */ 143 View.prototype.setContent_ = function(root, selector, content) { 144 var element = root && root.querySelector(selector); 145 if (element) 146 element.textContent = content || ''; 147 }; 148 149 /** @private */ 150 View.prototype.getContent_ = function(root, selector) { 151 var element = root && root.querySelector(selector); 152 return element ? element.textContent : null; 153 }; 154