Commit 4cd72855 authored by dzhioev@chromium.org's avatar dzhioev@chromium.org

Added usefull utility methods to Screen and ScreenContext.

List of changes:
 * Added ability to annotate HTML elements (see login.Screen.initialize method comment).
 * Prefix is added to messages sent from JS to C++ (login.Screen.send method).
 * Added ability to observe changes in specific keys of context (on JS side).
 * And more.

BUG=NONE

Review URL: https://codereview.chromium.org/328763003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278724 0039d316-1c4b-4281-b951-d872f2087c98
parent 078780b9
......@@ -98,13 +98,15 @@ void ScreenContext::ApplyChanges(const base::DictionaryValue& diff,
std::vector<std::string>* keys) {
DCHECK(CalledOnValidThread());
DCHECK(!HasChanges());
DCHECK(keys);
keys->clear();
keys->reserve(diff.size());
if (keys) {
keys->clear();
keys->reserve(diff.size());
}
base::DictionaryValue::Iterator it(diff);
while (!it.IsAtEnd()) {
Set(it.key(), it.value().DeepCopy());
keys->push_back(it.key());
if (keys)
keys->push_back(it.key());
it.Advance();
}
changes_.Clear();
......
......@@ -71,7 +71,7 @@ class ScreenContext : public base::NonThreadSafe {
void GetChangesAndReset(base::DictionaryValue* diff);
// Applies changes from |diff| to the context. All keys from |diff|
// are stored in |keys|.
// are stored in |keys|. |keys| argument is optional and can be NULL.
void ApplyChanges(const base::DictionaryValue& diff,
std::vector<std::string>* keys);
......
......@@ -7,6 +7,7 @@
*/
<include src="../../login/screen.js"></include>
<include src="screen_context.js"></include>
<include src="../user_images_grid.js"></include>
<include src="apps_menu.js"></include>
<include src="../../login/bubble.js"></include>
......
......@@ -7,6 +7,8 @@
* values that are shared between C++ and JS sides.
*/
cr.define('login', function() {
'use strict';
function require(condition, message) {
if (!condition) {
throw Error(message);
......@@ -27,6 +29,7 @@ cr.define('login', function() {
function ScreenContext() {
this.storage_ = {};
this.changes_ = {};
this.observers_ = {};
}
ScreenContext.prototype = {
......@@ -73,11 +76,21 @@ cr.define('login', function() {
*/
applyChanges: function(changes) {
require(!this.hasChanges(), 'Context has changes.');
Object.keys(changes).forEach(function(key) {
var oldValues = {};
for (var key in changes) {
checkKeyIsValid(key);
checkValueIsValid(changes[key]);
oldValues[key] = this.storage_[key];
this.storage_[key] = changes[key];
}, this);
}
var observers = this.cloneObservers_();
for (var key in changes) {
if (observers.hasOwnProperty(key)) {
var keyObservers = observers[key];
for (var observerIndex in keyObservers)
keyObservers[observerIndex](changes[key], oldValues[key], key);
}
}
return Object.keys(changes);
},
......@@ -88,6 +101,35 @@ cr.define('login', function() {
var result = this.changes_;
this.changes_ = {};
return result;
},
addObserver: function(key, observer) {
if (!this.observers_.hasOwnProperty(key))
this.observers_[key] = [];
if (this.observers_[key].indexOf(observer) !== -1) {
console.warn('Observer already registered.');
return;
}
this.observers_[key].push(observer);
},
removeObserver: function(observer) {
for (var key in this.observers_) {
var observerIndex = this.observers_[key].indexOf(observer);
if (observerIndex != -1)
this.observers_[key].splice(observerIndex, 1);
}
},
/**
* Creates deep copy of observers lists.
* @private
*/
cloneObservers_: function() {
var result = {};
for (var key in this.observers_)
result[key] = this.observers_[key].slice();
return result;
}
};
......
......@@ -8,11 +8,60 @@
cr.define('login', function() {
var Screen = cr.ui.define('div');
/** @const */ var CALLBACK_USER_ACTED = 'userActed';
/** @const */ var CALLBACK_CONTEXT_CHANGED = 'contextChanged';
function doNothing() {};
var querySelectorAll = HTMLDivElement.prototype.querySelectorAll;
Screen.prototype = {
__proto__: HTMLDivElement.prototype,
/**
* Prefix added to sent to Chrome messages' names.
*/
sendPrefix_: '',
/**
* Context used by this screen.
*/
context_: null,
/**
* Dictionary of context observers that are methods of |this| bound to
* |this|.
*/
contextObservers_: {},
get context() {
return this.context_;
},
/**
* Sends recent context changes to C++ handler.
*/
commitContextChanges: function() {
if (!this.context_.hasChanges())
return;
this.send(CALLBACK_CONTEXT_CHANGED, this.context_.getChangesAndReset());
},
/**
* Sends message to Chrome, adding needed prefix to message name. All
* arguments after |messageName| are packed into message parameters list.
*
* @param {string} messageName Name of message without a prefix.
* @param {...*} varArgs parameters for message.
*/
send: function(messageName, varArgs) {
if (arguments.length == 0)
throw Error('Message name is not provided.');
var fullMessageName = this.sendPrefix_ + messageName;
var payload = Array.prototype.slice.call(arguments, 1);
chrome.send(fullMessageName, payload);
},
decorate: doNothing,
/**
......@@ -28,8 +77,104 @@ cr.define('login', function() {
* Called for currently active screen when screen size changed.
*/
onWindowResize: doNothing,
/**
* Does the following things:
* * Creates screen context.
* * Looks for elements having "alias" property and adds them as the
* proprties of the screen with name equal to value of "alias", i.e. HTML
* element <div alias="myDiv"></div> will be stored in this.myDiv.
* * Looks for buttons having "action" properties and adds click handlers
* to them. These handlers send |CALLBACK_USER_ACTED| messages to
* C++ with "action" property's value as payload.
*/
initialize: function() {
this.context_ = new login.ScreenContext();
this.querySelectorAll('[alias]').forEach(function(element) {
this[element.getAttribute('alias')] = element;
}, this);
var self = this;
this.querySelectorAll('button[action]').forEach(function(button) {
button.addEventListener('click', function(e) {
var action = this.getAttribute('action');
self.send(CALLBACK_USER_ACTED, action);
e.stopPropagation();
});
});
},
/**
* Starts observation of property with |key| of the context attached to
* current screen. This method differs from "login.ScreenContext" in that
* it automatically detects if observer is method of |this| and make
* all needed actions to make it work correctly. So it's no need for client
* to bind methods to |this| and keep resulting callback for
* |removeObserver| call:
*
* this.addContextObserver('key', this.onKeyChanged_);
* ...
* this.removeContextObserver('key', this.onKeyChanged_);
*/
addContextObserver: function(key, observer) {
var realObserver = observer;
var propertyName = this.getPropertyNameOf_(observer);
if (propertyName) {
if (!this.contextObservers_.hasOwnProperty(propertyName))
this.contextObservers_[propertyName] = observer.bind(this);
realObserver = this.contextObservers_[propertyName];
}
this.context.addObserver(key, realObserver);
},
/**
* Removes |observer| from the list of context observers. Supports not only
* regular functions but also screen methods (see comment to
* |addContextObserver|).
*/
removeContextObserver: function(observer) {
var realObserver = observer;
var propertyName = this.getPropertyNameOf_(observer);
if (propertyName) {
if (!this.contextObservers_.hasOwnProperty(propertyName))
return;
realObserver = this.contextObservers_[propertyName];
delete this.contextObservers_[propertyName];
}
this.context.removeObserver(realObserver);
},
/**
* Calls standart |querySelectorAll| method and returns its result converted
* to Array.
*/
querySelectorAll: function(selector) {
var list = querySelectorAll.call(this, selector);
return Array.prototype.slice.call(list);
},
/**
* Called when context changes are recieved from C++.
* @private
*/
contextChanged_: function(diff) {
this.context.applyChanges(diff);
},
/**
* If |value| is the value of some property of |this| returns property's
* name. Otherwise returns empty string.
* @private
*/
getPropertyNameOf_: function(value) {
for (var key in this)
if (this[key] === value)
return key;
return '';
}
};
Screen.CALLBACK_USER_ACTED = CALLBACK_USER_ACTED;
return {
Screen: Screen
};
......@@ -87,7 +232,12 @@ cr.define('login', function() {
})(propertyName);
}
});
constructor.contextChanged = function() {
var screen = $(id);
screen.contextChanged_.apply(screen, arguments);
}
constructor.prototype.name = function() { return id; };
constructor.prototype.sendPrefix_ = 'login.' + name + '.';
constructor.register = function() {
var screen = $(id);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment