(function (Backbone) {
var hasProto = ('__proto__' in {});
/*========================================================================*/
// debugger;
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var arrayMethodsNameList = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
(function () {
arrayMethodsNameList.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator() {
debugger;
var ob = this._bc_ob_;
ob.arrayMethod = method;
var args = [];
var len = arguments.length;
var canTrigger = true;
while (len--) {
args[len] = arguments[len];
}
// 遇到 [undefined].pop() 會出問題
var result = original.apply(this, args);
var add = [];
var remove = [];
debugger;
switch (method) {
case 'push':
case 'unshift':
add = args;
break;
case 'pop':
case 'shift':
remove = result;
break;
case 'splice':
add = args.slice(2);
remove = result;
break;
}
if (!Array.isArray(add)) {
add = (add === undefined ? [] : [add]);
}
if (!Array.isArray(remove)) {
remove = (remove === undefined ? [] : [remove]);
}
// 為了 proxy 而設
ob.arrayMethod = '';
ob.notify_1([method], {
add: add,
remove: remove,
key: undefined
});
return result;
});
});
})();
var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
/*========================================================================*/
// Define a property
// 預設是不可枚舉
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
};
/*========================================================================*/
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
/*========================================================================*/
/**
* Quick object check - this is primarily used to tell
* Objects from primitive values when we know the value
* is a JSON-compliant type.
*/
function isObject(obj) {
return (obj !== null && typeof obj === 'object');
}
/*========================================================================*/
// Augment an target Object or Array by intercepting
// the prototype chain using __proto__
function protoAugment(target, src, keys) {
/* eslint-disable no-proto */
target.__proto__ = src;
/* eslint-enable no-proto */
};
/*========================================================================*/
// Augment an target Object or Array by defining
// hidden properties.
function copyAugment(target, src, keys) {
debugger;
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]);
}
};
/*========================================================================*/
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
function _observe(value, parent) {
debugger;
if (!isObject(value)) {
return value;
}
var ob;
if (hasOwn(value, '_bc_ob_') && value._bc_ob_ instanceof Backbone.Observer) {
ob = value._bc_ob_;
} else if ((Array.isArray(value) || _.isPlainObject(value)) && Object.isExtensible(value)) {
value = Backbone.Reactive(value, parent.options);
ob = value._bc_ob_;
}
ob.$link(parent);
return value;
}
////////////////////////////////////////////////////////////////////////////
Backbone.$buildBackbone(factory);
////////////////////////////////////////////////////////////////////////////
//
// Backbone.Reactive
//
// 事件有(sort, update, remove(model發出), reset)
//
////////////////////////////////////////////////////////////////////////////
function factory(root, Backbone, _, $) {
// here
Backbone.Reactive = makeDataReactive;
function makeDataReactive(value, options) {
// debugger;
if (value == null || (typeof value !== 'object')) {
throw new Error('cant be observer');
}
/*--------------------------*/
var observe = new Backbone.Observer(value, options);
def(value, '_bc_ob_', observe);
// debugger;
/*--------------------------*/
var proxy;
if (Array.isArray(value)) {
var augment = hasProto ? protoAugment : copyAugment;
// 替換 [] 的原型方法
augment(value, arrayMethods, arrayKeys);
// 插入 proxy
proxy = new Proxy(value, {
set: function (target, key, value, p) {
debugger;
var ob = target._bc_ob_;
var eventName = 'change';
var eventNameList = [];
var _value = _observe(value, ob);
/*====================================*/
if (key === 'length') {
eventName = 'reset';
}
/*====================================*/
target[key] = _value;
eventNameList.push(eventName);
if (ob.arrayMethod) {
eventNameList.push(ob.arrayMethod);
}
/*--------------------------*/
if (!ob.arrayMethod) {
ob.notify_2(key, eventNameList, {
key: key,
add: [],
remove: []
});
}
return true;
}
});
observe.proxy = proxy;
observe.$walkArray(value);
} else {
proxy = new Proxy(value, {
set: function (t, k, v, p) {
debugger;
var ob = t._bc_ob_;
var prevValue = t[k];
var newValue = _observe(v, ob);
t[k] = newValue
ob.notify_2(t, ['change'], {
key: k
});
return true;
}
});
observe.proxy = proxy;
observe.$walkPlainObject(value);
}
return proxy;
}
////////////////////////////////////////////////////////////////////////
(function (fn) {
fn.del = function () {
};
})(makeDataReactive);
////////////////////////////////////////////////////////////////////////////
//
// 數據的背後觀察者
// 放在數據的(_bc_ob_)裏,並且不可枚舉
//
// 要加入 controller 找數據底下的 _bc_ob_
//
////////////////////////////////////////////////////////////////////////////
Backbone.Observer = Observer;
function Observer(value, options) {
this.cid = _.uniqueId('observer');
/*--------------------------*/
this.triggerOptions = {};
/*--------------------------*/
this.$linkParent = new Map();
/*--------------------------*/
this.proxy;
this.value; // 只能是 object
this.prevValue;
/*--------------------------*/
this.keys = []; // 偵測 remove
this.prevKeys = [];
/*--------------------------*/
this.$listenerList = [];
this.arrayMethod = '';
this.eventName; // 只有是 array 的方法才會用到
this.options = {};
/*--------------------------*/
this.$prevChildList = [];
(function () {
// debugger;
this.options = options || {};
this.value = value;
this.keys = Object.keys(this.value);
this.$cloneValue();
}).call(this);
};
_.extend(Observer.prototype, Backbone.Events);
////////////////////////////////////////////////////////////////////////
(function () {
// proxy
this.$walkPlainObject = function (obj) {
var observe = obj._bc_ob_;
for (var key in obj) {
var child = obj[key];
var newValue = _observe(child, observe);
obj[key] = newValue
}
};
/*====================================================================*/
/**
* Observer a list of Array items.
*/
this.$walkArray = function observeArray(items) {
var observe = items._bc_ob_;
for (var i = 0, l = items.length; i < l; i++) {
var child = items[i];
var newValue = _observe(child, observe);
items[i] = newValue;
}
};
/*================================================================*/
}).call(Observer.prototype);
(function () {
this.$link = function (parent) {
// debugger;
if (!this.$linkParent.has(parent) && this.options.link) {
this.$linkParent.set(parent, true);
this.options.link.call(this, parent, this);
}
};
/*====================================================================*/
this.$cloneValue = function () {
// debugger;
if (Array.isArray(this.value)) {
this.prevValue = this.value.slice();
} else {
this.prevValue = _.extend({}, this.value);
}
this.prevKeys = Object.keys(this.value);
};
/*====================================================================*/
this.notify_1 = function (eventList, options) {
this.keys = Object.keys(this.value);
this._$notify_1(eventList, options);
this.$cloneValue();
this.reset();
};
/*====================================================================*/
// call by item
this.notify_2 = function (key, eventList, options) {
this.keys = Object.keys(this.value);
this._$notify_2(key, eventList, options);
if (!this.arrayMethod) {
this.$cloneValue();
}
this.reset();
}
/*====================================================================*/
// options =>(key, add, remove)
this._$notify_1 = function (eventList, options) {
debugger;
options = options || {};
options.eventList = eventList;
var add = options.add || [];
var remove = options.remove || [];
var key = options.key;
/*--------------------------*/
var triggerEventName = '';
// arrayMethod
if ((eventList.indexOf('sort') >= 0) || (eventList.indexOf('reverse') >= 0)) {
// sort, reverse
triggerEventName = 'sort';
} else {
triggerEventName = 'update';
}
/*--------------------------*/
var changePosition = [];
var wantTrigger = this.$compareAllValue(changePosition);
/*--------------------------*/
if (!wantTrigger) {
return;
}
/*--------------------------*/
remove.forEach(function (value, key) {
debugger;
if (_.isPlainObject(value) && (typeof value._bc_ob_ !== 'undefined')) {
var ob = value._bc_ob_;
ob.remove(this.triggerOptions, options);
}
}, this);
/*--------------------------*/
options.changePosition = changePosition;
// 通知 controller
this.$notifyListener(this.triggerOptions, triggerEventName, options);
// 通知其他註冊者
this.trigger(this.triggerOptions, triggerEventName, options);
};
/*====================================================================*/
this._$notify_2 = function (key, eventList, options) {
debugger;
options = options || {};
options.eventList = eventList;
var add = options.add || [];
var remove = options.remove || [];
var key = options.key;
var wantTrigger = true;
/*--------------------------*/
var triggerEventName = '';
// item發起
if (eventList.indexOf('change') >= 0) {
if ((eventList.indexOf('sort') >= 0) || (eventList.indexOf('reverse') >= 0)) {
wantTrigger = false;
} else {
triggerEventName = 'change';
}
} else if (eventList.indexOf('reset') >= 0) {
triggerEventName = 'reset';
remove = remove.concat(this.prevValue);
}
/*--------------------------*/
if (!wantTrigger) {
return;
}
if (this.arrayMethod) {
return;
}
var _remove = [];
if (key === 'length') {
this.$compareAllValue(_remove);
} else {
wantTrigger = this.$compareValue(key, _remove);
}
/*--------------------------*/
if (!wantTrigger) {
return;
}
/*--------------------------*/
_remove.forEach(function (_k, key) {
debugger;
var value = this.prevValue[_k];
if (_.isPlainObject(value) && (typeof value._bc_ob_ !== 'undefined')) {
var ob = value._bc_ob_;
ob.remove(this.triggerOptions, options);
}
}, this);
/*--------------------------*/
options.changePosition = _remove;
// 通知 controller
this.$notifyListener(this.triggerOptions, triggerEventName, options);
// 通知其他註冊者
this.trigger(this.triggerOptions, triggerEventName, options);
};
/*====================================================================*/
this.toJSON = function () {
return this.proxy;
};
/*================================================================*/
this.remove = function (e, options) {
debugger;
// 叫那些 controller
this.$notifyListener(e, 'remove');
this.$listenerList.length = 0;
// 叫那些跟隨者
this.trigger(e, 'remove', {});
this.stopListening();
this.off();
};
/*================================================================*/
this.reset = function () {
// debugger;
if (!this.arrayMethod) {
this.triggerOptions = {};
}
};
/*====================================================================*/
this.$compareAllValue = function (remove) {
// debugger;
/*--------------------------*/
var wantTriggerList = [];
if (Array.isArray(this.value)) {
var length = ((this.prevValue.length - this.value.length > 0) ? this.prevValue.length : this.value.length);
for (var i = 0; i < length; i++) {
debugger;
var wantTrigger = this.$compareValue(i, remove);
if (wantTrigger) {
wantTriggerList.push(i);
}
}
} else {
var keyLMap = {};
this.keys.forEach(function (k) {
keyLMap[k] = true;
});
this.prevKeys.forEach(function (k) {
keyLMap[k] = true;
});
for (var key in keyLMap) {
debugger;
var wantTrigger = this.$compareValue(key, remove);
if (wantTrigger) {
wantTriggerList.push(i);
}
}
}
/*--------------------------*/
if (wantTriggerList.length || remove.length) {
return true;
} else {
return false;
}
};
/*====================================================================*/
this.$compareValue = function (key, remove) {
// debugger;
var wantTrigger = false;
var prev = this.prevValue[key];
var current = this.value[key];
if (typeof prev !== typeof current) {
wantTrigger = true;
remove.push(key);
} else if ((prev == null && current == null) ||
(typeof prev._bc_ob_ === 'undefined' && typeof current._bc_ob_ === 'undefined')) {
// 都是簡單數據
if (!_.isEqual(prev, current)) {
wantTrigger = true;
remove.push(key);
}
} else if (prev != null && current != null &&
typeof prev._bc_ob_ !== 'undefined' &&
typeof current._bc_ob_ !== 'undefined') {
// 都不是簡單數據
if (prev._bc_ob_.cid !== current._bc_ob_.cid) {
wantTrigger = true;
remove.push(key);
}
} else {
wantTrigger = true;
remove.push(key);
}
return wantTrigger;
};
/*====================================================================*/
this.$addListener = function (controller) {
this.$listenerList.push(controller);
};
/*====================================================================*/
this.$removeListener = function (controller) {
if (controller == null) {
this.$listenerList.length = 0;
return;
}
var remain = [];
for (var i = 0, c; c = this.$listenerList[i]; i++) {
if (c.cid === controller.cid) {
continue;
}
remain.push(c);
}
this.$listenerList = remain;
};
/*====================================================================*/
this.$notifyListener = function (e, eventName, options) {
this.$listenerList.forEach(function (c, index) {
c.update(e, eventName, options);
}, this);
};
}).call(Observer.prototype);
}
})(Backbone);