| OLD | NEW |
| (Empty) |
| 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 | |
| 5 /** | |
| 6 * @fileoverview Base class for all login WebUI screens. | |
| 7 */ | |
| 8 cr.define('login', function() { | |
| 9 /** @const */ var CALLBACK_USER_ACTED = 'userActed'; | |
| 10 /** @const */ var CALLBACK_CONTEXT_CHANGED = 'contextChanged'; | |
| 11 | |
| 12 function doNothing() {}; | |
| 13 | |
| 14 var querySelectorAll = HTMLDivElement.prototype.querySelectorAll; | |
| 15 | |
| 16 var Screen = function(sendPrefix) { | |
| 17 this.sendPrefix_ = sendPrefix; | |
| 18 this.screenContext_ = null; | |
| 19 this.contextObservers_ = {}; | |
| 20 }; | |
| 21 | |
| 22 Screen.prototype = { | |
| 23 __proto__: HTMLDivElement.prototype, | |
| 24 | |
| 25 /** | |
| 26 * Prefix added to sent to Chrome messages' names. | |
| 27 */ | |
| 28 sendPrefix_: null, | |
| 29 | |
| 30 /** | |
| 31 * Context used by this screen. | |
| 32 */ | |
| 33 screenContext_: null, | |
| 34 | |
| 35 get context() { | |
| 36 return this.screenContext_; | |
| 37 }, | |
| 38 | |
| 39 /** | |
| 40 * Dictionary of context observers that are methods of |this| bound to | |
| 41 * |this|. | |
| 42 */ | |
| 43 contextObservers_: null, | |
| 44 | |
| 45 /** | |
| 46 * Called during screen registration. | |
| 47 */ | |
| 48 decorate: doNothing, | |
| 49 | |
| 50 /** | |
| 51 * Returns minimal size that screen prefers to have. Default implementation | |
| 52 * returns current screen size. | |
| 53 * @return {{width: number, height: number}} | |
| 54 */ | |
| 55 getPreferredSize: function() { | |
| 56 return {width: this.offsetWidth, height: this.offsetHeight}; | |
| 57 }, | |
| 58 | |
| 59 /** | |
| 60 * Called for currently active screen when screen size changed. | |
| 61 */ | |
| 62 onWindowResize: doNothing, | |
| 63 | |
| 64 /** | |
| 65 * @final | |
| 66 */ | |
| 67 initialize: function() { | |
| 68 return this.initializeImpl_.apply(this, arguments); | |
| 69 }, | |
| 70 | |
| 71 /** | |
| 72 * @final | |
| 73 */ | |
| 74 send: function() { | |
| 75 return this.sendImpl_.apply(this, arguments); | |
| 76 }, | |
| 77 | |
| 78 /** | |
| 79 * @final | |
| 80 */ | |
| 81 addContextObserver: function() { | |
| 82 return this.addContextObserverImpl_.apply(this, arguments); | |
| 83 }, | |
| 84 | |
| 85 /** | |
| 86 * @final | |
| 87 */ | |
| 88 removeContextObserver: function() { | |
| 89 return this.removeContextObserverImpl_.apply(this, arguments); | |
| 90 }, | |
| 91 | |
| 92 /** | |
| 93 * @final | |
| 94 */ | |
| 95 commitContextChanges: function() { | |
| 96 return this.commitContextChangesImpl_.apply(this, arguments); | |
| 97 }, | |
| 98 | |
| 99 /** | |
| 100 * @override | |
| 101 * @final | |
| 102 */ | |
| 103 querySelectorAll: function() { | |
| 104 return this.querySelectorAllImpl_.apply(this, arguments); | |
| 105 }, | |
| 106 | |
| 107 /** | |
| 108 * Does the following things: | |
| 109 * * Creates screen context. | |
| 110 * * Looks for elements having "alias" property and adds them as the | |
| 111 * proprties of the screen with name equal to value of "alias", i.e. HTML | |
| 112 * element <div alias="myDiv"></div> will be stored in this.myDiv. | |
| 113 * * Looks for buttons having "action" properties and adds click handlers | |
| 114 * to them. These handlers send |CALLBACK_USER_ACTED| messages to | |
| 115 * C++ with "action" property's value as payload. | |
| 116 * @private | |
| 117 */ | |
| 118 initializeImpl_: function() { | |
| 119 this.screenContext_ = new login.ScreenContext(); | |
| 120 this.querySelectorAllImpl_('[alias]').forEach(function(element) { | |
| 121 var alias = element.getAttribute('alias'); | |
| 122 if (alias in this) | |
| 123 throw Error('Alias "' + alias + '" of "' + this.name() + '" screen ' + | |
| 124 'shadows or redefines property that is already defined.'); | |
| 125 this[alias] = element; | |
| 126 this[element.getAttribute('alias')] = element; | |
| 127 }, this); | |
| 128 var self = this; | |
| 129 this.querySelectorAllImpl_('button[action]').forEach(function(button) { | |
| 130 button.addEventListener('click', function(e) { | |
| 131 var action = this.getAttribute('action'); | |
| 132 self.send(CALLBACK_USER_ACTED, action); | |
| 133 e.stopPropagation(); | |
| 134 }); | |
| 135 }); | |
| 136 }, | |
| 137 | |
| 138 /** | |
| 139 * Sends message to Chrome, adding needed prefix to message name. All | |
| 140 * arguments after |messageName| are packed into message parameters list. | |
| 141 * | |
| 142 * @param {string} messageName Name of message without a prefix. | |
| 143 * @param {...*} varArgs parameters for message. | |
| 144 * @private | |
| 145 */ | |
| 146 sendImpl_: function(messageName, varArgs) { | |
| 147 if (arguments.length == 0) | |
| 148 throw Error('Message name is not provided.'); | |
| 149 var fullMessageName = this.sendPrefix_ + messageName; | |
| 150 var payload = Array.prototype.slice.call(arguments, 1); | |
| 151 chrome.send(fullMessageName, payload); | |
| 152 }, | |
| 153 | |
| 154 /** | |
| 155 * Starts observation of property with |key| of the context attached to | |
| 156 * current screen. This method differs from "login.ScreenContext" in that | |
| 157 * it automatically detects if observer is method of |this| and make | |
| 158 * all needed actions to make it work correctly. So it's no need for client | |
| 159 * to bind methods to |this| and keep resulting callback for | |
| 160 * |removeObserver| call: | |
| 161 * | |
| 162 * this.addContextObserver('key', this.onKeyChanged_); | |
| 163 * ... | |
| 164 * this.removeContextObserver('key', this.onKeyChanged_); | |
| 165 * @private | |
| 166 */ | |
| 167 addContextObserverImpl_: function(key, observer) { | |
| 168 var realObserver = observer; | |
| 169 var propertyName = this.getPropertyNameOf_(observer); | |
| 170 if (propertyName) { | |
| 171 if (!this.contextObservers_.hasOwnProperty(propertyName)) | |
| 172 this.contextObservers_[propertyName] = observer.bind(this); | |
| 173 realObserver = this.contextObservers_[propertyName]; | |
| 174 } | |
| 175 this.screenContext_.addObserver(key, realObserver); | |
| 176 }, | |
| 177 | |
| 178 /** | |
| 179 * Removes |observer| from the list of context observers. Supports not only | |
| 180 * regular functions but also screen methods (see comment to | |
| 181 * |addContextObserver|). | |
| 182 * @private | |
| 183 */ | |
| 184 removeContextObserverImpl_: function(observer) { | |
| 185 var realObserver = observer; | |
| 186 var propertyName = this.getPropertyNameOf_(observer); | |
| 187 if (propertyName) { | |
| 188 if (!this.contextObservers_.hasOwnProperty(propertyName)) | |
| 189 return; | |
| 190 realObserver = this.contextObservers_[propertyName]; | |
| 191 delete this.contextObservers_[propertyName]; | |
| 192 } | |
| 193 this.screenContext_.removeObserver(realObserver); | |
| 194 }, | |
| 195 | |
| 196 /** | |
| 197 * Sends recent context changes to C++ handler. | |
| 198 * @private | |
| 199 */ | |
| 200 commitContextChangesImpl_: function() { | |
| 201 if (!this.screenContext_.hasChanges()) | |
| 202 return; | |
| 203 this.sendImpl_(CALLBACK_CONTEXT_CHANGED, | |
| 204 this.screenContext_.getChangesAndReset()); | |
| 205 }, | |
| 206 | |
| 207 /** | |
| 208 * Calls standart |querySelectorAll| method and returns its result converted | |
| 209 * to Array. | |
| 210 * @private | |
| 211 */ | |
| 212 querySelectorAllImpl_: function(selector) { | |
| 213 var list = querySelectorAll.call(this, selector); | |
| 214 return Array.prototype.slice.call(list); | |
| 215 }, | |
| 216 | |
| 217 /** | |
| 218 * Called when context changes are recieved from C++. | |
| 219 * @private | |
| 220 */ | |
| 221 contextChanged_: function(diff) { | |
| 222 this.screenContext_.applyChanges(diff); | |
| 223 }, | |
| 224 | |
| 225 /** | |
| 226 * If |value| is the value of some property of |this| returns property's | |
| 227 * name. Otherwise returns empty string. | |
| 228 * @private | |
| 229 */ | |
| 230 getPropertyNameOf_: function(value) { | |
| 231 for (var key in this) | |
| 232 if (this[key] === value) | |
| 233 return key; | |
| 234 return ''; | |
| 235 } | |
| 236 }; | |
| 237 | |
| 238 Screen.CALLBACK_USER_ACTED = CALLBACK_USER_ACTED; | |
| 239 | |
| 240 return { | |
| 241 Screen: Screen | |
| 242 }; | |
| 243 }); | |
| 244 | |
| 245 cr.define('login', function() { | |
| 246 return { | |
| 247 /** | |
| 248 * Creates class and object for screen. | |
| 249 * Methods specified in EXTERNAL_API array of prototype | |
| 250 * will be available from C++ part. | |
| 251 * Example: | |
| 252 * login.createScreen('ScreenName', 'screen-id', { | |
| 253 * foo: function() { console.log('foo'); }, | |
| 254 * bar: function() { console.log('bar'); } | |
| 255 * EXTERNAL_API: ['foo']; | |
| 256 * }); | |
| 257 * login.ScreenName.register(); | |
| 258 * var screen = $('screen-id'); | |
| 259 * screen.foo(); // valid | |
| 260 * login.ScreenName.foo(); // valid | |
| 261 * screen.bar(); // valid | |
| 262 * login.ScreenName.bar(); // invalid | |
| 263 * | |
| 264 * @param {string} name Name of created class. | |
| 265 * @param {string} id Id of div representing screen. | |
| 266 * @param {(function()|Object)} proto Prototype of object or function that | |
| 267 * returns prototype. | |
| 268 */ | |
| 269 createScreen: function(name, id, template) { | |
| 270 if (typeof template == 'function') | |
| 271 template = template(); | |
| 272 | |
| 273 var apiNames = template.EXTERNAL_API || []; | |
| 274 for (var i = 0; i < apiNames.length; ++i) { | |
| 275 var methodName = apiNames[i]; | |
| 276 if (typeof template[methodName] !== 'function') | |
| 277 throw Error('External method "' + methodName + '" for screen "' + | |
| 278 name + '" not a function or undefined.'); | |
| 279 } | |
| 280 | |
| 281 function checkPropertyAllowed(propertyName) { | |
| 282 if (propertyName.charAt(propertyName.length - 1) === '_' && | |
| 283 (propertyName in login.Screen.prototype)) { | |
| 284 throw Error('Property "' + propertyName + '" of "' + id + '" ' + | |
| 285 'shadows private property of login.Screen prototype.'); | |
| 286 } | |
| 287 }; | |
| 288 | |
| 289 var Constructor = function() { | |
| 290 login.Screen.call(this, 'login.' + name + '.'); | |
| 291 }; | |
| 292 Constructor.prototype = Object.create(login.Screen.prototype); | |
| 293 var api = {}; | |
| 294 | |
| 295 Object.getOwnPropertyNames(template).forEach(function(propertyName) { | |
| 296 if (propertyName === 'EXTERNAL_API') | |
| 297 return; | |
| 298 | |
| 299 checkPropertyAllowed(propertyName); | |
| 300 | |
| 301 var descriptor = | |
| 302 Object.getOwnPropertyDescriptor(template, propertyName); | |
| 303 Object.defineProperty(Constructor.prototype, propertyName, descriptor); | |
| 304 | |
| 305 if (apiNames.indexOf(propertyName) >= 0) { | |
| 306 api[propertyName] = function() { | |
| 307 var screen = $(id); | |
| 308 return screen[propertyName].apply(screen, arguments); | |
| 309 }; | |
| 310 } | |
| 311 }); | |
| 312 | |
| 313 Constructor.prototype.name = function() { return id; }; | |
| 314 | |
| 315 api.contextChanged = function() { | |
| 316 var screen = $(id); | |
| 317 screen.contextChanged_.apply(screen, arguments); | |
| 318 } | |
| 319 | |
| 320 api.register = function() { | |
| 321 var screen = $(id); | |
| 322 screen.__proto__ = new Constructor(); | |
| 323 screen.decorate(); | |
| 324 Oobe.getInstance().registerScreen(screen); | |
| 325 }; | |
| 326 | |
| 327 cr.define('login', function() { | |
| 328 var result = {}; | |
| 329 result[name] = api; | |
| 330 return result; | |
| 331 }); | |
| 332 } | |
| 333 }; | |
| 334 }); | |
| 335 | |
| OLD | NEW |