kmalakoff/knockback

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

TypeScript

1,058

588 commits

updated Oct 11, 2026

See the code

README

logo

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

Why Knockback?

  • Make amazingly dynamic applications by applying a small number of simple principles
  • Leverage the wonderful work from both the Backbone and Knockout communities
  • Full TypeScript support with type definitions included

Installation

npm install knockback

Peer Dependencies

Knockback requires these peer dependencies:

npm install backbone knockout underscore

Examples

Simple

The HTML:

<label>First Name: </label><input data-bind="value: first_name, valueUpdate: 'keyup'" />
<label>Last Name: </label><input data-bind="value: last_name, valueUpdate: 'keyup'" />

And...engage:

import Backbone from 'backbone';
import ko from 'knockout';
import kb from '@mcpeasy/knockback';

const model = new Backbone.Model({ first_name: 'Bob', last_name: 'Smith' });
ko.applyBindings(kb.viewModel(model));

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

Advanced

The View Model:

import Backbone from 'backbone';
import ko from 'knockout';
import kb from '@mcpeasy/knockback';

// Create a model
const model = new Backbone.Model({ first_name: 'Bob', last_name: 'Smith' });

interface PersonViewModel {
  first_name: ko.Observable<string>;
  last_name: ko.Observable<string>;
  full_name: ko.Computed<string>;
}

// Create a view model with a computed property
const vm = kb.viewModel<PersonViewModel>(model, {
  extend: (vm) => ({
    full_name: ko.computed(() => `${vm.first_name()} ${vm.last_name()}`);
  }),
});

// Apply bindings
ko.applyBindings(vm);

// ... do stuff then clean up
vm.dispose();

Named imports are also supported if you prefer them:

```typescript
import { viewModel } from '@mcpeasy/knockback';

const vm = viewModel<PersonViewModel>(model);
vm.dispose();

// dispose() is attached automatically and is called by releaseOnNodeRemove / KO component disposal. // You no longer call destroy() manually.


You can also pass `extend` as a plain object if you prefer:

```typescript
const vm = kb.viewModel<PersonViewModel>(model, {
  extend: {
    full_name: ko.computed(() => `${model.get('first_name')} ${model.get('last_name')}`),
  },
});

The HTML:

<h1 data-bind="text: 'Hello ' + full_name()"></h1>
<label>First Name: </label><input data-bind="value: first_name, valueUpdate: 'keyup'" />
<label>Last Name: </label><input data-bind="value: last_name, valueUpdate: 'keyup'" />

Now, the greeting updates as you type!

Breaking Changes in v2.0

Knockback v2.0 uses modern TypeScript conventions. All option names and API properties have been changed from snake_case to camelCase:

Core Options:

Old (v1.x)New (v2.0)
static_defaultsstaticDefaults
event_watchereventWatcher
view_modelviewModel
models_onlymodelsOnly
auto_compactautoCompact
sort_attributesortAttribute

Plugin Options:

Old (v1.x)New (v2.0)Plugin
kb.locale_managerkb.localeManagerLocalization
event_selectoreventSelectorTriggering
validation_optionsvalidationOptionsValidation
no_attachnoAttachValidation
$error_count$errorCountValidation
$active_error$activeErrorValidation

Migration example:

// v1.x (old)
const co = kb.collectionObservable(collection, {
  view_model: PersonViewModel,
  sort_attribute: 'name',
  auto_compact: true,
});

// v2.0 (new)
const co = kb.collectionObservable(collection, {
  viewModel: PersonViewModel,
  sortAttribute: 'name',
  autoCompact: true,
});

API

Core Functions

  • observable(model, key, options?) - Create an observable bound to a model attribute
  • viewModel(model, options?) - Create observables for all model attributes
  • collectionObservable(collection, options?) - Create an observable array bound to a collection
  • dispose() - Attached to view models/observables; called by releaseOnNodeRemove and KO component disposal
  • dispose(obj) - Helper to dispose plain objects that contain Knockback observables

Plugins

  • defaultObservable<T>(observable, defaultValue) - Provide default values
  • formattedObservable(format, ...args) - Two-way string formatting
  • localizedObservable(value, options) - Locale-aware observables

Localization (typed)

import kb from '@mcpeasy/knockback';
import type { LocaleManager } from '@mcpeasy/knockback';

class MyLocaleManager implements LocaleManager {
  get(id: string): string {
    return id;
  }
  getLocale(): string {
    return 'en';
  }
  setLocale(_locale: string): void {}
}

kb.setLocaleManager(new MyLocaleManager());
const localeManager = kb.getLocaleManager<MyLocaleManager>();

Leak checks with Statistics

const stats = new kb.Statistics();
kb.setStatistics(stats);

const before = stats.snapshot();
// ... route change + dispose old page ...
const after = stats.snapshot();

const delta = stats.diff(before, after);
console.log(delta);
  • triggeredObservable(emitter, event) - Event-based observable updates
  • valueValidator(observable, validators) - Value validation

Documentation

Dependencies

Contributing

To build the library:

npm run build

To run tests:

npm test

License

MIT

kmalakoff/knockback

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

TypeScript

1,058

588 commits

updated Oct 11, 2026

See the code

README

logo

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

Why Knockback?

  • Make amazingly dynamic applications by applying a small number of simple principles
  • Leverage the wonderful work from both the Backbone and Knockout communities
  • Full TypeScript support with type definitions included

Installation

npm install knockback

Peer Dependencies

Knockback requires these peer dependencies:

npm install backbone knockout underscore

Examples

Simple

The HTML:

<label>First Name: </label><input data-bind="value: first_name, valueUpdate: 'keyup'" />
<label>Last Name: </label><input data-bind="value: last_name, valueUpdate: 'keyup'" />

And...engage:

import Backbone from 'backbone';
import ko from 'knockout';
import kb from '@mcpeasy/knockback';

const model = new Backbone.Model({ first_name: 'Bob', last_name: 'Smith' });
ko.applyBindings(kb.viewModel(model));

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

Advanced

The View Model:

import Backbone from 'backbone';
import ko from 'knockout';
import kb from '@mcpeasy/knockback';

// Create a model
const model = new Backbone.Model({ first_name: 'Bob', last_name: 'Smith' });

interface PersonViewModel {
  first_name: ko.Observable<string>;
  last_name: ko.Observable<string>;
  full_name: ko.Computed<string>;
}

// Create a view model with a computed property
const vm = kb.viewModel<PersonViewModel>(model, {
  extend: (vm) => ({
    full_name: ko.computed(() => `${vm.first_name()} ${vm.last_name()}`);
  }),
});

// Apply bindings
ko.applyBindings(vm);

// ... do stuff then clean up
vm.dispose();

Named imports are also supported if you prefer them:

```typescript
import { viewModel } from '@mcpeasy/knockback';

const vm = viewModel<PersonViewModel>(model);
vm.dispose();

// dispose() is attached automatically and is called by releaseOnNodeRemove / KO component disposal. // You no longer call destroy() manually.


You can also pass `extend` as a plain object if you prefer:

```typescript
const vm = kb.viewModel<PersonViewModel>(model, {
  extend: {
    full_name: ko.computed(() => `${model.get('first_name')} ${model.get('last_name')}`),
  },
});

The HTML:

<h1 data-bind="text: 'Hello ' + full_name()"></h1>
<label>First Name: </label><input data-bind="value: first_name, valueUpdate: 'keyup'" />
<label>Last Name: </label><input data-bind="value: last_name, valueUpdate: 'keyup'" />

Now, the greeting updates as you type!

Breaking Changes in v2.0

Knockback v2.0 uses modern TypeScript conventions. All option names and API properties have been changed from snake_case to camelCase:

Core Options:

Old (v1.x)New (v2.0)
static_defaultsstaticDefaults
event_watchereventWatcher
view_modelviewModel
models_onlymodelsOnly
auto_compactautoCompact
sort_attributesortAttribute

Plugin Options:

Old (v1.x)New (v2.0)Plugin
kb.locale_managerkb.localeManagerLocalization
event_selectoreventSelectorTriggering
validation_optionsvalidationOptionsValidation
no_attachnoAttachValidation
$error_count$errorCountValidation
$active_error$activeErrorValidation

Migration example:

// v1.x (old)
const co = kb.collectionObservable(collection, {
  view_model: PersonViewModel,
  sort_attribute: 'name',
  auto_compact: true,
});

// v2.0 (new)
const co = kb.collectionObservable(collection, {
  viewModel: PersonViewModel,
  sortAttribute: 'name',
  autoCompact: true,
});

API

Core Functions

  • observable(model, key, options?) - Create an observable bound to a model attribute
  • viewModel(model, options?) - Create observables for all model attributes
  • collectionObservable(collection, options?) - Create an observable array bound to a collection
  • dispose() - Attached to view models/observables; called by releaseOnNodeRemove and KO component disposal
  • dispose(obj) - Helper to dispose plain objects that contain Knockback observables

Plugins

  • defaultObservable<T>(observable, defaultValue) - Provide default values
  • formattedObservable(format, ...args) - Two-way string formatting
  • localizedObservable(value, options) - Locale-aware observables

Localization (typed)

import kb from '@mcpeasy/knockback';
import type { LocaleManager } from '@mcpeasy/knockback';

class MyLocaleManager implements LocaleManager {
  get(id: string): string {
    return id;
  }
  getLocale(): string {
    return 'en';
  }
  setLocale(_locale: string): void {}
}

kb.setLocaleManager(new MyLocaleManager());
const localeManager = kb.getLocaleManager<MyLocaleManager>();

Leak checks with Statistics

const stats = new kb.Statistics();
kb.setStatistics(stats);

const before = stats.snapshot();
// ... route change + dispose old page ...
const after = stats.snapshot();

const delta = stats.diff(before, after);
console.log(delta);
  • triggeredObservable(emitter, event) - Event-based observable updates
  • valueValidator(observable, validators) - Value validation

Documentation

Dependencies

Contributing

To build the library:

npm run build

To run tests:

npm test

License

MIT

Project health

Overall

2.0/10

Maintained

0/10

Code review

0/10
OpenSSF Scorecard

Latest release

1.2.3 · May 9, 2019

Releases this year

0

Significant stargazers

Brian Goff

872 followers · starred Nov 2011

Ryan Carniato

3,859 followers · starred Apr 2025

Vitaly Puzrin

488 followers · starred Apr 2014

Andrew Cholakian

304 followers · starred Jun 2013