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.
6 * @fileoverview Base class for all login WebUI screens.
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_
= {};
23 __proto__
: HTMLDivElement
.prototype,
26 * Prefix added to sent to Chrome messages' names.
31 * Context used by this screen.
36 return this.screenContext_
;
40 * Dictionary of context observers that are methods of |this| bound to
43 contextObservers_
: null,
46 * Called during screen initialization.
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
};
60 * Called for currently active screen when screen size changed.
62 onWindowResize
: doNothing
,
67 initialize: function() {
68 return this.initializeImpl_
.apply(this, arguments
);
75 return this.sendImpl_
.apply(this, arguments
);
81 addContextObserver: function() {
82 return this.addContextObserverImpl_
.apply(this, arguments
);
88 removeContextObserver: function() {
89 return this.removeContextObserverImpl_
.apply(this, arguments
);
95 commitContextChanges: function() {
96 return this.commitContextChangesImpl_
.apply(this, arguments
);
102 declareButton: function(id
) {
104 var button
= this.ownerDocument
.createElement('button');
107 button
.addEventListener('click', function(e
) {
108 self
.sendImpl_(CALLBACK_USER_ACTED
, id
);
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.
134 initializeImpl_: function() {
136 this.screenContext_
= new login
.ScreenContext();
140 this.querySelectorAllImpl_('[alias]').forEach(function(element
) {
141 var alias
= element
.getAttribute('alias');
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
;
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
);
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.
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_);
184 * this.removeContextObserver('key', this.onKeyChanged_);
187 addContextObserverImpl_: function(key
, observer
) {
188 var realObserver
= observer
;
189 var propertyName
= this.getPropertyNameOf_(observer
);
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|).
204 removeContextObserverImpl_: function(observer
) {
205 var realObserver
= observer
;
206 var propertyName
= this.getPropertyNameOf_(observer
);
208 if (!this.contextObservers_
.hasOwnProperty(propertyName
))
210 realObserver
= this.contextObservers_
[propertyName
];
211 delete this.contextObservers_
[propertyName
];
213 this.screenContext_
.removeObserver(realObserver
);
217 * Sends recent context changes to C++ handler.
220 commitContextChangesImpl_: function() {
221 if (!this.screenContext_
.hasChanges())
223 this.sendImpl_(CALLBACK_CONTEXT_CHANGED
,
224 this.screenContext_
.getChangesAndReset());
228 * Calls standart |querySelectorAll| method and returns its result converted
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++.
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.
250 getPropertyNameOf_: function(value
) {
251 for (var key
in this)
252 if (this[key
] === value
)
258 Screen
.CALLBACK_USER_ACTED
= CALLBACK_USER_ACTED
;
265 cr
.define('login', function() {
268 * Creates class and object for screen.
269 * Methods specified in EXTERNAL_API array of prototype
270 * will be available from C++ part.
272 * login.createScreen('ScreenName', 'screen-id', {
273 * foo: function() { console.log('foo'); },
274 * bar: function() { console.log('bar'); }
275 * EXTERNAL_API: ['foo'];
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
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);
315 Object
.getOwnPropertyNames(template
).forEach(function(propertyName
) {
316 if (propertyName
=== 'EXTERNAL_API')
319 checkPropertyAllowed(propertyName
);
322 Object
.getOwnPropertyDescriptor(template
, propertyName
);
323 Object
.defineProperty(Constructor
.prototype, propertyName
, descriptor
);
325 if (apiNames
.indexOf(propertyName
) >= 0) {
326 api
[propertyName
] = function() {
328 return screen
[propertyName
].apply(screen
, arguments
);
333 Constructor
.prototype.name = function() { return id
; };
335 api
.contextChanged = function() {
337 screen
.contextChanged_
.apply(screen
, arguments
);
340 api
.register = function() {
342 screen
.__proto__
= new Constructor();
344 Oobe
.getInstance().registerScreen(screen
);
347 cr
.define('login', function() {