Last active
October 13, 2015 23:47
-
-
Save nathanboktae/4274461 to your computer and use it in GitHub Desktop.
Knockout observables with ECMAScript 5 Properties - now at https://github.com/nathanboktae/knockout-es5-option4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(factory, global) { | |
if (global.define && global.define.amd) { | |
global.define(['ko'], factory); | |
} else { | |
factory(global.ko); | |
} | |
})(function(ko) { | |
var deepObservifyArray = function(arr, deep) { | |
for (var i = 0, len = arr.length; i < len; i++) { | |
// TODO circular reference protection | |
if (arr[i] != null && typeof arr[i] == 'object') { | |
if (deep && Array.isArray(arr[i])) { | |
deepObservifyArray(arr[i], deep); | |
} else { | |
arr[i] = ko.observableModel(arr[i], deep); | |
} | |
} | |
} | |
}, | |
defineProperty = function(type, obj, prop, def, deep) { | |
if (obj == null || typeof obj != 'object' || typeof prop != 'string') { | |
throw new Error('invalid arguments passed'); | |
} | |
if (Object.prototype.toString.call(def) === '[object Array]' && type === 'observable') { | |
type = 'observableArray'; | |
} | |
if (deep !== false && (def != null && typeof def == 'object')) { | |
if (Array.isArray(def)) { | |
deepObservifyArray(def, deep); | |
} else { | |
def = ko.observableModel(def, deep); | |
} | |
} | |
var obv = ko[type](def); | |
Object.defineProperty(obj, prop, { | |
set: function(value) { obv(value) }, | |
get: function() { return obv() }, | |
enumerable: true, | |
configurable: true | |
}); | |
Object.defineProperty(obj, '_' + prop, { | |
get: function() { return obv }, | |
enumerable: false | |
}); | |
}; | |
ko.utils.defineObservableProperty = defineProperty.bind(null, 'observable'); | |
ko.utils.defineComputedProperty = defineProperty.bind(null, 'computed'); | |
ko.observableModel = function(defaults, deep) { | |
var model = {}, def; | |
for (var prop in defaults) { | |
if (defaults.hasOwnProperty(prop)) { | |
def = defaults[prop]; | |
if (!def || !ko.isSubscribable(def)) { | |
ko.utils.defineObservableProperty(model, prop, def, deep); | |
} else { | |
model[prop] = def; | |
} | |
} | |
} | |
return model; | |
}; | |
ko.observableArrayModel = function(arr, deep) { | |
var copy = arr.slice(0); | |
deepObservifyArray(copy, deep); | |
return ko.observableArray(copy); | |
}; | |
}, this); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>ko-es5.js tests</title> | |
<link rel="stylesheet" href="./lib/mocha.css" /> | |
<script src="./lib/knockout-2.1.0.debug.js"></script> | |
<script src="./lib/chai.js"></script> | |
<script src="./lib/mocha.js"></script> | |
</head> | |
<body> | |
<div id="mocha"></div> | |
<script>var should = chai.should(); var expect = chai.expect; mocha.setup('bdd')</script> | |
<script src="ko-es5.js"></script> | |
<script> | |
describe('es5-observable', function() { | |
describe('defineObservableProperty', function() { | |
it('should define an enumerable property', function() { | |
var foo = {}; | |
ko.utils.defineObservableProperty(foo, 'name'); | |
var descriptor = Object.getOwnPropertyDescriptor(foo, 'name'); | |
descriptor.enumerable.should.be.true; | |
descriptor.configurable.should.be.true; | |
descriptor.get.should.be.a('function'); | |
descriptor.set.should.be.a('function'); | |
}); | |
it('should set a default if provided', function() { | |
var foo = {}; | |
ko.utils.defineObservableProperty(foo, 'name', 'Bob'); | |
foo.name.should.equal('Bob'); | |
}); | |
it('should define an non-enumerable property to access the observable', function() { | |
var foo = {}; | |
ko.utils.defineObservableProperty(foo, 'name'); | |
var descriptor = Object.getOwnPropertyDescriptor(foo, '_name'); | |
descriptor.enumerable.should.be.false; | |
descriptor.configurable.should.be.false; | |
descriptor.get.should.be.a('function'); | |
expect(descriptor.set).to.equal(undefined); | |
ko.isObservable(foo._name).should.be.true; | |
}); | |
it('should special case arrays and create an observableArray', function() { | |
var foo = {}; | |
ko.utils.defineObservableProperty(foo, 'friends', []); | |
ko.isObservable(foo._friends).should.be.true; | |
foo._friends.push.should.be.a('function'); | |
}); | |
it('should deeply observify objects by default', function() { | |
var foo = {}; | |
ko.utils.defineObservableProperty(foo, 'friends', [{ | |
name: 'Bob', | |
titles: ['mr', 'sir'] | |
}]); | |
ko.isObservable(foo._friends).should.be.true; | |
foo._friends.push.should.be.a('function'); | |
ko.isObservable(foo.friends[0]._name).should.be.true; | |
ko.isObservable(foo.friends[0]._titles).should.be.true; | |
}); | |
it('should not deeply observify objects if requested', function() { | |
var foo = {}; | |
ko.utils.defineObservableProperty(foo, 'friends', [{ | |
name: 'Bob', | |
kids: { | |
sally: 10, | |
sue: 5 | |
} | |
}], false); | |
ko.isObservable(foo.friends[0]._kids).should.be.false; | |
foo.friends[0].kids.should.not.haveOwnProperty('_sally'); | |
foo.friends[0].kids.sally.should.equal(10); | |
}); | |
}); | |
describe('observableModel', function() { | |
it('should create an object with observable properties', function() { | |
var obj = ko.observableModel({ | |
name: 'Bob', | |
age: undefined | |
}); | |
var nameDescriptor = Object.getOwnPropertyDescriptor(obj, 'name'); | |
nameDescriptor.get.should.be.a('function'); | |
nameDescriptor.set.should.be.a('function'); | |
nameDescriptor.enumerable.should.be.true; | |
var ageDescriptor = Object.getOwnPropertyDescriptor(obj, 'age'); | |
ageDescriptor.get.should.be.a('function'); | |
ageDescriptor.set.should.be.a('function'); | |
ageDescriptor.enumerable.should.be.true; | |
obj.name.should.equal('Bob'); | |
expect(obj.age).to.equal(undefined); | |
}); | |
it('should create observable properties deeply', function() { | |
var obj = ko.observableModel({ | |
name: 'Bob', | |
job: { | |
title: undefined, | |
company: 'acme' | |
} | |
}); | |
var jobTitleDescriptor = Object.getOwnPropertyDescriptor(obj.job, 'title'); | |
jobTitleDescriptor.get.should.be.a('function'); | |
jobTitleDescriptor.set.should.be.a('function'); | |
jobTitleDescriptor.enumerable.should.be.true; | |
var jobDescriptor = Object.getOwnPropertyDescriptor(obj, 'job'); | |
jobDescriptor.get.should.be.a('function'); | |
jobDescriptor.set.should.be.a('function'); | |
obj.name.should.equal('Bob'); | |
obj.job.company.should.equal('acme'); | |
}); | |
it('should not create observable properties deeply if requested', function() { | |
var obj = ko.observableModel({ | |
name: 'Bob', | |
job: { | |
title: undefined, | |
company: 'acme' | |
} | |
}, false); | |
var jobTitleDescriptor = Object.getOwnPropertyDescriptor(obj.job, 'title'); | |
expect(jobTitleDescriptor.set).to.equal(undefined); | |
expect(jobTitleDescriptor.get).to.equal(undefined); | |
var jobDescriptor = Object.getOwnPropertyDescriptor(obj, 'job'); | |
jobDescriptor.get.should.be.a('function'); | |
jobDescriptor.set.should.be.a('function'); | |
obj.name.should.equal('Bob'); | |
obj.job.company.should.equal('acme'); | |
}); | |
it('should special case arrays and create an observableArray', function() { | |
var obj = ko.observableModel({ | |
name: 'Bob', | |
friends: ['Jane', 'Jill'] | |
}); | |
ko.isObservable(obj._friends).should.be.true; | |
obj._friends.push.should.be.a('function'); | |
obj.friends[0].should.equal('Jane'); | |
}); | |
it('should not define properties for subscribables', function() { | |
var obj = ko.observableModel({ | |
name: 'Bob', | |
friends: ko.observableArray() | |
}); | |
var friendsDescriptor = Object.getOwnPropertyDescriptor(obj, 'friends'); | |
expect(friendsDescriptor.get).to.equal(undefined); | |
expect(friendsDescriptor.set).to.equal(undefined); | |
ko.isObservable(obj.friends).should.be.true; | |
}); | |
it('should deeply create observableModels from arrays of objects', function() { | |
var obj = ko.observableModel({ | |
name: 'Bob', | |
friends: [{ | |
name: 'Jill' | |
}, { | |
name: ko.computed(function() { | |
return 'Jane'; | |
}) | |
}] | |
}); | |
ko.isObservable(obj._friends).should.be.true; | |
obj._friends.push.should.be.a('function'); | |
obj.friends[0].name.should.equal('Jill'); | |
ko.isObservable(obj.friends[0]._name).should.be.true; | |
ko.isObservable(obj.friends[1]._name).should.be.false; | |
obj.friends[1].name().should.equal('Jane'); | |
}); | |
}); | |
describe('observableArrayModel', function() { | |
it('should return a ko.observableArray', function() { | |
var stuff = ko.observableArrayModel(['hi', 'there']); | |
ko.isObservable(stuff).should.be.true; | |
stuff().should.be.an('array'); | |
}); | |
it('should turn object members into observableModels, deeply by default', function() { | |
var stuff = ko.observableArrayModel(['hi', { world: 'there' }]); | |
ko.isObservable(stuff()[0]).should.be.false; | |
stuff()[0].should.equal('hi'); | |
ko.isObservable(stuff()[1]._world).should.be.true; | |
stuff()[1].world.should.equal('there'); | |
}); | |
}); | |
}); | |
</script> | |
<script type="text/javascript"> | |
(function() { | |
if (window.mochaPhantomJS) { | |
mochaPhantomJS.run(); | |
} else { | |
mocha.run(); | |
} | |
})(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment