Giter VIP home page Giter VIP logo

knockback's Introduction

Build Status

logo

Knockback.js provides Knockout.js magic for Backbone.js Models and Collections.

Why Knockback?

  • Make amazingly dynamic applications by applying a small number of simple principles
  • Leverage the wonderful work from both the Backbone and Knockout communities
  • Easily view and edit relationships between Models using an ORM of your choice:
  • Simplify program control flow by configuring your application from your HTML Views. It's like Angular.js but without memorizing all of the special purpose ng-{something} attributes. See the Inject Tutorial for live examples!

Examples

Simple

The HTML:
<label>First Name: </label><input data-bind="value: first_name, valueUpdate: 'keyup'" />
<label>Last Name: </label><input data-bind="value: last_name, valueUpdate: 'keyup'" />
And...engage:
model = new Backbone.Model({first_name: 'Bob', last_name: 'Smith'})
ko.applyBindings(kb.viewModel(model))

When you type in the input boxes, the values are properly transferred bi-directionally to the model and all other bound view models!

Advanced

The View Model:

Javascript

var ContactViewModel = kb.ViewModel.extend({
  constructor: function(model) {
    kb.ViewModel.prototype.constructor.call(this, model);

    this.full_name = ko.computed(function() {
      return this.first_name() + " " + this.last_name();
    }, this);
});

or Coffeescript

class ContactViewModel extends kb.ViewModel
  constructor: (model) ->
    super model

    @full_name = ko.computed => "#{@first_name()} #{@last_name()}"
The HTML:
<h1 data-bind="text: 'Hello ' + full_name()"></h1>
<label>First Name: </label><input data-bind="value: first_name, valueUpdate: 'keyup'" />
<label>Last Name: </label><input data-bind="value: last_name, valueUpdate: 'keyup'" />
And...engage:
model = new Backbone.Model({first_name: 'Bob', last_name: 'Smith'})
view_model = new ContactViewModel(model)
ko.applyBindings(view_model)

# ... do stuff then clean up
kb.release(view_model)

Now, the greeting updates as you type!

Getting Started

Download Latest (1.2.3):

Please see the release notes for upgrade pointers.

The full versions bundle advanced features.

The core versions remove advanced features that can be included separately: localization, formatting, triggering, defaults, and validation.

The stack versions provide Underscore.js + Backbone.js + Knockout.js + Knockback.js in a single file.

###Distributions

You can also find Knockback on your favorite distributions:

  • npm: npm install knockback
  • Bower: bower install knockback
  • NuGet - install right in Visual Studio

###Dependencies

  • Backbone.js - provides the Model layer
  • Knockout.js - provides the ViewModel layer foundations for Knockback
  • Underscore.js - provides an awesome JavaScript utility belt
  • LoDash - optionally replaces Underscore.js with a library optimized for consistent performance
  • Parse - optionally replaces Backbone.js and Underscore.js

###Compatible Components

  • BackboneORM - A polystore ORM for Node.js and the browser
  • Backbone-Relational.js - Get and set relations (one-to-one, one-to-many, many-to-one) for Backbone models
  • Backbone Associations - Create object hierarchies with Backbone models. Respond to hierarchy changes using regular Backbone events
  • BackboneModelRef.js - provides a reference to a Backbone.Model that can be bound to your view before the model is loaded from the server (along with relevant load state notifications).

Contributing

To build the library for Node.js and browsers:

$ gulp build

Please run tests before submitting a pull request:

$ gulp test --quick

and eventually all tests:

$ npm test

knockback's People

Contributors

ben avatar forivall avatar gehan avatar joakimkemeny avatar kainosnoema avatar kmalakoff avatar kvnloo avatar m-lieshout-adi avatar naddiseo avatar richardasymmetric avatar tanenbaum avatar yuchi avatar

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

knockback's Issues

API improvements or simplifications

Hi everyone!

I have suggested one improvement to the API here: #2

If you have other suggestions, please open an issue to discuss or implement them and send me a pull request!

Cheers,

Kevin

Model locking and undo

I'm not sure if this is necessary, but wanted to solicit some feedback.

  1. Live changes: Currently, when I make a view for editing and model, I typically save the model attributes, and if the edit is cancelled, I restore them. So in short by default, all changes are propagated live.

  2. Pos-commit changes: If you don't want to propagate your changes live, you can clone() your model and edit the clone. If the changes are accepted, you can do a set() from the clone's attributes into the original.

Is this enough or does there need to be a lock model attribute option and the caller chooses when to commit? Or something else?! Obviously, this would complicate things.

Please let me know if you have a use case that isn't neatly handled by 1) and 2), and how you would like to see it handled.

Observables disappear when set to 0

When the value of an observable is changed to 0 it disappears since it's set to null.

I've located the problem to the following line in Observable.prototype.update.

new_value || (new_value = null);

I've changed this to only check if the value is undefined but I don't know if that is the right way to solve it.

typeof new_value !== "undefined" || (new_value = null);

Duplicate items in view model

Hi, I start using knockback recently,
I have an issue with duplicate entries in my knockout model, but my backbone model is Ok. and I manipulate only bb.Models and collections.

Debugging my application I notice that in the knockback-collection-observable.coffee file, and the method: _onCollectionChange: (event, arg) calls recursivelly the _onCollectionChange(...) but it seems also that the method _createViewModel(..) triggers some _onCollectionChange events. So, it seems the problem is that way.
For the moment, I move these two lines from the _onCollectionChange method:

#original code
view_model = @_createViewModel(arg)
@in_edit++  
#
#my fix code
@in_edit++ 
view_model = @_createViewModel(arg)

But I dont know if it is really a bug in knockback or I'm doing something wrong.

my bb.models are

 class DataModel.Models.Person extends Backbone.Model
            urlRoot : "#server#/persons/person".replace /#server#/, serverUrl

class DataModel.Collections.Persons extends Backbone.Collection
            url: "#server#/persons/persons".replace /#server#/, serverUrl
            model: DataModel.Models.Person

my view models

class PersonViewModel extends kb.ViewModel
...

 @persons = kb.collectionObservable(DataModel.collections.persons, {view_model: PersonViewModel});

Unable to clear nested view model property

Please check following fiddle:

http://jsfiddle.net/dn59C/3/

I am unable to unset the invite property on the view model MyViewModel. Not sure if this is a bug or I'm doing it wrong, so here goes:

Steps to Reproduce

  • Using the above fiddle, click on Bob
  • Next click on Clear Selection

What I expected to happen

I expected the panel displaying the current selection would disappear.

What happened instead

Instead the panel remained, but the underlying properties of the invite auto ViewModel were set to null.

Additional Information

Another thing that is weird is if you start clicking around the bullet list, you'll notice that whichever item was first clicked, get's overwritten by the last click bullet item.

Best practices for single-page apps

Hi. I'm writing a single-page mobile app using phonegap and knockback. At the moment, I seem to need observables for both the Backbone model layer and the KB viewmodel layer. For example:

// Given BB model PersonModel and KB view model function personViewModel...
var personAddView = function () {
    var that = {};

    that.bbPerson = ko.observable(new PersonModel());
    that.person = ko.computed(function () {
        return personViewModel(that.bbPerson());
    });

    that.save = function () {
        that.bbPerson().save();
        that.bbPerson(new PersonModel());
    };

    return that;
};

(In the 'data-bind's only 'person' is used, not bbPerson)

As a single-page app no page reloads are allowed, so this view has to be able to reset itself to a 'clean' state once the item has been saved.

Any thoughts on this pattern? What alternatives are there? I dislike having to have observables for both the Backbone model and the viewModel, but I haven't seen any suitable alternatives. I've tried exclusively using viewModels, but that falls down very quickly when you start passing them around.

Thanks.
Simon

Extending ViewModel with backbone

I really enjoy Backbone as a way to greatly simplify translating my Backbone model schema (with relations) to a ViewModel schema.
My biggest problem at the moment, is that though I have multiple Model "classes", when I convert them to ViewModels, They are all from the same ViewModel type.
For example, if I have a model of type House that contains many models of type Person, How can I extend the ViewModel that all the models of type Person get? I guess what I need is a way to give a mapping from a Backbone model type to a ViewModel type that will be used in the construction algorithm.
Is there a way to do this now?
Thanks.

Automatic cross-browser compatibility testing

We need to test the DOM in addition to the qunit tests for the bindings APIs....

Any volunteers to figure out what we need and the best approach? (the ruby racer, etc)

Please ;-P

Kevin

Knockback & Brunch

Knockback does not behave well when put in the vendors/javascript folder of a brunch managed project. The reason is that libraries there are supposed to live in the "global" namespace (i.e. not use require) , but at the same time you have CommonJS require present.

More precisely, the underscore require block needs to look something like this to bypass require if underscore is already defined:

if(this._===undefined){
    if (typeof require !== 'undefined') {
      try {
        _ = require('lodash');
      } catch (e) {
        _ = require('underscore');
      }
    } else {
       throw "Can not resolve underscore in this environment"
    }
} else {
  _ = this._;
}

Better demo is required

Hi everyone,

I'm not much of an HTML/CSS wiz so my demo of the library isn't too flashy: http://jsfiddle.net/kmalakoff/QSkpv/ and doesn't show off the pull power of Knockback.

If you make a more interesting demo or have an idea for one, can you please package/share it for demonstrating Knockback?

Also, I'm quite new to Github so I'm assuming the demo should be posted on Github pages. If you can submit a demo that is easily embeddable in Github pages (or somewhere else?) that would be great.

Get those ideas flowing!

Cheers,

Kevin

Best practice for optimizing/limiting observables/callbacks

I've ported my backbone application over to Knockback. It's a fairly typical use-case, similar to a blog with entries, authors, comments, etc. Everything is wired up nicely, and it's at the stage now where some optimization is in order. I don't have thousands of objects, and the page behaves fairly well, however when I go to reload it it takes 20-30 seconds for Chrome to unload the page, presumably doing a massive hierarchical unbinding/cleanup. I think it may be responsible for occasional crashing on mobile.

I have situations like:

 <ul  data-bind="foreach: entries">
   <li><span data-bind="text: author().name()"></span>
         <div data-bind="text: headline()"></div>
         date, body, etc etc... foreach comments... etc etc
   </li>
 </ul>

In reality, I don't need observable/two-way bindings for -all- the attributes. Some attributes, such as the author, do not change once set and do not need to be changed. The keys option to kb.ViewModel provides some relief, however this requires altering my templates to a form <span data-bind="text: model().get('author').get('name')"></span>, which I would prefer to be agnostic to the implementation by the ViewModel.

I'm still new to Knockback/Knockout, and I'm not sure precisely the best practice for handling such a situation or if such functionality is already baked into Knockback, however I've started using the following boilerplate, which I find works fairly well. The only change to my templates was to replace a few scattered attribute references with their functional equivalents (data-bind="text: headline" to data-bind="text: headline()").. which I probably should have been doing anyway.

Thoughts?

class BlogViewModel extends kb.ViewModel

    # These model attributes get simple getter functions
    @staticAttributes: ['author','headline','datePosted']

    # These model get full-blown observables
    @dynamicAttributes: ['headline','body','tags']

    @staticDefaults:
        headline: ''
        body: ''

    # Create simple getter functions for the static attributes that will
    # return the model's value
    for attr in @staticAttributes
        @prototype[attr] = ((_attr) -> 
            return () ->
                return if @model().get(_attr)? then @model().get(_attr) else BlogViewModel.staticDefaults[_attr]
            )(attr)

    constructor: (model, options) ->

        options = options or {}
        options.keys = BlogViewModel.dynamicAttributes

        super

        @_commentsCollection = new CommentsCollection([], {parent: model})
        @comments = kb.collectionObservable(@_commentsCollection, { view_model: CommentViewModel } )

        # etc

        # More constructor code here.....

AMD support

Knockout.js has AMD support for Require.js, and there are some branches that add this to Underscore and Backbone; are there any plans to add this to Knockback?

I'm loading backbone, underscore and knockout via require.js and Knockback gives me a "dependency alert", since it can't find them in the global scope.

-Mike

kb.release is too destructive

Using kb.release seems to be a necessity for preventing memory leakage when creating view models.
However it seems too destructive to me. Since I am using custom ViewModel classes that extend kb.ViewModel I encounter many situations where I am loosing shared objects when releasing a viewmodel.
For example kb.release([1,2]), actually empties the array. if the array object is shared between viewmodels, this causes problems.
I would suggest instead of black listing the Backbone Models from the destruction list, why not white list what really needs to be destroyed or disconnected.

Dynamically add models to Backbone-relational model using knockback (hasMany)

I have a Backbone-relational model called Home. Home has many Persons. Is it possible to add Persons after Home is created using knockback? If so can someone give an example?

In the knockback tutorials,I could only find out how it works the other way round (Persons are created and then when Home is created, the Persons are added).Also, this is static, i.e.,the ids of Persons are known beforehand.
In my case I will know the id of Home model.

How do you migrate from vanilla backbone

Hi guys,

I would like to start kicking the tyres of knockback for one of my apps that has grown quite large. My question has to do with integrating knockback with require.js

I downloaded knockback-full-stack, which seems to be doing something a bit odd. Specifically in line 5950:

define('knockback', ['underscore', 'backbone', 'knockout'], factory)

Underscore, backbone and knockout are included in this file, but are also being required?

I have a lot of places in by code where I'm doing

deps = [
    "backbone", "other deps...."
    "lib/ICanHaz"]

require deps, (bb, "other deps...", ich) ->

 {Router, View, Model} = bb

I'd like to start migrating over an app at a time, but including kb and using it where it makes sense.

I was hoping that I could just redefine my require.paths, which doesn't seem to be the case. What should I do here?

  1. Use a different download to knockback-full-stack (which one?), download knockout separately (I already have Backbone and underscore) and require ["knockback", "...] where needed?
  2. Load knockback-full-stack via a script first and change all of my modules to use window.Backbone etc?
  3. Some other method?

I'd be happy to contribute my experiences partially migrating a reasonably large backbone app over to knockback, I just need a little pointer to get me going ๐Ÿ˜„

Cheers,
Ben

Simplification for kb.LocalizedObservable and getObservedValue

@scottmessinger and @yuchi : I have a proposal for a simplification:

kb.LocalizedObservable -> kb.Localizer: I called it an "observable" because it wraps a ko.dependentObservable behind-the-scenes but its purpose is to localize something else. Does it make sense to drop Observable?

kb.Localizer.getObservedValue and kb.Localizer.setObservedValue -> kb.Localizer.wrappedValue(): the idea here would be to use a dual-purpose Javascript function (undefined parameter means get and defined parameter means set). The other name I was thinking about was kb.Localizer.value() - it is shorter but doesn't convey the meaning.

Swap out Underscore with Lo-Dash

Lo-Dash is drop-in replacement for Underscore.js, from the devs behind jsPerf.com, that delivers performance improvements, bug fixes, and additional features.

A couple Backbone boiler plates have made the switch too (here and here).

Also check out the screencasts over it here.

Volunteer for Knockback tutorial needed

Hi everyone,

I've had a lot of feedback that the Knockback documentation isn't great (yet!) and that an API guide and step-by-step tutorial is needed for Knockback adoption by a wider audience.

I'm looking for some people to volunteer to make some progress on a tutorial and linked API documentation. Besides sharing the effort, having some community members' critical eyes will help push the Knockback library forward by challenging me on some API decisions...and it may even be a fun and rewarding experience!

Here are some suggestions for documentation requirements by pulling together ideas from @dzhus and a few other people over the past months:

  1. API documentation - for example, what arguments kb.observable() accepts and their semantics
  2. API documentation hyperlinks - an ability to embed hyperlinks in the tutorial back to the API
  3. a step by step tutorial to build a sample application - for example: http://nerddinnerbook.s3.amazonaws.com/Intro.htm or http://spinejs.com/pages/examples
  4. ability to switch between JavaScript and Coffeescript API usage examples
  5. an overview of minimum set of Backbone and Knockout principles/APIs giving an insight into how Knockback bridges them

@yuchi has suggested to use https://github.com/logicalparadox/codex

ARE YOU INTERESTED? If so, please get in touch (https://github.com/kmalakoff) with your ideas and what you'd like to do.

Cheers,

Kevin

How to specify the viewModel for relations?

Hi. I'm having trouble specifying which viewModel knockback should use for related models.

At the moment I'm dealing with a 'HasOne' relation. The relation is working fine, and knockback is creating an ordinary kb.ViewModel for the related model.

However, if I specify:

options.children.foo = fooViewModel

It uses a fooViewModel for the related item, but it creates it directly instead of using a kb.viewModelAttributeConnector. This means that it works as expected if the related item has already been retrieved, but where it has not been retrieved yet, the lack of the connector layer means that the related item doesn't get updated.

This appears to be in the handling for AttributeConnector.createOrUpdate. Ordinarily it calls createByType to make a viewModelAttributeConnector, passing along the options. Adding the view_model to the options should give the desired behaviour as createByType handles this correctly. However, createOrUpdate intercepts the view_model option- if it finds one, it creates the viewModel directly without the connector. I cannot find any way of bypassing this and getting the view_model option into createByType.

Any hints?

Thanks.
Simon

Knockback <--> Parse compatibility

Has anyone tried this?

I started out developing in Backbone, then decided to use Parse (https://parse.com/docs/js_guide), which is based on Backbone, as a back-end. This has worked well so far. I found myself writing a lot of repetitive Backbone view-model binding code, and ultimately decided to adopt Knockback into the mix.

The only snag is that Parse doesn't derive from the Backbone classes but rather re-implements them. I modified the type detection code in knockback.js 0.16.8 to work with Parse Models and Collections, and had about 95% success.

kb.CollectionObservables work fine with Parse.Collection objects in response to add, remove and reset events while kb.Observable work almost perfectly with Parse.Object objects, except that it doesn't appear to see the change event.

   model = new Parse.Object()
   test = kb.observable(model, {key: 'width', default: '---'})
   model.set('width', 1)
   test()   "---"
   test(2)
   model.get('width')  "2"
   model.set('width', 3)
   test()   "2"
   model.trigger('change', model, {})
   test()   "2"
   model.trigger('change')   
   test()   "3"

The exposed behaviour for Parse and Backbone are almost identical, however Knockback appears (?) to integrate fairly deeply with the the internals of Backbone events, presumably leading to some minor discrepancy in Parse's implementation. I'm trying to pick through the event handling code in Knockback to see what the difference could be.

Thought I'd throw it out there in case anyone had any immediate thoughts.

Single selectbox

I have a relation between a Page and PageType model, which I have defined like so:

var Page = Backbone.RelationalModel.extend({});

var PageType = Backbone.RelationalModel.extend({
            relations: [
                {
                    type: 'HasMany',
                    key: 'pages',
                    relatedModel: Page,
                    reverseRelation: {
                        key: 'page_type_id'
                    }
                }
            ]
        });

var PageTypeCollection = Backbone.Collection.extend({
        model: PageType
    });

The view model for the Page loads a list of the available PageTypes, and also has a reference to it's own page_type_id.

var PageEditViewModel = function(model) {
            var _this = this;

            this._auto = kb.viewModel(model, {
                keys: {
                    page_type_id: { key: 'page_type_id' }
                }
            });

            this.pageTypes = kb.collectionObservable(new PageTypeCollection(), {
                defer: true
            });
}

And the view like so:

  <select data-bind="options: pageTypes, optionsValue: 'id', optionsText: 'name', value: page_type_id"></select>

I figure I'm missing a step here, but cannot find anything in the documentation - the 'shareOptions' didn't seem to be entirely relevant to this problem. Any pointers would be appreciated! :)

Simplified view model syntax and memory management

I needed to jump through some hoops to get Knockback.ModelAttributeObservable and Knockback.LocalizedObservable working. It went as follows:

  1. Use a normal class instantiated with new
class Knockback.ModelAttributeObservable
  constructor: (@model, @bind_info, @view_model) ->

attribute_observable = new Knockback.ModelAttributeObservable(model, {keypath: 'name'}, {}) 
  1. Inside the constructor create and return the observable:
constructor: (@model, @bind_info, @view_model) ->
  ...
  @_kb_observable = ko.dependentObservable(@_onGetValue)
  return kb.observable(this)  # equivalent to return @_kb_observable
  1. Inside the destroy method release the observable:
destroy: ->
  @_kb_observable.dispose(); @_kb_observable = null
  1. Bind and attach any methods to the returned observable so you can call destroy on the returned observable:
_.bindAll(this, 'destroy')
@_kb_observable.destroy = @destroy
  1. You then use an attribute observable like:
class ContactViewModel
  constructor: (model) ->
    @name = new kb.ModelAttributeObservable(model, {keypath: 'name'}, this)
  destroy: 
    @name.destroy()

I think the syntax could be simplified to:

PersonViewModel = () ->
  @first_name = kb.ModelAttributeObservable(model, {keypath: 'name'}, this)

ko.applyBindings(new PersonViewModel())

with automatic memory management from inside the wrapped dependentObservable if the dependentObservable has a customizable callback when it is destroyed which cleans up the kb.ModelAttributeObservable.

So this would require:

  1. Investigating Knockout mechanisms for having a custom callback when @_kb_observable.dispose() is called. If one could be found or created...

  2. Update the Knockback.ModelAttributeObservable and Knockback.LocalizedObservable implementations to something like:

constructor: (@model, @bind_info, @view_model) ->
  ...
  @_kb_observable = ko.dependentObservable(@_onGetValue)
  _.bind(this, 'destroy'); @_kb_observable.setCustomDisposeCallback(@destroy)
      return kb.observable(this)  # equivalent to return @_kb_observable

destroy: ->
  @_kb_observable = null
  # and other cleanup
  1. Expose a more Knockout like creation method like:
kb.ModelAttributeObservable = (model, bind_info, view_model) -> 
  return new kb._ModelAttributeObservable(model, bind_info, view_model)
  1. And testing memory management with a scenario like:
PersonViewModel = () ->
  @first_name = kb.ModelAttributeObservable(model, {keypath: 'name'}, this)

ko.applyBindings(new PersonViewModel())

release bindings - is there an explicit call? and how would you validate that your model attribute was released? (wrap the existing destroy method and update the registration to the ko.dependentObservable?)


When I started Knockout I was a bit surprised by their function method exporting and memory management schemes so I "stuck with what I knew": explicit new/destroy. But I think their syntax is more simple and I think Knockback can be simplified. Any volunteers ;-)

Knockback not RequireJS-compatible

Hello,

When using the 'AMD' versions of Underscore and Backbone libraries, knockback fails to load the proper dependencies using require("underscore"), etc.

As per this discussion require sync version needs to be used within a require or define callback, like so:

define(['foo-dep', function () {
  var foo = require('foo-dep');
});

The issue can be seen here

Patched version can be seen here

I apologize, I don't yet know coffeescript well enough to submit a pull request, but I have provided a diff of the javascript files if that helps. The main difference is a define wrapper at the top, and returning the global at the bottom. (code borrowed from knockout.js):

http://www.diffnow.com/?report=5c62m

Regards,
Aaron

Simplifying syntax for: {localizer: (value) -> return new kb.Localizer()}

I think this is too verbose:

viewModel = (model) ->
  kb.observables(kb.locale_manager, {
     label1: {key: 'view1.label1', localizer: (value) -> return new localizer_LocalizedString(value) }
     label2: {key: 'view1.label2', localizer: (value) -> return new localizer_LocalizedString(value) }
     label3: {key: 'view1.label3', localizer: (value) -> return new localizer_LocalizedString(value) }
     label4: {key: 'view1.label4', localizer: (value) -> return new  localizer_LocalizedString(value) }
  }, this}

  kb.observables(model, {
    my_value1: {key: 'string_attribute', localizer: (value) -> return new  localizer_LocalizedString(value)}
    my_value2: {key: 'date_attribute1', localizer: (value) -> return new localizer_DateShort(value)}
    my_value3: {key: 'date_attribute2', localizer: (value) -> return new localizer_DateTimeLong(value)}
  }, this)

Originally, I had a hack that looked on the value for a factory function, but didn't deem that flexible or proper for a public release (plus it doesn't allow you to choose the format between localizer_DateShort and localizer_DateTimeLong.

How can we simplify this?

Option1: We could go the route of the lookup by type and giving the user an option to override the localizer. The above example would become:

viewModel = (model) ->
  kb.observables(kb.locale_manager, {
     label1: {key: 'view1.label1' }
     label2: {key: 'view1.label2' }
     label3: {key: 'view1.label3' }
     label4: {key: 'view1.label4' }
  }, this}

  kb.observables(model, {
    my_value1: {key: 'string_attribute'}
    my_value2: {key: 'date_attribute1'}
    my_value3: {key: 'date_attribute2', localizer: (value) -> return new localizer_DateTimeLong(value)}
  }, this)

Although...I haven't packaged any localizers because I think that the user should implement localization however they like so they could do something like:

kb.addLocalizer((value) -> return (value instanceof LocalizedString) , localizer_LocalizedString)

Option2: use named localizers

viewModel = (model) ->
  kb.observables(kb.locale_manager, {
     label1: {key: 'view1.label1', localizer: "LocalizedString" }
     label2: {key: 'view1.label2', localizer: "LocalizedString" }
     label3: {key: 'view1.label3', localizer: "LocalizedString" }
     label4: {key: 'view1.label4', localizer: "LocalizedString" }
  }, this}

  kb.observables(model, {
    my_value1: {key: 'string_attribute', localizer: "LocalizedString"}
    my_value2: {key: 'date_attribute1', localizer: "DateShort"}
    my_value3: {key: 'date_attribute2', localizer: "DateTimeLong"}
  }, this)

The user would register them like:

kb.addLocalizer("LocalizedString", localizer_LocalizedString)

Option 3: Maybe a combination of Option1 and Option2?

Or maybe we should avoid a central registry of localizers? The main advantage of a central registry with a testing function (Option1) would be we could have a factory function like (event if it is mainly used behind-the-scenes):

kb.localizer = (value, options, view_model) ->
  (return new localizer_constructor(value, options, view_model) if checker(value) ) for checker, localizer_constructor of kb.localizers
  return value # no localizer required

but of course, that means that either we introduce a flag like localized: to enable the check for a localizer (explicit) or check every single value (implicit + potentially slow).

What is the best approach to reduce this verbosity?

Knockback with Backbone-relational: Infinite loop

Hi,

I'm running into a problem when I use Knockback together with Backbone-relational:

I created an example here: http://jsfiddle.net/nKaaE/10/ WARNING: this example might kill your browser!
In this example a Zoo hasMany Animals and an Animal hasOne Zoo.

If you select the Zoo of an Animal in this example the Animal's zoo attribute gets alternately changed to a Zoo (Backbone Model) and then back to a ZooViewModel in an infinite loop.
See your console.log output for more details.

Any ideas?

PS: I'm also creating a Backbone-relational issue because I don't know if I must blame knockback or backbone-relational ;)

Knockback.js-1.0.0 Wish List

Hi everyone!

Knockback.js is getting to a feature and maturity point where I'm thinking about finalizing a 1.0.0.

What do you think is missing or needed for a 1.0.0?

Bug when passing Backbone Model with array values

So, I'm giving KB a try for this project I'm working on, and I'm running into this issue where passing a Backbone.Model to a kb.ViewModel which has a key-value pair where the value is of type Array. For example, consider the following code:

model = new Backbone.Model
  text: ["heading.get these rewards"]
  widget: ["sign_up", "rewards"]
  model_data:
    reward:
      top_rewards:
        properties: ["title", "description", "num_points"]
        query:
          type: "active"
          limit: 6

landingPageModel  = kb.viewModel model

landingPageModel.model().toJSON() returns a JSON object where model_data has been populated correctly, but text and widget are empty Arrays.

ViewModel not responding to changes using Backbone 0.9.2

Backbone 0.9.2 introduced a new API for change events, which seems to have broken Knockback's ability to respond to changes in a Backbone Model.

It's no longer necessary to iterate over all attributes in the model, calling hasChanged on each one, since the changed hash now contains only those model attributes with changes.

I was able to fix it by changing Knockback's ViewModel.prototype._kb_vm_onModelChange function like so:

Change 1:

old:

if (!this._kb_vm.model._changed) {
    return;
}

new:

if (!this._kb_vm.model.changed) {
    return;
}

Change 2:

old:

for (key in this._kb_vm.model.attributes) {
    _results.push((this._kb_vm.model.hasChanged(key) ? this._updateAttributeObservor(this._kb_vm.model, key) : void 0));
}

new:

for (key in this._kb_vm.model.changed) {
    _results.push(this._updateAttributeObservor(this._kb_vm.model, key) );
}

I'm not sure how you'd like to handle this, since making this change would probably break things for users with versions of Backbone < 0.9.2. Perhaps some conditional logic which checks Backbone.VERSION would do the trick?

Ability to change/rebind a Model or ModelRef

I can see two use cases where you would want to change/rebind a model or model ref:

  1. A classic iPad layout - you render a list view for a collection in a column on the left and when you click on one of the entries, you replace the model or model ref in the view model on the right (in other words, rebinding the view model rather than generating a new model view for the newly selected model).

  2. A new model generator - you could for example create a new model (eg. no model id), bind it in a view model, and when the user presses submit, you add that model to a collection, save it, and create a new model which rebinds with the existing view model.

I've never needed this because I typically spawn views through my routing solution, but for a static page, it might be useful...does anyone have a need for this?

PS. we could to the same swappability for collections and CollectionSyncs, but because collections are dynamic by nature if you for example use the backbone-couch (https://github.com/janmonschke/backbone-couchdb) plus you can always change the url query parameters and re-fetch the collection which will just work. If someone has a use case, let me know.

Issue with Backbone Relational reverseRelation models

Model & ViewModel:

var Forum = Backbone.RelationalModel.extend({
    urlRoot: '/forums',
    relations: [{
        type: Backbone.HasMany,
        key: 'topics',
        relatedModel: 'ForumTopic',
        collectionType: 'ForumTopicCollection',
        reverseRelation: {
            key: 'forum',
            keySource: 'forum_id',
            includeInJSON: 'id'
        }
}]};
var ForumViewModel = kb.ViewModel.extend({
    constructor: function(model, options, view_model) {
        var _this = this;
        kb.ViewModel.prototype.constructor.call(this, model, {options: options});
        this.forum_link = ko.computed(function() {
            return '#forum/' + this.id();
        }, this);

        return this;
    }
});

ForumTopic is also a RelationalModel.

If I setup a viewmodel for the ForumTopic like so:

        var TopicViewModel = kb.ViewModel.extend({
//new functions definitions, non override any existing functions
        });
        var view_model = new TopicViewModel(topic, {
            requires: ['title', 'body'],
            factories: {
                'forum': ForumViewModel
            }
        });

Then try and do something with the TopicViewModel. ForumViewModel throws an error at return '#forum/' + _this.id(); claiming that this is null. Putting a breakpoint on return '#forum/' + this.id(); and evaluating this returns a kb.ViewModel.extend.constructor with a this.model() as null and __kb_null: true.

If you remove the reference to this.id(); and just return a string there is some strange behaviour. The first time it breaks on the return statement you get the same result as above when you evaluate this. However, the breakpoint is triggered a second time (no error was thrown this time since we are not trying to evaluate this.id()) for a unknown reason (None of my code is called in-between the two breakpoint triggers and no network requests are sent). When the breakpoint is triggered for the second time this.id() now correctly returns the id of the Forum model and this.model() is no longer null and actually returns the correct forum model.

If you break on var view_model = new TopicViewModel(topic, { and evaluate topic.get('forum').get('id'); then it returns the correct forum id.

Note, using ForumViewModel on a normal Forum model the ko.computed works fine all the time so this seems to suggest it only becomes an issue when the Topic model is trying to reference the Forum model via a reverse relation (i.e HasOne but not explicitly defined).

Obviously the workaround is to check if it's null first:

            if(this.model() == null) {
                return;
            } else{
                return '#forum/' + this.id();
            }

Which works, but I still can't understand why multiple calls are made since there will only ever be one Forum model for a Topic model (reverse of HasMany = HasOne) especially when the first call is using a null model.

Problem with kb.ViewModel and collection rendering.

Please check following fiddle

http://jsfiddle.net/MygjJ/7/

You will see viewModel is not rendered properly.

I have commented working and not working lines there. In short: problem comes when two books refer same author.
On other side at the end of JS code there is variant that fixes that using

view_model_create

instead of

view_model

in collection options.

So it looks like something wrong with kb,ViewModel.

ko.observableArray is populated twice

Suppose a Backbone model A that holds a collection of B using Backbone-relational

When i have a collectionObservable on the a viewmodel observableArray and the backbone collection, the observableArray gets populated twice when a fetch of A is done.

Once by the _onCollectionReset event and once by the _onModelAdd event.

Multiple select and collection observable doesn't seem to work

Please see this fiddle:

http://jsfiddle.net/mentat/Mw4FQ/

I'm trying to set a collection from a select multiple. Press "Show JSON" and you'll see the contents of thing.subthings.toJSON. Here I've put subthings as a variable on Thing, but is doesn't matter. No collection is updated by the multiple select.

I'm also showing the contents of the collectionObservable in the list below the select multiple.

Nested collections are made into ko.observable not ko.observableArray

We have a Backbone model which has a nested collection. When we call kb.viewModel it creates a ko.observable that wraps the Backbone collection rather than creating an ko.observableArray.

I'm guessing this is as simple as modifying /src/knockback_view_model.coffee to add a check for Backbone.Collection and create a ko.observableArray along with a call to kb.collectionObservable.

Let me know if that's the case and I'll submit a pull request with the necessaries in it.
Cheers,
Dean

CollectionObservable.noifySubscribers raises a TypeError

I have the following:

@collectionObs = kb.collectionObservable collection,
  models_only: true

calling @collectionObs.notifySubscribers() gives me

TypeError: Cannot read property 'length' of undefined

in the _onObservableArrayChange method.

From the js, is would seem that the problem is here:
https://github.com/kmalakoff/knockback/blob/master/src/knockback-core/knockback-collection-observable.coffee#L358

specifically models_or_view_models is undefined when the exception is thrown.

@collectionObs.collection.notifySubscribers() is fine, which is what I've gone with, so perhaps this is a documentation issues as well as an unexpected value issue.

Observables are not updated for related models

Another fiddle

http://jsfiddle.net/mybDV/23/

By pressing on Show BS you may see actual content of data model (book store).
When I change author for BookOne I expect author viemodel to update itself, but it is not, while Backbone.Model is updated properly.

I also expect it to work in both directions- so when I change books for authors - books and other authors are updated.

Monkey patch BB classes to provide simpler interface

ContactViewModel = (model) ->
  kb.observables(model, {
    name:     {key:'name'}
    email:    {key:'email', write: true, default: '[email protected]'}
    date:     {key:'date', write: true, localizer: (value) => return new LongDateLocalizer(value)}
  }, this)
  return this

# later ...

view_model = new ContactViewModel(collection.get('some_id'))
ko.applyBindings(view_model)

Actually does a) build a ViewModel, b) engage the created ViewModel with KO.

What about:

# In Knockback
# -------------------

Backbone.Model::buildView = (viewModel = @viewModel) ->
  @view = new viewModel this
  ko.applyBindings @view
  @

# later, in the application
# --------------------

MyModel = Backbone.Model.extend
  # many thinks here
  viewModel: (model) ->
      kb.observables(model, {
      name:     {key:'name'}
      email:    {key:'email', write: true, default: '[email protected]'}
      date:     {key:'date', write: true, localizer: (value) => return new LongDateLocalizer(value)}
    }, @)
    @

  initialize: (options = {}) ->
    # ...and for very lazy people
    @buildView()

With such an architecture everything is inside the BB.Model definition, but you can always do MyModel::ViewModel = MyViewModel if you want to declare the ViewModel in a different place.

Better documentation

There is a proposal from here to write a better walk-through based documentation: http://groups.google.com/group/knockoutjs/browse_frm/thread/480112c00aa37cae#

"While I've played around with BB and am using KO in production, I'm a bit unclear how they fit together. I've always been a big fan of the spine.js documentation. In the following example, I like how the author walks through the app from html to model to controller to view. I'd love to see something similar with Knockback, even if you just skipped the narrative bits inbetween the code. http://spinejs.com/docs/example_tasks"

Everyone fine with spline-like documentation or do you prefer jsfiddle-like documentation from Knockout? (I'm saying this without knowing what I'm actually getting myself into on the documentation since this would be my first documentation site so any help like making me a template site would be much appreciated!).

Renaming kb.ModelAttributeObservable?

From this posting: http://groups.google.com/group/knockoutjs/browse_frm/thread/480112c00aa37cae#

There is a renaming proposal: http://groups.google.com/group/knockoutjs/browse_frm/thread/480112c00aa37cae#

"Knockback.ModelAttributeObservables and Knockback.ModelAttributeObservable

What about just Knockback.observables and Knockback.observable ? For the
localized version, why not just kb.observableLocalized? It seems keeping
the method name shorter for the more general case makes sense here."

I was thinking maybe (to be more similar to the Knockout convention):

kb.ModelAttributeObservable -> kb.attributeObservable (factory function)
and kb._AttributeObservable (underlying class)
kb.ModelAttributeObservables -> kb.attributeObservables (factory function)
and kb._AttributeObservables (underlying class)
kb.LocalizedObservable -> kb.localizedObservable (factory function)
and kb._LocalizedObservable (underlying class)

What do you all think?

Validations Solution

It looks like there are two solutions:

  1. https://github.com/n-time/backbone.validation
  2. https://github.com/thedersen/backbone.validation

I think it would be good to support bindings or hooks for validations, but I haven't given much thought to how and if we can support one or both libraries or any library.

Either way, there are a few design points I would want to consider in any solution:

  1. The bindings for display of validations state should be up to the user of the library - given the flexibility of Knockout bindings we shouldn't need to hard code labels or classes like in jquery validate (http://bassistance.de/jquery-plugins/jquery-plugin-validation/) but leave it up to the user to display how they want. Although do we want a default implementation? (if so, we should keep it decoupled from jquery, MooTools, etc?)

  2. The user should be able to hook in any rules solution they want (like kb.LocalizedObservor). I'm not sure about this one, maybe we should choose one solution to make it work out of the box. Thoughts?

  3. Async/ajax validations - I use a Mobile Couchbase view to query for unique names, then process the returned rows differently depending if it is a new or existing model, so I like to see if fit my use case!

Anyone want to write a proof of concept? or to make a proposal? ;-)

Should we move some of the API to kb.utils?

Currently, there are a few utility functions:

  1. For an instance of an class (from within the class)
  • Knockback.wrappedObservable = (instance) ->
  1. For an instance of a view model (in general)
  • Knockback.viewModelDestroyObservables = Knockback.vmDestroy = (view model) ->
  1. For an instance of a view model (for a model's view model created by a CollectionSync)
  • Knockback.viewModelGetModel = Knockback.vmModel = (view_model) ->
  • Knockback.viewModelGetElement = Knockback.vmElement = (view_model) ->

Where should they go?

  1. Knockback.wrappedObservable can't be an function on the kb.Observable and kn.LocalizedObservable classes because it can also be called outside the class if you use this set up (instead of derivation):
class ShortDateLocalizer extends kb.LocalizedObservable
  constructor: (value, options={}, view_model) ->
    super(value, _.extend(options, {
      read: (value) ->
        return Globalize.format(date, Globalize.cultures[kb.locale_manager.getLocale()].calendars.standard.patterns.d, kb.locale_manager.getLocale())
      write: (localized_string, value, observable) =>
        new_value = Globalize.parseDate(localized_string, Globalize.cultures[kb.locale_manager.getLocale()].calendars.standard.patterns.d, kb.locale_manager.getLocale())
        return observable.forceRefresh() if not (new_value and _.isDate(new_value)) # reset if invalid
        date.setTime(new_value.valueOf())
    }), view_model)
    return kb.wrappedObservable(this)

Should there be some indication that it is sort of an internal function for kb.Observable and kn.LocalizedObservable instances rather than a common part of the API? eg. kb.utils._instanceGetWrappedObservable = kb.utils._instanceWO

  1. Maybe Knockback.vmDestroy should be kb.utils.vmDestroy = kb.utils.viewModelDestroy?

  2. Maybe Knockback.vmModel and Knockback.vmElement should be kb.utils.entryViewModelModel = kb.utils.evmModel?

Let me know what you think is best bearing in mind consistency with the Knockout API. Also, let me know if I should bother to have long and abbreviated forms of the functions!

Volunteer Needed - extend validations module

Yesterday, I released 0.16.2 and added support for validations after being inspired by Angular's validations. You can see a working example here in the project detail view:

<script type="text/x-jquery-tmpl" id="detail.html">
  <form name="myForm" data-bind="inject: kb.formValidator">
    <div class="control-group" data-bind="classes: {error: $myForm.name().invalid}">
      <label>Name</label>
      <input type="text" name="name" data-bind="value: name, valueUpdate: 'keyup'" required>
      <span data-bind="visible: $myForm.name().required" class="help-inline">
          Required</span>
    </div>

    <div class="control-group" data-bind="classes: {error: $myForm.site().invalid}">
      <label>Website</label>
      <input type="url" name="site" data-bind="value: site, valueUpdate: 'keyup'" required>
      <span data-bind="visible: $myForm.site().required" class="help-inline">
          Required</span>
      <span data-bind="visible: $myForm.site().url" class="help-inline">
          Not a URL</span>
    </div>

    <label>Description</label>
    <textarea name="description" data-bind="value: description"></textarea>

    <br>
    <a class="btn" data-bind="click: loadUrlFn('')">Cancel</a>
    <button data-bind="click: save, disable: isClean() || $myForm.invalid()"
            class="btn btn-primary">Save</button>
    <button data-bind="click: onDelete"
            class="btn btn-danger">Delete</button>
  </form>
</script>

They currently only support 'required','url', 'email', and 'number', but I would like them extended for validators with parameters.

Some attributes of an ideal extension:

  1. provides examples for 'length' (minimum length), 'uniqueness' (both client-side and server side), and a custom attribute added by an end user.

  2. the parameters must be observable so if the value changes, the validation gets re-checked.

  3. the implementation must be small (not add must code size) and in CoffeeScript.

You can also propose a naming convention like rails, etc either/both on the HTML or library side to maximize the ease-of-understanding.

If you are interested, please let me know!

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.