-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathbind.js
124 lines (119 loc) · 3.42 KB
/
bind.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
/** @module decor/bind */
define([
"./observe"
], function (observe) {
// Function to set or get a [nested] property.
// @example
// prop(obj, "a.b.c", 10) --> sets obj.a.b.c = 10;
// prop(obj, "a.b.c") --> returns obj.a.b.c
function prop(obj, path, value) {
var set = arguments.length >= 3;
path.split(".").forEach(function (prop, idx, ary) {
if (idx === ary.length - 1) {
if (set) {
obj[prop] = value;
} else {
value = obj[prop];
}
} else {
if (!obj[prop]) {
obj[prop] = {};
}
obj = obj[prop];
}
});
return value;
}
/**
* Bind object `view` to object `model`, so that changes to `model` are reflected to `view`.
* Can also dual reflection from view back to model.
*
* @example
* One way bind example:
*
* var model = {store: 12345, revenue: 1000, cost: 500};
* var view = {store: 0, revenue: 0, cost: 0, profit: 0}
* bind(model, view, {
* store: "store",
* revenue: "revenue",
* profit: function(model){ return model.revenue - model.cost; }
* });
*
* @example
* Two way bind example:
*
* var model = {foo: 2};
* var view = {bar: 1};
* bind(model, view, [
* {
* // Names of properties in model and view objects.
* model: "foo",
* view: "bar",
*
* // Optional transform functions to convert model prop to view prop and vice-versa.
* modelToView: function(model) { return model.foo - 1; },
* viewToModel: function(view) { return view.bar + 1; },
*
* // True if target changes should be reflected to source.
* twoWay: true
* },
* ...
* ]);
*
* @param {Object|decor/Stateful} model
* @param {Object|decor/Stateful} view
* @param {Object} mapping
* Hash or array specifying mapping from model to view [and view back to model].
*
* Hash keys corresponding to keys in the `view` object. Each key maps to either:
*
* * a function that takes the model object and returns a value
* * a string referring to the name of a property in the model
*
* If mapping is an array, then each entry in the array has:
*
* * model: name of property in model
* * view: name of property in view
* * modelToView: optional function to compute view's property's value based on model object
* * viewToModel: optional function to compute model's property's value based on view object
*/
return function (model, view, mapping) {
if (Array.isArray(mapping)) {
// Copy from model to view.
var handle1 = observe(model, function (props) {
mapping.forEach(function (map) {
if (map.modelToView) {
prop(view, map.view, map.modelToView(model));
} else if (map.model in props || /\./.test(map.model)) {
prop(view, map.view, prop(model, map.model));
}
});
});
// Copy from view to model.
var handle2 = observe(view, function (props) {
mapping.forEach(function (map) {
if (map.twoWay) {
if (map.viewToModel) {
prop(model, map.model, map.viewToModel(view));
} else if (map.view in props || /\./.test(map.view)) {
prop(model, map.model, prop(view, map.view));
}
}
});
});
return {
remove: function () {
handle1.remove();
handle2.remove();
}
};
} else {
return observe(model, function () {
Object.keys(mapping).forEach(function (key) {
var map = mapping[key];
prop(view, key, typeof map === "function" ? map(model) : prop(model, map));
});
});
}
};
});