├── .gitignore
├── .travis.yml
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── RELEASE_NOTES.md
├── bower.json
├── component.json
├── config
├── amd
│ └── gulp-options.coffee
├── browserify
│ └── tests.coffee
├── builds
│ ├── library
│ │ ├── knockback-core-stack.webpack.config.coffee
│ │ ├── knockback-core.webpack.config.coffee
│ │ ├── knockback-full-stack.webpack.config.coffee
│ │ └── knockback.webpack.config.coffee
│ └── test
│ │ ├── knockback-core-lodash.tests.webpack.config.coffee
│ │ ├── knockback-core.tests.webpack.config.coffee
│ │ ├── knockback-lodash.tests.webpack.config.coffee
│ │ └── knockback.tests.webpack.config.coffee
├── files.coffee
├── karma
│ ├── config-amd.coffee
│ ├── config-base.coffee
│ ├── generate.coffee
│ └── run.coffee
├── test_groups.coffee
└── webpack
│ └── base-config.coffee
├── gulpfile.coffee
├── knockback-core-stack.js
├── knockback-core-stack.min.js
├── knockback-core.js
├── knockback-core.min.js
├── knockback-full-stack.js
├── knockback-full-stack.min.js
├── knockback.js
├── knockback.min.js
├── media
└── logo.png
├── package-lock.json
├── package.json
├── packages
├── npm
│ ├── README.md
│ ├── RELEASE_NOTES.md
│ ├── bower.json
│ ├── component.json
│ ├── knockback-core-stack.js
│ ├── knockback-core-stack.min.js
│ ├── knockback-core.js
│ ├── knockback-core.min.js
│ ├── knockback-full-stack.js
│ ├── knockback-full-stack.min.js
│ ├── knockback.js
│ ├── knockback.min.js
│ └── package.json
└── nuget
│ ├── Content
│ └── Scripts
│ │ ├── README.md
│ │ ├── RELEASE_NOTES.md
│ │ ├── knockback-core-stack.js
│ │ ├── knockback-core-stack.min.js
│ │ ├── knockback-core.js
│ │ ├── knockback-core.min.js
│ │ ├── knockback-full-stack.js
│ │ ├── knockback-full-stack.min.js
│ │ ├── knockback.js
│ │ └── knockback.min.js
│ └── package.nuspec
├── src
├── core
│ ├── collection-observable.coffee
│ ├── configure.coffee
│ ├── event-watcher.coffee
│ ├── factory.coffee
│ ├── functions
│ │ ├── collapse_options.coffee
│ │ ├── extend.coffee
│ │ ├── unwrap_models.coffee
│ │ └── wrapped_destroy.coffee
│ ├── index.coffee
│ ├── inject.coffee
│ ├── kb.coffee
│ ├── monkey-patches.coffee
│ ├── observable.coffee
│ ├── orms
│ │ ├── backbone-associations.coffee
│ │ └── backbone-relational.coffee
│ ├── statistics.coffee
│ ├── store.coffee
│ ├── typed-value.coffee
│ ├── utils.coffee
│ └── view-model.coffee
├── defaults
│ ├── default-observable.coffee
│ └── extensions.coffee
├── formatting
│ └── formatted-observable.coffee
├── localization
│ └── localized-observable.coffee
├── triggering
│ └── triggered-observable.coffee
└── validation
│ ├── validation.coffee
│ └── validators.coffee
├── test
├── issues
│ ├── 2013_04_04
│ │ ├── issue_2013_04_04.coffee
│ │ ├── issue_2013_04_04.html
│ │ └── issue_2013_04_04.js
│ ├── issue141
│ │ ├── issue.html
│ │ └── issue.js
│ ├── issue145
│ │ ├── issue.html
│ │ └── issue.js
│ ├── issue157
│ │ ├── issue.html
│ │ └── issue.js
│ ├── issue161
│ │ ├── issue-0.19.0.html
│ │ ├── issue-0.19.0.js
│ │ ├── issue.html
│ │ └── issue.js
│ ├── issue34
│ │ ├── issue34.html
│ │ └── issue34.js
│ ├── issue35
│ │ ├── issue35.html
│ │ └── issue35.js
│ ├── issue37
│ │ ├── issue37.html
│ │ └── issue37.js
│ ├── issue40
│ │ ├── knockback-amd.js
│ │ ├── require.js
│ │ ├── require_fixed.html
│ │ └── require_problem.html
│ ├── issue44
│ │ ├── issue44.html
│ │ └── issue44.js
│ ├── issue46
│ │ ├── issue46.html
│ │ └── issue46.js
│ ├── issue47
│ │ ├── issue47.html
│ │ └── issue47.js
│ ├── issue89
│ │ ├── issue89.coffee
│ │ ├── issue89.html
│ │ └── issue89.js
│ └── issue98
│ │ ├── issue.coffee
│ │ ├── issue.html
│ │ └── issue.js
├── lib
│ ├── globalize.culture.en-GB.js
│ ├── globalize.culture.fr-FR.js
│ └── globalize.js
├── mocha.opts
└── spec
│ ├── core
│ ├── collection-observable.tests.coffee
│ ├── core.tests.coffee
│ ├── inject.tests.coffee
│ ├── memory-management.tests.coffee
│ ├── monkey-patches.tests.coffee
│ ├── observable.tests.coffee
│ ├── utils.tests.coffee
│ └── view-model.tests.coffee
│ ├── ecosystem
│ ├── backbone-associations.tests.coffee
│ ├── backbone-modelref.tests.coffee
│ ├── backbone-orm.tests.coffee
│ ├── backbone-relational.memory-management.tests.coffee
│ └── backbone-relational.tests.coffee
│ ├── issues
│ └── 159.tests.coffee
│ ├── parameters.coffee
│ └── plugins
│ ├── formatting.tests.coffee
│ ├── localization_and_defaults.tests.coffee
│ ├── triggering.tests.coffee
│ └── validation.tests.coffee
└── vendor
├── backbone-0.5.1.js
├── knockout-2.1.0.js
├── lodash-0.3.2.js
├── parse-1.2.0.js
└── underscore-1.1.7.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | *.map
4 |
5 | node_modules/
6 | doc/
7 | _temp/
8 |
9 | # web site noise
10 | tutorials/*
11 | stylesheets/*
12 | javascripts/*
13 | images/*
14 |
15 | components/*
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "6"
5 | env:
6 | - CXX=g++-4.8
7 | addons:
8 | apt:
9 | sources:
10 | - ubuntu-toolchain-r-test
11 | packages:
12 | - g++-4.8
13 |
14 | notifications:
15 | email:
16 | on_failure: always # [always|never|change] default: always
17 |
18 | before_script:
19 | - "export PATH=node_modules/.bin:node_modules/easy-bake/node_modules/.bin:$PATH"
20 | - "export DISPLAY=:99.0"
21 | - "sh -e /etc/init.d/xvfb start"
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "GulpTester",
6 | "type": "node",
7 | "request": "launch",
8 | "program": "${workspaceRoot}/node_modules/gulp/bin/gulp.js",
9 | "stopOnEntry": false,
10 | "args": [],
11 | "cwd": "${workspaceRoot}",
12 | "runtimeArgs": ["--nolazy"],
13 | "console": "internalConsole"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2016 Kevin Malakoff
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://travis-ci.org/kmalakoff/knockback#master)
2 |
3 | 
4 |
5 | Knockback.js provides Knockout.js magic for Backbone.js Models and Collections.
6 |
7 | # Why Knockback?
8 |
9 | * Make amazingly dynamic applications by applying a small number of simple principles
10 | * Leverage the wonderful work from both the Backbone and Knockout communities
11 | * Easily view and edit relationships between Models using an ORM of your choice:
12 | * [BackboneORM](http://vidigami.github.io/backbone-orm/)
13 | * [Backbone-Relational.js](http://backbonerelational.org/)
14 | * 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](http://kmalakoff.github.com/knockback/tutorial_inject.html) for live examples!
15 |
16 | # Examples
17 |
18 | ### Simple
19 |
20 | ###### The HTML:
21 |
22 | ```html
23 |
24 |
25 | ```
26 |
27 | ###### And...engage:
28 |
29 | ```coffeescript
30 | model = new Backbone.Model({first_name: 'Bob', last_name: 'Smith'})
31 | ko.applyBindings(kb.viewModel(model))
32 | ```
33 |
34 | When you type in the input boxes, the values are properly transferred bi-directionally to the model and all other bound view models!
35 |
36 |
37 | ### Advanced
38 |
39 | ###### The View Model:
40 |
41 | **Javascript**
42 |
43 | ```javascript
44 | var ContactViewModel = kb.ViewModel.extend({
45 | constructor: function(model) {
46 | kb.ViewModel.prototype.constructor.call(this, model);
47 |
48 | this.full_name = ko.computed(function() {
49 | return this.first_name() + " " + this.last_name();
50 | }, this);
51 | });
52 |
53 | ```
54 |
55 | **or Coffeescript**
56 |
57 | ```coffeescript
58 | class ContactViewModel extends kb.ViewModel
59 | constructor: (model) ->
60 | super model
61 |
62 | @full_name = ko.computed => "#{@first_name()} #{@last_name()}"
63 | ```
64 |
65 | ###### The HTML:
66 |
67 | ```html
68 |
69 |
70 |
71 | ```
72 |
73 | ###### And...engage:
74 |
75 | ```coffeescript
76 | model = new Backbone.Model({first_name: 'Bob', last_name: 'Smith'})
77 | view_model = new ContactViewModel(model)
78 | ko.applyBindings(view_model)
79 |
80 | # ... do stuff then clean up
81 | kb.release(view_model)
82 | ```
83 |
84 | Now, the greeting updates as you type!
85 |
86 |
87 | # Getting Started
88 |
89 | * [Website](http://kmalakoff.github.com/knockback/) - explore everything Knockback and connect to the community
90 | * [Tutorials](http://kmalakoff.github.io/knockback/tutorials_introduction.html) - try some live examples
91 | * [API Docs](http://kmalakoff.github.com/knockback/doc/index.html) - dig into the API
92 | * [TodoMVC App (Live!)](http://kmalakoff.github.com/knockback-todos-app/) - compare client-side framworks
93 |
94 | # Download Latest (1.2.3):
95 |
96 | Please see the [release notes](https://github.com/kmalakoff/knockback/blob/master/RELEASE_NOTES.md) for upgrade pointers.
97 |
98 | * Full Library [(dev, 64k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback.js) or [(min+gzip, 8k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback.min.js)
99 | * Full Stack [(dev, 330k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-full-stack.js) or [(min+gzip, 32k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-full-stack.min.js)
100 |
101 | * Core Library [(dev, 54k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core.js) or [(min+gzip, 7k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core.min.js)
102 | * Core Stack [(dev, 315k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core-stack.js) or [(min+gzip, 31k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core-stack.min.js)
103 |
104 | The **full versions** bundle advanced features.
105 |
106 | The **core versions** remove advanced features that can be included separately: localization, formatting, triggering, defaults, and validation.
107 |
108 | The **stack versions** provide Underscore.js + Backbone.js + Knockout.js + Knockback.js in a single file.
109 |
110 | ###Distributions
111 |
112 | You can also find Knockback on your favorite distributions:
113 |
114 | * **npm**: npm install knockback
115 | * **Bower**: bower install knockback
116 | * [NuGet](http://nuget.org/packages/Knockback.js) - install right in Visual Studio
117 |
118 | ###Dependencies
119 |
120 | * [Backbone.js](http://backbonejs.org/) - provides the Model layer
121 | * [Knockout.js](http://knockoutjs.com/) - provides the ViewModel layer foundations for Knockback
122 | * [Underscore.js](http://underscorejs.org/) - provides an awesome JavaScript utility belt
123 | * [LoDash](http://lodash.com/) - optionally replaces Underscore.js with a library optimized for consistent performance
124 | * [Parse](https://www.parse.com/) - optionally replaces Backbone.js and Underscore.js
125 |
126 | ###Compatible Components
127 |
128 | * [BackboneORM](http://vidigami.github.io/backbone-orm/) - A polystore ORM for Node.js and the browser
129 | * [Backbone-Relational.js](http://backbonerelational.org/) - Get and set relations (one-to-one, one-to-many, many-to-one) for Backbone models
130 | * [Backbone Associations](http://dhruvaray.github.io/backbone-associations/) - Create object hierarchies with Backbone models. Respond to hierarchy changes using regular Backbone events
131 | * [BackboneModelRef.js](https://github.com/kmalakoff/backbone-modelref/) - 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).
132 |
133 |
134 | # Contributing
135 |
136 | To build the library for Node.js and browsers:
137 |
138 | ```
139 | $ gulp build
140 | ```
141 |
142 | Please run tests before submitting a pull request:
143 |
144 | ```
145 | $ gulp test --quick
146 | ```
147 |
148 | and eventually all tests:
149 |
150 | ```
151 | $ npm test
152 | ```
153 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "knockback",
3 | "version": "1.2.3",
4 | "main": "knockback.js",
5 | "ignore": [
6 | "**/.*",
7 | "config",
8 | "components",
9 | "doc",
10 | "media",
11 | "node_modules",
12 | "packages",
13 | "src",
14 | "test",
15 | "vendor",
16 | "bower_components"
17 | ],
18 | "homepage": "https://github.com/kmalakoff/knockback",
19 | "authors": [
20 | "Kevin Malakoff "
21 | ],
22 | "description": "Knockback.js provides Knockout.js magic for Backbone.js Models and Collections",
23 | "keywords": [
24 | "knockback",
25 | "knockbackjs",
26 | "backbone",
27 | "backbonejs",
28 | "knockout",
29 | "knockoutjs"
30 | ],
31 | "moduleType": [
32 | "amd",
33 | "globals",
34 | "node"
35 | ],
36 | "dependencies": {
37 | "underscore": ">=1.1.7",
38 | "backbone": ">=0.5.1",
39 | "knockout": ">=2.2.1"
40 | },
41 |
42 | "license": "MIT"
43 | }
44 |
--------------------------------------------------------------------------------
/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "knockback",
3 | "author": "Kevin Malakoff (https://github.com/kmalakoff)",
4 | "version": "1.2.3",
5 | "description": "Knockback.js provides Knockout.js magic for Backbone.js Models and Collections",
6 | "keywords" : ["knockback", "knockbackjs", "backbone", "backbonejs", "knockout", "knockoutjs"],
7 | "repo": "kmalakoff/knockback",
8 | "dependencies": {
9 | "jashkenas/underscore": "*",
10 | "jashkenas/backbone": "*",
11 | "kmalakoff/knockout": "*"
12 | },
13 | "main": "knockback.js",
14 | "scripts": [
15 | "knockback.js"
16 | ],
17 | "license": "MIT"
18 | }
--------------------------------------------------------------------------------
/config/amd/gulp-options.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 | karma: true,
3 | shims:
4 | underscore: {exports: '_'}
5 | backbone: {exports: 'Backbone', deps: ['underscore']}
6 | knockback: {deps: ['backbone', 'knockout']}
7 | 'globalize.culture.en-GB': {deps: ['globalize']}
8 | 'globalize.culture.fr-FR': {deps: ['globalize']}
9 | post_load: 'this._ = this.Backbone = this.ko = this.kb = null;' # clear window references so amd version is used
10 | aliases: {'knockback-core': 'knockback', 'lodash': 'underscore', 'knockout-latest.debug': 'knockout'}
11 |
--------------------------------------------------------------------------------
/config/browserify/tests.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 |
3 | module.exports =
4 | full:
5 | output: './_temp/browserify/knockback.tests.js'
6 | files: ['./test/spec/core/**/*.tests.coffee', './test/spec/plugins/**/*.tests.coffee']
7 | options:
8 | # basedir: path.resolve(__dirname, '../..')
9 | shim:
10 | knockback: {path: './knockback.js', exports: 'kb', depends: {underscore: '_', backbone: 'Backbone', knockout: 'ko'}}
11 |
12 | core:
13 | output: './_temp/browserify/knockback-core.tests.js'
14 | files: ['./test/spec/core/**/*.tests.coffee']
15 | options:
16 | # basedir: path.resolve(__dirname, '../..')
17 | shim:
18 | knockback: {path: './knockback.js', exports: 'kb', depends: {underscore: '_', backbone: 'Backbone', knockout: 'ko'}}
19 |
--------------------------------------------------------------------------------
/config/builds/library/knockback-core-stack.webpack.config.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | path = require 'path'
3 | _ = require 'underscore'
4 |
5 | module.exports = _.extend _.clone(require '../../webpack/base-config.coffee'), {
6 | entry: _.flatten([require('../../files').src_core, './src/core/index.coffee'])
7 | output:
8 | library: 'kb'
9 | libraryTarget: 'umd2'
10 | filename: 'knockback-core-stack.js'
11 |
12 | externals: [
13 | {'backbone-relational': {optional: true, root: 'Backbone', amd: 'backbone-relational', commonjs: 'backbone-relational', commonjs2: 'backbone-relational'}}
14 | {'backbone-associations': {optional: true, root: 'Backbone', amd: 'backbone-associations', commonjs: 'backbone-associations', commonjs2: 'backbone-associations'}}
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/config/builds/library/knockback-core.webpack.config.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | path = require 'path'
3 | _ = require 'underscore'
4 |
5 | module.exports = _.extend _.clone(require '../../webpack/base-config.coffee'), {
6 | entry: _.flatten([require('../../files').src_core, './src/core/index.coffee'])
7 | output:
8 | library: 'kb'
9 | libraryTarget: 'umd2'
10 | filename: 'knockback-core.js'
11 |
12 | externals: [
13 | {underscore: {root: '_', amd: 'underscore', commonjs: 'underscore', commonjs2: 'underscore'}}
14 | {backbone: {root: 'Backbone', amd: 'backbone', commonjs: 'backbone', commonjs2: 'backbone'}}
15 | {knockout: {root: 'ko', amd: 'knockout', commonjs: 'knockout', commonjs2: 'knockout'}}
16 |
17 | {'backbone-relational': {optional: true, root: 'Backbone', amd: 'backbone-relational', commonjs: 'backbone-relational', commonjs2: 'backbone-relational'}}
18 | {'backbone-associations': {optional: true, root: 'Backbone', amd: 'backbone-associations', commonjs: 'backbone-associations', commonjs2: 'backbone-associations'}}
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/config/builds/library/knockback-full-stack.webpack.config.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | path = require 'path'
3 | _ = require 'underscore'
4 |
5 | module.exports = _.extend _.clone(require '../../webpack/base-config.coffee'), {
6 | entry: _.flatten([require('../../files').src_core, require('../../files').src_plugin, './src/core/index.coffee'])
7 | output:
8 | library: 'kb'
9 | libraryTarget: 'umd2'
10 | filename: 'knockback-full-stack.js'
11 |
12 | externals: [
13 | {'backbone-relational': {optional: true, root: 'Backbone', amd: 'backbone-relational', commonjs: 'backbone-relational', commonjs2: 'backbone-relational'}}
14 | {'backbone-associations': {optional: true, root: 'Backbone', amd: 'backbone-associations', commonjs: 'backbone-associations', commonjs2: 'backbone-associations'}}
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/config/builds/library/knockback.webpack.config.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | path = require 'path'
3 | _ = require 'underscore'
4 |
5 | module.exports = _.extend _.clone(require '../../webpack/base-config.coffee'), {
6 | entry: _.flatten([require('../../files').src_core, require('../../files').src_plugin, './src/core/index.coffee'])
7 | output:
8 | library: 'kb'
9 | libraryTarget: 'umd2'
10 | filename: 'knockback.js'
11 |
12 | externals: [
13 | {underscore: {root: '_', amd: 'underscore', commonjs: 'underscore', commonjs2: 'underscore'}}
14 | {backbone: {root: 'Backbone', amd: 'backbone', commonjs: 'backbone', commonjs2: 'backbone'}}
15 | {knockout: {root: 'ko', amd: 'knockout', commonjs: 'knockout', commonjs2: 'knockout'}}
16 |
17 | {'backbone-relational': {optional: true, root: 'Backbone', amd: 'backbone-relational', commonjs: 'backbone-relational', commonjs2: 'backbone-relational'}}
18 | {'backbone-associations': {optional: true, root: 'Backbone', amd: 'backbone-associations', commonjs: 'backbone-associations', commonjs2: 'backbone-associations'}}
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/config/builds/test/knockback-core-lodash.tests.webpack.config.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | _ = require 'underscore'
3 |
4 | module.exports = _.extend _.clone(require '../../webpack/base-config.coffee'), {
5 | entry:
6 | 'knockback-core-lodash.tests': require('../../files').tests_core
7 | }
8 |
9 | module.exports.resolve.alias =
10 | underscore: require.resolve('lodash')
11 | knockback: path.resolve('./knockback-core.js')
12 |
--------------------------------------------------------------------------------
/config/builds/test/knockback-core.tests.webpack.config.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | _ = require 'underscore'
3 |
4 | module.exports = _.extend _.clone(require '../../webpack/base-config.coffee'), {
5 | entry:
6 | 'knockback-core.tests': require('../../files').tests_core
7 | }
8 |
9 | module.exports.resolve.alias =
10 | knockback: path.resolve('./knockback-core.js')
11 |
--------------------------------------------------------------------------------
/config/builds/test/knockback-lodash.tests.webpack.config.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | _ = require 'underscore'
3 |
4 | module.exports = _.extend _.clone(require '../../webpack/base-config.coffee'), {
5 | entry:
6 | 'knockback-lodash.tests': _.flatten([require('../../files').tests_core, require('../../files').tests_plugin])
7 | }
8 |
9 | module.exports.resolve.alias =
10 | underscore: require.resolve('lodash')
11 | knockback: path.resolve('./knockback.js')
12 |
--------------------------------------------------------------------------------
/config/builds/test/knockback.tests.webpack.config.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | _ = require 'underscore'
3 |
4 | module.exports = _.extend _.clone(require '../../webpack/base-config.coffee'), {
5 | entry:
6 | 'knockback.tests': _.flatten([require('../../files').tests_core, require('../../files').tests_plugin])
7 | }
8 |
9 | module.exports.resolve.alias =
10 | knockback: path.resolve('./knockback.js')
11 |
--------------------------------------------------------------------------------
/config/files.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | path = require 'path'
3 | _ = require 'underscore'
4 | readdirSyncRecursive = require 'fs-readdir-recursive'
5 |
6 | PLUGIN_ENTRIES =
7 | defaults: './src/defaults/default-observable.coffee'
8 | formatting: './src/formatting/formatted-observable.coffee'
9 | localization: './src/localization/localized-observable.coffee'
10 | triggering: './src/triggering/triggered-observable.coffee'
11 | validation: './src/validation/validation.coffee'
12 |
13 | module.exports =
14 | libraries: _.flatten(_.map(_.filter(fs.readdirSync('./config/builds/library'), (file) -> path.extname(file) is '.coffee' and file.indexOf('webpack.config.coffee') >= 0), (file) -> [file.replace('webpack.config.coffee', 'js'), file.replace('webpack.config.coffee', 'min.js')]))
15 |
16 | src_core: _.map(_.filter(fs.readdirSync('./src/core'), (file) -> path.extname(file) is '.coffee' and file isnt 'index.coffee'), (file) -> "./src/core/#{file}")
17 | src_plugin: _.values(PLUGIN_ENTRIES)
18 |
19 | tests_core: ("./test/spec/core/#{filename}" for filename in readdirSyncRecursive(__dirname + '/../test/spec/core') when /\.tests.coffee$/.test(filename))
20 | tests_plugin: ("./test/spec/plugins/#{filename}" for filename in readdirSyncRecursive(__dirname + '/../test/spec/plugins') when /\.tests.coffee$/.test(filename))
21 |
22 | tests_webpack: _.map(_.filter(fs.readdirSync('./config/builds/test'), (file) -> path.extname(file) is '.coffee' and file.indexOf('.tests.webpack.config.coffee') >= 0), (file) -> "_temp/webpack/#{file.replace('.tests.webpack.config.coffee', '.tests.js')}")
23 |
--------------------------------------------------------------------------------
/config/karma/config-amd.coffee:
--------------------------------------------------------------------------------
1 | _ = require 'lodash'
2 |
3 | module.exports = _.clone(require './config-base')
4 | module.exports.frameworks = ['mocha']
5 |
--------------------------------------------------------------------------------
/config/karma/config-base.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 | basePath: '.'
3 | frameworks: ['mocha', 'chai']
4 | preprocessors: {'**/*.coffee': ['coffee']}
5 |
6 | coffeePreprocessor:
7 | options: {bare: true, sourceMap: false}
8 | transformPath: (path) -> path.replace(/\.coffee$/, '.js')
9 |
10 | reporters: ['dots']
11 | port: 9876
12 | colors: true
13 | logLevel: 'INFO'
14 |
15 | browsers: ['ChromeHeadless']
16 | singleRun: true
17 |
18 | # browsers: ['ChromeHeadless', 'Chrome, 'Firefox', 'Chrome', 'Safari']
19 | # singleRun: true
20 |
21 | # browsers: ['Chrome']
22 | # singleRun: false
23 |
--------------------------------------------------------------------------------
/config/karma/generate.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs-extra'
2 | path = require 'path'
3 | _ = require 'underscore'
4 | Queue = require 'queue-async'
5 | es = require 'event-stream'
6 |
7 | gulp = require 'gulp'
8 | gutil = require 'gulp-util'
9 | shell = require 'gulp-shell'
10 | coffee = require 'gulp-coffee'
11 | concat = require 'gulp-concat'
12 | wrapAMD = require 'gulp-wrap-amd-infer'
13 | webpack = require 'gulp-webpack-config'
14 | browserify = require 'gulp-browserify'
15 |
16 | TEST_GROUPS = require('../test_groups')
17 |
18 | module.exports = (options={}, callback) ->
19 | return callback() if options.tags.indexOf('@quick') >= 0
20 |
21 | queue = new Queue(1)
22 |
23 | # install knockback
24 | queue.defer (callback) ->
25 | gulp.src(['./knockback.js', './package.json'])
26 | .pipe(gulp.dest('node_modules/knockback'))
27 | .on('end', callback)
28 |
29 | # build webpack
30 | queue.defer (callback) ->
31 | gulp.src(['config/builds/test/**/*.webpack.config.coffee'], {read: false, buffer: false})
32 | .pipe(webpack())
33 | .pipe(gulp.dest('_temp/webpack'))
34 | .on('end', callback)
35 |
36 | # build test browserify
37 | for test in TEST_GROUPS.browserify or []
38 | do (test) -> queue.defer (callback) ->
39 | gulp.src(test.build.files)
40 | .pipe(coffee({bare: true}))
41 | .pipe(concat(path.basename(test.build.destination)))
42 | .pipe(browserify(test.build.options))
43 | .pipe(gulp.dest(path.dirname(test.build.destination)))
44 | .on('end', callback)
45 |
46 | # wrap AMD tests
47 | for test in TEST_GROUPS.amd or []
48 | do (test) -> queue.defer (callback) ->
49 | gulp.src(test.build.files)
50 | .pipe(coffee({bare: true, header: false}))
51 | .pipe(wrapAMD(test.build.options))
52 | .pipe(gulp.dest(test.build.destination))
53 | .on('end', callback)
54 |
55 | # uninstall knockback
56 | queue.await (err) ->
57 | fs.removeSync('node_modules/knockback')
58 | callback(err)
59 |
--------------------------------------------------------------------------------
/config/karma/run.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs-extra'
2 | path = require 'path'
3 | _ = require 'underscore'
4 | Queue = require 'queue-async'
5 | {Server} = require 'karma'
6 | gutil = require 'gulp-util'
7 | generate = require './generate'
8 |
9 | KARMA_CONFIG_BASE = require './config-base'
10 | KARMA_CONFIG_AMD = require './config-amd'
11 |
12 | module.exports = (options={}, callback) ->
13 | fs.removeSync('./_temp')
14 | fs.removeSync('node_modules/knockback')
15 | queue = new Queue(1)
16 | queue.defer (callback) -> generate(options, callback)
17 |
18 | TEST_GROUPS = require '../test_groups'
19 | TEST_GROUPS = {browser_globals: TEST_GROUPS.browser_globals.slice(0, 1)} if options.tags.indexOf('@quick') >= 0
20 |
21 | # TODO: disabled AMD tests until can troubleshoot knockout not loading in AMD (it stoped loading at 3.5.0)
22 | # TEST_GROUPS = {amd: TEST_GROUPS.amd}
23 | delete TEST_GROUPS.amd
24 |
25 | for name, tests of TEST_GROUPS then do (name, tests) ->
26 | for test in tests then do (test) ->
27 | # return unless test.name is 'amd_backbone_lodash_latest_browser_globals'
28 |
29 | queue.defer (callback) ->
30 | gutil.log "RUNNING TESTS: #{name} #{test.name}"
31 | gutil.log "#{JSON.stringify test.files}"
32 | karma_config = if (name == 'amd') then KARMA_CONFIG_AMD else KARMA_CONFIG_BASE
33 | args = if options.tags then ['--grep', options.tags] else []
34 | new Server(
35 | _.defaults({files: test.files, client: {args}}, karma_config),
36 | (return_value) ->
37 | console.log("DONE TESTS: #{name} #{test.name}. Return value: #{return_value}")
38 | callback(new Error "Tests failed: #{return_value}" if return_value)
39 | ).start()
40 |
41 | queue.await (err) ->
42 | fs.removeSync('./_temp') unless err
43 | callback(err)
44 |
--------------------------------------------------------------------------------
/config/test_groups.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | path = require 'path'
3 | _ = require 'underscore'
4 | gutil = require 'gulp-util'
5 |
6 | resolveModule = (module_name) -> path.relative('.', require.resolve(module_name))
7 |
8 | KNOCKBACK =
9 | browser_globals: ['./knockback.js']
10 | browser_globals_min: ['./knockback.min.js']
11 | browser_globals_stack: ['./knockback-browser_globals-stack.js']
12 | core: ['./knockback-core.js']
13 | core_min: ['./knockback-core.min.js']
14 | core_stack: ['./knockback-core-stack.js']
15 |
16 | REQUIRED_DEPENDENCIES =
17 | backbone_underscore_latest: (resolveModule(module_name) for module_name in ['jquery', 'underscore', 'backbone', 'knockout'])
18 | backbone_underscore_legacy: ['./vendor/underscore-1.1.7.js', './vendor/backbone-0.5.1.js', './vendor/knockout-2.1.0.js']
19 | backbone_lodash_latest: (resolveModule(module_name) for module_name in ['lodash', 'backbone', 'knockout'])
20 | backbone_lodash_legacy: ['./vendor/lodash-0.3.2.js', './vendor/backbone-0.5.1.js', './vendor/knockout-2.1.0.js']
21 | parse_latest_compatibile: (resolveModule(module_name) for module_name in ['knockout', 'parse'])
22 | parse_legacy: (resolveModule(module_name) for module_name in ['knockout']).concat(['./vendor/parse-1.2.0.js'])
23 |
24 | LOCALIZATION_DEPENCIES = ['./test/lib/globalize.js', './test/lib/globalize.culture.en-GB.js', './test/lib/globalize.culture.fr-FR.js']
25 |
26 | FILES = require './files'
27 |
28 | module.exports = TEST_GROUPS = {}
29 |
30 | ###############################
31 | # Full Library
32 | ###############################
33 | TEST_GROUPS.browser_globals = []
34 | for library_name, library_files of KNOCKBACK when (library_name.indexOf('browser_globals') >= 0 and library_name.indexOf('stack') < 0)
35 | for dep_name, dep_files of REQUIRED_DEPENDENCIES
36 | if dep_name.indexOf('backbone') >= 0 # Backbone
37 | TEST_GROUPS.browser_globals.push({name: "#{dep_name}_#{library_name}", files: _.flatten([dep_files, library_files, LOCALIZATION_DEPENCIES, resolveModule('backbone-modelref'), './test/spec/core/**/*.tests.coffee', './test/spec/plugins/**/*.tests.coffee', './test/spec/issues/**/*.tests.coffee'])})
38 | else # Parse
39 | TEST_GROUPS.browser_globals.push({name: "#{dep_name}_#{library_name}", files: _.flatten([dep_files, library_files, LOCALIZATION_DEPENCIES, './test/spec/core/**/*.tests.coffee', './test/spec/plugins/**/*.tests.coffee'])})
40 |
41 | ###############################
42 | # Core Library
43 | ###############################
44 | TEST_GROUPS.core = []
45 | for test_name, library_files of KNOCKBACK when (test_name.indexOf('core') >= 0 and test_name.indexOf('stack') < 0)
46 | TEST_GROUPS.core.push({name: "core_#{test_name}", files: _.flatten([REQUIRED_DEPENDENCIES.backbone_underscore_latest, library_files, './test/spec/core/**/*.tests.coffee'])})
47 |
48 | ###############################
49 | # ORM
50 | ###############################
51 | ORM_TESTS =
52 | backbone_orm: [KNOCKBACK.browser_globals, resolveModule('backbone-orm'), './test/spec/ecosystem/**/backbone-orm*.tests.coffee']
53 | backbone_relational: [KNOCKBACK.browser_globals, resolveModule('backbone-relational'), './test/spec/ecosystem/**/backbone-relational*.tests.coffee']
54 | backbone_associations: [KNOCKBACK.browser_globals, resolveModule('backbone-associations'), './test/spec/ecosystem/**/backbone-associations*.tests.coffee']
55 |
56 | TEST_GROUPS.orm = []
57 | for dep_name, dep_files of _.pick(REQUIRED_DEPENDENCIES, 'backbone_underscore_latest')
58 | TEST_GROUPS.orm.push({name: "#{dep_name}_#{test_name}", files: _.flatten([dep_files, test_files])}) for test_name, test_files of ORM_TESTS
59 |
60 | ###############################
61 | # AMD
62 | ###############################
63 | AMD_OPTIONS = require './amd/gulp-options'
64 | TEST_GROUPS.amd = []
65 | for test in TEST_GROUPS.browser_globals.concat(TEST_GROUPS.core) when (test.name.indexOf('_min') < 0 and test.name.indexOf('legacy_') < 0 and test.name.indexOf('parse_') < 0)
66 | test_files = test.files.concat(['./node_modules/chai/chai.js', './node_modules/jquery/dist/jquery.js']); files = []; test_patterns = []; path_files = []
67 | files.push({pattern: './node_modules/requirejs/require.js'})
68 | for file in test_files
69 | (test_patterns.push(file); continue) if file.indexOf('.tests.') >= 0
70 | files.push({pattern: file, included: false})
71 | path_files.push(file)
72 | files.push("_temp/amd/#{test.name}/**/*.js")
73 | TEST_GROUPS.amd.push({name: "amd_#{test.name}", files: files, build: {files: test_patterns, destination: "_temp/amd/#{test.name}", options: _.extend({path_files: path_files}, AMD_OPTIONS)}})
74 |
75 | ###############################
76 | # Webpack
77 | ###############################
78 | TEST_GROUPS.webpack = []
79 | for file in FILES.tests_webpack
80 | TEST_GROUPS.webpack.push({name: "webpack_#{file.replace('.js', '')}", files: _.flatten([(if file.indexOf('core') >= 0 then [] else LOCALIZATION_DEPENCIES), file])})
81 |
82 | ###############################
83 | # Browserify
84 | ###############################
85 | TEST_GROUPS.browserify = []
86 | for test_name, test_info of require('./browserify/tests')
87 | TEST_GROUPS.browserify.push({name: "browserify_#{test_name}", files: _.flatten([(if file.indexOf('core') >= 0 then [] else LOCALIZATION_DEPENCIES), test_info.output]), build: {destination: test_info.output, options: test_info.options, files: test_info.files}})
88 |
--------------------------------------------------------------------------------
/config/webpack/base-config.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 | module:
3 | rules: [{test: /\.coffee$/, loader: 'coffee-loader'}]
4 |
5 | resolve:
6 | extensions: ['.coffee', '.js']
7 | modules: ['node_modules']
8 |
--------------------------------------------------------------------------------
/gulpfile.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | _ = require 'underscore'
3 | Queue = require 'queue-async'
4 | Async = require 'async'
5 | es = require 'event-stream'
6 |
7 | gulp = require 'gulp'
8 | gutil = require 'gulp-util'
9 | webpack = require 'gulp-webpack-config'
10 | rename = require 'gulp-rename'
11 | uglify = require 'gulp-uglify'
12 | header = require 'gulp-header'
13 | mocha = require 'gulp-mocha'
14 | nuget = require 'nuget'
15 | nugetGulp = -> es.map (file, callback) ->
16 | nuget.pack file, (err, nupkg_file) ->
17 | return callback(err) if err
18 | nuget.push nupkg_file, (err) -> if err then gutil.log(err) else callback()
19 |
20 | HEADER = module.exports = """
21 | /*
22 | <%= file.path.split('/').splice(-1)[0].replace('.min', '') %> <%= pkg.version %>
23 | Copyright (c) 2011-#{(new Date()).getFullYear()} Kevin Malakoff.
24 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
25 | Source: https://github.com/kmalakoff/knockback
26 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
27 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
28 | */\n
29 | """
30 | LIBRARY_FILES = require('./config/files').libraries
31 |
32 | gulp.task 'build', buildLibraries = (callback) ->
33 | errors = []
34 | gulp.src('config/builds/library/**/*.webpack.config.coffee')
35 | .pipe(webpack())
36 | .pipe(header(HEADER, {pkg: require('./package.json')}))
37 | .pipe(gulp.dest('.'))
38 | .on('end', callback)
39 | return # promises workaround: https://github.com/gulpjs/gulp/issues/455
40 |
41 | gulp.task 'watch', ['build'], ->
42 | gulp.watch './src/**/*.coffee', -> buildLibraries(->)
43 | return # promises workaround: https://github.com/gulpjs/gulp/issues/455
44 |
45 | gulp.task 'minify', ['build'], (callback) ->
46 | gulp.src(['*.js', '!*.min.js', '!_temp/**/*.js', '!node_modules/'])
47 | .pipe(uglify())
48 | .pipe(rename({suffix: '.min'}))
49 | .pipe(header(HEADER, {pkg: require('./package.json')}))
50 | .pipe(gulp.dest((file) -> file.base))
51 | .on('end', callback)
52 | return # promises workaround: https://github.com/gulpjs/gulp/issues/455
53 |
54 | testNode = (callback) ->
55 | tags = ("@#{tag.replace(/^[-]+/, '')}" for tag in process.argv.slice(3)).join(' ')
56 |
57 | mochaOptions = {reporter: 'dot', compilers: 'coffee:coffee-script/register'}
58 | mochaOptions.grep = tags if tags
59 | gutil.log "Running Node.js tests #{tags}"
60 | gulp.src('test/spec/**/*.tests.coffee')
61 | .pipe(mocha(mochaOptions))
62 | .pipe es.writeArray callback
63 | return # promises workaround: https://github.com/gulpjs/gulp/issues/455
64 |
65 | testBrowsers = (callback) ->
66 | tags = ("@#{tag.replace(/^[-]+/, '')}" for tag in process.argv.slice(3)).join(' ')
67 |
68 | gutil.log "Running Browser tests #{tags}"
69 | (require './config/karma/run')({tags}, callback)
70 | return # promises workaround: https://github.com/gulpjs/gulp/issues/455
71 |
72 | gulp.task 'test-node', ['build'], testNode
73 |
74 | # gulp.task 'test-browsers', testBrowsers
75 | gulp.task 'test-browsers', ['minify'], testBrowsers
76 |
77 | gulp.task 'test', ['minify'], (callback) ->
78 | Async.series [testNode, testBrowsers], (err) -> not err || console.log(err); process.exit(if err then 1 else 0)
79 | return # promises workaround: https://github.com/gulpjs/gulp/issues/455
80 |
81 | gulp.task 'publish', ['minify'], (callback) ->
82 | copyLibraryFiles = (destination, others, callback) ->
83 | gulp.src(LIBRARY_FILES.concat(['README.md', 'RELEASE_NOTES.md'].concat(others)))
84 | .pipe(gulp.dest((file) -> path.join(destination, path.dirname(file.path).replace(__dirname, ''))))
85 | .on('end', callback)
86 |
87 | queue = new Queue(1)
88 | queue.defer (callback) -> Async.series [testNode, testBrowsers], callback
89 | queue.defer (callback) -> copyLibraryFiles('packages/npm', ['component.json', 'bower.json'], callback)
90 | queue.defer (callback) -> copyLibraryFiles('packages/nuget/Content/Scripts', [], callback)
91 | queue.defer (callback) ->
92 | gulp.src('packages/nuget/*.nuspec')
93 | .pipe(nugetGulp())
94 | .on('end', callback)
95 | queue.await (err) -> not err || console.log(err); process.exit(if err then 1 else 0)
96 | return # promises workaround: https://github.com/gulpjs/gulp/issues/455
97 |
98 | gulp.task 'default', ['test-node']
99 |
--------------------------------------------------------------------------------
/media/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmalakoff/knockback/8ac1f5f8d013f1eede74f1abc38ecffc8e4bd9f8/media/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "knockback",
3 | "version": "1.2.3",
4 | "description": "Knockback.js provides Knockout.js magic for Backbone.js Models and Collections",
5 | "main": "knockback.js",
6 | "author": {
7 | "name": "Kevin Malakoff",
8 | "url": "https://github.com/kmalakoff"
9 | },
10 | "license": "MIT",
11 | "keywords": [
12 | "knockback",
13 | "knockbackjs",
14 | "backbone",
15 | "backbonejs",
16 | "knockout",
17 | "knockoutjs"
18 | ],
19 | "private": true,
20 | "url": "http://kmalakoff.github.com/knockback/",
21 | "homepage": "http://kmalakoff.github.com/knockback/",
22 | "repository": {
23 | "type": "git",
24 | "url": "http://github.com/kmalakoff/knockback.git"
25 | },
26 | "scripts": {
27 | "test": "gulp test",
28 | "docs": "./node_modules/.bin/codo -n Knockback.js -o doc src/."
29 | },
30 | "dependencies": {
31 | "backbone": ">=0.5.1",
32 | "knockout": ">=2.1.0",
33 | "underscore": ">=1.1.7"
34 | },
35 | "devDependencies": {
36 | "async": "*",
37 | "chai": "*",
38 | "codo": "2.x",
39 | "coffee-loader": "0.6.x",
40 | "coffee-script": "^1.6.3",
41 | "event-stream": "*",
42 | "fs-extra": "*",
43 | "fs-readdir-recursive": "*",
44 | "gulp": "^3.9.1",
45 | "gulp-browserify": "*",
46 | "gulp-coffee": "*",
47 | "gulp-concat": "*",
48 | "gulp-header": "*",
49 | "gulp-mocha": "3.x",
50 | "gulp-rename": "*",
51 | "gulp-shell": "*",
52 | "gulp-uglify": "*",
53 | "gulp-util": "*",
54 | "gulp-webpack-config": "*",
55 | "gulp-wrap-amd-infer": "*",
56 | "gulp-zip": "*",
57 | "imports-loader": "*",
58 | "jsdom": "*",
59 | "karma": "*",
60 | "karma-chai": "*",
61 | "karma-chrome-launcher": "^2.2.0",
62 | "karma-cli": "*",
63 | "karma-coffee-preprocessor": "*",
64 | "karma-firefox-launcher": "*",
65 | "karma-mocha": "*",
66 | "karma-safari-launcher": "*",
67 | "karma-spec-reporter": "*",
68 | "minimatch": "^3.0.4",
69 | "mocha": "*",
70 | "nuget": "*",
71 | "puppeteer": "^1.15.0",
72 | "queue-async": "*",
73 | "requirejs": "*",
74 | "webpack": "*"
75 | },
76 | "optionalDependencies": {
77 | "backbone-relational": "*",
78 | "backbone-modelref": "*",
79 | "backbone.localstorage": "*",
80 | "lodash": "*",
81 | "backbone-orm": "*",
82 | "jquery": "*",
83 | "moment": "*",
84 | "backbone-associations": "*",
85 | "parse": "1.5.x"
86 | },
87 | "jam": {
88 | "dependencies": {
89 | "underscore": ">=1.1.7",
90 | "backbone": ">=0.5.1",
91 | "knockout": ">=2.2.1"
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/packages/npm/README.md:
--------------------------------------------------------------------------------
1 | [](http://travis-ci.org/kmalakoff/knockback#master)
2 |
3 | 
4 |
5 | Knockback.js provides Knockout.js magic for Backbone.js Models and Collections.
6 |
7 | # Why Knockback?
8 |
9 | * Make amazingly dynamic applications by applying a small number of simple principles
10 | * Leverage the wonderful work from both the Backbone and Knockout communities
11 | * Easily view and edit relationships between Models using an ORM of your choice:
12 | * [BackboneORM](http://vidigami.github.io/backbone-orm/)
13 | * [Backbone-Relational.js](http://backbonerelational.org/)
14 | * 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](http://kmalakoff.github.com/knockback/tutorial_inject.html) for live examples!
15 |
16 | # Examples
17 |
18 | ### Simple
19 |
20 | ###### The HTML:
21 |
22 | ```html
23 |
24 |
25 | ```
26 |
27 | ###### And...engage:
28 |
29 | ```coffeescript
30 | model = new Backbone.Model({first_name: 'Bob', last_name: 'Smith'})
31 | ko.applyBindings(kb.viewModel(model))
32 | ```
33 |
34 | When you type in the input boxes, the values are properly transferred bi-directionally to the model and all other bound view models!
35 |
36 |
37 | ### Advanced
38 |
39 | ###### The View Model:
40 |
41 | **Javascript**
42 |
43 | ```javascript
44 | var ContactViewModel = kb.ViewModel.extend({
45 | constructor: function(model) {
46 | kb.ViewModel.prototype.constructor.call(this, model);
47 |
48 | this.full_name = ko.computed(function() {
49 | return this.first_name() + " " + this.last_name();
50 | }, this);
51 | });
52 |
53 | ```
54 |
55 | **or Coffeescript**
56 |
57 | ```coffeescript
58 | class ContactViewModel extends kb.ViewModel
59 | constructor: (model) ->
60 | super model
61 |
62 | @full_name = ko.computed => "#{@first_name()} #{@last_name()}"
63 | ```
64 |
65 | ###### The HTML:
66 |
67 | ```html
68 |
69 |
70 |
71 | ```
72 |
73 | ###### And...engage:
74 |
75 | ```coffeescript
76 | model = new Backbone.Model({first_name: 'Bob', last_name: 'Smith'})
77 | view_model = new ContactViewModel(model)
78 | ko.applyBindings(view_model)
79 |
80 | # ... do stuff then clean up
81 | kb.release(view_model)
82 | ```
83 |
84 | Now, the greeting updates as you type!
85 |
86 |
87 | # Getting Started
88 |
89 | * [Website](http://kmalakoff.github.com/knockback/) - explore everything Knockback and connect to the community
90 | * [Tutorials](http://kmalakoff.github.io/knockback/tutorials_introduction.html) - try some live examples
91 | * [API Docs](http://kmalakoff.github.com/knockback/doc/index.html) - dig into the API
92 | * [TodoMVC App (Live!)](http://kmalakoff.github.com/knockback-todos-app/) - compare client-side framworks
93 |
94 | # Download Latest (1.2.3):
95 |
96 | Please see the [release notes](https://github.com/kmalakoff/knockback/blob/master/RELEASE_NOTES.md) for upgrade pointers.
97 |
98 | * Full Library [(dev, 64k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback.js) or [(min+gzip, 8k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback.min.js)
99 | * Full Stack [(dev, 330k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-full-stack.js) or [(min+gzip, 32k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-full-stack.min.js)
100 |
101 | * Core Library [(dev, 54k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core.js) or [(min+gzip, 7k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core.min.js)
102 | * Core Stack [(dev, 315k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core-stack.js) or [(min+gzip, 31k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core-stack.min.js)
103 |
104 | The **full versions** bundle advanced features.
105 |
106 | The **core versions** remove advanced features that can be included separately: localization, formatting, triggering, defaults, and validation.
107 |
108 | The **stack versions** provide Underscore.js + Backbone.js + Knockout.js + Knockback.js in a single file.
109 |
110 | ###Distributions
111 |
112 | You can also find Knockback on your favorite distributions:
113 |
114 | * **npm**: npm install knockback
115 | * **Bower**: bower install knockback
116 | * [NuGet](http://nuget.org/packages/Knockback.js) - install right in Visual Studio
117 |
118 | ###Dependencies
119 |
120 | * [Backbone.js](http://backbonejs.org/) - provides the Model layer
121 | * [Knockout.js](http://knockoutjs.com/) - provides the ViewModel layer foundations for Knockback
122 | * [Underscore.js](http://underscorejs.org/) - provides an awesome JavaScript utility belt
123 | * [LoDash](http://lodash.com/) - optionally replaces Underscore.js with a library optimized for consistent performance
124 | * [Parse](https://www.parse.com/) - optionally replaces Backbone.js and Underscore.js
125 |
126 | ###Compatible Components
127 |
128 | * [BackboneORM](http://vidigami.github.io/backbone-orm/) - A polystore ORM for Node.js and the browser
129 | * [Backbone-Relational.js](http://backbonerelational.org/) - Get and set relations (one-to-one, one-to-many, many-to-one) for Backbone models
130 | * [Backbone Associations](http://dhruvaray.github.io/backbone-associations/) - Create object hierarchies with Backbone models. Respond to hierarchy changes using regular Backbone events
131 | * [BackboneModelRef.js](https://github.com/kmalakoff/backbone-modelref/) - 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).
132 |
133 |
134 | # Contributing
135 |
136 | To build the library for Node.js and browsers:
137 |
138 | ```
139 | $ gulp build
140 | ```
141 |
142 | Please run tests before submitting a pull request:
143 |
144 | ```
145 | $ gulp test --quick
146 | ```
147 |
148 | and eventually all tests:
149 |
150 | ```
151 | $ npm test
152 | ```
153 |
--------------------------------------------------------------------------------
/packages/npm/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "knockback",
3 | "version": "1.2.3",
4 | "main": "knockback.js",
5 | "ignore": [
6 | "**/.*",
7 | "config",
8 | "components",
9 | "doc",
10 | "media",
11 | "node_modules",
12 | "packages",
13 | "src",
14 | "test",
15 | "vendor",
16 | "bower_components"
17 | ],
18 | "homepage": "https://github.com/kmalakoff/knockback",
19 | "authors": [
20 | "Kevin Malakoff "
21 | ],
22 | "description": "Knockback.js provides Knockout.js magic for Backbone.js Models and Collections",
23 | "keywords": [
24 | "knockback",
25 | "knockbackjs",
26 | "backbone",
27 | "backbonejs",
28 | "knockout",
29 | "knockoutjs"
30 | ],
31 | "moduleType": [
32 | "amd",
33 | "globals",
34 | "node"
35 | ],
36 | "dependencies": {
37 | "underscore": ">=1.1.7",
38 | "backbone": ">=0.5.1",
39 | "knockout": ">=2.2.1"
40 | },
41 |
42 | "license": "MIT"
43 | }
44 |
--------------------------------------------------------------------------------
/packages/npm/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "knockback",
3 | "author": "Kevin Malakoff (https://github.com/kmalakoff)",
4 | "version": "1.2.3",
5 | "description": "Knockback.js provides Knockout.js magic for Backbone.js Models and Collections",
6 | "keywords" : ["knockback", "knockbackjs", "backbone", "backbonejs", "knockout", "knockoutjs"],
7 | "repo": "kmalakoff/knockback",
8 | "dependencies": {
9 | "jashkenas/underscore": "*",
10 | "jashkenas/backbone": "*",
11 | "kmalakoff/knockout": "*"
12 | },
13 | "main": "knockback.js",
14 | "scripts": [
15 | "knockback.js"
16 | ],
17 | "license": "MIT"
18 | }
--------------------------------------------------------------------------------
/packages/npm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "knockback",
3 | "version": "1.2.3",
4 | "description": "Knockback.js provides Knockout.js magic for Backbone.js Models and Collections",
5 | "main": "knockback.js",
6 | "author": {
7 | "name": "Kevin Malakoff",
8 | "url": "https://github.com/kmalakoff"
9 | },
10 | "license": "MIT",
11 | "keywords": [
12 | "knockback",
13 | "knockbackjs",
14 | "backbone",
15 | "backbonejs",
16 | "knockout",
17 | "knockoutjs"
18 | ],
19 | "url": "http://kmalakoff.github.com/knockback/",
20 | "homepage": "http://kmalakoff.github.com/knockback/",
21 | "contributors": [],
22 | "repository": {
23 | "type": "git",
24 | "url": "http://github.com/kmalakoff/knockback.git"
25 | },
26 | "bugs": "https://github.com/kmalakoff/knockback/issues",
27 | "dependencies": {},
28 | "optionalDependencies": {
29 | "underscore": ">=1.1.7",
30 | "backbone": ">=0.5.1",
31 | "knockout": ">=2.1.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/nuget/Content/Scripts/README.md:
--------------------------------------------------------------------------------
1 | [](http://travis-ci.org/kmalakoff/knockback#master)
2 |
3 | 
4 |
5 | Knockback.js provides Knockout.js magic for Backbone.js Models and Collections.
6 |
7 | # Why Knockback?
8 |
9 | * Make amazingly dynamic applications by applying a small number of simple principles
10 | * Leverage the wonderful work from both the Backbone and Knockout communities
11 | * Easily view and edit relationships between Models using an ORM of your choice:
12 | * [BackboneORM](http://vidigami.github.io/backbone-orm/)
13 | * [Backbone-Relational.js](http://backbonerelational.org/)
14 | * 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](http://kmalakoff.github.com/knockback/tutorial_inject.html) for live examples!
15 |
16 | # Examples
17 |
18 | ### Simple
19 |
20 | ###### The HTML:
21 |
22 | ```html
23 |
24 |
25 | ```
26 |
27 | ###### And...engage:
28 |
29 | ```coffeescript
30 | model = new Backbone.Model({first_name: 'Bob', last_name: 'Smith'})
31 | ko.applyBindings(kb.viewModel(model))
32 | ```
33 |
34 | When you type in the input boxes, the values are properly transferred bi-directionally to the model and all other bound view models!
35 |
36 |
37 | ### Advanced
38 |
39 | ###### The View Model:
40 |
41 | **Javascript**
42 |
43 | ```javascript
44 | var ContactViewModel = kb.ViewModel.extend({
45 | constructor: function(model) {
46 | kb.ViewModel.prototype.constructor.call(this, model);
47 |
48 | this.full_name = ko.computed(function() {
49 | return this.first_name() + " " + this.last_name();
50 | }, this);
51 | });
52 |
53 | ```
54 |
55 | **or Coffeescript**
56 |
57 | ```coffeescript
58 | class ContactViewModel extends kb.ViewModel
59 | constructor: (model) ->
60 | super model
61 |
62 | @full_name = ko.computed => "#{@first_name()} #{@last_name()}"
63 | ```
64 |
65 | ###### The HTML:
66 |
67 | ```html
68 |
69 |
70 |
71 | ```
72 |
73 | ###### And...engage:
74 |
75 | ```coffeescript
76 | model = new Backbone.Model({first_name: 'Bob', last_name: 'Smith'})
77 | view_model = new ContactViewModel(model)
78 | ko.applyBindings(view_model)
79 |
80 | # ... do stuff then clean up
81 | kb.release(view_model)
82 | ```
83 |
84 | Now, the greeting updates as you type!
85 |
86 |
87 | # Getting Started
88 |
89 | * [Website](http://kmalakoff.github.com/knockback/) - explore everything Knockback and connect to the community
90 | * [Tutorials](http://kmalakoff.github.io/knockback/tutorials_introduction.html) - try some live examples
91 | * [API Docs](http://kmalakoff.github.com/knockback/doc/index.html) - dig into the API
92 | * [TodoMVC App (Live!)](http://kmalakoff.github.com/knockback-todos-app/) - compare client-side framworks
93 |
94 | # Download Latest (1.2.3):
95 |
96 | Please see the [release notes](https://github.com/kmalakoff/knockback/blob/master/RELEASE_NOTES.md) for upgrade pointers.
97 |
98 | * Full Library [(dev, 64k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback.js) or [(min+gzip, 8k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback.min.js)
99 | * Full Stack [(dev, 330k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-full-stack.js) or [(min+gzip, 32k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-full-stack.min.js)
100 |
101 | * Core Library [(dev, 54k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core.js) or [(min+gzip, 7k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core.min.js)
102 | * Core Stack [(dev, 315k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core-stack.js) or [(min+gzip, 31k)](https://raw.github.com/kmalakoff/knockback/1.2.3/knockback-core-stack.min.js)
103 |
104 | The **full versions** bundle advanced features.
105 |
106 | The **core versions** remove advanced features that can be included separately: localization, formatting, triggering, defaults, and validation.
107 |
108 | The **stack versions** provide Underscore.js + Backbone.js + Knockout.js + Knockback.js in a single file.
109 |
110 | ###Distributions
111 |
112 | You can also find Knockback on your favorite distributions:
113 |
114 | * **npm**: npm install knockback
115 | * **Bower**: bower install knockback
116 | * [NuGet](http://nuget.org/packages/Knockback.js) - install right in Visual Studio
117 |
118 | ###Dependencies
119 |
120 | * [Backbone.js](http://backbonejs.org/) - provides the Model layer
121 | * [Knockout.js](http://knockoutjs.com/) - provides the ViewModel layer foundations for Knockback
122 | * [Underscore.js](http://underscorejs.org/) - provides an awesome JavaScript utility belt
123 | * [LoDash](http://lodash.com/) - optionally replaces Underscore.js with a library optimized for consistent performance
124 | * [Parse](https://www.parse.com/) - optionally replaces Backbone.js and Underscore.js
125 |
126 | ###Compatible Components
127 |
128 | * [BackboneORM](http://vidigami.github.io/backbone-orm/) - A polystore ORM for Node.js and the browser
129 | * [Backbone-Relational.js](http://backbonerelational.org/) - Get and set relations (one-to-one, one-to-many, many-to-one) for Backbone models
130 | * [Backbone Associations](http://dhruvaray.github.io/backbone-associations/) - Create object hierarchies with Backbone models. Respond to hierarchy changes using regular Backbone events
131 | * [BackboneModelRef.js](https://github.com/kmalakoff/backbone-modelref/) - 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).
132 |
133 |
134 | # Contributing
135 |
136 | To build the library for Node.js and browsers:
137 |
138 | ```
139 | $ gulp build
140 | ```
141 |
142 | Please run tests before submitting a pull request:
143 |
144 | ```
145 | $ gulp test --quick
146 | ```
147 |
148 | and eventually all tests:
149 |
150 | ```
151 | $ npm test
152 | ```
153 |
--------------------------------------------------------------------------------
/packages/nuget/package.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1.2.3
5 | Kevin Malakoff
6 |
7 | https://github.com/kmalakoff/knockback/blob/master/LICENSE
8 | http://kmalakoff.github.com/knockback/
9 |
10 |
11 |
12 |
13 |
14 | Knockback.js
15 | Knockback.js
16 | false
17 | Knockback.js provides Knockout.js magic for Backbone.js Models and Collections.
18 | Knockback.js provides Knockout.js magic for Backbone.js Models and Collections.
19 | Copyright (c) 2011-2016 Kevin Malakoff
20 | Kockout KnockoutJS Knockout.js Knockback Backbone BackboneJS Bakcbone.js Javascript
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/core/configure.coffee:
--------------------------------------------------------------------------------
1 | {_, ko} = kb = require './kb'
2 |
3 | ALL_ORMS =
4 | 'default': null
5 | 'backbone-orm': null
6 | 'backbone-associations': require './orms/backbone-associations'
7 | 'backbone-relational': require './orms/backbone-relational'
8 |
9 | # @nodoc
10 | kb.settings = {orm: ALL_ORMS.default}
11 | (kb.settings.orm = value; break) for key, value of ALL_ORMS when value and value.isAvailable()
12 |
13 | # @nodoc
14 | module.exports = (options={}) ->
15 | for key, value of options
16 | switch key
17 | when 'orm'
18 | # set by name
19 | if _.isString(value)
20 | (console.log "Knockback configure: could not find orm: #{value}. Available: #{_.keys(ALL_ORMS).join(', ')}"; continue) unless ALL_ORMS.hasOwnProperty(value)
21 | (console.log "Knockback configure: could not enable orm #{value}. Make sure it is included before Knockback"; continue) if (orm = ALL_ORMS[value]) and not orm.isAvailable()
22 | kb.settings.orm = orm
23 | continue
24 |
25 | # set by functions
26 | else
27 | kb.settings.orm = value
28 |
29 | else
30 | kb.settings[key] = value
31 | return
32 |
--------------------------------------------------------------------------------
/src/core/event-watcher.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require './kb'
11 |
12 | # Used to provide a central place to aggregate registered Model events rather than having all kb.Observables register for updates independently.
13 | #
14 | class kb.EventWatcher
15 |
16 | # Used to either register yourself with the existing emitter watcher or to create a new one.
17 | #
18 | # @param [Object] options please pass the options from your constructor to the register method. For example, constructor(emitter, options)
19 | # @param [Model|ModelRef] obj the Model that will own or register with the store
20 | # @param [ko.observable|Object] emitter the emitters of the event watcher
21 | # @param [Object] callback_options information about the event and callback to register
22 | # @option options [Function] emitter callback for when the emitter changes (eg. is loaded). Signature: function(new_emitter)
23 | # @option options [Function] update callback for when the registered event is triggered. Signature: function(new_value)
24 | # @option options [String] event_selector the name or names of events.
25 | # @option options [String] key the optional key to filter update attribute events.
26 | @useOptionsOrCreate: (options, emitter, obj, callback_options) ->
27 | if options.event_watcher
28 | kb._throwUnexpected(@, 'emitter not matching') unless (options.event_watcher.emitter() is emitter or (options.event_watcher.model_ref is emitter))
29 | return kb.utils.wrappedEventWatcher(obj, options.event_watcher).registerCallbacks(obj, callback_options)
30 | else
31 | kb.utils.wrappedEventWatcherIsOwned(obj, true)
32 | return kb.utils.wrappedEventWatcher(obj, new kb.EventWatcher(emitter)).registerCallbacks(obj, callback_options)
33 |
34 | constructor: (emitter, obj, callback_options) ->
35 | @__kb or= {}
36 | @__kb.callbacks = {}
37 |
38 | @ee = null
39 | @registerCallbacks(obj, callback_options) if callback_options
40 | @emitter(emitter) if emitter
41 |
42 | # Required clean up function to break cycles, release view emitters, etc.
43 | # Can be called directly, via kb.release(object) or as a consequence of ko.releaseNode(element).
44 | destroy: ->
45 | @emitter(null); @__kb.callbacks = null
46 | kb.utils.wrappedDestroy(@)
47 |
48 | # Dual-purpose getter/setter for the observed emitter.
49 | #
50 | # @overload emitter()
51 | # Gets the emitter or emitter reference
52 | # @return [Model|ModelRef] the emitter whose attributes are being observed (can be null)
53 | # @overload emitter(new_emitter)
54 | # Sets the emitter or emitter reference
55 | # @param [Model|ModelRef] new_emitter the emitter whose attributes will be observed (can be null)
56 | emitter: (new_emitter) ->
57 | # get or no change
58 | return @ee if (arguments.length is 0) or (@ee is new_emitter)
59 |
60 | # clear and unbind previous
61 | if @model_ref
62 | @model_ref.unbind('loaded', @_onModelLoaded)
63 | @model_ref.unbind('unloaded', @_onModelUnloaded)
64 | @model_ref.release(); @model_ref = null
65 |
66 | # set up current
67 | if kb.Backbone and kb.Backbone.ModelRef and (new_emitter instanceof kb.Backbone.ModelRef)
68 | @model_ref = new_emitter; @model_ref.retain()
69 | @model_ref.bind('loaded', @_onModelLoaded)
70 | @model_ref.bind('unloaded', @_onModelUnloaded)
71 | new_emitter = @model_ref.model() or null
72 | else
73 | delete @model_ref
74 |
75 | # switch bindings
76 | if @ee isnt new_emitter
77 | if new_emitter then @_onModelLoaded(new_emitter) else @_onModelUnloaded(@ee)
78 | return new_emitter
79 |
80 | # Used to register callbacks for an emitter.
81 | #
82 | # @param [Object] obj the owning object.
83 | # @param [Object] callback_info the callback information
84 | # @option options [Function] emitter callback for when the emitter changes (eg. is loaded). Signature: function(new_emitter)
85 | # @option options [Function] update callback for when the registered emitter is triggered. Signature: function(new_value)
86 | # @option options [String] emitter_name the name of the emitter.
87 | # @option options [String] key the optional key to filter update attribute events.
88 | registerCallbacks: (obj, callback_info) ->
89 | obj or kb._throwMissing(this, 'obj')
90 | callback_info or kb._throwMissing(this, 'callback_info')
91 | event_names = if callback_info.event_selector then callback_info.event_selector.split(' ') else ['change']
92 | model = @ee
93 |
94 | for event_name in event_names
95 | continue unless event_name # extra spaces
96 | do (event_name) =>
97 | unless callbacks = @__kb.callbacks[event_name]
98 | callbacks = @__kb.callbacks[event_name] = {
99 | model: null
100 | list: []
101 | fn: (model) =>
102 | for info in callbacks.list
103 | continue unless info.update
104 | continue if model and info.key and (model.hasChanged and not model.hasChanged(ko.utils.unwrapObservable(info.key))) # key doesn't match
105 | not kb.statistics or kb.statistics.addModelEvent({name: event_name, model: model, key: info.key, path: info.path})
106 | info.update() # trigger update
107 | return null
108 | }
109 |
110 | callbacks.list.push(info = _.defaults({obj: obj}, callback_info)) # store the callback information
111 | @_onModelLoaded(model) if model
112 | return @
113 |
114 | releaseCallbacks: (obj) ->
115 | @ee = null
116 | @_unbindCallbacks(event_name, callbacks, kb.wasReleased(obj)) for event_name, callbacks of @__kb.callbacks # unbind all events
117 | delete @__kb.callbacks
118 |
119 | ####################################################
120 | # Internal
121 | ####################################################
122 |
123 | # @nodoc
124 | # NOTE: this is called by registerCallbacks so the model could already be bound and we just want to bind the new info
125 | # NOTE: this is called by emitter so it may be used to clear a previous emitter without triggering an intermediate change
126 | _onModelLoaded: (model) =>
127 | @ee = model
128 | for event_name, callbacks of @__kb.callbacks # bind all events
129 | @_unbindCallbacks(event_name, callbacks, true) if callbacks.model and (callbacks.model isnt model)
130 |
131 | (callbacks.model = model; model.bind(event_name, callbacks.fn)) unless callbacks.model
132 | for info in callbacks.list
133 | info.unbind_fn or= kb.settings.orm?.bind(model, info.key, info.update, info.path)
134 | (info.emitter(model) if info.emitter)
135 | return
136 |
137 | # @nodoc
138 | _onModelUnloaded: (model) =>
139 | return if @ee isnt model
140 | @ee = null
141 | @_unbindCallbacks(event_name, callbacks) for event_name, callbacks of @__kb.callbacks # unbind all events
142 | return
143 |
144 | # @nodoc
145 | _unbindCallbacks: (event_name, callbacks, skip_emitter) =>
146 | (callbacks.model.unbind(event_name, callbacks.fn); callbacks.model = null) if callbacks.model
147 | for info in callbacks.list
148 | (info.unbind_fn(); info.unbind_fn = null) if info.unbind_fn
149 | info.emitter(null) if info.emitter and not skip_emitter and not kb.wasReleased(info.obj)
150 | return
151 |
152 | # factory function
153 | kb.emitterObservable = (emitter, observable) -> return new kb.EventWatcher(emitter, observable)
154 |
--------------------------------------------------------------------------------
/src/core/factory.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_} = kb = require './kb'
11 |
12 | # Used to share the hierachy of constructors and create functions by path to allow for custom creation per Model attribute.
13 | #
14 | # @example Create an instance by path.
15 | # var factory = new kb.Factory();
16 | # factory.addPathMapping('bob.the.builder', kb.ViewModel);
17 | # view_model = factory.createForPath(new Backbone.Model({name: 'Bob'}), 'bob.the.builder'); // creates kb.ViewModel
18 | class kb.Factory
19 |
20 | # Used to either register yourself with the existing factory or to create a new factory.
21 | #
22 | # @param [Object] options please pass the options from your constructor to the register method. For example, constructor(model, options)
23 | # @option options [Object] factories a map of dot-deliminated paths; for example 'models.owner': kb.ViewModel to either constructors or create functions. Signature: 'some.path': function(object, options)
24 | # @param [Instance] obj the instance that will own or register with the store
25 | # @param [String] owner_path the path to the owning object for turning relative scoping of the factories to absolute paths.
26 | @useOptionsOrCreate: (options, obj, owner_path) ->
27 | # share
28 | if options.factory and (not options.factories or (options.factories and options.factory.hasPathMappings(options.factories, owner_path)))
29 | return kb.utils.wrappedFactory(obj, options.factory)
30 |
31 | # create a new factory
32 | factory = kb.utils.wrappedFactory(obj, new kb.Factory(options.factory))
33 | factory.addPathMappings(options.factories, owner_path) if options.factories
34 | return factory
35 |
36 | constructor: (parent_factory) -> @paths = {}; @parent_factory = parent_factory if parent_factory
37 |
38 | hasPath: (path) -> return @paths.hasOwnProperty(path) or @parent_factory?.hasPath(path)
39 |
40 | addPathMapping: (path, create_info) -> @paths[path] = create_info
41 |
42 | addPathMappings: (factories, owner_path) ->
43 | @paths[kb.utils.pathJoin(owner_path, path)] = create_info for path, create_info of factories
44 | return
45 |
46 | hasPathMappings: (factories, owner_path) ->
47 | all_exist = true
48 | for path, creator of factories
49 | all_exist &= ((existing_creator = @creatorForPath(null, kb.utils.pathJoin(owner_path, path))) and (creator is existing_creator))
50 | return all_exist
51 |
52 | # If possible, creates an observable for an object using a dot-deliminated path.
53 | #
54 | # @example Create an instance by path.
55 | # var factory = new kb.Factory();
56 | # factory.addPathMapping('bob.the.builder', kb.ViewModel);
57 | # view_model = factory.createForPath(new Backbone.Model({name: 'Bob'}), 'bob.the.builder'); // creates kb.ViewModel
58 | creatorForPath: (obj, path) ->
59 | return (if creator.view_model then creator.view_model else creator) if creator = @paths[path]
60 | return creator if creator = @parent_factory?.creatorForPath(obj, path)
61 | return null
62 |
--------------------------------------------------------------------------------
/src/core/functions/collapse_options.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_} = require '../kb'
11 |
12 | # @nodoc
13 | _mergeArray = (result, key, value) ->
14 | result[key] or= []
15 | value = [value] unless _.isArray(value)
16 | result[key] = if result[key].length then _.union(result[key], value) else value
17 | return result
18 |
19 | # @nodoc
20 | _mergeObject = (result, key, value) -> result[key] or= {}; return _.extend(result[key], value)
21 |
22 | # @nodoc
23 | _keyArrayToObject = (value) -> result = {}; result[item] = {key: item} for item in value; return result
24 |
25 | _mergeOptions = (result, options) ->
26 | return result unless options
27 | for key, value of options
28 | switch key
29 | when 'internals', 'requires', 'excludes', 'statics' then _mergeArray(result, key, value)
30 | when 'keys'
31 | # an object
32 | if (_.isObject(value) and not _.isArray(value)) or (_.isObject(result[key]) and not _.isArray(result[key]))
33 | value = [value] unless _.isObject(value)
34 | value = _keyArrayToObject(value) if _.isArray(value)
35 | result[key] = _keyArrayToObject(result[key]) if _.isArray(result[key])
36 | _mergeObject(result, key, value)
37 |
38 | # an array
39 | else
40 | _mergeArray(result, key, value)
41 |
42 | when 'factories'
43 | if _.isFunction(value) then result[key] = value else _mergeObject(result, key, value)
44 | when 'static_defaults' then _mergeObject(result, key, value)
45 | when 'options' then
46 | else
47 | result[key] = value
48 |
49 | return _mergeOptions(result, options.options)
50 |
51 | # @nodoc
52 | module.exports = (options) -> _mergeOptions({}, options)
53 |
--------------------------------------------------------------------------------
/src/core/functions/extend.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | # From Backbone.js (https:github.com/documentcloud/backbone)
11 | copyProps = (dest, source) -> dest[key] = value for key, value of source; return dest
12 |
13 | `// Shared empty constructor function to aid in prototype-chain creation.
14 | var ctor = function(){};
15 |
16 | // Helper function to correctly set up the prototype chain, for subclasses.
17 | // Similar to 'goog.inherits', but uses a hash of prototype properties and
18 | // class properties to be extended.
19 | var inherits = function(parent, protoProps, staticProps) {
20 | var child;
21 |
22 | // The constructor function for the new subclass is either defined by you
23 | // (the "constructor" property in your extend definition), or defaulted
24 | // by us to simply call the parent's constructor.
25 | if (protoProps && protoProps.hasOwnProperty('constructor')) {
26 | child = protoProps.constructor;
27 | } else {
28 | child = function(){ parent.apply(this, arguments); };
29 | }
30 |
31 | // Inherit class (static) properties from parent.
32 | copyProps(child, parent);
33 |
34 | // Set the prototype chain to inherit from parent, without calling
35 | // parent's constructor function.
36 | ctor.prototype = parent.prototype;
37 | child.prototype = new ctor();
38 |
39 | // Add prototype properties (instance properties) to the subclass,
40 | // if supplied.
41 | if (protoProps) copyProps(child.prototype, protoProps);
42 |
43 | // Add static properties to the constructor function, if supplied.
44 | if (staticProps) copyProps(child, staticProps);
45 |
46 | // Correctly set child's 'prototype.constructor'.
47 | child.prototype.constructor = child;
48 |
49 | // Set a convenience property in case the parent's prototype is needed later.
50 | child.__super__ = parent.prototype;
51 |
52 | return child;
53 | };
54 |
55 | // The self-propagating extend function that BacLCone classes use.
56 | var extend = function (protoProps, classProps) {
57 | var child = inherits(this, protoProps, classProps);
58 | child.extend = this.extend;
59 | return child;
60 | };
61 | `
62 |
63 | module.exports = extend
64 |
--------------------------------------------------------------------------------
/src/core/functions/unwrap_models.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_} = require '../kb'
11 |
12 | # @nodoc
13 | module.exports = unwrapModels = (obj) ->
14 | return obj unless obj
15 |
16 | return (if obj.__kb.hasOwnProperty('object') then obj.__kb.object else obj) if obj.__kb
17 | return _.map(obj, (test) -> return unwrapModels(test)) if _.isArray(obj)
18 | if _.isObject(obj) and (obj.constructor is {}.constructor) # a simple object
19 | result = {}
20 | result[key] = unwrapModels(value) for key, value of obj
21 | return result
22 |
23 | return obj
24 |
--------------------------------------------------------------------------------
/src/core/functions/wrapped_destroy.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_} = require '../kb'
11 |
12 | # @nodoc
13 | module.exports = wrappedDestroy = (obj) ->
14 | return unless obj.__kb
15 | obj.__kb.event_watcher.releaseCallbacks(obj) if obj.__kb.event_watcher
16 |
17 | __kb = obj.__kb; obj.__kb = null # clear now to break cycles
18 |
19 | if __kb.observable
20 | __kb.observable.destroy = __kb.observable.release = null
21 | wrappedDestroy(__kb.observable)
22 | __kb.observable = null
23 |
24 | __kb.factory = null
25 |
26 | __kb.event_watcher.destroy() if __kb.event_watcher_is_owned # release the event_watcher
27 | __kb.event_watcher = null
28 |
29 | __kb.store.destroy() if __kb.store_is_owned # release the store
30 | __kb.store = null
31 | if __kb.stores_references
32 | while store_references = __kb.stores_references.pop()
33 | store_references.store.release(obj) unless store_references.store.__kb_released
34 | return
35 |
--------------------------------------------------------------------------------
/src/core/index.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | module.exports = kb = require './kb'
11 |
12 | kb.configure = require './configure'
13 |
14 | # re-expose modules
15 | kb.modules = {underscore: kb._, backbone: kb.Parse or kb.Backbone, knockout: kb.ko}
16 |
--------------------------------------------------------------------------------
/src/core/inject.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | window = if window? then window else global
11 |
12 | {_, ko} = kb = require './kb'
13 |
14 | kb.RECUSIVE_AUTO_INJECT = true
15 |
16 | # custom Knockout `inject` binding
17 | ko.bindingHandlers['inject'] =
18 | 'init': (element, value_accessor, all_bindings_accessor, view_model) ->
19 | kb.Inject.inject(ko.utils.unwrapObservable(value_accessor()), view_model, element, value_accessor, all_bindings_accessor)
20 |
21 | # Used to inject ViewModels and observables dynamically from your HTML Views. For both the `'kb-inject'` attribute and the data-bind `'inject'` custom binding, the following properties are reserved:
22 | #
23 | # * `'view_model'` class used to create a new ViewModel instance
24 | # * `'create'` function used to manually add observables to a view model
25 | # * `'options'` to pass to ko.applyBindings
26 | # * `'afterBinding'` callback (can alternatively be in the options)
27 | # * `'beforeBinding'` callback (can alternatively be in the options)
28 | #
29 | # Each function/constructor gets called with the following signature `'function(view_model, element)'`.
30 | #
31 | # @example Bind your application automatically when the DOM is loaded.
32 | #
33 | # @example Bind your application with properties.
34 | #
35 | # @example Bind your application creating a specific ViewModel instance when the DOM is loaded.
36 | #
37 | # var MyViewModel = function(view_model, el) {
38 | # this.message = ko.observable('Hello World!');
39 | # }
40 | # @example Bind your application using a function when the DOM is loaded (like Angular.js controllers).
41 | #
42 | # var MyController = function(view_model, el) {
43 | # view_model.message = ko.observable('Hello World!');
44 | # }
45 | # @example Bind your application with a specific ViewModel instance and a callback before and after the binding.
46 | #
47 | # var MyViewModel = function(view_model, el) {
48 | # this.message = ko.observable('Hello World!');
49 | # this.beforeBinding = function() {alert('before'); };
50 | # this.afterBinding = function() {alert('after'); };
51 | # }
52 | # @example Dynamically inject new properties into your ViewModel.
53 | #
54 | #
55 | #
56 | #
57 | #
58 | #
59 | # var MyViewModel = function(view_model, el) {
60 | # // site will be dynamically attached to this ViewModel
61 | # }
62 | # @example Dynamically bind a form.
63 | #
75 | # var MyViewModel = kb.ViewModel.extend({
76 | # constructor: ->
77 | # model = new Backbone.Model({name: '', site: 'http://your.url.com'});
78 | # kb.ViewModel.prototype.constructor.call(this, model);
79 | # });
80 | class kb.Inject
81 | # @private
82 | @inject: (data, view_model, element, value_accessor, all_bindings_accessor, nested) ->
83 | inject = (data) ->
84 | if _.isFunction(data)
85 | view_model = new data(view_model, element, value_accessor, all_bindings_accessor) # use 'new' to allow for classes in addition to functions
86 | kb.releaseOnNodeRemove(view_model, element)
87 | else
88 | # view_model constructor causes a scope change
89 | if (data.view_model)
90 | # specifying a view_model changes the scope so we need to bind a destroy
91 | view_model = new data.view_model(view_model, element, value_accessor, all_bindings_accessor)
92 | kb.releaseOnNodeRemove(view_model, element)
93 |
94 | # resolve and merge in each key
95 | for key, value of data
96 | continue if (key is 'view_model')
97 |
98 | # create function
99 | if (key is 'create')
100 | value(view_model, element, value_accessor, all_bindings_accessor)
101 |
102 | # resolve nested with assign or not
103 | else if _.isObject(value) and not _.isFunction(value)
104 | target = if nested or (value and value.create) then {} else view_model
105 | view_model[key] = kb.Inject.inject(value, target, element, value_accessor, all_bindings_accessor, true)
106 |
107 | # simple set
108 | else
109 | view_model[key] = value
110 |
111 | return view_model
112 |
113 | # in recursive calls, we are already protected from propagating dependencies to the template
114 | return if nested then inject(data) else kb.ignore(-> inject(data))
115 |
116 | # Searches the DOM from root or document for elements with the `'kb-inject'` attribute and create/customizes ViewModels for the DOM tree when encountered. Also, used with the data-bind `'inject'` custom binding.
117 | # @param [DOM element] root the root DOM element to start searching for `'kb-inject'` attributes.
118 | # @return [Array] array of Objects with the DOM elements and ViewModels that were bound in the form `{el: DOM element, view_model: ViewModel}`.
119 | @injectViewModels: (root) ->
120 | # find all of the app elements
121 | results = []
122 | findElements = (el) ->
123 | unless el.__kb_injected # already injected -> skip, but still process children in case they were added afterwards
124 | if el.attributes and (attr = _.find(el.attributes, (attr)-> attr.name is 'kb-inject'))
125 | el.__kb_injected = true # mark injected
126 | results.push({el: el, view_model: {}, binding: attr.value})
127 | findElements(child_el) for child_el in el.childNodes
128 | return
129 | root = window.document if not root and window?.document
130 | findElements(root)
131 |
132 | # bind the view models
133 | for app in results
134 | # evaluate the app data
135 | if expression = app.binding
136 | (expression.search(/[:]/) < 0) or (expression = "{#{expression}}") # wrap if is an object
137 | data = (new Function("", "return ( #{expression} )"))()
138 | data or (data = {}) # no data
139 | (not data.options) or (options = data.options; delete data.options) # extract options
140 | options or (options={})
141 | app.view_model = kb.Inject.inject(data, app.view_model, app.el, null, null, true)
142 | afterBinding = app.view_model.afterBinding or options.afterBinding
143 | beforeBinding = app.view_model.beforeBinding or options.beforeBinding
144 |
145 | # auto-bind
146 | beforeBinding.call(app.view_model, app.view_model, app.el, options) if beforeBinding
147 | kb.applyBindings(app.view_model, app.el, options)
148 | afterBinding.call(app.view_model, app.view_model, app.el, options) if afterBinding
149 | return results
150 |
151 | # auto-inject recursively
152 | _ko_applyBindings = ko.applyBindings
153 | ko.applyBindings = (context, element) ->
154 | results = if kb.RECUSIVE_AUTO_INJECT then kb.injectViewModels(element) else []
155 | _ko_applyBindings.apply(@, arguments) unless results.length
156 |
157 | #############################
158 | # Aliases
159 | #############################
160 | kb.injectViewModels = kb.Inject.injectViewModels
161 |
162 | #############################
163 | # Auto Inject results
164 | #############################
165 | if document?
166 | # use simple ready check
167 | (onReady = ->
168 | return setTimeout(onReady, 0) unless document.readyState is 'complete' # keep waiting for the document to load
169 | kb.injectViewModels() # the document is loaded
170 | )()
171 |
--------------------------------------------------------------------------------
/src/core/monkey-patches.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {ko} = kb = require './kb'
11 |
12 | # Allow for dependent release until is resolved https://github.com/knockout/knockout/issues/1464
13 | if ko.subscribable?.fn?.extend
14 | _extend = ko.subscribable.fn.extend
15 | ko.subscribable.fn.extend = ->
16 | target = _extend.apply(@, arguments)
17 |
18 | # release the extended observable
19 | if target isnt @ and kb.isReleaseable(@)
20 | _dispose = target.dispose
21 | target.dispose = => _dispose?.apply(target, arguments); kb.release(@)
22 |
23 | return target
24 |
--------------------------------------------------------------------------------
/src/core/observable.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require './kb'
11 | TypedValue = require './typed-value'
12 |
13 | KEYS_PUBLISH = ['value', 'valueType', 'destroy']
14 | KEYS_INFO = ['args', 'read', 'write']
15 |
16 | # Base class for observing model attributes.
17 | #
18 | # @example How to create a ko.CollectionObservable using the ko.collectionObservable factory.
19 | # var ContactViewModel = function(model) {
20 | # this.name = kb.observable(model, 'name');
21 | # this.number = kb.observable(model, { key: 'number'});
22 | # };
23 | # var model = new Contact({ name: 'Ringo', number: '555-555-5556' });
24 | # var view_model = new ContactViewModel(model);
25 | #
26 | # @example How to create a kb.Observable with a default value.
27 | # var model = Backbone.Model({name: 'Bob'});
28 | # var name = kb.observable(model, {key:'name', default: '(none)'}); // name is Bob
29 | # name.setToDefault(); // name is (none)
30 | #
31 | # @method #model()
32 | # Dual-purpose getter/setter ko.computed for the observed model.
33 | # @return [Model|ModelRef|void] getter: the model whose attributes are being observed (can be null) OR setter: void
34 | # @example
35 | # var observable = kb.observable(new Backbone.Model({name: 'bob'}), 'name');
36 | # var the_model = observable.model(); // get
37 | # observable.model(new Backbone.Model({name: 'fred'})); // set
38 | #
39 | class kb.Observable
40 |
41 | # Used to create a new kb.Observable.
42 | #
43 | # @param [Model] model the model to observe (can be null)
44 | # @param [String|Array|Object] options the create options. String is a single attribute name, Array is an array of attribute names.
45 | # @option options [String] key the name of the attribute.
46 | # @option options [Function] read a function used to provide transform the attribute value before passing it to the caller. Signature: read()
47 | # @option options [Function] write a function used to provide transform the value before passing it to the model set function. Signature: write(value)
48 | # @option options [Array] args arguments to pass to the read and write functions (they can be ko.observables). Can be useful for passing arguments to a locale manager.
49 | # @option options [Constructor] localizer a concrete kb.LocalizedObservable constructor for localization.
50 | # @option options [Data|ko.observable] default the default value. Can be a value, string or ko.observable.
51 | # @option options [String] path the path to the value (used to create related observables from the factory).
52 | # @option options [kb.Store] store a store used to cache and share view models.
53 | # @option options [kb.Factory] factory a factory used to create view models.
54 | # @option options [Object] options a set of options merge into these options. Useful for extending options when deriving classes rather than merging them by hand.
55 | # @return [ko.observable] the constructor does not return 'this' but a ko.observable
56 | # @note the constructor does not return 'this' but a ko.observable
57 | constructor: (model, key_or_info, options, @_vm={}) -> return kb.ignore =>
58 | key_or_info or kb._throwMissing(this, 'key_or_info')
59 | @key = key_or_info.key or key_or_info
60 | @[key] = key_or_info[key] for key in KEYS_INFO when key_or_info[key]
61 |
62 | create_options = kb.utils.collapseOptions(options)
63 | event_watcher = create_options.event_watcher
64 | delete create_options.event_watcher
65 |
66 | # set up basics
67 | @_value = new TypedValue(create_options)
68 | @_model = ko.observable()
69 | observable = kb.utils.wrappedObservable @, ko.computed {
70 | read: =>
71 | _model = @_model(); ko.utils.unwrapObservable(arg) for arg in args = [@key].concat(@args or [])
72 | kb.utils.wrappedEventWatcher(@)?.emitter(_model or null) # update the event watcher
73 | if @read
74 | @update(@read.apply(@_vm, args))
75 | else if !_.isUndefined(_model)
76 | kb.ignore => @update(kb.getValue(_model, kb.peek(@key), @args))
77 | return @_value.value()
78 |
79 | write: (new_value) => kb.ignore =>
80 | unwrapped_new_value = kb.utils.unwrapModels(new_value) # unwrap for set (knockout may pass view models which are required for the observable but not the model)
81 | _model = kb.peek(@_model)
82 | if @write
83 | @write.call(@_vm, unwrapped_new_value)
84 | new_value = kb.getValue(_model, kb.peek(@key), @args)
85 | else if _model
86 | kb.setValue(_model, kb.peek(@key), unwrapped_new_value)
87 | @update(new_value)
88 |
89 | owner: @_vm
90 | }
91 |
92 | observable.__kb_is_o = true # mark as a kb.Observable
93 | create_options.store = kb.utils.wrappedStore(observable, create_options.store)
94 | create_options.path = kb.utils.pathJoin(create_options.path, @key)
95 | if create_options.factories and ((typeof(create_options.factories) is 'function') or create_options.factories.create)
96 | create_options.factory = kb.utils.wrappedFactory(observable, new kb.Factory(create_options.factory))
97 | create_options.factory.addPathMapping(create_options.path, create_options.factories)
98 | else
99 | create_options.factory = kb.Factory.useOptionsOrCreate(create_options, observable, create_options.path)
100 | delete create_options.factories
101 |
102 | # publish public interface on the observable and return instead of this
103 | kb.publishMethods(observable, @, KEYS_PUBLISH)
104 |
105 | # use external model observable or create
106 | observable.model = @model = ko.computed {
107 | read: => ko.utils.unwrapObservable(@_model)
108 | write: (new_model) => kb.ignore =>
109 | return if @__kb_released or (kb.peek(@_model) is new_model) # destroyed or no change
110 |
111 | # update references
112 | new_value = kb.getValue(new_model, kb.peek(@key), @args)
113 | @_model(new_model)
114 | if not new_model
115 | @update(null)
116 | else if not _.isUndefined(new_value)
117 | @update(new_value)
118 | }
119 | kb.EventWatcher.useOptionsOrCreate({event_watcher: event_watcher}, model or null, @, {emitter: @model, update: (=> kb.ignore => @update()), key: @key, path: create_options.path})
120 | @_value.rawValue() or @_value.update() # wasn't loaded so create
121 |
122 | observable = new key_or_info.localizer(observable) if kb.LocalizedObservable and key_or_info.localizer # wrap ourselves with a localizer
123 | observable = kb.defaultObservable(observable, key_or_info.default) if kb.DefaultObservable and key_or_info.hasOwnProperty('default') # wrap ourselves with a default value
124 |
125 | return observable
126 |
127 | # Required clean up function to break cycles, release view models, etc.
128 | # Can be called directly, via kb.release(object) or as a consequence of ko.releaseNode(element).
129 | destroy: ->
130 | observable = kb.utils.wrappedObservable(@)
131 | @__kb_released = true
132 | @_value.destroy(); @_value = null
133 | @model.dispose(); @model = observable.model = null
134 | kb.utils.wrappedDestroy(@)
135 |
136 | # @return [kb.CollectionObservable|kb.ViewModel|ko.observable] exposes the raw value inside the kb.observable. For example, if your attribute is a Collection, it will hold a CollectionObservable.
137 | value: -> @_value.rawValue()
138 |
139 | # @return [kb.TYPE_UNKNOWN|kb.TYPE_SIMPLE|kb.TYPE_ARRAY|kb.TYPE_MODEL|kb.TYPE_COLLECTION] provides the type of the wrapped value.
140 | valueType: -> @_value.valueType(kb.peek(@_model), kb.peek(@key))
141 |
142 | ####################################################
143 | # Internal
144 | ####################################################
145 | # @nodoc
146 | update: (new_value) ->
147 | return if @__kb_released # destroyed, nothing to do
148 | new_value = kb.getValue(kb.peek(@_model), kb.peek(@key)) unless arguments.length
149 | @_value.update(new_value)
150 |
151 | kb.observable = (model, key, options, view_model) -> new kb.Observable(model, key, options, view_model)
152 |
--------------------------------------------------------------------------------
/src/core/orms/backbone-associations.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, Backbone} = kb = require '../kb'
11 |
12 | AssociatedModel = null # lazy check
13 |
14 | # @nodoc
15 | module.exports = class BackboneAssociations
16 | @isAvailable: -> return !!AssociatedModel = Backbone?.AssociatedModel # or require?('backbone-associations')?.AssociatedModel # webpack optionals
17 |
18 | @keys: (model) ->
19 | return null unless model instanceof AssociatedModel
20 | return _.map(model.relations, (test) -> test.key)
21 |
22 | @relationType: (model, key) ->
23 | return null unless model instanceof AssociatedModel
24 | return null unless relation = _.find(model.relations, (test) -> return test.key is key)
25 | return if (relation.type is 'Many') then kb.TYPE_COLLECTION else kb.TYPE_MODEL
26 |
27 | @useFunction: -> false
28 |
--------------------------------------------------------------------------------
/src/core/orms/backbone-relational.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, Backbone} = kb = require '../kb'
11 |
12 | RelationalModel = null # lazy check
13 |
14 | # @nodoc
15 | module.exports = class BackboneRelational
16 | @isAvailable: -> return !!RelationalModel = Backbone?.RelationalModel # or require?('backbone-relational')?.RelationalModel # webpack optionals
17 |
18 | @relationType: (model, key) ->
19 | return null unless model instanceof RelationalModel
20 | return null unless relation = _.find(model.getRelations(), (test) -> return test.key is key)
21 | return if (relation.collectionType or _.isArray(relation.keyContents)) then kb.TYPE_COLLECTION else kb.TYPE_MODEL
22 |
23 | @bind: (model, key, update, path) ->
24 | return null unless type = @relationType(model, key)
25 | rel_fn = (model) ->
26 | not kb.statistics or kb.statistics.addModelEvent({name: 'update (relational)', model: model, key: key, path: path})
27 | update()
28 |
29 | # VERSIONING: pre Backbone-Relational 0.8.0
30 | events = if kb.Backbone.Relation.prototype.sanitizeOptions then ['update', 'add', 'remove'] else ['change', 'add', 'remove']
31 | if type is kb.TYPE_COLLECTION
32 | model.bind("#{event}:#{key}", rel_fn) for event in events
33 | else
34 | model.bind("#{events[0]}:#{key}", rel_fn)
35 |
36 | return ->
37 | if type is kb.TYPE_COLLECTION
38 | model.unbind("#{event}:#{key}", rel_fn) for event in events
39 | else
40 | model.unbind("#{events[0]}:#{key}", rel_fn)
41 | return
42 |
43 | @useFunction: -> false
44 |
--------------------------------------------------------------------------------
/src/core/statistics.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_} = kb = require './kb'
11 |
12 | # kb.Statistics is an optional components that is useful for measuring your application's performance. You can record all of the Backbone.Events that have triggered ko.observable subscription updates and the memory footprint (instance count-only) of your ViewModels and collection observables.
13 | #
14 | # kb.Statistics is not included in `knockback.js` nor `knockback-core.js` so you need to manually include it from the `lib` directory.
15 | #
16 | module.exports = class kb.Statistics
17 | constructor: ->
18 | @model_events_tracker = []
19 | @registered_tracker = {}
20 |
21 | # Clear the tracked model events (but keep the registered objects intact)
22 | clear: ->
23 | @model_events_tracker = []
24 |
25 | ###############################
26 | # Registered Events
27 | ###############################
28 |
29 | # Register a model event
30 | addModelEvent: (event) ->
31 | @model_events_tracker.push(event)
32 |
33 | # A debug helper to summarize the registered events in human-readable form
34 | modelEventsStatsString: ->
35 | stats_string = ''
36 | stats_string += "Total Count: #{@model_events_tracker.length}"
37 | event_groups = _.groupBy(@model_events_tracker, (test) -> return "event name: '#{test.name}', attribute name: '#{test.key}'")
38 | for key, value of event_groups
39 | stats_string += "\n #{key}, count: #{value.length}"
40 | return stats_string
41 |
42 | ###############################
43 | # Registered Observables and View Models
44 | ###############################
45 |
46 | # Register an object by key
47 | register: (key, obj) ->
48 | @registeredTracker(key).push(obj)
49 |
50 | # Unregister an object by key
51 | unregister: (key, obj) ->
52 | type_tracker = @registeredTracker(key)
53 | return console?.log("kb.Statistics: failed to unregister type: #{key}") if (index = _.indexOf(type_tracker, obj)) < 0
54 | type_tracker.splice(index, 1)
55 |
56 | # @return [Integer] the number of registered objects by type
57 | registeredCount: (type) ->
58 | return @registeredTracker(type).length if type
59 | count = 0
60 | count += type_tracker.length for type, type_tracker of @registered_tracker[type]
61 | return count
62 |
63 | # A debug helper to summarize the current registered objects by key
64 | #
65 | # @param [String] success_message a message to return if there are no registered objects
66 | # @return [String] a human readable string summarizing the currently registered objects or success_message
67 | registeredStatsString: (success_message) ->
68 | stats_string = ''
69 | for type, type_tracker of @registered_tracker
70 | continue unless type_tracker.length
71 | stats_string += '\n ' if written
72 | stats_string += "#{if type then type else 'No Name'}: #{type_tracker.length}"
73 | written = true
74 | return if stats_string then stats_string else success_message
75 |
76 | # @nodoc
77 | registeredTracker: (key) ->
78 | return @registered_tracker[key] if @registered_tracker.hasOwnProperty(key)
79 | type_tracker = []; @registered_tracker[key] = type_tracker
80 | return type_tracker
81 |
82 | @eventsStats: (obj, key) ->
83 | stats = {count: 0}
84 |
85 | events = obj._events or obj._callbacks or {}
86 | for key in (if key then [key] else _.keys(events)) when node = events[key]
87 | if _.isArray(node)
88 | stats[key] = _.compact(node).length
89 | else
90 | stats[key] = 0; tail = node.tail
91 | stats[key]++ while ((node = node.next) isnt tail)
92 | stats.count += stats[key]
93 | return stats
94 |
--------------------------------------------------------------------------------
/src/core/typed-value.coffee:
--------------------------------------------------------------------------------
1 | {_, ko} = kb = require './kb'
2 |
3 | # @nodoc
4 | module.exports = class TypedValue
5 | constructor: (@create_options) ->
6 | @_vo = ko.observable(null) # create a value observable for the first dependency
7 |
8 | destroy: ->
9 | @__kb_released = true
10 | if previous_value = @__kb_value
11 | @__kb_value = null
12 | if @create_options.store and kb.utils.wrappedCreator(previous_value) then @create_options.store.release(previous_value) else kb.release(previous_value)
13 | @create_options = null
14 |
15 | value: -> ko.utils.unwrapObservable(@_vo())
16 | rawValue: -> return @__kb_value
17 |
18 | valueType: (model, key) ->
19 | new_value = kb.getValue(model, key)
20 | @value_type or @_updateValueObservable(new_value) # create so we can check the type
21 | return @value_type
22 |
23 | update: (new_value) ->
24 | return if @__kb_released # destroyed, nothing to do
25 |
26 | # determine the new type
27 | (new_value isnt undefined) or (new_value = null) # ensure null instead of undefined
28 | new_type = kb.utils.valueType(new_value)
29 |
30 | (@__kb_value = @value_type = undefined) if @__kb_value?.__kb_released
31 | value = @__kb_value
32 |
33 | switch @value_type
34 | when kb.TYPE_COLLECTION
35 | return value(new_value) if @value_type is kb.TYPE_COLLECTION and new_type is kb.TYPE_ARRAY
36 | if new_type is kb.TYPE_COLLECTION or _.isNull(new_value)
37 | # use the provided CollectionObservable
38 | if new_value and new_value instanceof kb.CollectionObservable
39 | @_updateValueObservable(kb.utils.wrappedObject(new_value), new_value)
40 | else
41 | value.collection(new_value) if kb.peek(value.collection) isnt new_value # collection observables are allocated once
42 | return
43 |
44 | when kb.TYPE_MODEL
45 | if new_type is kb.TYPE_MODEL or _.isNull(new_value)
46 | # use the provided ViewModel
47 | if new_value and not kb.isModel(new_value)
48 | @_updateValueObservable(kb.utils.wrappedObject(new_value), new_value)
49 | else
50 | @_updateValueObservable(new_value) if kb.utils.wrappedObject(value) isnt kb.utils.resolveModel(new_value)
51 | return
52 |
53 | if @value_type is new_type and not _.isUndefined(@value_type)
54 | value(new_value) if kb.peek(value) isnt new_value
55 | else
56 | @_updateValueObservable(new_value) if kb.peek(value) isnt new_value
57 |
58 | _updateValueObservable: (new_value, new_observable) ->
59 | create_options = @create_options
60 | creator = kb.utils.inferCreator(new_value, create_options.factory, create_options.path)
61 |
62 | # retain previous type
63 | if (new_value is null) and not creator
64 | if @value_type is kb.TYPE_MODEL
65 | creator = kb.ViewModel
66 | else if @value_type is kb.TYPE_COLLECTION
67 | creator = kb.CollectionObservable
68 | create_options.creator = creator
69 |
70 | value_type = kb.TYPE_UNKNOWN
71 | [previous_value, @__kb_value] = [@__kb_value, undefined]
72 |
73 | if new_observable
74 | value = new_observable
75 | create_options.store.retain(new_observable, new_value, creator) if create_options.store
76 |
77 | # found a creator
78 | else if creator
79 | # have the store, use it to create
80 | if create_options.store
81 | value = create_options.store.retainOrCreate(new_value, create_options, true)
82 |
83 | # create manually
84 | else
85 | if creator.models_only
86 | value = new_value
87 | value_type = kb.TYPE_SIMPLE
88 | else if creator.create
89 | value = creator.create(new_value, create_options)
90 | else
91 | value = new creator(new_value, create_options)
92 |
93 | # create and cache the type
94 | else
95 | if _.isArray(new_value)
96 | value_type = kb.TYPE_ARRAY
97 | value = ko.observableArray(new_value)
98 | else
99 | value_type = kb.TYPE_SIMPLE
100 | value = ko.observable(new_value)
101 |
102 | # determine the type
103 | if (@value_type = value_type) is kb.TYPE_UNKNOWN
104 | if not ko.isObservable(value) # a view model, recognize view_models as non-observable
105 | @value_type = kb.TYPE_MODEL
106 | kb.utils.wrappedObject(value, kb.utils.resolveModel(new_value))
107 | else if value.__kb_is_co
108 | @value_type = kb.TYPE_COLLECTION
109 | kb.utils.wrappedObject(value, new_value)
110 | else if not @value_type
111 | @value_type = kb.TYPE_SIMPLE
112 |
113 | # release previous
114 | if previous_value
115 | if @create_options.store then @create_options.store.release(previous_value) else kb.release(previous_value)
116 |
117 | # store the value
118 | @__kb_value = value
119 | @_vo(value)
120 |
121 | _inferType: (value) ->
122 |
--------------------------------------------------------------------------------
/src/core/view-model.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require './kb'
11 |
12 | # @nodoc
13 | assignViewModelKey = (vm, key) ->
14 | vm_key = if vm.__kb.internals and ~_.indexOf(vm.__kb.internals, key) then "_#{key}" else key
15 | return if vm.__kb.view_model.hasOwnProperty(vm_key) # already exists, skip
16 | vm.__kb.view_model[vm_key] = null
17 | return vm_key
18 |
19 | # @nodoc
20 | createObservable = (vm, model, key, create_options) ->
21 | return if vm.__kb.excludes and ~_.indexOf(vm.__kb.excludes, key)
22 | return if vm.__kb.statics and ~_.indexOf(vm.__kb.statics, key)
23 | return unless vm_key = assignViewModelKey(vm, key)
24 | vm[vm_key] = vm.__kb.view_model[vm_key] = kb.observable(model, key, create_options, vm)
25 |
26 | # @nodoc
27 | createStaticObservables = (vm, model) ->
28 | for key in vm.__kb.statics when vm_key = assignViewModelKey(vm, key)
29 | if model.has(vm_key)
30 | vm[vm_key] = vm.__kb.view_model[vm_key] = model.get(vm_key)
31 | else if vm.__kb.static_defaults and vm_key of vm.__kb.static_defaults
32 | vm[vm_key] = vm.__kb.view_model[vm_key] = vm.__kb.static_defaults[vm_key]
33 | else
34 | delete vm.__kb.view_model[vm_key]
35 | return
36 |
37 | KEYS_OPTIONS = ['keys', 'internals', 'excludes', 'statics', 'static_defaults']
38 |
39 | # Base class for ViewModels for Models.
40 | #
41 | # @example How to create a ViewModel with first_name and last_name observables.
42 | # var view_model = kb.viewModel(new Backbone.Model({first_name: "Planet", last_name: "Earth"}));
43 | #
44 | # @example Bulk kb.Observable create using 'key' Object to customize the kb.Observable created per attribute.
45 | # var ContactViewModel = function(model) {
46 | # this.loading_message = new kb.LocalizedStringLocalizer(new LocalizedString('loading'));
47 | # this._auto = kb.viewModel(model, {
48 | # keys: {
49 | # name: { key: 'name', 'default': this.loading_message },
50 | # number: { key: 'number', 'default': this.loading_message },
51 | # date: { key: 'date', 'default': this.loading_message, localizer: kb.ShortDateLocalizer }
52 | # }
53 | # }, this);
54 | # return this;
55 | # };
56 | #
57 | # @example Creating ko.Observables on a target ViewModel
58 | # var view_model = {};
59 | # kb.viewModel(model, ['name', 'date'], view_model); // observables are added to view_model
60 | #
61 | # @method .extend(prototype_properties, class_properties)
62 | # Class method for JavaScript inheritance.
63 | # @param [Object] prototype_properties the properties to add to the prototype
64 | # @param [Object] class_properties the properties to add to the class
65 | # @return [kb.ViewModel] the constructor returns 'this'
66 | # @example
67 | # var ContactViewModel = kb.ViewModel.extend({
68 | # constructor: function(model) {
69 | # kb.ViewModel.prototype.constructor.call(this, model, {internals: ['email', 'date']}); // call super constructor: @name, @_email, and @_date created in super from the model attributes
70 | # this.email = kb.defaultObservable(this._email, 'your.name@yourplace.com');
71 | # this.date = new LongDateLocalizer(this._date);
72 | # return this;
73 | # }
74 | # });
75 | # @example
76 | # var ViewModel = kb.ViewModel.extend({
77 | # constructor: function(model){
78 | # kb.ViewModel.prototype.constructor.apply(this, arguments);
79 | # this.full_name = ko.computed(function() { return this.first_name() + " " + this.last_name(); }, this);
80 | # }
81 | # });
82 | # var view_model = new ViewModel(model);
83 | #
84 | # @method #model()
85 | # Dual-purpose getter/setter ko.computed for the observed model.
86 | # @return [Model|ModelRef|void] getter: the model whose attributes are being observed (can be null) OR setter: void
87 | # @example
88 | # var view_model = kb.viewModel(new Backbone.Model({name: 'bob'}));
89 | # var the_model = view_model.model(); // get
90 | # view_model.model(new Backbone.Model({name: 'fred'})); // set
91 | #
92 | class kb.ViewModel
93 | # @nodoc
94 | @extend = kb.extend # for Backbone non-Coffeescript inheritance (use "kb.SuperClass.extend({})" in Javascript instead of "class MyClass extends kb.SuperClass")
95 |
96 | # Used to create a new kb.ViewModel.
97 | #
98 | # @param [Model|ModelRef] model the model to observe (can be null)
99 | # @param [Object] options the create options
100 | # @option options [Array|String] internals an array of atttributes that should be scoped with an underscore, eg. name -> _name
101 | # @option options [Array|String] requires an array of atttributes that will have kb.Observables created even if they do not exist on the Model. Useful for binding Views that require specific observables to exist
102 | # @option options [Array|String] keys restricts the keys used on a model. Useful for reducing the number of kb.Observables created from a limited set of Model attributes
103 | # @option options [Object|Array|String] excludes if an array is supplied, excludes keys to exclude on the view model; for example, if you want to provide a custom implementation. If an Object, it provides options to the kb.Observable constructor.
104 | # @option options [Array] statics creates non-observable properties on your view model for Model attributes that do not need to be observed for changes.
105 | # @option options [Object] static_defaults provides default values for statics.
106 | # @option options [String] path the path to the value (used to create related observables from the factory).
107 | # @option options [kb.Store] store a store used to cache and share view models.
108 | # @option options [Object] factories a map of dot-deliminated paths; for example `{'models.name': kb.ViewModel}` to either constructors or create functions. Signature: `{'some.path': function(object, options)}`
109 | # @option options [kb.Factory] factory a factory used to create view models.
110 | # @option options [Object] options a set of options merge into these options. Useful for extending options when deriving classes rather than merging them by hand.
111 | # @return [ko.observable] the constructor returns 'this'
112 | # @param [Object] view_model a view model to also set the kb.Observables on. Useful when batch creating observable on an owning view model.
113 | constructor: (model, options={}, view_model) -> args = Array.prototype.slice.call(if _.isArguments(model) then model else arguments); return kb.ignore =>
114 | not (model = args.shift()) or kb.isModel(model) or kb._throwUnexpected(@, 'not a model')
115 | args[0] = {keys: args[0]} if _.isArray(args[0])
116 | @__kb or= {}; @__kb.view_model = (if args.length > 1 then args.pop() else @)
117 | options = {}; _.extend(options, arg) for arg in args; options = kb.utils.collapseOptions(options)
118 | @__kb[key] = options[key] for key in KEYS_OPTIONS when options.hasOwnProperty(key)
119 |
120 | # always use a store to ensure recursive view models are handled correctly
121 | kb.Store.useOptionsOrCreate(options, model, @)
122 |
123 | # view model factory
124 | @__kb.path = options.path
125 | kb.Factory.useOptionsOrCreate(options, @, options.path)
126 |
127 | _model = kb.utils.set(@, '_model', ko.observable())
128 | @model = ko.computed {
129 | read: => ko.utils.unwrapObservable(_model)
130 | write: (new_model) => kb.ignore =>
131 | return if kb.wasReleased(@) or not event_watcher
132 |
133 | @__kb.store.reuse(@, kb.utils.resolveModel(new_model))
134 | event_watcher.emitter(new_model); _model(event_watcher.ee)
135 | not event_watcher.ee or @createObservables(event_watcher.ee)
136 | }
137 | event_watcher = kb.utils.wrappedEventWatcher(@, new kb.EventWatcher(model, @, {emitter: @_model, update: (=> kb.ignore => not event_watcher?.ee or @createObservables(event_watcher?.ee))}))
138 | kb.utils.wrappedObject(@, model = event_watcher.ee); _model(event_watcher.ee)
139 |
140 | # update the observables
141 | @__kb.create_options = {store: kb.utils.wrappedStore(@), factory: kb.utils.wrappedFactory(@), path: @__kb.path, event_watcher: kb.utils.wrappedEventWatcher(@)}
142 | not options.requires or @createObservables(model, options.requires)
143 | not @__kb.internals or @createObservables(model, @__kb.internals)
144 | not options.mappings or @createObservables(model, options.mappings)
145 | not @__kb.statics or createStaticObservables(@, model)
146 | @createObservables(model, @__kb.keys)
147 |
148 | not kb.statistics or kb.statistics.register('ViewModel', @) # collect memory management statistics
149 | return @
150 |
151 | # Required clean up function to break cycles, release view models, etc.
152 | # Can be called directly, via kb.release(object) or as a consequence of ko.releaseNode(element).
153 | destroy: ->
154 | @__kb_released = true
155 | (@__kb.view_model[vm_key] = null for vm_key of @__kb.vm_keys) if @__kb.view_model isnt @ # clear the external references
156 | @__kb.view_model = @__kb.create_options = null
157 | kb.releaseKeys(@)
158 | kb.utils.wrappedDestroy(@)
159 |
160 | not kb.statistics or kb.statistics.unregister('ViewModel', @) # collect memory management statistics
161 |
162 | # Get the options for a new view model that can be used for sharing view models.
163 | shareOptions: -> {store: kb.utils.wrappedStore(@), factory: kb.utils.wrappedFactory(@)}
164 |
165 | # create observables manually
166 | createObservables: (model, keys) ->
167 | if not keys
168 | return if @__kb.keys or not model # only use the keys provided
169 | createObservable(@, model, key, @__kb.create_options) for key of model.attributes
170 | (createObservable(@, model, key, @__kb.create_options) for key in rel_keys) if rel_keys = kb.settings.orm?.keys?(model)
171 | else if _.isArray(keys)
172 | createObservable(@, model, key, @__kb.create_options) for key in keys
173 | else
174 | for key, mapping_info of keys when vm_key = assignViewModelKey(@, key)
175 | mapping_info.key or= vm_key unless _.isString(mapping_info)
176 | @[vm_key] = @__kb.view_model[vm_key] = kb.observable(model, mapping_info, @__kb.create_options, @)
177 | return
178 |
179 | # Factory function to create a kb.ViewModel.
180 | kb.viewModel = (model, options, view_model) -> return new kb.ViewModel(arguments)
--------------------------------------------------------------------------------
/src/defaults/default-observable.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require '../core/kb'
11 | require './extensions'
12 |
13 | KEYS_PUBLISH = ['destroy', 'setToDefault']
14 |
15 | # Used to provide a default value when an observable is null, undefined, or the empty string.
16 | #
17 | # @example Provide a observable with observable and/or non observable default argument in the form of:
18 | # var wrapped_name = kb.defaultObservable(kb.observable(model, 'name'), '(no name)');
19 | module.exports = class kb.DefaultObservable
20 |
21 | # Used to create a new kb.DefaultObservable.
22 | #
23 | # @param [ko.observable] target_observable the observable to check for null, undefined, or the empty string
24 | # @param [Any] default_value the default value. Can be a value, string or ko.observable
25 | # @return [ko.observable] the constructor does not return 'this' but a ko.observable
26 | # @note the constructor does not return 'this' but a ko.observable
27 | constructor: (target_observable, @dv) -> # @dv is default value
28 | observable = kb.utils.wrappedObservable @, ko.computed {
29 | read: =>
30 | current_target = ko.utils.unwrapObservable(target_observable())
31 | return if _.isNull(current_target) or _.isUndefined(current_target) then ko.utils.unwrapObservable(@dv) else current_target
32 | write: (value) -> target_observable(value)
33 | }
34 |
35 | # publish public interface on the observable and return instead of this
36 | kb.publishMethods(observable, @, KEYS_PUBLISH)
37 |
38 | return observable
39 |
40 | # Required clean up function to break cycles, release view models, etc.
41 | # Can be called directly, via kb.release(object) or as a consequence of ko.releaseNode(element).
42 | destroy: -> kb.utils.wrappedDestroy(@)
43 |
44 | # Forces the observable to take the default value.
45 | # @note Can be used with kb.utils.setToDefault, kb.Observable.setToDefault, kb.ViewModel.setToDefault
46 | setToDefault: -> kb.utils.wrappedObservable(@)(@dv)
47 |
48 | kb.defaultObservable = (target, default_value) -> return new kb.DefaultObservable(target, default_value)
49 | kb.observableDefault = kb.defaultObservable
50 |
--------------------------------------------------------------------------------
/src/defaults/extensions.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require '../core/kb'
11 |
12 | kb.Observable::setToDefault = -> @__kb_value?.setToDefault?(); return
13 | kb.ViewModel::setToDefault = -> @[vm_key]?.setToDefault?() for vm_key of @__kb.vm_keys; return
14 |
15 | # @example
16 | # var model = new Backbone.Model({name: 'Bob'});
17 | # var view_model = {
18 | # wrapped_name: kb.defaultWrapper(kb.observable(model, 'name'), '(no name)')
19 | # }; // view_model.wrapped name: Bob
20 | # kb.utils.setToDefault(view_model); // view_model.wrapped name: (no name)
21 | kb.utils.setToDefault = (obj) ->
22 | return unless obj
23 |
24 | # observable
25 | if ko.isObservable(obj)
26 | obj.setToDefault?()
27 |
28 | # view model
29 | else if _.isObject(obj)
30 | for key, value of obj
31 | @setToDefault(value) if value and (ko.isObservable(value) or (typeof(value) isnt 'function')) and ((key[0] isnt '_') or key.search('__kb'))
32 | return obj
--------------------------------------------------------------------------------
/src/formatting/formatted-observable.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require '../core/kb'
11 |
12 | arraySlice = Array.prototype.slice
13 |
14 | kb.toFormattedString = (format) ->
15 | result = format.slice()
16 | args = arraySlice.call(arguments, 1)
17 | for index, arg of args
18 | value = ko.utils.unwrapObservable(arg)
19 | value = '' if _.isUndefined(value) or _.isNull(value)
20 |
21 | parameter_index = format.indexOf("\{#{index}\}")
22 | while (parameter_index>=0)
23 | result = result.replace("{#{index}}", value)
24 | parameter_index = format.indexOf("\{#{index}\}", parameter_index+1)
25 | return result
26 |
27 | kb.parseFormattedString = (string, format) ->
28 | regex_string = format.slice(); index = 0; parameter_count = 0; positions = {}
29 | while(regex_string.search("\\{#{index}\\}")>=0)
30 | # store the positions of the replacements
31 | parameter_index = format.indexOf("\{#{index}\}")
32 | while (parameter_index>=0)
33 | regex_string = regex_string.replace("\{#{index}\}", '(.*)')
34 | positions[parameter_index] = index; parameter_count++
35 | parameter_index = format.indexOf("\{#{index}\}", parameter_index+1)
36 | index++
37 | count = index
38 |
39 | regex = new RegExp(regex_string)
40 | matches = regex.exec(string)
41 | matches.shift() if (matches)
42 | # return fake empty data
43 | if not matches or (matches.length isnt parameter_count)
44 | result = []
45 | result.push('') while count-- > 0
46 | return result
47 |
48 | # sort the matches since the parameters could be requested unordered
49 | sorted_positions = _.sortBy(_.keys(positions), (parameter_index, format_index) -> return parseInt(parameter_index,10))
50 | format_indices_to_matched_indices = {}
51 | for match_index, parameter_index of sorted_positions
52 | index = positions[parameter_index]
53 | continue if format_indices_to_matched_indices.hasOwnProperty(index)
54 | format_indices_to_matched_indices[index] = match_index
55 |
56 | results = []; index=0
57 | while (index
75 | # being called by the factory function
76 | if _.isArray(args)
77 | format = format
78 | observable_args = args
79 | else
80 | observable_args = arraySlice.call(arguments, 1)
81 |
82 | observable = kb.utils.wrappedObservable @, ko.computed {
83 | read: ->
84 | args = [ko.utils.unwrapObservable(format)]
85 | args.push(ko.utils.unwrapObservable(arg)) for arg in observable_args
86 | return kb.toFormattedString.apply(null, args)
87 | write: (value) ->
88 | matches = kb.parseFormattedString(value, ko.utils.unwrapObservable(format))
89 | max_count = Math.min(observable_args.length, matches.length); index = 0
90 | while (index kb.utils.wrappedDestroy(@)
101 |
102 | kb.formattedObservable = (format, args) -> return new kb.FormattedObservable(format, arraySlice.call(arguments, 1))
103 | kb.observableFormatted = kb.formattedObservable
104 |
--------------------------------------------------------------------------------
/src/localization/localized-observable.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require '../core/kb'
11 |
12 | KEYS_PUBLISH = ['destroy', 'observedValue', 'resetToCurrent']
13 |
14 | # Locale Manager - if you are using localization, set this property.
15 | # It must have Backbone.Events mixed in and implement a get method like Backbone.Model, eg. get: (attribute_name) -> return somthing
16 | kb.locale_manager or= undefined
17 |
18 | # @abstract You must provide the following two methods:
19 | # * read: function(value, observable) called to get the value and each time the locale changes
20 | # * write: function(localized_string, value, observable) called to set the value (optional)
21 | #
22 | # Base class for observing localized data that changes when the locale changes.
23 | #
24 | # @example How to create a ko.CollectionObservable using the ko.collectionObservable factory.
25 | # kb.ShortDateLocalizer = kb.LocalizedObservable.extend({
26 | # constructor: function(value, options, view_model) {
27 | # kb.LocalizedObservable.prototype.constructor.apply(this, arguments);
28 | # return kb.utils.wrappedObservable(this);
29 | # },
30 | # read: function(value) {
31 | # return Globalize.format(value, Globalize.cultures[kb.locale_manager.getLocale()].calendars.standard.patterns.d, kb.locale_manager.getLocale());
32 | # },
33 | # write: function(localized_string, value) {
34 | # var new_value;
35 | # new_value = Globalize.parseDate(localized_string, Globalize.cultures[kb.locale_manager.getLocale()].calendars.standard.patterns.d, kb.locale_manager.getLocale());
36 | # if (!(new_value && _.isDate(new_value))) {
37 | # return kb.utils.wrappedObservable(this).resetToCurrent();
38 | # }
39 | # return value.setTime(new_value.valueOf());
40 | # }
41 | # });
42 | # var ViewModel = function(model) {
43 | # this.localized_date = kb.observable(model, {
44 | # key: 'date',
45 | # 'default': this.loading_message,
46 | # localizer: ShortDateLocalizer
47 | # }, this);
48 | # };
49 | # var view_model = new ViewModel(new Backbone.Model({date: new Date()}));
50 | #
51 | # @method .extend(prototype_properties, class_properties)
52 | # Class method for JavaScript inheritance.
53 | # @param [Object] prototype_properties the properties to add to the prototype
54 | # @param [Object] class_properties the properties to add to the class
55 | # @return [ko.observable] the constructor does not return 'this' but a ko.observable
56 | # @example
57 | # var MyLocalizedObservable = kb.LocalizedObservable.extend({
58 | # constructor: function(value, options, view_model) {
59 | # // the constructor does not return 'this' but a ko.observable
60 | # return kb.LocalizedObservable.prototype.constructor.apply(this, arguments);
61 | # }
62 | # });
63 | module.exports = class kb.LocalizedObservable
64 | @extend = kb.extend # for Backbone non-Coffeescript inheritance (use "kb.SuperClass.extend({})" in Javascript instead of "class MyClass extends kb.SuperClass")
65 |
66 | # Used to create a new kb.LocalizedObservable. This an abstract class.
67 | #
68 | # @param [Data|ko.observable] value the value to localize
69 | # @param [Object] options the create options
70 | # @option options [Data|ko.observable] default a default value to present when the value is null, an empty string, etc.
71 | # @option options [Function] onChange a notification that gets called when the locale changes. Signature: function(localized_string, value, observable)
72 | # @return [ko.observable] the constructor does not return 'this' but a ko.observable
73 | # @note the constructor does not return 'this' but a ko.observable
74 | constructor: (@value, options, @vm) -> # @vm is view_model
75 | options or= {}; @vm or= {}
76 | @read or kb._throwMissing(this, 'read')
77 | kb.locale_manager or kb._throwMissing(this, 'kb.locale_manager')
78 |
79 | # bind callbacks
80 | @__kb or= {}
81 | @__kb._onLocaleChange = _.bind(@_onLocaleChange, @)
82 | @__kb._onChange = options.onChange
83 |
84 | # internal state
85 | value = ko.utils.unwrapObservable(@value) if @value
86 | @vo = ko.observable(if not value then null else @read(value, null))
87 | observable = kb.utils.wrappedObservable @, ko.computed {
88 | read: =>
89 | ko.utils.unwrapObservable(@value) if @value
90 | @vo() # create a depdenency
91 | return @read(ko.utils.unwrapObservable(@value))
92 |
93 | write: (value) =>
94 | @write or kb._throwUnexpected(@, 'writing to read-only')
95 | @write(value, ko.utils.unwrapObservable(@value))
96 | @vo(value)
97 | @__kb._onChange(value) if @__kb._onChange
98 |
99 | owner: @vm
100 | }
101 |
102 | # publish public interface on the observable and return instead of this
103 | kb.publishMethods(observable, @, KEYS_PUBLISH)
104 |
105 | # start
106 | kb.locale_manager.bind('change', @__kb._onLocaleChange)
107 |
108 | # wrap ourselves with a default value
109 | observable = kb.DefaultObservable and ko.defaultObservable(observable, options.default) if options.hasOwnProperty('default')
110 |
111 | return observable
112 |
113 | # Required clean up function to break cycles, release view models, etc.
114 | # Can be called directly, via kb.release(object) or as a consequence of ko.releaseNode(element).
115 | destroy: ->
116 | kb.locale_manager.unbind('change', @__kb._onLocaleChange)
117 | @vm = null
118 | kb.utils.wrappedDestroy(@)
119 |
120 | # Used to reset the value if localization is not possible.
121 | resetToCurrent: ->
122 | observable = kb.utils.wrappedObservable(@)
123 | current_value = if @value then @read(ko.utils.unwrapObservable(@value)) else null
124 | return if observable() is current_value
125 | observable(current_value)
126 |
127 | # Dual purpose set/get
128 | observedValue: (value) ->
129 | return @value if arguments.length == 0
130 | @value = value; @_onLocaleChange()
131 | return
132 |
133 | ####################################################
134 | # Internal
135 | ####################################################
136 |
137 | # @nodoc
138 | _onLocaleChange: ->
139 | value = @read(ko.utils.unwrapObservable(@value))
140 | @vo(value)
141 | @__kb._onChange(value) if @__kb._onChange
142 |
143 | # factory function
144 | kb.localizedObservable = (value, options, view_model) -> return new kb.LocalizedObservable(value, options, view_model)
145 | kb.observableLocalized = kb.localizedObservable
146 |
--------------------------------------------------------------------------------
/src/triggering/triggered-observable.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require '../core/kb'
11 |
12 | KEYS_PUBLISH = ['destroy']
13 |
14 | # Class for observing emitter events.
15 | #
16 | # @example create an observable whose subscriptions are notified with the change event is triggered.
17 | # var triggered_observable = kb.triggeredObservable(name, 'change');
18 | #
19 | # @example How to watch a emitter for events.
20 | # var trigger_count = 0;
21 | # var emitter = new Backbone.Model();
22 | # var view_emitter = {
23 | # triggered_observable: kb.triggeredObservable(emitter, 'change')
24 | # };
25 | # view_emitter.counter = ko.computed(function() {
26 | # view_emitter.triggered_observable() // add a dependency
27 | # return trigger_count++
28 | # });
29 | # emitter.set(name: 'bob'); # trigger_count: 1
30 | # emitter.set(name: 'george'); # trigger_count: 2
31 | # emitter.set(last: 'smith'); # trigger_count: 3
32 | module.exports = class kb.TriggeredObservable
33 |
34 | # Used to create a new kb.Observable.
35 | #
36 | # @param [Model] emitter the emitter to observe (can be null)
37 | # @param [String] event_selector the event name to trigger Knockout subscriptions on.
38 | # @return [ko.observable] the constructor does not return 'this' but a ko.observable
39 | # @note the constructor does not return 'this' but a ko.observable
40 | constructor: (emitter, @event_selector) ->
41 | emitter or kb._throwMissing(this, 'emitter')
42 | @event_selector or kb._throwMissing(this, 'event_selector')
43 |
44 | # internal state
45 | @vo = ko.observable()
46 | observable = kb.utils.wrappedObservable(@, ko.computed(=> @vo()))
47 |
48 | # publish public interface on the observable and return instead of this
49 | kb.publishMethods(observable, @, KEYS_PUBLISH)
50 |
51 | # create emitter observable
52 | kb.utils.wrappedEventWatcher(@, new kb.EventWatcher(emitter, @, {emitter: _.bind(@emitter, @), update: _.bind(@update, @), event_selector: @event_selector}))
53 |
54 | return observable
55 |
56 | # Required clean up function to break cycles, release view models, etc.
57 | # Can be called directly, via kb.release(object) or as a consequence of ko.releaseNode(element).
58 | destroy: -> kb.utils.wrappedDestroy(@)
59 |
60 | # Dual-purpose getter/setter for the observed emitter.
61 | #
62 | # @overload emitter()
63 | # Gets the emitter or emitter reference
64 | # @return [Model|ModelRef|Collection] the emitter whose events are being bound (can be null)
65 | # @overload emitter(new_emitter)
66 | # Sets the emitter or emitter reference
67 | # @param [Model|ModelRef|Collection] new_emitter the emitter whose events will be bound (can be null)
68 | emitter: (new_emitter) ->
69 | # get or no change
70 | return @ee if (arguments.length == 0) or (@ee is new_emitter)
71 | @update() if (@ee = new_emitter)
72 |
73 | ####################################################
74 | # Internal
75 | ####################################################
76 | # @nodoc
77 | update: ->
78 | return unless @ee # do not trigger if there is no emitter
79 | if @vo() isnt @ee then @vo(@ee) else @vo.valueHasMutated() # manually trigger the dependable
80 |
81 | # factory function
82 | kb.triggeredObservable = (emitter, event_selector) -> return new kb.TriggeredObservable(emitter, event_selector)
83 | kb.observableTriggered = kb.triggeredObservable
84 |
--------------------------------------------------------------------------------
/src/validation/validators.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | knockback.js 1.2.3
3 | Copyright (c) 2011-2016 Kevin Malakoff.
4 | License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 | Source: https://github.com/kmalakoff/knockback
6 | Dependencies: Knockout.js, Backbone.js, and Underscore.js (or LoDash.js).
7 | Optional dependencies: Backbone.ModelRef.js and BackboneORM.
8 | ###
9 |
10 | {_, ko} = kb = require '../core/kb'
11 |
12 | # Regular expressions from Angular.js: https://github.com/angular/angular.js
13 | URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/
14 | EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/
15 | NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/
16 |
17 | # A validator should return true if there are errors (similar to the binding check in HTML, eg. $name().required).
18 | kb.valid =
19 | required: (value) -> !value
20 | url: (value) -> !URL_REGEXP.test(value)
21 | email: (value) -> !EMAIL_REGEXP.test(value)
22 | number: (value) -> !NUMBER_REGEXP.test(value)
23 |
24 | # Convention is that if they end in Fn then returns a function pointer based on parameters passed.
25 | kb.hasChangedFn = (model) ->
26 | m = null; attributes = null
27 | return ->
28 | if m isnt (current_model = ko.utils.unwrapObservable(model)) # change in model
29 | m = current_model
30 | attributes = (if m then m.toJSON() else null)
31 | return false
32 | return false if not (m and attributes)
33 | return !_.isEqual(m.toJSON(), attributes)
34 |
35 | kb.minLengthFn = (length) ->
36 | return (value) -> return not value or value.length < length
37 |
38 | kb.uniqueValueFn = (model, key, collection) ->
39 | return (value) ->
40 | m = ko.utils.unwrapObservable(model); k = ko.utils.unwrapObservable(key); c = ko.utils.unwrapObservable(collection)
41 | return false if not (m and k and c)
42 | return !!_.find(c.models, (test) => (test isnt m) and test.get(k) is value)
43 |
44 | kb.untilTrueFn = (stand_in, fn, model) ->
45 | was_true = false
46 | model.subscribe(-> was_true = false) if model and ko.isObservable(model) # reset if the model changes
47 | return (value) ->
48 | return ko.utils.unwrapObservable(stand_in) unless (f = ko.utils.unwrapObservable(fn))
49 | was_true |= !!(result = f(ko.utils.unwrapObservable(value)))
50 | return (if was_true then result else ko.utils.unwrapObservable(stand_in))
51 |
52 | kb.untilFalseFn = (stand_in, fn, model) ->
53 | was_false = false
54 | model.subscribe(-> was_false = false) if model and ko.isObservable(model) # reset if the model changes
55 | return (value) ->
56 | return ko.utils.unwrapObservable(stand_in) unless (f = ko.utils.unwrapObservable(fn))
57 | was_false |= !(result = f(ko.utils.unwrapObservable(value)))
58 | return (if was_false then result else ko.utils.unwrapObservable(stand_in))
--------------------------------------------------------------------------------
/test/issues/2013_04_04/issue_2013_04_04.coffee:
--------------------------------------------------------------------------------
1 | window.SingleExecutionViewModel = kb.ViewModel.extend({
2 | constructor : (model,options)->
3 | kb.ViewModel.prototype.constructor.call(@,model,{},options )
4 | })
5 |
6 | window.SingleExecutionCollection = kb.CollectionObservable.extend({
7 | constructor: (collection, options) ->
8 | return kb.CollectionObservable.prototype.constructor.call(@, collection, {view_model: SingleExecutionViewModel, options: options})
9 | })
10 |
11 |
12 | window.WorkspaceViewModel = kb.ViewModel.extend({
13 | constructor: (model,options)->
14 | kb.ViewModel.prototype.constructor.call(@,model,{
15 | requires: ['singleExecutions']
16 | factories:
17 | 'singleExecutions': SingleExecutionCollection
18 | },options)
19 | console.log("calling workspace view model")
20 | return
21 |
22 |
23 | init: ->
24 | @subscribeEvents()
25 |
26 | subscribeEvents: -> @singleExecutions.subscribe( (executionViewModels) => alert(executionViewModels) )
27 | })
28 |
29 | class window.Workspace1 extends Backbone.Model
30 | defaults:
31 | singleExecutions: new Backbone.Collection()
32 | batchExecutions: new Backbone.Collection()
33 |
34 | constructor: -> super()
35 |
36 | workspace = new Workspace1()
37 | workspaceViewModel= new WorkspaceViewModel(workspace)
38 | workspaceViewModel.init()
39 | workspace.get('singleExecutions').add(new Backbone.Model())
--------------------------------------------------------------------------------
/test/issues/2013_04_04/issue_2013_04_04.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 2013_04_04 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/issues/2013_04_04/issue_2013_04_04.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.6.2
2 | (function() {
3 | var workspace, workspaceViewModel,
4 | __hasProp = {}.hasOwnProperty,
5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
6 |
7 | window.SingleExecutionViewModel = kb.ViewModel.extend({
8 | constructor: function(model, options) {
9 | return kb.ViewModel.prototype.constructor.call(this, model, {}, options);
10 | }
11 | });
12 |
13 | window.SingleExecutionCollection = kb.CollectionObservable.extend({
14 | constructor: function(collection, options) {
15 | return kb.CollectionObservable.prototype.constructor.call(this, collection, {
16 | view_model: SingleExecutionViewModel,
17 | options: options
18 | });
19 | }
20 | });
21 |
22 | window.WorkspaceViewModel = kb.ViewModel.extend({
23 | constructor: function(model, options) {
24 | kb.ViewModel.prototype.constructor.call(this, model, {
25 | requires: ['singleExecutions'],
26 | factories: {
27 | 'singleExecutions': SingleExecutionCollection
28 | }
29 | }, options);
30 | console.log("calling workspace view model");
31 | },
32 | init: function() {
33 | return this.subscribeEvents();
34 | },
35 | subscribeEvents: function() {
36 | var _this = this;
37 |
38 | return this.singleExecutions.subscribe(function(executionViewModels) {
39 | return alert(executionViewModels);
40 | });
41 | }
42 | });
43 |
44 | window.Workspace1 = (function(_super) {
45 | __extends(Workspace1, _super);
46 |
47 | Workspace1.prototype.defaults = {
48 | singleExecutions: new Backbone.Collection(),
49 | batchExecutions: new Backbone.Collection()
50 | };
51 |
52 | function Workspace1() {
53 | Workspace1.__super__.constructor.call(this);
54 | }
55 |
56 | return Workspace1;
57 |
58 | })(Backbone.Model);
59 |
60 | workspace = new Workspace1();
61 |
62 | workspaceViewModel = new WorkspaceViewModel(workspace);
63 |
64 | workspaceViewModel.init();
65 |
66 | workspace.get('singleExecutions').add(new Backbone.Model());
67 |
68 | }).call(this);
69 |
--------------------------------------------------------------------------------
/test/issues/issue141/issue.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 141 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ... |
19 | |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/issues/issue141/issue.js:
--------------------------------------------------------------------------------
1 | var Model = Backbone.RelationalModel.extend({
2 | url: '...'
3 | });
4 |
5 |
6 | var ListModel = Backbone.RelationalModel.extend({
7 | url: '...',
8 | relations: [{
9 | type: Backbone.HasMany,
10 | key: 'coll',
11 | relatedModel: Model
12 | }
13 | ]
14 | });
15 |
16 | ModelViewModel = kb.ViewModel.extend({
17 | constructor: function (model) {
18 | kb.ViewModel.prototype.constructor.apply(this, arguments);
19 | this.cancel = function () {this.model().destroy();}
20 | }
21 | });
22 |
23 | var listModel = new ListModel();
24 | listModel.get('coll').reset([new Model(), new Model()]);
25 |
26 | var viewModel = new kb.viewModel(listModel, {
27 | factories: {
28 | 'coll.models': ModelViewModel
29 | }
30 | });
31 |
32 |
33 | ko.applyBindings(viewModel, $('#some-div')[0]);
34 |
--------------------------------------------------------------------------------
/test/issues/issue145/issue.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 141 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/test/issues/issue145/issue.js:
--------------------------------------------------------------------------------
1 | // MODIFIED from: https://stackoverflow.com/questions/15111361/backbone-localstorage-saves-but-does-not-fetch-collection-using-knockback
2 |
3 | var Player = Backbone.Model.extend({
4 | urlRoot: '/players',
5 | localStorage: new Backbone.LocalStorage('players'),
6 | defaults: {name: 'Unnamed', team: 'Unassigned'}
7 | });
8 | var Players = Backbone.Collection.extend({
9 | url: '/players',
10 | model: Player,
11 | localStorage: new Backbone.LocalStorage('players')
12 | });
13 |
14 | function GameViewModel() {
15 | var self = this;
16 |
17 | this.players = kb.collectionObservable(new Players());
18 |
19 | console.log('BEFORE fetch: ', self.players.collection().models);
20 | this.players.collection().fetch({success: function() {
21 | console.log('AFTER fetch: ', self.players.collection().models);
22 | }});
23 | };
24 |
25 | // save one if none exist yet
26 | var players = new Players();
27 | if (!players.localStorage.findAll().length) {
28 | new Player().save({name: _.uniqueId('Player ')});
29 | }
30 |
31 | ko.applyBindings(new GameViewModel());
32 |
--------------------------------------------------------------------------------
/test/issues/issue157/issue.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 157 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | This collection is sortable:
19 |
24 |
25 | This is a clone which should stay the same:
26 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/test/issues/issue157/issue.js:
--------------------------------------------------------------------------------
1 | var SortItemModel = Backbone.Model.extend({});
2 |
3 | var ViewModel = kb.ViewModel.extend({
4 | i: 1,
5 |
6 | addItem: function ()
7 | {
8 | var obj = {name: "test" + (this.i++)};
9 | var line = new SortItemModel(obj);
10 | this.model().get('lines').push(line);
11 | console.log("CollectionObservable length:", this.lines().length);
12 | console.log("Collection length:", this.model().get('lines').length);
13 | }
14 | });
15 |
16 | ko.applyBindings(view_model, $('#some-div')[0]);
17 |
--------------------------------------------------------------------------------
/test/issues/issue161/issue-0.19.0.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 161 Test
10 |
11 |
12 |
13 |
14 |
15 |
Parent
16 |
17 |
18 |
Children
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/issues/issue161/issue-0.19.0.js:
--------------------------------------------------------------------------------
1 | var children = new Backbone.Collection([
2 | new Backbone.Model({name:"Charles"}),
3 | new Backbone.Model({name:"Eve"})
4 | ]);
5 |
6 | var parent = new Backbone.Model({
7 | name:"Bob", children:children
8 | });
9 |
10 | var subFactory = function (model) {
11 | var subVm = new kb.ViewModel(model);
12 | subVm.cid = kb.ko.computed(function () {
13 | return model.cid;
14 | });
15 | return subVm;
16 | };
17 |
18 | var vm = new kb.ViewModel(null, {excludes : ["children"]});
19 |
20 | vm.shareOptions().factory.addPathMapping("children.models", subFactory);
21 | vm.createObservables(null, ["children"]);
22 |
23 | vm.model(parent);
24 |
25 | ko.applyBindings(vm, document.getElementById("some-div"));
26 |
27 | console.log(vm.name());
28 | console.log(vm.children()[0].cid());
29 |
--------------------------------------------------------------------------------
/test/issues/issue161/issue.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 161 Test
10 |
11 |
12 |
13 |
14 |
15 |
Parent
16 |
17 |
18 |
Children
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/test/issues/issue161/issue.js:
--------------------------------------------------------------------------------
1 | var children = new Backbone.Collection([
2 | new Backbone.Model({name:"Charles"}),
3 | new Backbone.Model({name:"Eve"})
4 | ]);
5 |
6 | var parent = new Backbone.Model({
7 | name:"Bob", children:children
8 | });
9 |
10 | var subFactory = function (model) {
11 | var subVm = new kb.ViewModel(model);
12 | subVm.cid = kb.ko.computed(function () {
13 | return model.cid;
14 | });
15 | return subVm;
16 | };
17 |
18 | var vm = new kb.ViewModel(null, {
19 | excludes : ["children"],
20 | factories: {"children.models": subFactory}
21 | });
22 |
23 | // Passing in parent instead of null works but in my case I don't have parent
24 | // here so I must be able to set it later using vm.model(parent).
25 | vm.children = kb.observable(null, "children", vm.shareOptions());
26 |
27 | vm.model(parent);
28 |
29 | ko.applyBindings(vm, document.getElementById("some-div"));
30 |
31 | console.log(vm.name());
32 | console.log(vm.children()[0].cid());
33 |
--------------------------------------------------------------------------------
/test/issues/issue34/issue34.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 34 Test
10 |
11 |
12 |
13 |
14 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/test/issues/issue34/issue34.js:
--------------------------------------------------------------------------------
1 | var
2 | Book = Backbone.RelationalModel.extend({
3 | defaults:{
4 | name: 'untitled'
5 | },
6 | idAttribute: "_id"
7 | }),
8 | Author = Backbone.RelationalModel.extend({
9 | defaults:{
10 | name: 'untitled'
11 | },
12 | idAttribute: "_id",
13 | relations:[
14 | {
15 | type: 'HasMany',
16 | key: 'books',
17 | relatedModel: Book,
18 | includeInJSON: "_id",
19 | reverseRelation: {
20 | key: 'author',
21 | includeInJSON: "_id"
22 | }}
23 | ]
24 | }),
25 | BookStore = Backbone.RelationalModel.extend({
26 | relations:[
27 | {
28 | type: 'HasMany',
29 | key: 'books',
30 | relatedModel: Book
31 | },
32 | {
33 | type: 'HasMany',
34 | key: 'authors',
35 | relatedModel: Author
36 | }
37 | ]
38 | });
39 |
40 | Author.setup();
41 | BookStore.setup();
42 |
43 |
44 | var
45 | bs = new BookStore({
46 | books:[
47 | {
48 | _id:"b1",
49 | name: "Book One",
50 | author: "a1"
51 | },
52 | {
53 | _id:"b2",
54 | name: "Book Two",
55 | author: "a1" // does not work
56 | // author: "a2" // works
57 | },
58 | ],
59 | authors:[
60 | {
61 | name: 'fred',
62 | _id: "a1",
63 | books: ["b1","b2"] // does not work
64 | // books: ["b1"] // works
65 | },
66 | {
67 | name: 'ted',
68 | _id: "a2",
69 | books: [] // does not work
70 | // books: ["b2"] // works
71 |
72 | }
73 | ]
74 | });
75 |
76 | var bookViewModel = kb.ViewModel.extend({
77 | constructor:function(model){
78 | kb.ViewModel.prototype.constructor.apply(this, arguments);
79 | this.editMode = ko.observable();
80 | this.author = ko.computed(function(){
81 |
82 | var a = model.get('author');
83 | if(a) return a.get('name');
84 | return null;
85 | });
86 | this.edit = function(){
87 | model._save = model.toJSON();
88 | this.editMode(true);
89 | };
90 | this.confirm = function(){
91 | model._save = null;
92 | this.editMode(false);
93 | };
94 | this.cancel= function(){
95 | model.set(model._save);
96 | this.editMode(false);
97 | };
98 | }
99 | });
100 |
101 | function bookVmcreate(model){
102 | return {
103 | name: kb.observable(model,'name'),
104 | author: ko.computed(function(){
105 | var a = model.get('author');
106 | if(a) return a.get('name');
107 | return null;
108 | }),
109 | editMode: ko.observable(),
110 | edit:function(){
111 | model._save = model.toJSON();
112 | this.editMode(true);},
113 | confirm:function(){
114 | model._save = null;
115 | this.editMode(false);},
116 | cancel:function(){
117 | model.set(model._save);
118 | this.editMode(false);
119 | }
120 | };
121 | };
122 |
123 | var view_model = { books: kb.collectionObservable(bs.get('books'), {
124 | view_model: bookViewModel,
125 | factories: {
126 | 'models.author.books.models': bookViewModel
127 | }
128 | }) }; // does not work when two books refer same author
129 | //view_model_create: bookVmcreate}) }; // fixes not working
130 | ko.applyBindings(view_model);
131 |
--------------------------------------------------------------------------------
/test/issues/issue35/issue35.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 35 Test
10 |
11 |
12 |
13 |
14 |
34 |
35 |
36 | -
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/test/issues/issue35/issue35.js:
--------------------------------------------------------------------------------
1 | var
2 | Book = Backbone.RelationalModel.extend({
3 | defaults:{
4 | name: 'untitled'
5 | },
6 | idAttribute: "_id"
7 | }),
8 | Author = Backbone.RelationalModel.extend({
9 | defaults:{
10 | name: 'untitled'
11 | },
12 | idAttribute: "_id",
13 | relations:[
14 | {
15 | type: 'HasMany',
16 | key: 'books',
17 | relatedModel: Book,
18 | includeInJSON: "_id",
19 | reverseRelation: {
20 | key: 'author',
21 | includeInJSON: "_id"
22 | }}
23 | ]
24 | }),
25 | BookStore = Backbone.RelationalModel.extend({
26 | relations:[
27 | {
28 | type: 'HasMany',
29 | key: 'books',
30 | relatedModel: Book
31 | },
32 | {
33 | type: 'HasMany',
34 | key: 'authors',
35 | relatedModel: Author
36 | }
37 | ]
38 | });
39 |
40 | Author.setup();
41 | BookStore.setup();
42 |
43 |
44 | var
45 | bs = new BookStore({
46 | books:[
47 | {
48 | _id:"b1",
49 | name: "Book One",
50 | author: "a1"
51 | },
52 | {
53 | _id:"b2",
54 | name: "Book Two",
55 | author: "a1" // does not work
56 | // author: "a2" // works
57 | },
58 | ],
59 | authors:[
60 | {
61 | name: 'fred',
62 | _id: "a1",
63 | books: ["b1","b2"] // does not work
64 | // books: ["b1"] // works
65 | },
66 | {
67 | name: 'ted',
68 | _id: "a2",
69 | books: [] // does not work
70 | // books: ["b2"] // works
71 |
72 | }
73 | ]
74 | });
75 |
76 | function bookVmcreate(model){
77 | var vm = {
78 | name: kb.observable(model,'name'),
79 | author: kb.observable(model,'author'),
80 | editMode: ko.observable(),
81 | edit:function(){
82 | model._save = model.toJSON();
83 | this.editMode(true);},
84 | confirm:function(){
85 | model._save = null;
86 | this.editMode(false);},
87 | cancel:function(){
88 | model.set(model._save);
89 | this.editMode(false);
90 | },
91 | model: model
92 | };
93 | vm.authorName = vm.author().name
94 | return vm;
95 | };
96 |
97 | function authorVMcreate(model){
98 | return {
99 | name: kb.observable(model,'name'),
100 | books: kb.collectionObservable(model.get('books'), {models_only: true}),
101 | // books: kb.observable(model,{
102 | // key: 'books',
103 | // read: function(){
104 | // return model.get('books').models;
105 | // },
106 | // write: function(val){
107 | // model.get('books').reset(val);
108 | // }
109 | // }),
110 | model: model
111 |
112 | }
113 | }
114 |
115 | view_model = { books: kb.collectionObservable(bs.get('books'), {
116 | create: bookVmcreate}) ,
117 | authors: kb.collectionObservable(bs.get('authors'), {
118 | create: authorVMcreate}),
119 | showBS: function(){
120 | alert(JSON.stringify(bs.toJSON()));
121 | },
122 | changeInCode:function(){
123 | bs.get('books').at(0).set('author',bs.get('authors').at(1));
124 | }
125 | };
126 | ko.applyBindings(view_model);
--------------------------------------------------------------------------------
/test/issues/issue37/issue37.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 37 Test
10 |
11 |
12 |
13 |
14 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/issues/issue37/issue37.js:
--------------------------------------------------------------------------------
1 | var SubThing = Backbone.Model.extend({}),
2 | SubThings = Backbone.Collection.extend({
3 | model:SubThing
4 | }),
5 | Thing = Backbone.Model.extend({
6 | initialize: function() {
7 | this.subthings = new SubThings;
8 | }
9 | }),
10 | Things = Backbone.Collection.extend({
11 | model:Thing
12 | });
13 |
14 | var things = new Things([
15 | {'name':'Thing One', 'id':1}, {'name':'Thing Two', 'id':2}
16 | ]),
17 | thing = new Thing();
18 |
19 | var SubthingViewModel = function(model) {
20 | this.id = kb.observable(model, 'id');
21 | this.name = kb.observable(model, 'name');
22 | };
23 |
24 | var ThingViewModel = function(model) {
25 | this.subthings = kb.collectionObservable(model.subthings, {
26 | view_model: SubthingViewModel
27 | });
28 | this.name = kb.observable(model, 'name');
29 |
30 | this.allThings = kb.collectionObservable(things, {
31 | view_model: SubthingViewModel
32 | });
33 |
34 | this.onSubmit = function(el) {
35 | document.getElementById('theJson').innerText = JSON.stringify(thing.subthings.toJSON());
36 | console.log(this.subthings());
37 | };
38 | };
39 |
40 | var pageViewModel = {
41 | thing: new ThingViewModel(thing),
42 |
43 | }
44 | ko.applyBindings(pageViewModel);
--------------------------------------------------------------------------------
/test/issues/issue40/require_fixed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 34 Test
10 |
11 |
12 |
13 |
14 |
15 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/test/issues/issue40/require_problem.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 34 Test
10 |
11 |
12 |
13 |
14 |
15 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/test/issues/issue44/issue44.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 34 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/test/issues/issue44/issue44.js:
--------------------------------------------------------------------------------
1 | var model = new Backbone.Model({ value : 5 });
2 |
3 | var viewModel = kb.viewModel(model);
4 |
5 | viewModel.countDown = function () {
6 | model.set("value", model.get("value") - 1);
7 | };
8 | viewModel.reset = function () {
9 | model.set("value", 5);
10 | };
11 |
12 | ko.applyBindings(viewModel, document.body);
--------------------------------------------------------------------------------
/test/issues/issue46/issue46.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 46 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | -
18 | *
19 |
20 | has age:
21 |
22 |
23 |
24 |
25 |
26 |
Clear Selection
27 |
28 |
29 |
30 | You have selected:
31 | and his age is
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/test/issues/issue46/issue46.js:
--------------------------------------------------------------------------------
1 | var bob = new Backbone.Model({id: 1, name:'bob', age:20});
2 | var fred = new Backbone.Model({id: 2, name:'fred', age:30});
3 | var alice = new Backbone.Model({id: 3, name:'alice', age:34});
4 | var people = new Backbone.Collection([bob,fred,alice]);
5 |
6 | var inviteForm = new Backbone.Model({
7 | people: people,
8 | invite: bob
9 | });
10 |
11 | var MyViewModel = function(model) {
12 | this.people = kb.observable(model, 'people');
13 | this.invite = kb.observable(model, 'invite');
14 |
15 | this.clearSelection = function() {
16 | model.set("invite", null);
17 | };
18 |
19 | this.setSelection = function() {
20 | model.set("invite", this.model());
21 | };
22 | };
23 |
24 | $(function() {
25 | window.vm = new MyViewModel(inviteForm);
26 | ko.applyBindings(window.vm);
27 | });
28 |
--------------------------------------------------------------------------------
/test/issues/issue47/issue47.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 47 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/issues/issue47/issue47.js:
--------------------------------------------------------------------------------
1 | var ForumTopic = Backbone.RelationalModel.extend({});
2 |
3 | var Forum = Backbone.RelationalModel.extend({
4 | urlRoot: '/forums',
5 | relations: [{
6 | type: Backbone.HasMany,
7 | key: 'topics',
8 | relatedModel: 'ForumTopic',
9 | collectionType: 'ForumTopicCollection',
10 | reverseRelation: {
11 | key: 'forum',
12 | keySource: 'forum_id',
13 | includeInJSON: 'id'
14 | }
15 | }]});
16 | var ForumViewModel = kb.ViewModel.extend({
17 | constructor: function(model, options, view_model) {
18 | kb.ViewModel.prototype.constructor.call(this, model, {requires: ['id'], options: options});
19 | this.forum_link = ko.computed(function() {
20 | return '#forum/' + this.id();
21 | }, this);
22 |
23 | return this;
24 | }
25 | });
26 |
27 | var TopicViewModel = kb.ViewModel.extend({
28 | //new functions definitions, non override any existing functions
29 | });
30 |
31 | var topic = new ForumTopic();
32 | var view_model = new TopicViewModel(topic, {
33 | requires: ['title', 'body'],
34 | factories: {
35 | 'forum': ForumViewModel
36 | }
37 | });
38 |
39 | ko.applyBindings(view_model);
--------------------------------------------------------------------------------
/test/issues/issue89/issue89.coffee:
--------------------------------------------------------------------------------
1 | class RecordViewModel extends kb.ViewModel
2 | constructor: (model, options={}) ->
3 | common_requires = ['_selected', '_for_delete', 'active', 'name', 'type', '_change_type', 'zone', 'value']
4 | _.extend(options, {requires: common_requires})
5 | super(model, options)
6 |
7 | class Record extends Backbone.Model
8 | class RecordCollection extends Backbone.Collection
9 | model: Record
10 |
11 | records = kb.collectionObservable(new RecordCollection([new Record({id: 1}), new Record({id: 2})]), {view_model: RecordViewModel})
12 | _.last(records()).destroy()
13 |
14 | kb.release(records)
--------------------------------------------------------------------------------
/test/issues/issue89/issue89.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 47 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/issues/issue89/issue89.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.6.3
2 | (function() {
3 | var Record, RecordCollection, RecordViewModel, records, _ref, _ref1,
4 | __hasProp = {}.hasOwnProperty,
5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
6 |
7 | RecordViewModel = (function(_super) {
8 | __extends(RecordViewModel, _super);
9 |
10 | function RecordViewModel(model, options) {
11 | var common_requires;
12 | if (options == null) {
13 | options = {};
14 | }
15 | common_requires = ['_selected', '_for_delete', 'active', 'name', 'type', '_change_type', 'zone', 'value'];
16 | _.extend(options, {
17 | requires: common_requires
18 | });
19 | RecordViewModel.__super__.constructor.call(this, model, options);
20 | }
21 |
22 | return RecordViewModel;
23 |
24 | })(kb.ViewModel);
25 |
26 | Record = (function(_super) {
27 | __extends(Record, _super);
28 |
29 | function Record() {
30 | _ref = Record.__super__.constructor.apply(this, arguments);
31 | return _ref;
32 | }
33 |
34 | return Record;
35 |
36 | })(Backbone.Model);
37 |
38 | RecordCollection = (function(_super) {
39 | __extends(RecordCollection, _super);
40 |
41 | function RecordCollection() {
42 | _ref1 = RecordCollection.__super__.constructor.apply(this, arguments);
43 | return _ref1;
44 | }
45 |
46 | RecordCollection.prototype.model = Record;
47 |
48 | return RecordCollection;
49 |
50 | })(Backbone.Collection);
51 |
52 | records = kb.collectionObservable(new RecordCollection([
53 | new Record({
54 | id: 1
55 | }), new Record({
56 | id: 2
57 | })
58 | ]), {
59 | view_model: RecordViewModel
60 | });
61 |
62 | _.last(records()).destroy();
63 |
64 | kb.release(records);
65 |
66 | }).call(this);
67 |
--------------------------------------------------------------------------------
/test/issues/issue98/issue.coffee:
--------------------------------------------------------------------------------
1 | MainClass = Backbone.RelationalModel.extend
2 | defaults:
3 | number1: 1
4 | relations: [
5 | {
6 | type: Backbone.HasMany
7 | key: 'categories'
8 | relatedModel: 'Category'
9 | collectionType: 'Categories'
10 | reverseRelation:
11 | key: 'main',
12 | includeInJSON: false
13 | }
14 | ]
15 |
16 | window.Category = Backbone.RelationalModel.extend
17 | relations: [
18 | {
19 | type: Backbone.HasMany
20 | key: 'subcategories'
21 | relatedModel: 'Subcategory'
22 | collectionType: 'Subcategories'
23 | reverseRelation:
24 | key: 'category',
25 | includeInJSON: false
26 | }
27 | ]
28 |
29 | window.Subcategory = Backbone.RelationalModel.extend
30 | defaults:
31 | number1: 1
32 | number2: 1
33 |
34 | window.Categories = Backbone.Collection.extend
35 | model: Category
36 |
37 | window.Subcategories = Backbone.Collection.extend
38 | model: Subcategory
39 |
40 | SubcategoryVm = null
41 | CategoryVm = null
42 | MainClassVm = null
43 |
44 | class SubcategoryVm extends kb.ViewModel
45 | constructor: (model, options) ->
46 | super model,
47 | factories:
48 | 'category': CategoryVm
49 | options: options
50 | @computed = ko.computed => @category().main().number1() + @number1() + @number2()
51 |
52 | class CategoryVm extends kb.ViewModel
53 | constructor: (model, options) ->
54 | super model,
55 | requires: ['main']
56 | factories:
57 | 'subcategories.models': SubcategoryVm
58 | 'main': MainClassVm
59 | options: options
60 |
61 | class MainClassVm extends kb.ViewModel
62 | constructor: (model, options) ->
63 | super model,
64 | factories:
65 | 'categories.models': CategoryVm
66 | options: options
67 |
68 | model = new MainClass
69 | categories: [{subcategories: [{}]}]
70 |
71 | try
72 | model_vm = new MainClassVm(model);
73 | catch error
74 | jQuery('#errorPanel').html(error)
--------------------------------------------------------------------------------
/test/issues/issue98/issue.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Knockback Issue 98 Test
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/test/issues/issue98/issue.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.6.3
2 | (function() {
3 | var CategoryVm, MainClass, MainClassVm, SubcategoryVm, error, model, model_vm,
4 | __hasProp = {}.hasOwnProperty,
5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
6 |
7 | MainClass = Backbone.RelationalModel.extend({
8 | defaults: {
9 | number1: 1
10 | },
11 | relations: [
12 | {
13 | type: Backbone.HasMany,
14 | key: 'categories',
15 | relatedModel: 'Category',
16 | collectionType: 'Categories',
17 | reverseRelation: {
18 | key: 'main',
19 | includeInJSON: false
20 | }
21 | }
22 | ]
23 | });
24 |
25 | window.Category = Backbone.RelationalModel.extend({
26 | relations: [
27 | {
28 | type: Backbone.HasMany,
29 | key: 'subcategories',
30 | relatedModel: 'Subcategory',
31 | collectionType: 'Subcategories',
32 | reverseRelation: {
33 | key: 'category',
34 | includeInJSON: false
35 | }
36 | }
37 | ]
38 | });
39 |
40 | window.Subcategory = Backbone.RelationalModel.extend({
41 | defaults: {
42 | number1: 1,
43 | number2: 1
44 | }
45 | });
46 |
47 | window.Categories = Backbone.Collection.extend({
48 | model: Category
49 | });
50 |
51 | window.Subcategories = Backbone.Collection.extend({
52 | model: Subcategory
53 | });
54 |
55 | SubcategoryVm = null;
56 |
57 | CategoryVm = null;
58 |
59 | MainClassVm = null;
60 |
61 | SubcategoryVm = (function(_super) {
62 | __extends(SubcategoryVm, _super);
63 |
64 | function SubcategoryVm(model, options) {
65 | var _this = this;
66 | SubcategoryVm.__super__.constructor.call(this, model, {
67 | factories: {
68 | 'category': CategoryVm
69 | },
70 | options: options
71 | });
72 | this.computed = ko.computed(function() {
73 | return _this.category().main().number1() + _this.number1() + _this.number2();
74 | });
75 | }
76 |
77 | return SubcategoryVm;
78 |
79 | })(kb.ViewModel);
80 |
81 | CategoryVm = (function(_super) {
82 | __extends(CategoryVm, _super);
83 |
84 | function CategoryVm(model, options) {
85 | CategoryVm.__super__.constructor.call(this, model, {
86 | requires: ['main'],
87 | factories: {
88 | 'subcategories.models': SubcategoryVm,
89 | 'main': MainClassVm
90 | },
91 | options: options
92 | });
93 | }
94 |
95 | return CategoryVm;
96 |
97 | })(kb.ViewModel);
98 |
99 | MainClassVm = (function(_super) {
100 | __extends(MainClassVm, _super);
101 |
102 | function MainClassVm(model, options) {
103 | MainClassVm.__super__.constructor.call(this, model, {
104 | factories: {
105 | 'categories.models': CategoryVm
106 | },
107 | options: options
108 | });
109 | }
110 |
111 | return MainClassVm;
112 |
113 | })(kb.ViewModel);
114 |
115 | model = new MainClass({
116 | categories: [
117 | {
118 | subcategories: [{}]
119 | }
120 | ]
121 | });
122 |
123 | try {
124 | model_vm = new MainClassVm(model);
125 | } catch (_error) {
126 | error = _error;
127 | jQuery('#errorPanel').html(error);
128 | }
129 |
130 | }).call(this);
131 |
--------------------------------------------------------------------------------
/test/lib/globalize.culture.en-GB.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Globalize Culture en-GB
3 | *
4 | * http://github.com/jquery/globalize
5 | *
6 | * Copyright Software Freedom Conservancy, Inc.
7 | * Dual licensed under the MIT or GPL Version 2 licenses.
8 | * http://jquery.org/license
9 | *
10 | * This file was generated by the Globalize Culture Generator
11 | * Translation: bugs found in this file need to be fixed in the generator
12 | */
13 |
14 | (function( window, undefined ) {
15 |
16 | var Globalize;
17 |
18 | if ( typeof require !== "undefined"
19 | && typeof exports !== "undefined"
20 | && typeof module !== "undefined" ) {
21 | // Assume CommonJS
22 | Globalize = require( "./globalize" );
23 | } else {
24 | // Global variable
25 | Globalize = window.Globalize;
26 | }
27 |
28 | Globalize.addCultureInfo( "en-GB", "default", {
29 | name: "en-GB",
30 | englishName: "English (United Kingdom)",
31 | nativeName: "English (United Kingdom)",
32 | numberFormat: {
33 | currency: {
34 | pattern: ["-$n","$n"],
35 | symbol: "£"
36 | }
37 | },
38 | calendars: {
39 | standard: {
40 | firstDay: 1,
41 | patterns: {
42 | d: "dd/MM/yyyy",
43 | D: "dd MMMM yyyy",
44 | t: "HH:mm",
45 | T: "HH:mm:ss",
46 | f: "dd MMMM yyyy HH:mm",
47 | F: "dd MMMM yyyy HH:mm:ss",
48 | M: "dd MMMM",
49 | Y: "MMMM yyyy"
50 | }
51 | }
52 | }
53 | });
54 |
55 | }( this ));
56 |
--------------------------------------------------------------------------------
/test/lib/globalize.culture.fr-FR.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Globalize Culture fr-FR
3 | *
4 | * http://github.com/jquery/globalize
5 | *
6 | * Copyright Software Freedom Conservancy, Inc.
7 | * Dual licensed under the MIT or GPL Version 2 licenses.
8 | * http://jquery.org/license
9 | *
10 | * This file was generated by the Globalize Culture Generator
11 | * Translation: bugs found in this file need to be fixed in the generator
12 | */
13 |
14 | (function( window, undefined ) {
15 |
16 | var Globalize;
17 |
18 | if ( typeof require !== "undefined"
19 | && typeof exports !== "undefined"
20 | && typeof module !== "undefined" ) {
21 | // Assume CommonJS
22 | Globalize = require( "./globalize" );
23 | } else {
24 | // Global variable
25 | Globalize = window.Globalize;
26 | }
27 |
28 | Globalize.addCultureInfo( "fr-FR", "default", {
29 | name: "fr-FR",
30 | englishName: "French (France)",
31 | nativeName: "français (France)",
32 | language: "fr",
33 | numberFormat: {
34 | ",": " ",
35 | ".": ",",
36 | NaN: "Non Numérique",
37 | negativeInfinity: "-Infini",
38 | positiveInfinity: "+Infini",
39 | percent: {
40 | ",": " ",
41 | ".": ","
42 | },
43 | currency: {
44 | pattern: ["-n $","n $"],
45 | ",": " ",
46 | ".": ",",
47 | symbol: "€"
48 | }
49 | },
50 | calendars: {
51 | standard: {
52 | firstDay: 1,
53 | days: {
54 | names: ["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],
55 | namesAbbr: ["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],
56 | namesShort: ["di","lu","ma","me","je","ve","sa"]
57 | },
58 | months: {
59 | names: ["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre",""],
60 | namesAbbr: ["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc.",""]
61 | },
62 | AM: null,
63 | PM: null,
64 | eras: [{"name":"ap. J.-C.","start":null,"offset":0}],
65 | patterns: {
66 | d: "dd/MM/yyyy",
67 | D: "dddd d MMMM yyyy",
68 | t: "HH:mm",
69 | T: "HH:mm:ss",
70 | f: "dddd d MMMM yyyy HH:mm",
71 | F: "dddd d MMMM yyyy HH:mm:ss",
72 | M: "d MMMM",
73 | Y: "MMMM yyyy"
74 | }
75 | }
76 | }
77 | });
78 |
79 | }( this ));
80 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --compilers coffee:coffee-script/register
2 | --timeout 10000
3 | --grep @no_options
4 |
--------------------------------------------------------------------------------
/test/spec/core/core.tests.coffee:
--------------------------------------------------------------------------------
1 | assert = assert or require?('chai').assert
2 |
3 | describe 'knockback_core utils @quick @core', ->
4 | kb = window?.kb; try kb or= require?('knockback') catch; try kb or= require?('../../../knockback')
5 | {_, ko} = kb
6 | $ = window?.$
7 |
8 | it 'TEST DEPENDENCY MISSING', (done) ->
9 | assert.ok(!!ko, 'ko')
10 | assert.ok(!!_, '_')
11 | assert.ok(!!kb.Model, 'kb.Model')
12 | assert.ok(!!kb.Collection, 'kb.Collection')
13 | assert.ok(!!kb, 'kb')
14 | done()
15 |
16 | it 'kb.renderTemplate', (done) ->
17 | return done() unless ($ and window?.document)
18 |
19 | kb.statistics = new kb.Statistics() # turn on stats
20 |
21 | class ViewModel
22 | constructor: ->
23 | @name = ko.observable('Bob')
24 | afterRender: ->
25 | @was_called = true
26 |
27 | # test without options override
28 | view_model = new ViewModel()
29 | assert.ok(!view_model.was_called, 'afterRender not called yet')
30 | el = $('')
31 | $('body').append(el)
32 | kb.renderTemplate('the_template1', view_model)
33 | assert.ok(view_model.was_called, 'afterRender was called')
34 |
35 | # test with options override
36 | was_called = false
37 | view_model = new ViewModel()
38 | assert.ok(!view_model.was_called, 'afterRender (options) not called yet')
39 | assert.ok(!view_model.was_called, 'afterRender not called yet')
40 | el = $('')
41 | $('body').append(el)
42 | kb.renderTemplate('the_template2', view_model, {afterRender: -> was_called = true})
43 | assert.ok(was_called, 'afterRender (options) was called')
44 | assert.ok(!view_model.was_called, 'afterRender was not called')
45 |
46 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
47 | done()
48 |
49 | it 'kb.ignore', (done) ->
50 | kb.statistics = new kb.Statistics() # turn on stats
51 |
52 | counter = 0
53 | counter_ignore = 0
54 |
55 | name = ko.observable('Bob')
56 | counter_computed = ko.computed =>
57 | name()
58 | ++counter
59 |
60 | counter_computed_ignore = ko.computed =>
61 | value = kb.ignore ->
62 | name()
63 | ++counter_ignore
64 | assert.equal(value, counter_ignore)
65 |
66 | assert.equal(counter, 1)
67 | assert.equal(counter_ignore, 1)
68 |
69 | name('Fred')
70 | assert.equal(counter, 2)
71 | assert.equal(counter_ignore, 1)
72 |
73 | # ignore with arguments
74 | kb.ignore ((arg1, arg2) -> assert.equal(arg1, 1); assert.equal(arg2, 2)), null, [1, 2]
75 |
76 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
77 | done()
--------------------------------------------------------------------------------
/test/spec/core/monkey-patches.tests.coffee:
--------------------------------------------------------------------------------
1 | assert = assert or require?('chai').assert
2 |
3 | describe 'money-patches @quick @monkey', ->
4 | kb = window?.kb; try kb or= require?('knockback') catch; try kb or= require?('../../../knockback')
5 | {_, ko} = kb
6 |
7 | it 'TEST DEPENDENCY MISSING', (done) ->
8 | assert.ok(!!ko, 'ko')
9 | assert.ok(!!_, '_')
10 | assert.ok(!!kb.Model, 'kb.Model')
11 | assert.ok(!!kb.Collection, 'kb.Collection')
12 | assert.ok(!!kb, 'kb')
13 | done()
14 |
15 | Contact = if kb.Parse then kb.Model.extend('Contact', { defaults: {name: '', number: 0, date: new Date()} }) else kb.Model.extend({ defaults: {name: '', number: 0, date: new Date()} })
16 | Contacts = kb.Collection.extend({model: Contact})
17 |
18 | ## https://github.com/kmalakoff/knockback/issues/124
19 | it 'fixes memory management for extend on kb.observable', (done) ->
20 | return done() unless ko.subscribable?.fn?.extend
21 | kb.statistics = new kb.Statistics() # turn on stats
22 |
23 | model = new Contact({name: 'Bob'})
24 | observable = kb.observable(model, {key: 'name'})
25 | assert.ok !kb.wasReleased(observable), 'observable not released'
26 |
27 | extended_observable = observable.extend({throttle: 100})
28 | assert.ok !kb.wasReleased(observable), 'observable not released'
29 | assert.ok !kb.wasReleased(extended_observable), 'observable not released'
30 |
31 | kb.release(extended_observable)
32 | assert.ok !!kb.wasReleased(observable), 'observable released'
33 | assert.ok !!kb.wasReleased(extended_observable), 'observable released'
34 |
35 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
36 | done()
37 |
38 | ## https://github.com/kmalakoff/knockback/issues/124
39 | it 'fixes memory management for extend on kb.CollectionObservable', (done) ->
40 | return done() unless ko.subscribable?.fn?.extend
41 | kb.statistics = new kb.Statistics() # turn on stats
42 |
43 | ko.extenders.lazyArray = (target, timeout) ->
44 | addTimeout = null
45 | target.elementsToDisplay = ko.observable(0)
46 |
47 | return ko.computed ->
48 | all = target()
49 | if (target.elementsToDisplay() > all.length)
50 | target.elementsToDisplay(all.length)
51 | else if (addTimeout is null && target.elementsToDisplay() < all.length)
52 | addTimeout = setTimeout (->
53 | addTimeout = null
54 | target.elementsToDisplay(target.elementsToDisplay() + 1)
55 | ), timeout
56 | return all.slice(0, target.elementsToDisplay())
57 |
58 | collection_observable = kb.collectionObservable(collection = new Contacts([{name: 'Bob'}]))
59 | assert.ok !kb.wasReleased(collection_observable), 'collection_observable not released'
60 |
61 | extended_collection_observable = collection_observable.extend({lazyArray: 10})
62 | assert.ok !kb.wasReleased(collection_observable), 'collection_observable not released'
63 | assert.ok !kb.wasReleased(extended_collection_observable), 'collection_observable not released'
64 |
65 | kb.release(extended_collection_observable)
66 | assert.ok !!kb.wasReleased(collection_observable), 'collection_observable released'
67 | assert.ok !!kb.wasReleased(extended_collection_observable), 'collection_observable released'
68 |
69 | delete ko.extenders.lazyArray
70 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
71 | done()
72 |
73 | # https://github.com/kmalakoff/knockback/issues/127
74 | it 'extend monkey patch does not cause arrays to destroy', (done) ->
75 | kb.statistics = new kb.Statistics() # turn on stats
76 |
77 | class ViewModel
78 | constructor: ->
79 | @list1 = ko.observableArray(['sds1', 'sdsd1'])
80 | @list2 = ko.observableArray(['sds2', 'sdsd2'])
81 | @totalNumberOfItems = ko.computed => @list1().length + @list2().length
82 |
83 | view_model = new ViewModel()
84 | assert.deepEqual(view_model.list1(), ['sds1', 'sdsd1'])
85 | assert.deepEqual(view_model.list2(), ['sds2', 'sdsd2'])
86 | assert.equal view_model.totalNumberOfItems(), 4
87 |
88 | assert.doesNotThrow -> kb.release(view_model)
89 | assert.ok !view_model.list1
90 | assert.ok !view_model.list2
91 | assert.ok !view_model.totalNumberOfItems
92 |
93 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
94 | done()
95 |
--------------------------------------------------------------------------------
/test/spec/core/utils.tests.coffee:
--------------------------------------------------------------------------------
1 | assert = assert or require?('chai').assert
2 |
3 | describe 'knockback_core utils @quick @utils', ->
4 |
5 | kb = window?.kb; try kb or= require?('knockback') catch; try kb or= require?('../../../knockback')
6 | {_, ko} = kb
7 |
8 | it 'TEST DEPENDENCY MISSING', (done) ->
9 | assert.ok(!!ko, 'ko')
10 | assert.ok(!!_, '_')
11 | assert.ok(!!kb.Model, 'kb.Model')
12 | assert.ok(!!kb.Collection, 'kb.Collection')
13 | assert.ok(!!kb, 'kb')
14 | done()
15 |
16 | it 'kb.utils.wrappedObservable', (done) ->
17 | kb.statistics = new kb.Statistics() # turn on stats
18 |
19 | observable = ko.observable()
20 | instance = {}
21 | kb.utils.wrappedObservable(instance, observable) # set
22 | assert.equal(kb.utils.wrappedObservable(instance), observable, "observable was wrapped") # get
23 |
24 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
25 | done()
26 |
27 | it 'kb.utils.wrappedModel', (done) ->
28 | kb.statistics = new kb.Statistics() # turn on stats
29 |
30 | model = new kb.Model({name: 'Bob'})
31 | instance = {}
32 | assert.equal(kb.utils.wrappedModel(instance), instance, "no model was wrapped so return the instance") # get
33 |
34 | kb.utils.wrappedModel(instance, model) # set
35 | assert.equal(kb.utils.wrappedModel(instance), model, "model was wrapped") # get
36 |
37 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
38 | done()
39 |
40 | it 'kb.utils.wrappedStore', (done) ->
41 | kb.statistics = new kb.Statistics() # turn on stats
42 |
43 | collection_observable = kb.collectionObservable(new kb.Collection())
44 | assert.ok(!!kb.utils.wrappedStore(collection_observable), 'Store is available on a collection observable')
45 |
46 | # can get and share store
47 | collection_observable_shared = kb.collectionObservable(new kb.Collection(), {store: kb.utils.wrappedStore(collection_observable)})
48 | assert.equal(kb.utils.wrappedStore(collection_observable), kb.utils.wrappedStore(collection_observable_shared), 'Store is shared between collection observables')
49 | kb.release(collection_observable_shared) # clean up
50 |
51 | view_model = kb.viewModel(new kb.Model({name: 'Bob'}))
52 | assert.ok(!!kb.utils.wrappedStore(view_model), 'Store is available on a view model')
53 |
54 | # can get and share store
55 | collection_observable_shared = kb.collectionObservable(new kb.Collection(), {store: kb.utils.wrappedStore(view_model)})
56 |
57 | assert.equal(kb.utils.wrappedStore(view_model), kb.utils.wrappedStore(collection_observable_shared), 'Store is shared between collection observable and view model')
58 |
59 | # clean up
60 | kb.release(collection_observable)
61 | kb.release(collection_observable_shared)
62 | kb.release(view_model)
63 |
64 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
65 | done()
66 |
67 | it 'kb.utils.valueType', (done) ->
68 | kb.statistics = new kb.Statistics() # turn on stats
69 |
70 | co = kb.collectionObservable(new kb.Collection())
71 | assert.equal(kb.utils.valueType(co), kb.TYPE_COLLECTION, "kb.CollectionObservable is a collection type")
72 | kb.release(co) # clean up
73 |
74 | o = kb.observable(new kb.Model({name: 'name1'}), 'name')
75 | assert.equal(kb.utils.valueType(o), kb.TYPE_SIMPLE, "kb.Observable is a kb.TYPE_SIMPLE")
76 | kb.release(o) # clean up
77 |
78 | model = new kb.Model({simple_type: 3, model_type: new kb.Model(), collection_type: new kb.Collection()})
79 | view_model = kb.viewModel(model)
80 |
81 | assert.equal(kb.utils.valueType(view_model.simple_type), kb.TYPE_SIMPLE, "simple is kb.TYPE_SIMPLE")
82 | assert.equal(kb.utils.valueType(view_model.model_type), kb.TYPE_MODEL, "model is kb.TYPE_MODEL")
83 | assert.equal(kb.utils.valueType(view_model.collection_type), kb.TYPE_COLLECTION, "collection is kb.TYPE_COLLECTION")
84 | kb.release(view_model) # clean up
85 |
86 | view_model = kb.viewModel(new kb.Model({simple_attr: null, model_attr: null}), {factories: model_attr: kb.ViewModel})
87 | assert.equal(kb.utils.valueType(view_model.simple_attr), kb.TYPE_SIMPLE, 'simple_attr is kb.TYPE_SIMPLE')
88 | assert.equal(kb.utils.valueType(view_model.model_attr), kb.TYPE_MODEL, 'model_attr is kb.TYPE_MODEL')
89 | kb.release(view_model) # clean up
90 |
91 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
92 | done()
93 |
94 | it 'kb.utils.path', (done) ->
95 | kb.statistics = new kb.Statistics() # turn on stats
96 |
97 | assert.equal(kb.utils.pathJoin(null, 'key'), 'key', "key path joined")
98 | assert.equal(kb.utils.pathJoin('bob', 'key'), 'bob.key', "bob.key path joined")
99 | assert.equal(kb.utils.pathJoin('bob.', 'key'), 'bob.key', "bob.key path joined")
100 | assert.equal(kb.utils.pathJoin('bob.harry', 'key'), 'bob.harry.key', "bob.harry.key path joined")
101 | assert.equal(kb.utils.pathJoin('bob.harry.', 'key'), 'bob.harry.key', "bob.harry.key path joined")
102 |
103 | assert.equal(kb.utils.optionsPathJoin({}, 'key').path, 'key', "key path joined")
104 | assert.equal(kb.utils.optionsPathJoin({path: 'bob'}, 'key').path, 'bob.key', "bob.key path joined")
105 | assert.equal(kb.utils.optionsPathJoin({path: 'bob.'}, 'key').path, 'bob.key', "bob.key path joined")
106 | assert.equal(kb.utils.optionsPathJoin({path: 'bob.harry'}, 'key').path, 'bob.harry.key', "bob.harry.key path joined")
107 | assert.equal(kb.utils.optionsPathJoin({path: 'bob.harry.'}, 'key').path, 'bob.harry.key', "bob.harry.key path joined")
108 |
109 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
110 | done()
111 |
112 | # https://github.com/kmalakoff/knockback/issues/103
113 | it 'kb.release handling type changes', (done) ->
114 | kb.statistics = new kb.Statistics() # turn on stats
115 |
116 | model = new kb.Model()
117 | model.set({foo: [1,2,3]})
118 | observable = kb.viewModel(model)
119 | model.set({foo: null})
120 | kb.release(observable)
121 |
122 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
123 | done()
124 |
125 | # https://github.com/kmalakoff/knockback/issues/101
126 | it 'kb.release releases all events', (done) ->
127 | kb.statistics = new kb.Statistics() # turn on stats
128 |
129 | model = new kb.Model({id: 1, name: "Zebra", age: 22, genus: "Equus"});
130 | assert.ok(kb.Statistics.eventsStats(model).count is 0, 'No events yet')
131 |
132 | view_model = {model: kb.viewModel(model)}
133 | assert.ok(kb.Statistics.eventsStats(model).count is 1, "There is 1 event. Expected: 1. Actual: #{JSON.stringify(kb.Statistics.eventsStats(model))}")
134 |
135 | kb.release(view_model)
136 |
137 | assert.ok(kb.Statistics.eventsStats(model).count is 0, "All events cleared. Expected: 0. Actual: #{JSON.stringify(kb.Statistics.eventsStats(model))}")
138 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
139 | done()
140 |
--------------------------------------------------------------------------------
/test/spec/ecosystem/backbone-modelref.tests.coffee:
--------------------------------------------------------------------------------
1 | root = if window? then window else global
2 | assert = assert or require?('chai').assert
3 |
4 | describe 'Knockback.js with Backbone.ModelRef.js @backbone-modelref', ->
5 |
6 | # import Underscore (or Lo-Dash with precedence), Backbone, Knockout, and Knockback
7 | kb = window?.kb; try kb or= require?('knockback') catch; try kb or= require?('../../../knockback')
8 | {_, Backbone, ko} = kb
9 | root.Backbone?.ModelRef or= require?('backbone-modelref')
10 |
11 | it 'TEST DEPENDENCY MISSING', (done) ->
12 | assert.ok(!!ko, 'ko')
13 | assert.ok(!!_, '_')
14 | assert.ok(!!Backbone, 'Backbone')
15 | assert.ok(!!Backbone.ModelRef, 'Backbone.ModelRef')
16 | assert.ok(!!kb, 'kb')
17 | done()
18 |
19 | return unless root.Backbone?.ModelRef
20 |
21 | Contact = if kb.Parse then kb.Model.extend('Contact', { defaults: {name: '', number: 0, date: new Date()} }) else kb.Model.extend({ defaults: {name: '', number: 0, date: new Date()} })
22 | Contacts = kb.Collection.extend({model: Contact})
23 |
24 | it 'Standard use case: just enough to get the picture', (done) ->
25 | kb.statistics = new kb.Statistics() # turn on stats
26 |
27 | ContactViewModel = (model) ->
28 | @_auto = kb.viewModel(model, {keys: {
29 | name: {key:'name'}
30 | number: {key:'number'}
31 | date: {key:'date'}
32 | }}, this)
33 | return
34 |
35 | collection = new Contacts()
36 | model_ref = new Backbone.ModelRef(collection, 'b4')
37 | view_model = new ContactViewModel(model_ref)
38 |
39 | assert.equal(view_model.name(), null, "Is that what we want to convey?")
40 |
41 | collection.add(collection.parse({id: 'b4', name: 'John', number: '555-555-5558', date: new Date(1940, 10, 9)}))
42 | model = collection.get('b4')
43 |
44 | # get
45 | assert.equal(view_model.name(), 'John', "It is a name")
46 | assert.equal(view_model.number(), '555-555-5558', "Not so interesting number")
47 | assert.equal(view_model.date().toString(), new Date(1940, 10, 9).toString(), "John's birthdate matches")
48 |
49 | # set from the view model
50 | assert.equal(model.get('name'), 'John', "Name not changed")
51 | assert.equal(view_model.name(), 'John', "Name not changed")
52 | view_model.number('9222-222-222')
53 | assert.equal(model.get('number'), '9222-222-222', "Number was changed")
54 | assert.equal(view_model.number(), '9222-222-222', "Number was changed")
55 | view_model.date(new Date(1963, 11, 10))
56 | current_date = model.get('date')
57 | assert.equal(current_date.getFullYear(), 1963, "year is good")
58 | assert.equal(current_date.getMonth(), 11, "month is good")
59 | assert.equal(current_date.getDate(), 10, "day is good")
60 |
61 | # set from the model
62 | model.set({name: 'Yoko', number: '818-818-8181'})
63 | assert.equal(view_model.name(), 'Yoko', "Name changed")
64 | assert.equal(view_model.number(), '818-818-8181', "Number was changed")
65 | model.set({date: new Date(1940, 10, 9)})
66 | assert.equal(view_model.date().toString(), (new Date(1940, 10, 9)).toString(), "John's birthdate")
67 | view_model.date(new Date(1940, 10, 10))
68 | current_date = model.get('date')
69 | assert.equal(current_date.getFullYear(), 1940, "year is good")
70 | assert.equal(current_date.getMonth(), 10, "month is good")
71 | assert.equal(current_date.getDate(), 10, "day is good")
72 |
73 | # and cleanup after yourself when you are done.
74 | kb.release(view_model)
75 |
76 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
77 | done()
78 |
79 | it 'Standard use case with kb.ViewModels', (done) ->
80 | kb.statistics = new kb.Statistics() # turn on stats
81 |
82 | class ContactViewModel extends kb.ViewModel
83 | constructor: (model) ->
84 | super(model, {requires: ['name', 'number', 'date']})
85 |
86 | collection = new Contacts()
87 | model_ref = new Backbone.ModelRef(collection, 'b4')
88 | view_model = new ContactViewModel(model_ref)
89 |
90 | assert.equal(view_model.name(), null, "Is that what we want to convey?")
91 |
92 | collection.add(collection.parse({id: 'b4', name: 'John', number: '555-555-5558', date: new Date(1940, 10, 9)}))
93 | model = collection.get('b4')
94 |
95 | # get
96 | assert.equal(view_model.name(), 'John', "It is a name")
97 | assert.equal(view_model.number(), '555-555-5558', "Not so interesting number")
98 | assert.equal(view_model.date().toString(), (new Date(1940, 10, 9)).toString(), "John's birthdate matches")
99 |
100 | # set from the view model
101 | assert.equal(model.get('name'), 'John', "Name not changed")
102 | assert.equal(view_model.name(), 'John', "Name not changed")
103 | view_model.number('9222-222-222')
104 | assert.equal(model.get('number'), '9222-222-222', "Number was changed")
105 | assert.equal(view_model.number(), '9222-222-222', "Number was changed")
106 | view_model.date(new Date(1963, 11, 10))
107 | current_date = model.get('date')
108 | assert.equal(current_date.getFullYear(), 1963, "year is good")
109 | assert.equal(current_date.getMonth(), 11, "month is good")
110 | assert.equal(current_date.getDate(), 10, "day is good")
111 |
112 | # set from the model
113 | model.set({name: 'Yoko', number: '818-818-8181'})
114 | assert.equal(view_model.name(), 'Yoko', "Name changed")
115 | assert.equal(view_model.number(), '818-818-8181', "Number was changed")
116 | model.set({date: new Date(1940, 10, 9)})
117 | assert.equal(view_model.date().toString(), (new Date(1940, 10, 9)).toString(), "John's birthdate")
118 | view_model.date(new Date(1940, 10, 10))
119 | current_date = model.get('date')
120 | assert.equal(current_date.getFullYear(), 1940, "year is good")
121 | assert.equal(current_date.getMonth(), 10, "month is good")
122 | assert.equal(current_date.getDate(), 10, "day is good")
123 |
124 | # and cleanup after yourself when you are done.
125 | kb.release(view_model)
126 |
127 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
128 | done()
129 |
130 | it 'CLEANUP', -> delete Backbone.ModeRef
131 |
--------------------------------------------------------------------------------
/test/spec/ecosystem/backbone-relational.memory-management.tests.coffee:
--------------------------------------------------------------------------------
1 | root = if window? then window else global
2 | assert = assert or require?('chai').assert
3 |
4 | describe 'Knockback.js with Backbone-Relational.js @backbone-relational', ->
5 |
6 | # after -> delete root.Person
7 |
8 | # import Underscore (or Lo-Dash with precedence), Backbone, Knockout, and Knockback
9 | kb = window?.kb; try kb or= require?('knockback') catch; try kb or= require?('../../../knockback')
10 | {_, Backbone, ko} = kb
11 | require?('backbone-relational') unless Backbone?.Relational
12 |
13 | it 'TEST DEPENDENCY MISSING', (done) ->
14 | assert.ok(!!ko, 'ko')
15 | assert.ok(!!_, '_')
16 | assert.ok(!!Backbone, 'Backbone')
17 | assert.ok(!!kb, 'kb')
18 | assert.ok(!!Backbone.Relational, 'Backbone.Relational')
19 | kb.configure({orm: 'backbone-relational'})
20 | done()
21 |
22 | return unless Backbone?.Relational
23 | Backbone.Relational.store = new Backbone.Store(); Backbone.Relational.store.addModelScope?(root)
24 |
25 | root.Person = Person = Backbone.RelationalModel.extend({
26 | relations: [{
27 | type: Backbone.HasMany
28 | key: 'friends'
29 | relatedModel: 'Person'
30 | }]
31 | })
32 |
33 | # ref counted view model
34 | class RefCountableViewModel
35 | constructor: ->
36 | RefCountableViewModel.view_models.push(this)
37 | @ref_count = 1
38 |
39 | refCount: -> return @ref_count
40 | retain: ->
41 | @ref_count++
42 | return @
43 | release: ->
44 | --@ref_count
45 | throw "ref count is corrupt" if @ref_count < 0
46 | unless @ref_count
47 | @is_destroyed = true
48 | @__destroy()
49 | return @
50 |
51 | __destroy: ->
52 | RefCountableViewModel.view_models.splice(_.indexOf(RefCountableViewModel.view_models, this), 1)
53 |
54 | @view_models: []
55 |
56 | # destroyable view model
57 | class DestroyableViewModel
58 | constructor: ->
59 | DestroyableViewModel.view_models.push(this)
60 |
61 | destroy: ->
62 | DestroyableViewModel.view_models.splice(_.indexOf(DestroyableViewModel.view_models, this), 1)
63 |
64 | @view_models: []
65 |
66 | # simple view model
67 | class SimpleViewModel
68 | constructor: ->
69 | @prop = ko.observable()
70 | SimpleViewModel.view_models.push(this)
71 |
72 | @view_models: []
73 |
74 | it 'kb.CollectionObservable with recursive view models', (done) ->
75 | kb.statistics = new kb.Statistics() # turn on stats
76 |
77 | john = new Person({
78 | id: 'person-1-1'
79 | name: 'John'
80 | friends: ['person-1-2', 'person-1-3', 'person-1-4']
81 | })
82 | paul = new Person({
83 | id: 'person-1-2'
84 | name: 'Paul'
85 | friends: ['person-1-1', 'person-1-3', 'person-1-4']
86 | })
87 | george = new Person({
88 | id: 'person-1-3'
89 | name: 'George'
90 | friends: ['person-1-1', 'person-1-2', 'person-1-4']
91 | })
92 | ringo = new Person({
93 | id: 'person-1-4'
94 | name: 'Ringo'
95 | friends: ['person-1-1', 'person-1-2', 'person-1-3']
96 | })
97 |
98 | band = new kb.Collection([john, paul, george, ringo])
99 |
100 | # ref counted view model
101 | RefCountableViewModel.view_models = []
102 | collection_observable = kb.collectionObservable(band, {view_model: RefCountableViewModel})
103 | assert.equal(RefCountableViewModel.view_models.length, 4, "Created: 4")
104 |
105 | instance = collection_observable()[0].retain()
106 |
107 | kb.release(collection_observable)
108 | assert.equal(RefCountableViewModel.view_models.length, 1, "Still one reference")
109 | assert.equal(instance.refCount(), 1, "All instances were destroyed in the collection's store")
110 |
111 | # destroyable view model
112 | DestroyableViewModel.view_models = []
113 | collection_observable = kb.collectionObservable(band, {view_model: DestroyableViewModel})
114 | assert.equal(DestroyableViewModel.view_models.length, 4, "Created: 4")
115 |
116 | kb.release(collection_observable)
117 | assert.equal(DestroyableViewModel.view_models.length, 0, "All destroyed")
118 |
119 | # simple view model
120 | SimpleViewModel.view_models = []
121 | collection_observable = kb.collectionObservable(band, {view_model: SimpleViewModel})
122 | assert.equal(SimpleViewModel.view_models.length, 4, "Created: 4")
123 |
124 | kb.release(collection_observable)
125 | assert.equal(SimpleViewModel.view_models.length, 4, "Destroyed: 4")
126 | assert.ok(!view_model.prop, "Prop destroyed") for view_model in SimpleViewModel.view_models
127 |
128 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
129 | done()
130 |
131 | it 'kb.CollectionObservable with recursive view models and external store', (done) ->
132 | kb.statistics = new kb.Statistics() # turn on stats
133 |
134 | john = new Person({
135 | id: 'person-2-1'
136 | name: 'John'
137 | friends: ['person-2-2', 'person-2-3', 'person-2-4']
138 | })
139 | paul = new Person({
140 | id: 'person-2-2'
141 | name: 'Paul'
142 | friends: ['person-2-1', 'person-2-3', 'person-2-4']
143 | })
144 | george = new Person({
145 | id: 'person-2-3'
146 | name: 'George'
147 | friends: ['person-2-1', 'person-2-2', 'person-2-4']
148 | })
149 | ringo = new Person({
150 | id: 'person-2-4'
151 | name: 'Ringo'
152 | friends: ['person-2-1', 'person-2-2', 'person-2-3']
153 | })
154 |
155 | band = new kb.Collection([john, paul, george, ringo])
156 |
157 | # ref counted view model
158 | store = new kb.Store()
159 | RefCountableViewModel.view_models = []
160 | collection_observable = kb.collectionObservable(band, {view_model: RefCountableViewModel, store: store})
161 | assert.equal(RefCountableViewModel.view_models.length, 4, "Created: 4")
162 |
163 | instance = collection_observable()[0].retain()
164 |
165 | kb.release(collection_observable)
166 | assert.equal(RefCountableViewModel.view_models.length, 4, "Remaining: 4")
167 |
168 | assert.equal(instance.refCount(), 2, "One instance retained and one in the store")
169 |
170 | store.destroy(); store = null
171 |
172 | assert.equal(RefCountableViewModel.view_models.length, 1, "Still one reference")
173 | assert.equal(instance.refCount(), 1, "All instances were destroyed in the collection's store")
174 |
175 | # destroyable view model
176 | store = new kb.Store()
177 | DestroyableViewModel.view_models = []
178 | collection_observable = kb.collectionObservable(band, {view_model: DestroyableViewModel, store: store})
179 | assert.equal(DestroyableViewModel.view_models.length, 4, "Created: 4")
180 |
181 | kb.release(collection_observable)
182 | assert.equal(DestroyableViewModel.view_models.length, 4, "All destroyed")
183 |
184 | store.destroy(); store = null
185 |
186 | # all instances in the collection's store were released when it was destroyed (to remove potential cycles)
187 | assert.equal(DestroyableViewModel.view_models.length, 0, "All destroyed")
188 |
189 | # simple view model
190 | store = new kb.Store()
191 | SimpleViewModel.view_models = []
192 | collection_observable = kb.collectionObservable(band, {view_model: SimpleViewModel, store: store})
193 | assert.equal(SimpleViewModel.view_models.length, 4, "Created: 4")
194 |
195 | kb.release(collection_observable)
196 | assert.equal(SimpleViewModel.view_models.length, 4, "Remaining: 4")
197 | assert.ok(view_model.prop, "Prop destroyed") for view_model in SimpleViewModel.view_models
198 |
199 | store.destroy(); store = null
200 |
201 | # all instances in the collection's store were released when it was destroyed (to remove potential cycles)
202 | assert.equal(SimpleViewModel.view_models.length, 4, "Destroyed: 4")
203 | assert.ok(!view_model.prop, "Prop destroyed") for view_model in SimpleViewModel.view_models
204 |
205 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
206 | done()
207 |
208 | it 'CLEANUP', -> kb.configure({orm: 'default'})
209 |
--------------------------------------------------------------------------------
/test/spec/issues/159.tests.coffee:
--------------------------------------------------------------------------------
1 | assert = assert or require?('chai').assert
2 |
3 | # https://github.com/kmalakoff/knockback/issues/159
4 | describe 'issue 159 @issue159 @quick', ->
5 | kb = window?.kb; try kb or= require?('knockback') catch; try kb or= require?('../../../knockback')
6 | {_, ko} = kb
7 | $ = window?.$
8 |
9 | it 'TEST DEPENDENCY MISSING', (done) ->
10 | assert.ok(!!ko, 'ko')
11 | assert.ok(!!_, '_')
12 | assert.ok(!!kb.Model, 'kb.Model')
13 | assert.ok(!!kb.Collection, 'kb.Collection')
14 | assert.ok(!!kb, 'kb')
15 | done()
16 |
17 | it 'has no issue', (done) ->
18 | return done() unless ($ and window?.document)
19 |
20 | kb.statistics = new kb.Statistics() # turn on stats
21 |
22 | class ViewModel
23 | afterRender: -> @was_called = true
24 |
25 | # test without options override
26 | view_model = new ViewModel()
27 | assert.ok(!view_model.was_called, 'afterRender not called yet')
28 | el = $("""""")
36 | $('body').append(el)
37 | kb.renderTemplate('the_template1', view_model)
38 | assert.ok(view_model.was_called, 'afterRender was called')
39 |
40 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
41 | done()
42 |
--------------------------------------------------------------------------------
/test/spec/parameters.coffee:
--------------------------------------------------------------------------------
1 | kb = window?.kb; try kb or= require?('knockback') catch; try kb or= require?('../../../knockback')
2 |
3 | exports =
4 | LocaleManager: require('./lib/locale_manager')
5 |
6 | (if window? then window else if global? then global).__test__parameters = exports; module?.exports = exports
7 |
--------------------------------------------------------------------------------
/test/spec/plugins/triggering.tests.coffee:
--------------------------------------------------------------------------------
1 | assert = assert or require?('chai').assert
2 |
3 | describe 'triggered-observable @quick @triggering', ->
4 |
5 | kb = window?.kb; try kb or= require?('knockback') catch; try kb or= require?('../../../knockback')
6 | {_, ko} = kb
7 |
8 | it 'TEST DEPENDENCY MISSING', (done) ->
9 | assert.ok(!!ko, 'ko')
10 | assert.ok(!!_, '_')
11 | assert.ok(!!kb.Model, 'kb.Model')
12 | assert.ok(!!kb.Collection, 'kb.Collection')
13 | assert.ok(!!kb, 'kb')
14 | done()
15 |
16 | it '1. Standard use case: simple events notifications', (done) ->
17 | kb.statistics = new kb.Statistics() # turn on stats
18 | model = new kb.Model
19 | model.setLocale = -> model.trigger('change', model)
20 |
21 | trigger_count = 0
22 |
23 | view_model =
24 | triggered_observable: kb.triggeredObservable(model, 'change')
25 | view_model.counter = ko.computed(->
26 | view_model.triggered_observable() # add a dependency
27 | return trigger_count++
28 | )
29 |
30 | assert.equal(trigger_count, 1, "1: was set up")
31 |
32 | model.trigger('change', model)
33 | assert.equal(trigger_count, 2, "2: changed")
34 |
35 | model.setLocale('fr-FR')
36 | assert.equal(trigger_count, 3, "3: changed")
37 |
38 | # and cleanup after yourself when you are done.
39 | kb.release(view_model)
40 | assert.equal(trigger_count, 3, "3: no change")
41 |
42 | # KO doesn't have a dispose for ko.observable
43 | model.setLocale('fr-FR')
44 | assert.equal(trigger_count, 3, "3: no change")
45 |
46 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
47 | done()
48 |
49 | it '2. Standard use case: simple events notifications (kb.observableTriggered)', (done) ->
50 | kb.statistics = new kb.Statistics() # turn on stats
51 | model = new kb.Model
52 | model.setLocale = -> model.trigger('change', model)
53 |
54 | trigger_count = 0
55 |
56 | view_model =
57 | triggered_observable: kb.observableTriggered(model, 'change')
58 | view_model.counter = ko.computed(->
59 | view_model.triggered_observable() # add a dependency
60 | return trigger_count++
61 | )
62 |
63 | assert.equal(trigger_count, 1, "1: was set up")
64 |
65 | model.trigger('change', model)
66 | assert.equal(trigger_count, 2, "2: changed")
67 |
68 | model.setLocale('fr-FR')
69 | assert.equal(trigger_count, 3, "3: changed")
70 |
71 | # and cleanup after yourself when you are done.
72 | kb.release(view_model)
73 | assert.equal(trigger_count, 3, "3: no change")
74 |
75 | # KO doesn't have a dispose for ko.observable
76 | model.setLocale('fr-FR')
77 | assert.equal(trigger_count, 3, "3: no change")
78 |
79 | assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
80 | done()
81 |
--------------------------------------------------------------------------------