├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── dist
├── mux.js
└── mux.min.js
├── gulpfile.js
├── index.js
├── lib
├── array-hook.js
├── info.js
├── keypath.js
├── message.js
├── mux.js
└── util.js
├── package.json
└── test
├── index.dist.js
├── index.js
├── spec-base.js
├── spec-global-api.js
├── spec-instance-array.js
├── spec-instance-method.js
├── spec-options-deep.js
└── spec-options.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | coverage.html
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | dist
2 | test
3 | coverage.html
4 | .travis.yml
5 | gulpfile.js
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.11'
4 | - '0.10'
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 guankaishe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |  Muxjs
2 | ===========
3 | [](https://travis-ci.org/switer/muxjs)
4 | [](https://coveralls.io/r/switer/muxjs?branch=master)
5 | [](http://badge.fury.io/js/muxjs)
6 |
7 | Using Muxjs is easy to track the app state. What's state and it's tansition? When look back your codes, often find some logic are described as below:
8 | > if this condition and this other condition are met, then this value should be 'x'.
9 |
10 | Here, *vars* of condition are state, and *condition* is transition, so *'x'* is the transposition's result.
11 | **Muxjs** give the way to subscribe *vars*'s changs and transition's result *'x'*'s changes.
12 |
13 | Let's look at the diagram of an example case of an stateful application:
14 |
15 | 
16 |
17 | `Left` of diagram is a **view-controller** with 5 state (circle) and 3 **transition** (rhombus).
18 | `Right` of disgram is an **UI Page** with 4 parts, each part depend on one state or transition.
19 | If we can subscribe all changes of state and transition, so we can bind specified DOM opertion when state/transition change,
20 | finally it implement the **data to DOM binding** , event can do more stuff for a completed **MVVM** framework such as [Zect](https://github.com/switer/Zect).
21 | It's usefull, right?
22 |
23 | ## Installation
24 | **browser:**
25 | - [mux.js](https://raw.githubusercontent.com/switer/muxjs/master/dist/mux.js)
26 | - [mux.min.js](https://raw.githubusercontent.com/switer/muxjs/master/dist/mux.min.js) (4.1k when gzipped)
27 |
28 | ```html
29 |
30 | ```
31 | **node.js:**
32 | ```bash
33 | npm install muxjs --save
34 | ```
35 | ## Examples:
36 | - [Zect](https://github.com/switer/Zect) Vue.js like
37 | - [virtual-dom-binding](https://github.com/switer/virtual-dom-binding) render view with virtual-dom
38 | - [data-dom-binding](https://github.com/switer/data-dom-binding) zepto do DOM maniputation
39 |
40 | ## API Reference
41 | - **[Gloabal API](#global-api)**
42 | - [Mux(\[props\])](#muxoptions)
43 | - [Mux.extend(\[options\])](#muxextendoptions)
44 | - [Mux.config(\[conf\])](#muxconfigconf)
45 | - [Mux.emitter(\[context\])](#muxemittercontext)
46 | - **[Instance Options](#instance-options)**
47 | - [props](#props)
48 | - [computed](#computed)
49 | - [emitter](#emitter)
50 | - **[Instance Methods](#instance-methods)**
51 | - [$set(\[keyPath, value\] | props)](#setkeypath-value--props)
52 | - [$get(propname)](#computed)
53 | - [$add(\[propname \[, defaultValue\]\] | propnameArray | propsObj)](#addpropname--defaultvalue--propnamearray--propsobj)
54 | - [$computed(\[propname, deps, get, set, enum\] | computedPropsObj)](#computedpropname-deps-get-set-enum--computedpropsobj)
55 | - [$watch(\[propname, \] callback)](#watchpropname--callback)
56 | - [$unwatch(\[propname, \] \[callback\])](#unwatchpropname--callback)
57 | - [$props( )](#props-)
58 | - [$destroy()](#destroy)
59 | - [$destroyed()](#destroyed)
60 |
61 | ## Wiki
62 | - [Deep observe](https://github.com/switer/muxjs/wiki/Deep-observe)
63 | - [The imperfection of "Object.defineProperty"](https://github.com/switer/muxjs/wiki/The-imperfection-of-%22Object.defineProperty%22)
64 | - [Compare defineproperties to looped defineproperty](https://github.com/switer/muxjs/wiki/Compare-defineproperties-to-looped-defineproperty)
65 | - [Can't observe array's indices](https://github.com/switer/muxjs/wiki/The-performance-problem-of-defineProperty-to-array-index)
66 |
67 | ### Global API
68 | ##### `Mux(options)`
69 |
70 | [ :bookmark: API Reference Navigation](#api-reference)
71 |
72 | It is a constructor function that allows you to create Mux instance.*`options`* see: [Instance Options](#instance-options).
73 |
74 | ```js
75 | var author = new Mux({
76 | props: {
77 | name: 'firstName lastName'
78 | },
79 | computed: {
80 | firstName: {
81 | deps: ['name'],
82 | fn: function () {
83 | return this.name.split(' ')[0]
84 | }
85 | }
86 | }
87 | })
88 | assert.equal(author.firstName, 'firstName')
89 | ```
90 |
91 | ##### `Mux.extend([options])`
92 | - Return: `Function` Class
93 |
94 | [ :bookmark: API Reference Navigation](#api-reference)
95 |
96 | Create a *subclass* of the base Mux constructor. *`options`* see: [Instance Options](#instance-options).
97 |
98 | *Class* can instance with param `propsObj` which will set values to those observered properties of the instance.
99 |
100 | ```js
101 | var Person = Mux.extend({
102 | props: {
103 | profession: 'programer',
104 | name: ''
105 | }
106 | })
107 | var author = new Person({
108 | name: 'switer'
109 | })
110 | assert.equal(author.profession, 'programer')
111 | assert.equal(author.name, 'switer')
112 | ```
113 |
114 | ##### `Mux.config([conf])`
115 |
116 | [ :bookmark: API Reference Navigation](#api-reference)
117 |
118 | Global configure. Currently supported configurations:
119 | * warn `Boolean` if value is `false`, don't show any warning log. **Default** is `true`
120 |
121 | ```js
122 | Mux.config({
123 | warn: false // no warning log
124 | })
125 | ```
126 |
127 | ##### `Mux.emitter([context])`
128 | - Params:
129 | * context `Object` binding "this" to `context` for event callbacks.
130 |
131 | [ :bookmark: API Reference Navigation](#api-reference)
132 |
133 | Create a emitter instance.
134 |
135 | ```js
136 | var emitter = Mux.emitter()
137 | emitter.on('change:name', function (name) {
138 | // do something
139 | }) // subscribe
140 | emitter.off('change:name') // unsubscribe
141 | emitter.emitter('change:name', 'switer') // publish message
142 | ```
143 |
144 | ### Instance Options
145 | ##### `props`
146 | - Type: ` Function` | `Object`
147 |
148 | [ :bookmark: API Reference Navigation](#api-reference)
149 |
150 | Return the initial observed property object for this mux instance.Recommend to using function which return
151 | a object if you don't want to share **props** option's object in each instance:
152 |
153 | ```js
154 | var Person = Mux.extend({
155 | props: function () {
156 | return {
157 | name: 'mux'
158 | }
159 | }
160 | })
161 | assert.equal((new person).name, 'mux')
162 | ```
163 | **props** option could be an object:
164 |
165 | ```js
166 | var Person = new Mux({
167 | props: {
168 | name: 'mux'
169 | }
170 | })
171 | assert.equal((new person).name, 'mux')
172 | ```
173 |
174 | ##### `computed`
175 | - Type: ` Object`
176 | - Options:
177 | - **deps** `Array` property dependencies.
178 | *Restricton:* *`deps`*'s item could be keyPath (contain `.` and `[]`, such as: "post.comments[0]").
179 | - **fn** `Function` Compute function , using as a getter
180 | - **enum** `Boolean` Whether the computed property enumerable or not
181 |
182 | [ :bookmark: API Reference Navigation](#api-reference)
183 |
184 | Computed properties definition option. `"fn"` will be called if one of dependencies has change, then will emit a change event if `"fn"` returns result has change.
185 |
186 | ```js
187 | var mux = new Mux({
188 | props: {
189 | items: [1,2,3]
190 | },
191 | computed: {
192 | count: {
193 | deps: ['items'],
194 | fn: function () {
195 | return this.items.length
196 | }
197 | }
198 | }
199 | })
200 | assert.equal(mux.cout, 3)
201 | ```
202 | Watch computed property changes:
203 |
204 | ```js
205 | mux.$watch('count', function (next, pre) {
206 | assert.equal(next, 3)
207 | assert.equal(next, 4)
208 | })
209 | mux.items.push(4)
210 | assert.equal(mux.count, 4)
211 | ```
212 |
213 | ##### `emitter`
214 | - Type: ` EventEmitter`
215 |
216 | [ :bookmark: API Reference Navigation](#api-reference)
217 |
218 | Use custom emitter instance.
219 | ```js
220 | var emitter = Mux.emitter()
221 | emitter.on('change:name', function (next) {
222 | next // --> switer
223 | })
224 | var mux = new Mux({
225 | emitter: emitter,
226 | props: {
227 | name: ''
228 | }
229 | })
230 | mux.name = 'switer'
231 | ```
232 |
233 | ### Instance Methods
234 | ##### `$set([keyPath, value] | props)`
235 | * Params:
236 | - **keyPath** `String` property path , such as: *"items[0].name"*
237 | - **value** *[optional]*
238 | - *or*
239 | - **props** `Object` *[optional]* data structure as below:
240 | ```js
241 | { "propertyName | keyPath": propertyValue }
242 | ```
243 | * Return: **this**
244 |
245 | [ :bookmark: API Reference Navigation](#api-reference)
246 |
247 | Set value to property by property's keyPath or propertyName, which could trigger change event when value change or value is an object reference (instanceof Object).
248 | **Notice:** PropertyName shouldn't a keyPath (name string without contains *"[", "]", "."* )
249 |
250 | ```js
251 | var list = new Mux({
252 | items: [{name: '1'}]
253 | })
254 | list.$set('items[0].name', '')
255 | ```
256 |
257 | ##### `$get(propname)`
258 | * Params:
259 | - **propname** `String` only propertyname not keyPath (without contains "[", "]", ".")
260 | * Return: *value*
261 |
262 | [ :bookmark: API Reference Navigation](#api-reference)
263 |
264 | Get property value. It's equal to using "." or "[]" to access value except computed properties.
265 |
266 | ```js
267 | var mux = new Mux({
268 | props: {
269 | replyUsers: [{
270 | author: 'switer'
271 | }]
272 | }
273 | })
274 | assert.equal(mux.$get('replyUses[0].author', 'switer'))
275 | ```
276 |
277 | **Notice:** Using "." or "[]" to access computed property's value will get a cached result,
278 | so you can use "$get()" to recompute the property's value whithout cache.
279 |
280 | ```js
281 | // define a computed property which use to get the first user of replyUsers
282 | mux.$computed(firstReplyUser, ['replyUsers'], function () {
283 | return this.replyUsers[0].author
284 | })
285 |
286 | var users = [{
287 | author: 'switer'
288 | }]
289 |
290 | mux.$set('replyUsers', users)
291 |
292 | user[0].author = 'guankaishe' // modify selft
293 |
294 | assert.equal(post.firstReplyUser, 'switer'))
295 | assert.equal(post.$get('firstReplyUser'), 'guankaishe'))
296 | ```
297 |
298 | ##### `$add([propname [, defaultValue]] | propnameArray | propsObj)`
299 | * Params:
300 | - **propname** `String`
301 | - **defaultValue** *[optional]*
302 | - *or*
303 | - **propnameArray** `Array`
304 | - *or*
305 | - **propsObj** `Object`
306 | * Return: **this**
307 |
308 | [ :bookmark: API Reference Navigation](#api-reference)
309 |
310 | Define an observerable property or multiple properties.
311 | ```js
312 | mux.$add('name', 'switer')
313 | // or
314 | mux.$add(['name']) // without default value
315 | // or
316 | mux.$add({ 'name': 'switer' })
317 | ```
318 |
319 | ##### `$computed([propname, deps, get, set, enum] | computedPropsObj)`
320 | * Params:
321 | - **propname** `String` property name
322 | - **deps** `Array` Property's dependencies
323 | - **get** `Function` Getter function
324 | - **set** `Function` Setter function
325 | - **enum** `Boolean` whether the computed property enumerable or not
326 | - *or*
327 | - **computedPropsObj** `Object`
328 | * Return: **this**
329 |
330 | [ :bookmark: API Reference Navigation](#api-reference)
331 |
332 | Define a computed property. *deps* and *fn* is necessary. If one of **deps** is observable of the instance, emitter a change event after define.
333 | *computedPropsObj* is using to define multiple computed properties in once,
334 | each key of *computedPropsObj* is property's name and value is a object contains "deps", "fn".
335 | Usage as below:
336 |
337 | ```js
338 | // before define computed
339 | assert.equal(mux.commentCount, undefined)
340 | mux.$computed('commentCount', ['comments'], function () {
341 | return this.comments.length
342 | })
343 | // after define computed
344 | assert.equal(mux.commentCount, 1)
345 | ```
346 |
347 | ##### `$watch([propname, ] callback)`
348 | * Params:
349 | - **propname** `String` *[optional]*
350 | - **callback** `Function`
351 | * Return: `Function` unwatch handler
352 |
353 | [ :bookmark: API Reference Navigation](#api-reference)
354 |
355 | Subscribe property or computed property changes of the Mux instance.
356 |
357 |
358 | ```js
359 | var unwatch = mux.$watch('title', function (nextValue, preValue) {
360 | // callback when title has change
361 | })
362 | unwatch() // cancel subscribe
363 | ```
364 | if *propname* is not present, will watch all property or computed property changes:
365 |
366 | ```js
367 | mux.$watch(function (propname, nextValue, preValue) {
368 | // callback when title has change
369 | })
370 | ```
371 |
372 |
373 | ##### `$unwatch([propname, ] [callback])`
374 | * Params:
375 | - **propname** `String` *[optional]*
376 | - **callback** `Function` *[optional]*
377 | * Return: **this**
378 |
379 | [ :bookmark: API Reference Navigation](#api-reference)
380 |
381 | Unsubscribe property or computed property changes of the Mux instance.
382 | ```js
383 | // subscribe
384 | mux.$watch('name', handler)
385 | // unsubscribe
386 | mux.$unwatch('name', handler)
387 | // unsubscribe all of specified propertyname
388 | mux.$unwatch('name')
389 | // unsubscribe all of the Mux instance
390 | mux.$unwatch()
391 | ```
392 |
393 | ##### `$props( )`
394 | * Return: `Object`
395 |
396 | [ :bookmark: API Reference Navigation](#api-reference)
397 |
398 | Return all properties of the instance. Properties do not contain computed properties(Only observed properties).
399 | ```js
400 | var mux = Mux({ props: { name: 'Muxjs' } })
401 | mux.$props() // --> {name: 'Muxjs'}
402 | ```
403 |
404 | ##### `$emitter(emitter)`
405 | * Return: **this** | **emitter**
406 |
407 | [ :bookmark: API Reference Navigation](#api-reference)
408 |
409 | Reset emitter of the mux instance. If arguments is empty, return the emitter of the instance.
410 | ```js
411 | var mux = Mux()
412 | var em = Mux.emitter()
413 | mux.$emitter(em)
414 |
415 | var muxEmitter = mux.$emitter() // equal to em
416 | ```
417 |
418 | ##### `$destroy()`
419 |
420 | [ :bookmark: API Reference Navigation](#api-reference)
421 |
422 | Destroy the instance, remove all listener of the internal emiter of the instance, free all props references.
423 |
424 |
425 | ##### `$destroyed()`
426 | * Return: **Boolean**
427 |
428 | [ :bookmark: API Reference Navigation](#api-reference)
429 |
430 | Whether the instance is destroyed or not.
431 |
432 | ## Changgelog
433 |
434 | - 2016/10/14
435 | + $set() will not return this.
436 | + $set({ ... }) will emit in batch
437 |
438 |
439 | ## License
440 |
441 | MIT
--------------------------------------------------------------------------------
/dist/mux.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Mux.js v2.4.18
3 | * (c) 2014 guankaishe
4 | * Released under the MIT License.
5 | */
6 | (function webpackUniversalModuleDefinition(root, factory) {
7 | if(typeof exports === 'object' && typeof module === 'object')
8 | module.exports = factory();
9 | else if(typeof define === 'function' && define.amd)
10 | define(factory);
11 | else if(typeof exports === 'object')
12 | exports["Mux"] = factory();
13 | else
14 | root["Mux"] = factory();
15 | })(this, function() {
16 | return /******/ (function(modules) { // webpackBootstrap
17 | /******/ // The module cache
18 | /******/ var installedModules = {};
19 |
20 | /******/ // The require function
21 | /******/ function __webpack_require__(moduleId) {
22 |
23 | /******/ // Check if module is in cache
24 | /******/ if(installedModules[moduleId])
25 | /******/ return installedModules[moduleId].exports;
26 |
27 | /******/ // Create a new module (and put it into the cache)
28 | /******/ var module = installedModules[moduleId] = {
29 | /******/ exports: {},
30 | /******/ id: moduleId,
31 | /******/ loaded: false
32 | /******/ };
33 |
34 | /******/ // Execute the module function
35 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
36 |
37 | /******/ // Flag the module as loaded
38 | /******/ module.loaded = true;
39 |
40 | /******/ // Return the exports of the module
41 | /******/ return module.exports;
42 | /******/ }
43 |
44 |
45 | /******/ // expose the modules object (__webpack_modules__)
46 | /******/ __webpack_require__.m = modules;
47 |
48 | /******/ // expose the module cache
49 | /******/ __webpack_require__.c = installedModules;
50 |
51 | /******/ // __webpack_public_path__
52 | /******/ __webpack_require__.p = "";
53 |
54 | /******/ // Load entry module and return exports
55 | /******/ return __webpack_require__(0);
56 | /******/ })
57 | /************************************************************************/
58 | /******/ ([
59 | /* 0 */
60 | /***/ function(module, exports, __webpack_require__) {
61 |
62 | 'use strict';
63 |
64 | module.exports = __webpack_require__(1)
65 |
66 |
67 | /***/ },
68 | /* 1 */
69 | /***/ function(module, exports, __webpack_require__) {
70 |
71 | 'use strict';
72 |
73 | /**
74 | * External module's name startof "$"
75 | */
76 | var $Message = __webpack_require__(2)
77 | var $keypath = __webpack_require__(3)
78 | var $arrayHook = __webpack_require__(4)
79 | var $info = __webpack_require__(5)
80 | var $util = __webpack_require__(6)
81 | var $normalize = $keypath.normalize
82 | var $join = $keypath.join
83 | var $type = $util.type
84 | var $indexOf = $util.indexOf
85 | var $hasOwn = $util.hasOwn
86 | var $warn = $info.warn
87 |
88 | /**
89 | * CONTS
90 | */
91 | var STRING = 'string'
92 | var ARRAY = 'array'
93 | var OBJECT = 'object'
94 | var FUNCTION = 'function'
95 | var CHANGE_EVENT = 'change'
96 |
97 | var _id = 0
98 | function allotId() {
99 | return _id ++
100 | }
101 |
102 | /**
103 | * Mux model constructor
104 | * @public
105 | */
106 | function Mux(options) {
107 | // static config checking
108 | options = options || {}
109 | Ctor.call(this, options)
110 | }
111 |
112 | /**
113 | * Mux model creator
114 | * @public
115 | */
116 | Mux.extend = function(options) {
117 | return MuxFactory(options || {})
118 | }
119 |
120 | /**
121 | * Mux global config
122 | * @param conf