stevesanderson / knockout-es5 Goto Github PK
View Code? Open in Web Editor NEWKnockout.js meets ECMAScript 5 properties
Knockout.js meets ECMAScript 5 properties
Hi, i tried to use the plugin with browserify (with npm install knockout-es5 ) . But ran into issues with weakmap and ko dependencies. I did some small changes to make it work with browserify in case someone needs it:
Cart.productDataPending in the example below is never recalculated even when the "referenced" observable, CatLine.productDataPendingFlag, is changed.
Am I doing something wrong? Should I be doing this something different?
`
var CartLine = function(rawLine) {
if(!rawLine)
rawLine = {
quantity: 1,
quantity_packed: '',
productid_raw: 0,
productid: '',
price: '',
};
var self = this;
for(var key in rawLine)
self[key] = rawLine[key];
// ............
self.productDataPendingFlag = '';
// Instead of declaring ko.observable properties, we just have one call to ko.track
ko.track(this);
ko.getObservable(this, 'productid_raw').subscribe(
function(newValue) {
//console.log("productid changed:", newValue);
self.productDataPendingFlag = 'product-data-loading';
$.ajax({
url: '/office/inventory/api/query',
data: {
type: 'all',
productid: newValue,
patientid:
window.NCSData.patientid ?
window.NCSData.patientid : 1
},
success: function(data) {
self.productDataPendingFlag = '';
self.price = data.price;
},
error: function(result) {
self.productDataPendingFlag = '';
alert("Error: "+result.resultText);
},
});
});
};
var Cart = function() {
// Stores an array of lines, and from these, can work out the grandTotal
var self = this;
var convertedLines = $.map(initialData, function(line) { return new CartLine(line) } );
if(!convertedLines.length)
convertedLines.push(new CartLine());
// ............
self.productDataPendingFlag = function() {
var isPending = false;
console.log("Cart.productDataPendingFlag: recalculating...");
self.lines.forEach(function(line) {
if(line.productDataPendingFlag)
isPending = true;
});
return isPending ? 'product-data-loading' : '';
};
// ............
// Instead of declaring ko.observable properties, we just have one call to ko.track
ko.track(this);
};
ko.applyBindings(window.viewModel = new Cart());
`
As @rniemeyer mentioned in the comments of your blog post, when using this plugin, the bindings aren't provided with the observable, only with the value of the property. The built-in bindings use Knockout's internal _ko_property_writers
feature to be able to write back to the property, but custom bindings may not be able to use that, especially if they use an options object.
Hi,
when having imported knockout-es5 globally, the F12 debugger breaks when you type variables in the console.
It throws an exception of
Invalid caller object
on line 562:
if ('toString' in coercedObj && coercedObj.toString() === '[object Window]') {
the variable coercedObj
contains a WindowPrototype
and throws on the toString()
call.
I could have sworn this was working last time I used it, but if you basically do:
var ko = require("knockout-es5");
It does not seem to expose the main methods, like track etc. Is this the correct way to use it in the commonjs world? (Let us not get onto the conversation as to why I am having to use knockout on the server side).
I know there is a getObservable
method, however is there a way to get an observable Array?
I am using typescript and the descriptor file implies that getObservable
returns KnockoutObservable<any>
so I am not sure if I could just cast it and get away or if another method is needed.
@SteveSanderson good day!
Thank you for this work!
What about to update dependencies and look for PR?
Thanks
Hi, it would be really nice if knockout-es5 was CSP compatible. The issues seems to be in the weakmap lib. I've filed a issue at Benvie/WeakMap#9 about it.
this needs a bower.json with
"knockout-es5": {
"main": "dist/knockout-es5.min.js"
}
Any chance we could get a d.ts file to add the tracking and stuff to the ko interface.
<button data-bind="click: handle"></button>
this.handle = function(){
ko.track({foo: "bar"}) // Error
};
The source of the error is this line which does var ko = this
, however knockout click bindings change the meaning of this
.
see https://github.com/archangel-irk/storage/blob/master/lib/document.js#L1091
var obj = {
selected: {},
locale_days: {
mon: 'Понедельник',
tue: 'Вторник',
wed: 'Среда',
thu: 'Четверг',
fri: 'Пятница',
sat: 'Суббота',
sun: 'Воскресенье'
},
pages: [
{ identity: 'general', title: 'О заведении', isActive: false },
{ identity: 'photos', title: 'Фото и фотоотчеты', isActive: false },
{ identity: 'reviews', title: 'Отзывы', isActive: false },
{ identity: 'offers', title: 'Акции', isActive: false },
{ identity: 'menu', title: 'Меню', isActive: false },
{ identity: 'feeds', title: 'Бизнес-ланч', isActive: false },
{ identity: 'banquet', title: 'Банкеты', isActive: false }
],
activePage: 'general'
};
ko.track(obj, {
deep: true
});
console.log(ko.getObservable(obj.pages[0], 'isActive')); // null
Hi,
Let's consider following code:
<body>
<div data-bind="text: name"></div>
<div data-bind="text: weight"></div>
<div data-bind="text: price"></div>
<div data-bind="text: size"></div>
<button data-bind="click: function() { i.price = 556677; i.name = 'abccccc'; }">Change</button>
<script>
var i = new Module.Item();
i.name = "abc";
i.weight = 34;
i.price = 777;
i.size = 123;
ko.applyBindings(i);
</script>
</body>
module Module {
export class Item {
name: string;
weight: number;
private _price: number;
get price(): number {
return this._price;
}
set price(value: number) {
this._price = value;
}
private _size: number;
get size(): number {
return this._size;
}
constructor() {
this.name = "x";
this.weight = 2233;
ko.track(this, ["name", "price", "size"]);
}
}
}
The problem is that ko-es5 uses only instance members.
I've prepared tweak to ko-es5 code, that handles that.
Consider following tweak to ko-es5
propertyNames.forEach(function(propertyName) {
// Skip properties that are already tracked
if (propertyName in allObservablesForObject) {
return;
}
var workOnObj = obj;
var descriptor = Object.getOwnPropertyDescriptor(workOnObj, propertyName);
if (descriptor === undefined) {
workOnObj = Object.getPrototypeOf(obj);
descriptor = Object.getOwnPropertyDescriptor(workOnObj, propertyName);
}
// Skip properties where descriptor can't be redefined
if (undefined === descriptor || false === descriptor.configurable) {
return;
}
var origValue = workOnObj[propertyName],
isArray = Array.isArray(origValue),
observable = ko.isObservable(origValue) ? origValue
: isArray ? ko.observableArray(origValue)
: ko.observable(origValue);
Object.defineProperty(workOnObj, propertyName, {
configurable: true,
enumerable: true,
get: observable,
set: ko.isWriteableObservable(observable) ? observable : undefined
});
allObservablesForObject[propertyName] = observable;
if (isArray) {
notifyWhenPresentOrFutureArrayValuesMutate(ko, observable);
}
});
I'll investigate further compatibility with TS. Having core/pure KO that targets at least ES5/modern browsers with stripped legacy code would be nice.
So for normal objects you would do something like ko.getObservable(myModel, myPropertyName);
and that works great, however lets assume I have a model like so:
var someModel = {
arrayData = [22, 21]
}
I have tried the following:
var observableArray = ko.getObservable(someModel, "arrayData");
var observableIndex0 = observableArray()[0];
// observableIndex0 is the value not the observable
var observableIndex0 = ko.getObservable(someModel.arrayData, 0);
// observableIndex0 is null
var observableIndex0 = ko.getObservable(someModel.arrayData, "0");
// observableIndex0 is null
var observableIndex0 = ko.getObservable(someModel.arrayData[0]);
// observableIndex0 is null
var observableArray = ko.getObservable(someModel, "arrayData");
var observableIndex0 = ko.getObservable(observableArray, 0);
// observableIndex0 is null
var observableArray = ko.getObservable(someModel, "arrayData");
var observableIndex0 = ko.getObservable(observableArray, "0");
// observableIndex0 is null
So is there some other way I am meant to get access to the observable objects within the array?
As described in the original blog post, functions in an object that is ko.track
ed will be wrapped in observables as well and can be used as if they were computed
. However, unlike computed observables, these "implicit" computed
s can be overwritten very easily (possibly accidentally, ossibly maliciously).
I have created a simple example to illustrate this problem.
Go to http://fiddle.jshell.net/nen1u8o1/9/show/light/ in Chrome or Firefox
Open the Developer Tools
Go to the 'Console' tab
Switch to the frame context. In Chrome's Devtools, this is done using a dropdown at the top of the console, in Firefox, the dropdown is at the bottom of the console.
Paste the following code in the console:
var vm = ko.dataFor(document.getElementById('line'))
item = ko.getObservable(vm, 'items')()[0]
item.getSubtotal = function() { return '$0.01'; }
Observe that the 'Subtotal' column's value has changed, and will no longer track the other observables.
I would suggest that, by default, these observables created from functions should have only getters, not setters.
Hi,
Whenever I inspect computedVariables
error shows Uncaught TypeError: Function.prototype.toString is not generic
. Any Idea @ntrrgc.
From the meantime, I inserted this code as checking.
var isToStringAllowed = true;
try{
coercedObj.toString()
} catch(e) {
isToStringAllowed = false
}
if (isToStringAllowed && coercedObj !== Window.prototype && 'toString' in coercedObj
&& coercedObj.toString() === '[object Window]') {
...
Any chance we could get the latest version of this added to NPM, the latest on there is 0.1.1.
KO components receives a reference of observable when we passes it as params. As componentes receives it as a reference you can write to this observable and it will reflect on your viewmodel;
But, when you using ko-es5 plugin (super awesome) your KO component instead of receive also a reference to your observable receives some kind of computeObservable, and we cant write to it. is this a unexpected behaviour ?
http://jsfiddle.net/kapuca/k0fw8w18/
Hello, I'm a intern from cdnjs. I want to add your library in cdnjs. However, the latest version in npm is not same as in github. So would you please update the version in npm? Thank you.
Hi,
I just discovered this library on Steve Sanderson's blog today and it looks really neat.
Though I haven't tried playing around with it yet, from reading the blog post I think I might have a good suggestion regarding functions and computed.
In my opinion, unlike properties the primary use of functions is not to output a value but rather to execute some logic/task (Ex. save changes) which may not necessary return anything. Thus, automatically converting all functions to computed observables is not always desirable nor a correct approach. On the other hand since we have getters/setters for properties in es5, a property get function could easily be converted to a computed.
Example:
var order = {
item : "Product",
price : 99.9,
quantity : 2,
//ko.track converts subtotal getter to computed
get subtotal () {
return "$" + (this.price * this.quantity).toFixed(2);
},
//No need for ko.track to convert buy function to computed
buy : function(){
//Execute purchase logic
}
}
ko.track(order);
I hope you find my suggestion helpful. Keep up the good work.
To make it iframe safe, instead of
foo instanceof Array
use
foo && foo.constructor.name === "Array"
Since this library is often used with other packages that may already include weakmap (for example https://github.com/zloirock/core-js) it would be nice to have a release version that doesn't include the polyfill.
In knockout 3.0+, for observable arrays, in the foreach binding, you can use $rawData to get a two way bound variable.
But it does not seem to work with ko.track. How to access the equivalence of $rawData in ko.track modified object?
Can we update the ko dependency to 3.4.0? thx
https://github.com/SteveSanderson/knockout-es5/blob/v0.3.0/package.json#L20
Usually it's best to not mutate observables in a computed, but if you do so, you generally don't also want a dependency on that observable. If you simply write to the property, it's not a problem, but if you first read it, you'll get a dependency. Example: this.quantity += 1;
or this.myArray.remove(item);
. With this plugin, it's a but harder to avoid the dependency. You'd have to use ko.getObservable(someModel, 'email').peek()
to access the value without creating a dependency. The above examples would become this.quantity = ko.getObservable(this, 'quantity').peek() + 1;
and ko.getObservable(this, 'myArray').remove(item);
my username in npm https://www.npmjs.com/~archangel-irk
On Chrome 30 and IE10 it says the length
property cannot be redefined when you are tracking an array.
Right now the second parameter is a list the properties to observe. I think this should instead be an option object, so we can add more features easily without breaking compatibility. Sample features that could be added are recursion (see #24), properties to exclude instead of to include, custom property filter function (for example to exclude all properties beginning by _ or $), and plenty of other way better ideas.
In our application our computed properties keep getting calculated even when the component (where they were defined) has been destroyed. When I changed the code to used pureComputed, this is not happening. So is there a way to switch to pureComputeds?
@SteveSanderson Good day! Thank you for this work!
What about Travis?
P.S. See also commits of a fork, they can have some ideas:
passy/knockout-es5-passy@28a9acc...master
If my object already has an ES5 style property, it is not wrapped and is not observable!
Could you please fix this?
How to use with extend ?
Hey @SteveSanderson,
I'm writing a library of custom bindings. Is there any way to subscribe in the init
function when I'm just getting the observable passed?
My current binding:
init: function(element, valueAccessor){
var $el = $(element);
// Toggle the observable on click
$(element).click(function(){
var observable = valueAccessor();
// Update the observable (true or false)
// this also effects the class change
observable(!ko.unwrap(observable));
});
var updateClass = function(){
// if we set it to true, add the "active" class
if (!!ko.unwrap(valueAccessor())) {
$el.addClass('active');
}
// otherwise, remove it
else {
$el.removeClass('active');
}
};
/** throwing an error because I'm calling subscribe on a boolean **/
valueAccessor().subscribe(updateClass);
// invoke immediately to get the initial class correct
updateClass();
}
Also, how can I set the observable?
We use an extender to force type stored in the underlying value ( value binding in text boxes pushes strings into observable ).
How would I wire this up if I was to use ko.track ?
var amount = ko.observable().asInteger(0);
ko.observable.fn['asInteger'] = function (defaultValue) {
var target = this;
var interceptor = ko.computed({
read: target,
write: function (value) {
var parsed = parseInt(value, 10);
var manualNotifyFlag = false;
if(isNaN(parsed)) {
parsed = defaultValue;
manualNotifyFlag = (target() === parsed);
}
if(!manualNotifyFlag) {
target(parsed);
} else {
target.valueHasMutated();
}
}
});
interceptor(target()); // Ensure target is properly initialised.
return interceptor;
};
I understand that the non-recursion is by design, but I have an use case where it would be pretty useful. In a spa, I'm receiving a complex object from an API, and I want to be able to make it observable quickly. Adding and option to crawl the object recursively would be really helpful. I have a proof of concept working, and the changes appear rather minimal. I can give it a go, but I'd like to make sure that it something that would be considered first.
If a property is already an observableArray it simply gets passed as a getter/setter and does not end up getting wrapped with the array mutator methods.
PR coming.
I have a case where i have a property on a model that is a plain observable. The model is then run through ko.track
. Later on as the model is processed it needs to be modified with a custom computed property in some cases. Setting the property will have it wrapped in the original observable and exposed to consumers as an observable instead of a plain property. I'm looking for a way to unregister the property or the entire object from the internal registry so that it can be run through ko.track
again with the updated observable.
I have a ViewModel defined that contains two sub-ViewModels as properties like this:
var vm = function () {
var self = this;
self.subVM1 = new SubVM1();
self.subMV2 = new SubMV2();
ko.track(self);
};
Both SubVM's are structured the same way as a normal VM and both call ko.track(self)
as well.
In my markup, I am trying to use the "with:" syntax to specify the use of one of the SubVMs like so:
<div data-bind="with: SubVM1">
<h1 data-bind="text: SomePropertyOnSubVM1"></h1>
</div>
This binds initially just fine. However, when SomePropertyOnSubVM1
changes, the UI is not updated. This worked fine when I put the property directly on the main ViewModel and not on the Sub-ViewModel.
Any suggestions? Is this a known issue?
Weakmap is not supplied by bower package, but is required in code from '../lib/weakmap'. I can not use weakmap, installed independently.
Even when using a shim as in the example here: http://stackoverflow.com/questions/22113999/using-knockout-es5-plugin-in-amd-module
ko.track is undefined.
ko-es5 now supports AMD loaders and I'm using it successfully with require.js.
But I can't optimize my application by packaging all modules in a few files with the require.js optimizer (r.js). The issue seems to be that r.js doesn't parse global.define()
.
I patched lines 335-336 by replacing the 3 global.define
with define
and now everything works well.
The license of this seems to be missing, unless it is your own copyright, but I have a feeling its the same as knockout :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.