Skip to content

Instantly share code, notes, and snippets.

@nathanboktae
Last active October 13, 2015 23:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nathanboktae/4274461 to your computer and use it in GitHub Desktop.
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
(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);
<!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