Rename isSystemLocationEnabled to isLocationEnabled, as per internal review (185995).
[chromium-blink-merge.git] / ui / login / screen.js
blobb3fee5b15ed958be80d9a669a306b04310320039
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.
5 /**
6 * @fileoverview Base class for all login WebUI screens.
7 */
8 cr.define('login', function() {
9 /** @const */ var CALLBACK_CONTEXT_CHANGED = 'contextChanged';
10 /** @const */ var CALLBACK_USER_ACTED = 'userActed';
12 function doNothing() {};
14 var querySelectorAll = HTMLDivElement.prototype.querySelectorAll;
16 var Screen = function(sendPrefix) {
17 this.sendPrefix_ = sendPrefix;
18 this.screenContext_ = null;
19 this.contextObservers_ = {};
22 Screen.prototype = {
23 __proto__: HTMLDivElement.prototype,
25 /**
26 * Prefix added to sent to Chrome messages' names.
28 sendPrefix_: null,
30 /**
31 * Context used by this screen.
33 screenContext_: null,
35 get context() {
36 return this.screenContext_;
39 /**
40 * Dictionary of context observers that are methods of |this| bound to
41 * |this|.
43 contextObservers_: null,
45 /**
46 * Called during screen initialization.
48 decorate: doNothing,
50 /**
51 * Returns minimal size that screen prefers to have. Default implementation
52 * returns current screen size.
53 * @return {{width: number, height: number}}
55 getPreferredSize: function() {
56 return {width: this.offsetWidth, height: this.offsetHeight};
59 /**
60 * Called for currently active screen when screen size changed.
62 onWindowResize: doNothing,
64 /**
65 * @final
67 initialize: function() {
68 return this.initializeImpl_.apply(this, arguments);
71 /**
72 * @final
74 send: function() {
75 return this.sendImpl_.apply(this, arguments);
78 /**
79 * @final
81 addContextObserver: function() {
82 return this.addContextObserverImpl_.apply(this, arguments);
85 /**
86 * @final
88 removeContextObserver: function() {
89 return this.removeContextObserverImpl_.apply(this, arguments);
92 /**
93 * @final
95 commitContextChanges: function() {
96 return this.commitContextChangesImpl_.apply(this, arguments);
99 /**
100 * @final
102 declareButton: function(id) {
103 var self = this;
104 var button = this.ownerDocument.createElement('button');
105 button.id = id;
107 button.addEventListener('click', function(e) {
108 self.sendImpl_(CALLBACK_USER_ACTED, id);
109 e.stopPropagation();
112 return button;
116 * @override
117 * @final
119 querySelectorAll: function() {
120 return this.querySelectorAllImpl_.apply(this, arguments);
124 * Does the following things:
125 * * Creates screen context.
126 * * Looks for elements having "alias" property and adds them as the
127 * proprties of the screen with name equal to value of "alias", i.e. HTML
128 * element <div alias="myDiv"></div> will be stored in this.myDiv.
129 * * Looks for buttons having "action" properties and adds click handlers
130 * to them. These handlers send |CALLBACK_USER_ACTED| messages to
131 * C++ with "action" property's value as payload.
132 * @private
134 initializeImpl_: function() {
135 if (cr.isChromeOS)
136 this.screenContext_ = new login.ScreenContext();
138 this.decorate();
140 this.querySelectorAllImpl_('[alias]').forEach(function(element) {
141 var alias = element.getAttribute('alias');
142 if (alias in this)
143 throw Error('Alias "' + alias + '" of "' + this.name() + '" screen ' +
144 'shadows or redefines property that is already defined.');
145 this[alias] = element;
146 this[element.getAttribute('alias')] = element;
147 }, this);
148 var self = this;
149 this.querySelectorAllImpl_('button[action]').forEach(function(button) {
150 button.addEventListener('click', function(e) {
151 var action = this.getAttribute('action');
152 self.send(CALLBACK_USER_ACTED, action);
153 e.stopPropagation();
159 * Sends message to Chrome, adding needed prefix to message name. All
160 * arguments after |messageName| are packed into message parameters list.
162 * @param {string} messageName Name of message without a prefix.
163 * @param {...*} varArgs parameters for message.
164 * @private
166 sendImpl_: function(messageName, varArgs) {
167 if (arguments.length == 0)
168 throw Error('Message name is not provided.');
169 var fullMessageName = this.sendPrefix_ + messageName;
170 var payload = Array.prototype.slice.call(arguments, 1);
171 chrome.send(fullMessageName, payload);
175 * Starts observation of property with |key| of the context attached to
176 * current screen. This method differs from "login.ScreenContext" in that
177 * it automatically detects if observer is method of |this| and make
178 * all needed actions to make it work correctly. So it's no need for client
179 * to bind methods to |this| and keep resulting callback for
180 * |removeObserver| call:
182 * this.addContextObserver('key', this.onKeyChanged_);
183 * ...
184 * this.removeContextObserver('key', this.onKeyChanged_);
185 * @private
187 addContextObserverImpl_: function(key, observer) {
188 var realObserver = observer;
189 var propertyName = this.getPropertyNameOf_(observer);
190 if (propertyName) {
191 if (!this.contextObservers_.hasOwnProperty(propertyName))
192 this.contextObservers_[propertyName] = observer.bind(this);
193 realObserver = this.contextObservers_[propertyName];
195 this.screenContext_.addObserver(key, realObserver);
199 * Removes |observer| from the list of context observers. Supports not only
200 * regular functions but also screen methods (see comment to
201 * |addContextObserver|).
202 * @private
204 removeContextObserverImpl_: function(observer) {
205 var realObserver = observer;
206 var propertyName = this.getPropertyNameOf_(observer);
207 if (propertyName) {
208 if (!this.contextObservers_.hasOwnProperty(propertyName))
209 return;
210 realObserver = this.contextObservers_[propertyName];
211 delete this.contextObservers_[propertyName];
213 this.screenContext_.removeObserver(realObserver);
217 * Sends recent context changes to C++ handler.
218 * @private
220 commitContextChangesImpl_: function() {
221 if (!this.screenContext_.hasChanges())
222 return;
223 this.sendImpl_(CALLBACK_CONTEXT_CHANGED,
224 this.screenContext_.getChangesAndReset());
228 * Calls standart |querySelectorAll| method and returns its result converted
229 * to Array.
230 * @private
232 querySelectorAllImpl_: function(selector) {
233 var list = querySelectorAll.call(this, selector);
234 return Array.prototype.slice.call(list);
238 * Called when context changes are recieved from C++.
239 * @private
241 contextChanged_: function(diff) {
242 this.screenContext_.applyChanges(diff);
246 * If |value| is the value of some property of |this| returns property's
247 * name. Otherwise returns empty string.
248 * @private
250 getPropertyNameOf_: function(value) {
251 for (var key in this)
252 if (this[key] === value)
253 return key;
254 return '';
258 Screen.CALLBACK_USER_ACTED = CALLBACK_USER_ACTED;
260 return {
261 Screen: Screen
265 cr.define('login', function() {
266 return {
268 * Creates class and object for screen.
269 * Methods specified in EXTERNAL_API array of prototype
270 * will be available from C++ part.
271 * Example:
272 * login.createScreen('ScreenName', 'screen-id', {
273 * foo: function() { console.log('foo'); },
274 * bar: function() { console.log('bar'); }
275 * EXTERNAL_API: ['foo'];
276 * });
277 * login.ScreenName.register();
278 * var screen = $('screen-id');
279 * screen.foo(); // valid
280 * login.ScreenName.foo(); // valid
281 * screen.bar(); // valid
282 * login.ScreenName.bar(); // invalid
284 * @param {string} name Name of created class.
285 * @param {string} id Id of div representing screen.
286 * @param {(function()|Object)} proto Prototype of object or function that
287 * returns prototype.
289 createScreen: function(name, id, template) {
290 if (typeof template == 'function')
291 template = template();
293 var apiNames = template.EXTERNAL_API || [];
294 for (var i = 0; i < apiNames.length; ++i) {
295 var methodName = apiNames[i];
296 if (typeof template[methodName] !== 'function')
297 throw Error('External method "' + methodName + '" for screen "' +
298 name + '" not a function or undefined.');
301 function checkPropertyAllowed(propertyName) {
302 if (propertyName.charAt(propertyName.length - 1) === '_' &&
303 (propertyName in login.Screen.prototype)) {
304 throw Error('Property "' + propertyName + '" of "' + id + '" ' +
305 'shadows private property of login.Screen prototype.');
309 var Constructor = function() {
310 login.Screen.call(this, 'login.' + name + '.');
312 Constructor.prototype = Object.create(login.Screen.prototype);
313 var api = {};
315 Object.getOwnPropertyNames(template).forEach(function(propertyName) {
316 if (propertyName === 'EXTERNAL_API')
317 return;
319 checkPropertyAllowed(propertyName);
321 var descriptor =
322 Object.getOwnPropertyDescriptor(template, propertyName);
323 Object.defineProperty(Constructor.prototype, propertyName, descriptor);
325 if (apiNames.indexOf(propertyName) >= 0) {
326 api[propertyName] = function() {
327 var screen = $(id);
328 return screen[propertyName].apply(screen, arguments);
333 Constructor.prototype.name = function() { return id; };
335 api.contextChanged = function() {
336 var screen = $(id);
337 screen.contextChanged_.apply(screen, arguments);
340 api.register = function() {
341 var screen = $(id);
342 screen.__proto__ = new Constructor();
343 screen.initialize();
344 Oobe.getInstance().registerScreen(screen);
347 cr.define('login', function() {
348 var result = {};
349 result[name] = api;
350 return result;