1 // Copyright (c) 2013 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 Implementation of ScreenContext class: key-value storage for 7 * values that are shared between C++ and JS sides. 8 */ 9 cr.define('login', function() { 10 'use strict'; 11 12 function require(condition, message) { 13 if (!condition) { 14 throw Error(message); 15 } 16 } 17 18 function checkKeyIsValid(key) { 19 var keyType = typeof key; 20 require(keyType === 'string', 'Invalid type of key: "' + keyType + '".'); 21 } 22 23 function checkValueIsValid(value) { 24 var valueType = typeof value; 25 require((['string', 'boolean', 'number'].indexOf(valueType) != -1 || 26 Array.isArray(value)), 27 'Invalid type of value: "' + valueType + '".'); 28 } 29 30 function ScreenContext() { 31 this.storage_ = {}; 32 this.changes_ = {}; 33 this.observers_ = {}; 34 } 35 36 ScreenContext.prototype = { 37 /** 38 * Returns stored value for |key| or |defaultValue| if key not found in 39 * storage. Throws Error if key not found and |defaultValue| omitted. 40 */ 41 get: function(key, defaultValue) { 42 checkKeyIsValid(key); 43 if (this.hasKey(key)) { 44 return this.storage_[key]; 45 } else if (typeof defaultValue !== 'undefined') { 46 return defaultValue; 47 } else { 48 throw Error('Key "' + key + '" not found.'); 49 } 50 }, 51 52 /** 53 * Sets |value| for |key|. Returns true if call changes state of context, 54 * false otherwise. 55 */ 56 set: function(key, value) { 57 checkKeyIsValid(key); 58 checkValueIsValid(value); 59 if (this.hasKey(key) && this.storage_[key] === value) 60 return false; 61 this.changes_[key] = value; 62 this.storage_[key] = value; 63 return true; 64 }, 65 66 hasKey: function(key) { 67 checkKeyIsValid(key); 68 return this.storage_.hasOwnProperty(key); 69 }, 70 71 hasChanges: function() { 72 return Object.keys(this.changes_).length > 0; 73 }, 74 75 /** 76 * Applies |changes| to context. Returns Array of changed keys' names. 77 */ 78 applyChanges: function(changes) { 79 require(!this.hasChanges(), 'Context has changes.'); 80 var oldValues = {}; 81 for (var key in changes) { 82 checkKeyIsValid(key); 83 checkValueIsValid(changes[key]); 84 oldValues[key] = this.storage_[key]; 85 this.storage_[key] = changes[key]; 86 } 87 var observers = this.cloneObservers_(); 88 for (var key in changes) { 89 if (observers.hasOwnProperty(key)) { 90 var keyObservers = observers[key]; 91 for (var observerIndex in keyObservers) 92 keyObservers[observerIndex](changes[key], oldValues[key], key); 93 } 94 } 95 return Object.keys(changes); 96 }, 97 98 /** 99 * Returns changes made on context since previous call. 100 */ 101 getChangesAndReset: function() { 102 var result = this.changes_; 103 this.changes_ = {}; 104 return result; 105 }, 106 107 addObserver: function(key, observer) { 108 if (!this.observers_.hasOwnProperty(key)) 109 this.observers_[key] = []; 110 if (this.observers_[key].indexOf(observer) !== -1) { 111 console.warn('Observer already registered.'); 112 return; 113 } 114 this.observers_[key].push(observer); 115 }, 116 117 removeObserver: function(observer) { 118 for (var key in this.observers_) { 119 var observerIndex = this.observers_[key].indexOf(observer); 120 if (observerIndex != -1) 121 this.observers_[key].splice(observerIndex, 1); 122 } 123 }, 124 125 /** 126 * Creates deep copy of observers lists. 127 * @private 128 */ 129 cloneObservers_: function() { 130 var result = {}; 131 for (var key in this.observers_) 132 result[key] = this.observers_[key].slice(); 133 return result; 134 } 135 }; 136 137 return { 138 ScreenContext: ScreenContext 139 }; 140 }); 141