-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
184 lines (163 loc) · 4.38 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/**
* NPM modules
*/
var happens = require( "happens" );
/**
* Module constructor
* @param {Object} target Target to inject the props and methods
* @return {Object} Target with injected props and methods
*/
module.exports = function(target) {
target = target || {};
for(var prop in Aware)
target[prop] = Aware[prop];
target.__init();
return target;
};
/**
* Class Aware.
* @type {Object}
*/
var Aware = {
/**
* Initialize variables.
*/
__init: function() {
this.__store = {};
this.__handlers = {};
this.__happens = happens();
},
/**
* Add listener
* @param {String} key Key name to listen for changes
* @param {Function} fn Listener handler
* @param {Boolean} bypass When true, skips first trigger when prop is
* already set
*/
on: function(key, fn, bypass){
if(!(fn && fn instanceof Function))
throw new Error(fn + ' is not a Function');
this.__happens.on(key, fn);
if(this.__store.hasOwnProperty(key) && !bypass)
fn(this.get(key));
},
/**
* Removes listener
* @param {String} key Key name to unlisten
* @param {Function} fn Handler to remove
*/
off: function(key, fn){
this.__happens.off(key, fn);
},
/**
* Reads some key's value
* @param {String} key Key name to get value from
* @return {Object} Key value
*/
get: function(key){
return interpolate(this, this.__store[key]);
},
/**
* Set some key's value
* @param {String} key Key name to set value to
* @param {Object} value The set value
*/
set: function(key, value){
if(arguments.length == 2) {
if(this.__store[key] !== value){
this.__store[key] = value;
emit(this, key);
toogle(this, key, value);
}
return value;
}
else if(key instanceof Object) {
for(var p in key) this.set(p, key[p]);
return key;
}
throw new Error('Cannot set property, it must be a dictionary');
}
};
/*******************************************************************************
* Goodies -- methods used internally
******************************************************************************/
/**
* Interpolates values internally re-binding key dependencies
* @param {Aware} aw Aware reference
* @param {Object} value Key value being set
* @return {Object} Setted value
*/
function interpolate(aw, value){
if(value === undefined) return null;
var t, token, tokens = tokenize(value);
for(t in tokens){
token = tokens[t];
value = value.replace(token, aw.get(clean(token)));
}
return value;
}
/**
* Automatically bind/unbinds keys internally
* @param {Aware} aw Aware reference
* @param {String} key Key being set
* @param {[Object} value Key value to set
*/
function toogle(aw, key, value){
var parent, t,tokens = tokenize(value);
for(t in tokens)
bind(aw, key, tokens[t] = clean(tokens[t]));
for(parent in aw.__handlers[key])
if(tokens.indexOf(parent) === -1)
unbind(aw, key, parent);
}
/**
* Binds key internally
* @param {Aware} aw Aware reference
* @param {String} key Key to bind
* @param {String} parent Parent key to be binded
*/
function bind(aw, key, parent){
if(!aw.__handlers[key] || !aw.__handlers[key][parent]){
if(!aw.__handlers[key]) aw.__handlers[key] = {};
aw.on(parent, aw.__handlers[key][parent] = function(value){
emit(aw, key);
}, true);
}
}
/**
* Unbinds key internally
* @param {Aware} aw Aware reference
* @param {String} key Key to unbind
* @param {String} parent Parent key to be unbinded
*/
function unbind(aw, key, parent){
aw.off(parent, aw.__handlers[key][parent]);
aw.__handlers[key][parent] = null;
delete aw.__handlers[key][parent];
}
/**
* Emits event
* @param {Aware} aw Aware reference
* @param {String} key Key to be emitted
*/
function emit(aw, key){
aw.__happens.emit(key, aw.get(key));
}
/**
* Extracts possible tokens (interpolated key names) from given value
* @param {String} str String to extract tokens from
* @return {Array} Found tokens
*/
function tokenize(str){
if(typeof(str) === 'string')
return str.match(/#\{[^\}]+\}/g) || [];
return [];
}
/**
* Clean given token, returning its name without the brackets
* @param {String} str Token to be clean
* @return {String} Clean token name
*/
function clean(str) {
return str.replace(/#\{([^\}]+)\}/g, '$1');
}