UNPKG

9.52 kBJavaScriptView Raw
1var __assign = (this && this.__assign) || function () {
2 __assign = Object.assign || function(t) {
3 for (var s, i = 1, n = arguments.length; i < n; i++) {
4 s = arguments[i];
5 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6 t[p] = s[p];
7 }
8 return t;
9 };
10 return __assign.apply(this, arguments);
11};
12var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
13 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
14 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
15 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
16 return c > 3 && r && Object.defineProperty(target, key, r), r;
17};
18import { action, observable, isObservableObject, isObservableArray, isObservableMap, isComputedProp, isComputed, computed, keys, _getAdministration, $mobx, makeObservable, } from "mobx";
19import { invariant, getAllMethodsAndProperties } from "./utils";
20var RESERVED_NAMES = ["model", "reset", "submit", "isDirty", "isPropertyDirty", "resetProperty"];
21var ViewModel = /** @class */ (function () {
22 function ViewModel(model) {
23 var _this = this;
24 Object.defineProperty(this, "model", {
25 enumerable: true,
26 configurable: true,
27 writable: true,
28 value: model
29 });
30 Object.defineProperty(this, "localValues", {
31 enumerable: true,
32 configurable: true,
33 writable: true,
34 value: observable.map({})
35 });
36 Object.defineProperty(this, "localComputedValues", {
37 enumerable: true,
38 configurable: true,
39 writable: true,
40 value: observable.map({})
41 });
42 Object.defineProperty(this, "isPropertyDirty", {
43 enumerable: true,
44 configurable: true,
45 writable: true,
46 value: function (key) {
47 return _this.localValues.has(key);
48 }
49 });
50 makeObservable(this);
51 invariant(isObservableObject(model), "createViewModel expects an observable object");
52 var ownMethodsAndProperties = getAllMethodsAndProperties(this);
53 // use this helper as Object.getOwnPropertyNames doesn't return getters
54 getAllMethodsAndProperties(model).forEach(function (key) {
55 var _a;
56 if (ownMethodsAndProperties.includes(key)) {
57 return;
58 }
59 if (key === $mobx || key === "__mobxDidRunLazyInitializers") {
60 return;
61 }
62 invariant(RESERVED_NAMES.indexOf(key) === -1, "The propertyname " + key + " is reserved and cannot be used with viewModels");
63 if (isComputedProp(model, key)) {
64 var computedBox = _getAdministration(model, key); // Fixme: there is no clear api to get the derivation
65 var get = computedBox.derivation.bind(_this);
66 var set = (_a = computedBox.setter_) === null || _a === void 0 ? void 0 : _a.bind(_this);
67 _this.localComputedValues.set(key, computed(get, { set: set }));
68 }
69 var descriptor = Object.getOwnPropertyDescriptor(model, key);
70 var additionalDescriptor = descriptor ? { enumerable: descriptor.enumerable } : {};
71 Object.defineProperty(_this, key, __assign(__assign({}, additionalDescriptor), { configurable: true, get: function () {
72 if (isComputedProp(model, key))
73 return _this.localComputedValues.get(key).get();
74 if (_this.isPropertyDirty(key))
75 return _this.localValues.get(key);
76 else
77 return _this.model[key];
78 }, set: action(function (value) {
79 if (isComputedProp(model, key)) {
80 _this.localComputedValues.get(key).set(value);
81 }
82 else if (value !== _this.model[key]) {
83 _this.localValues.set(key, value);
84 }
85 else {
86 _this.localValues.delete(key);
87 }
88 }) }));
89 });
90 }
91 Object.defineProperty(ViewModel.prototype, "isDirty", {
92 get: function () {
93 return this.localValues.size > 0;
94 },
95 enumerable: false,
96 configurable: true
97 });
98 Object.defineProperty(ViewModel.prototype, "changedValues", {
99 get: function () {
100 return new Map(this.localValues);
101 },
102 enumerable: false,
103 configurable: true
104 });
105 Object.defineProperty(ViewModel.prototype, "submit", {
106 enumerable: false,
107 configurable: true,
108 writable: true,
109 value: function () {
110 var _this = this;
111 keys(this.localValues).forEach(function (key) {
112 var source = _this.localValues.get(key);
113 var destination = _this.model[key];
114 if (isObservableArray(destination)) {
115 destination.replace(source);
116 }
117 else if (isObservableMap(destination)) {
118 destination.clear();
119 destination.merge(source);
120 }
121 else if (!isComputed(source)) {
122 _this.model[key] = source;
123 }
124 });
125 this.localValues.clear();
126 }
127 });
128 Object.defineProperty(ViewModel.prototype, "reset", {
129 enumerable: false,
130 configurable: true,
131 writable: true,
132 value: function () {
133 this.localValues.clear();
134 }
135 });
136 Object.defineProperty(ViewModel.prototype, "resetProperty", {
137 enumerable: false,
138 configurable: true,
139 writable: true,
140 value: function (key) {
141 this.localValues.delete(key);
142 }
143 });
144 __decorate([
145 computed
146 ], ViewModel.prototype, "isDirty", null);
147 __decorate([
148 computed
149 ], ViewModel.prototype, "changedValues", null);
150 __decorate([
151 action.bound
152 ], ViewModel.prototype, "submit", null);
153 __decorate([
154 action.bound
155 ], ViewModel.prototype, "reset", null);
156 __decorate([
157 action.bound
158 ], ViewModel.prototype, "resetProperty", null);
159 return ViewModel;
160}());
161export { ViewModel };
162/**
163 * `createViewModel` takes an object with observable properties (model)
164 * and wraps a viewmodel around it. The viewmodel proxies all enumerable properties of the original model with the following behavior:
165 * - as long as no new value has been assigned to the viewmodel property, the original property will be returned.
166 * - any future change in the model will be visible in the viewmodel as well unless the viewmodel property was dirty at the time of the attempted change.
167 * - once a new value has been assigned to a property of the viewmodel, that value will be returned during a read of that property in the future. However, the original model remain untouched until `submit()` is called.
168 *
169 * The viewmodel exposes the following additional methods, besides all the enumerable properties of the model:
170 * - `submit()`: copies all the values of the viewmodel to the model and resets the state
171 * - `reset()`: resets the state of the viewmodel, abandoning all local modifications
172 * - `resetProperty(propName)`: resets the specified property of the viewmodel
173 * - `isDirty`: observable property indicating if the viewModel contains any modifications
174 * - `isPropertyDirty(propName)`: returns true if the specified property is dirty
175 * - `changedValues`: returns a key / value map with the properties that have been changed in the model so far
176 * - `model`: The original model object for which this viewModel was created
177 *
178 * You may use observable arrays, maps and objects with `createViewModel` but keep in mind to assign fresh instances of those to the viewmodel's properties, otherwise you would end up modifying the properties of the original model.
179 * Note that if you read a non-dirty property, viewmodel only proxies the read to the model. You therefore need to assign a fresh instance not only the first time you make the assignment but also after calling `reset()` or `submit()`.
180 *
181 * @example
182 * class Todo {
183 * \@observable title = "Test"
184 * }
185 *
186 * const model = new Todo()
187 * const viewModel = createViewModel(model);
188 *
189 * autorun(() => console.log(viewModel.model.title, ",", viewModel.title))
190 * // prints "Test, Test"
191 * model.title = "Get coffee"
192 * // prints "Get coffee, Get coffee", viewModel just proxies to model
193 * viewModel.title = "Get tea"
194 * // prints "Get coffee, Get tea", viewModel's title is now dirty, and the local value will be printed
195 * viewModel.submit()
196 * // prints "Get tea, Get tea", changes submitted from the viewModel to the model, viewModel is proxying again
197 * viewModel.title = "Get cookie"
198 * // prints "Get tea, Get cookie" // viewModel has diverged again
199 * viewModel.reset()
200 * // prints "Get tea, Get tea", changes of the viewModel have been abandoned
201 *
202 * @param {T} model
203 * @returns {(T & IViewModel<T>)}
204 * ```
205 */
206export function createViewModel(model) {
207 return new ViewModel(model);
208}