Coder Social home page Coder Social logo

knockout-es5's Introduction

knockout-es5

Knockout.js meets ECMAScript 5 properties. Read the introduction and basic usage guide.

Installation

To use in a bower:

bower install knockout-es5

Or, get a copy of the knockout-es5.min.js file

Then just add a <script> tag referencing at. Be sure to place the reference after your reference to Knockout itself:

<script src='knockout-x.y.z.js'></script>
<script src='knockout-es5.min.js'></script>

If you are using this on the server in Node.js, just require the file as a module:

var ko = require('./knockout-es5');
// Now use ko - it has been enhanced with ES5 features

Version without weakmap in build

  • knockout-es5-clean.js
  • knockout-es5-clean.min.js

This files don't have weakmap shim in code. You have to link weakmap shim or use es6 for it.

Recursive traversal of nested objects

ko.track(nestedObj, { deep: true });

With fields:

ko.track(nestedObj, { deep: true, fields: ['prop1', 'prop2'] });

Selecting of nested fields (e.g. 'prop.nested_prop') are not yet supported. If someone need this feature, please create an issue.

Usage fields prop without deep:

ko.track(obj, { fields: ['prop1', 'prop2'] });

How to build from source

First, install NPM if you don't already have it. It comes with Node.js.

Second, install Grunt globally, if you don't already have it:

npm install -g grunt-cli

Third, use NPM to download all the dependencies for this module:

cd wherever_you_cloned_this_repo
npm install

Now you can build the package (linting and running tests along the way):

grunt

Or you can just run the linting tool and tests:

grunt test

Or you can make Grunt watch for changes to the sources/specs and auto-rebuild after each change:

grunt watch

The browser-ready output files will be dumped at the following locations:

  • dist/knockout-es5.js
  • dist/knockout-es5.min.js

knockout-es5's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

knockout-es5's Issues

Functions as computed

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.

ko.track is undefined in click handlers

<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.

Add options

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.

How do you get an observable within an observableArray?

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?

Need to be able to unregister tracked property

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.

Add recursion

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.

ko-es5 is not r.js-friendly

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.

Recalculation Never Happens in a Nitch Case

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());

`

Add array support for deep track

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

"Implicit" Computed properties should not be able to be overwritten

As described in the original blog post, functions in an object that is ko.tracked will be wrapped in observables as well and can be used as if they were computed. However, unlike computed observables, these "implicit" computeds can be overwritten very easily (possibly accidentally, ossibly maliciously).

I have created a simple example to illustrate this problem.

  1. Go to http://fiddle.jshell.net/nen1u8o1/9/show/light/ in Chrome or Firefox

  2. Open the Developer Tools

  3. Go to the 'Console' tab

  4. 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.

  5. 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'; }
    
  6. 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.

how to access $rawData

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?

Uncaught TypeError: Function.prototype.toString is not generic for inspecting variables

Hi,
Whenever I inspect computedVariables error shows Uncaught TypeError: Function.prototype.toString is not generic. Any Idea @ntrrgc.
ko-es5-error

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]') {
...

Computeds should be pureComputeds?

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?

getObservableArray support?

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.

Question: Custom Bindings and Subscribe?

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?

Breaks F12 Tools in IE11

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.

Cannot write to an observable received as param inside of component

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/

http://jsfiddle.net/kapuca/jwea6zaL/

workaround: http://stackoverflow.com/questions/42427516/cannot-write-to-an-observable-received-as-param-inside-of-knockoutjs-component

Nested ViewModels

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?

Missing license

The license of this seems to be missing, unless it is your own copyright, but I have a feeling its the same as knockout :)

Not working via npm?

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).

Question: How would you handle observable extensions ?

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;
};

bower

this needs a bower.json with

"knockout-es5": {
      "main": "dist/knockout-es5.min.js"
    }

Mutating a property can create a dependency on it

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);

Add latest to NPM?

Any chance we could get the latest version of this added to NPM, the latest on there is 0.1.1.

Browser CSP compatibility

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.

TypeScript support

Hi,

Let's consider following code:

HTML

<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>

TypeScript

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.

Support for custom two-way bindings

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.