I am using Stripe and to avoid any issues with PCI compliance the credit card data entered on my forms should never be transmitted from browser to server. Users shall enter credit-card data into forms with Knockout.js.
Mostly as a matter of interest (mostly so I understand better how knockout.js works), I would like to come up with a way to prevent serialization of credit-card data by having an explicit error occur whenever serialization of the data is attempted in the usual knockout.js way (ie ko.toJS or ko.toJSON or ko.mapping.* equivalents).
To achieve this I thought I could do something clever, like this (in CoffeeScript):
class NeverSerialize
datums = ko.observable()
blocker = ko.computed(
read: () -> throw new Error("You shall not pass!")
deferEvaluation: true
)
and when ko.toJS/toJSON or equivalent is called it would execute blocker() and that would call the read function and be the end of it.
This idea almost works – as you can see from this jsFiddle. Unfortunately it only executes read() the first time, but it is never called after. I gather this is because there are no observables that read depends upon, so knockout presumes that the value of the computed will not have changed.
So I guess I am curious about two things:
-
Is this conceptually a sensible way to go about preventing an observable from being serialized with
ko.toJS/toJSON? (or, generically, having a function execute whenever areadis called on a computed observable) -
How would one go about having a computed variable’s
readfunction called every time it is accessed (i.e. avoid caching it)?
I am just curious about how one might go about this sort of thing with Knockout.js.
Rather than doing all that, just define a
toJSONfunction which will return an object that contains the properties you want serialized. i.e., create a copy of your view model and remove the properties from the copy you don’t want serialized.If you really wanted to do this, I suppose you could do this. The
ko.toJSfunction goes through all the properties of an object and maps the value to a new object. So if it is not an observable, it will simply copy it, otherwise it will invoke (read) it. You need to trick knockout into unconditionally invoke your observable so you may always throw an error upon reading it.You can write up a new observable hierarchy that would be a non-caching version, but that’s a lot of stuff you probably don’t need. You just need to create an observable which throws errors ultimately. So what you can do is create a function that will just throw an error and trick knockout into thinking it is an observable. Fortunately, knockout checks this by seeing it has an observable as a prototype through a special property.
Then it’s just a matter of adding the observable to your view model and you’re set.
Of course, this relies on undocumented behavior. The way knockout determines these things may change in the future. Use at your own risk.