Skip to content

Instantly share code, notes, and snippets.

@chrisvfritz
Last active August 23, 2021 10:14
Show Gist options
  • Star 37 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save chrisvfritz/9e5f2d6826af00fcbace7be8f6dccb89 to your computer and use it in GitHub Desktop.
Save chrisvfritz/9e5f2d6826af00fcbace7be8f6dccb89 to your computer and use it in GitHub Desktop.
<!--
In Vue, we use v-model for all form bindings, while
Knockout maintains separate binding types, such as
textInput, checked, and options. In some cases,
such as for an input of type "range", Knockout
simply doesn't have an equivalent two-way binding
helper and the more verbose value and valueUpdate
must be used.
-->
<div id="app">
<input data-bind="textInput: newTodoText, event: { keypress: addTodo }">
<ul data-bind="foreach: todos">
<!--
Inside of the foreach, a new scope is implicitly
created and we must reference $parent or $root to
access other scopes. In contrast, Vue maintains
the root scope and allows you to explicitly name
any nested scopes, so that it's always clear
what data you're accessing.
-->
<li>
<span data-bind="text: title"></span>
<button data-bind="event: { click: $root.removeTodo }">
X
</button>
</li>
</ul>
</div>
var TodoList = function () {
var self = this
self.todos = ko.observableArray([])
self.newTodoText = ko.observable('')
self.addTodo = function (_, event) {
// Since it's such a common use case, it's worth
// noting that Knockout lacks native keyCode
// modifiers. This means it's necessary to either
// detect the keyCode here, thus coupling addTodo
// to the specific context it's called in, as we
// do below, or go through the tedious process of
// declaring a new bindingHandler.
if (event.keyCode === 13 && self.newTodoText()) {
self.todos.push({ title: self.newTodoText() })
self.newTodoText('')
}
// Since event handlers in Knockout will interfere
// with each other by default, we must explicitly
// return true to avoid breaking the input.
return true
}
self.removeTodo = function (todo) {
// Instead of making it easy to pass arguments
// to a method, Knockout implicitly passes the
// current scope as the first argument then adds
// a proprietary method to observable arrays so
// that manipulating them is easier.
self.todos.remove(todo)
}
}
ko.applyBindings(new TodoList(), document.getElementById('app'))
<div id="app">
<!--
Vue uses separate directives for different kinds of
binding, such as v-model for two-way input binding,
v-on for events, and v-for to repeat elements.
-->
<input v-model="newTodoText" v-on:keyup.enter="addTodo">
<ul>
<!--
Vue prefers to explicitly name new variables, rather
than implicitly creating new scopes.
-->
<li v-for="(todo, index) in todos">
{{ todo.title }}
<!--
In general, Vue tries its best to act in very
unsurprising ways. One example of this is that
event callbacks in Vue behave exactly like they do
outside of Vue. In Knockout however, it's
surprisingly cumbersome to just pass an argument
to a method.
-->
<button v-on:click="removeTodo(index)">X</button>
</li>
</ul>
</div>
new Vue({
el: '#app',
// Instead of wrapping reactive data in observable
// objects, Vue knows what to make reactive by
// having it declared in the `data` option
data: {
todos: []
newTodoText: '',
},
// Instead of adding everything directly to the
// instance, Vue has specific options to help
// organize your components. Any methods are
// registered under the "methods" options, computed
// properties are registered under "computed", etc.
methods: {
addTodo: function () {
// In Knockout, reactive data acts mostly like
// plain JavaScript objects, except getting
// and setting are done through a function call.
// In Vue, there's no special syntax for getters
// and setters.
if (this.newTodoText) {
this.todos.push({ title: this.newTodoText })
this.newTodoText = ''
}
},
removeTodo: function (index) {
this.todos.splice(index, 1)
}
}
})
@chrisvfritz
Copy link
Author

chrisvfritz commented Sep 4, 2016

For anyone who'd like to comment here, please note that GitHub does not trigger notifications for Gist comments, so I probably won't see it! If you notice an error, you should instead submit an issue to the Vue docs repo.

@onlyurei
Copy link

onlyurei commented Oct 4, 2016

With some small customizations, such as opt-in the hidden gem https://github.com/nathanboktae/knockout-es5-option4, Knockout's approach, in my opinion can be as unopinionated as it can be. Here's my attempt for example: https://github.com/onlyurei/todomvc-knockout-spa

@JunCCore
Copy link

JunCCore commented Apr 1, 2017

nice

@CrypticCalamari
Copy link

The data object in the Vue js example has a missing comma. Looks like you switched the order of data.todos and data.newTodoText, but forgot to change the position of the comma.

@datapolaris
Copy link

First!

NOT!

@BMeyerKC
Copy link

BMeyerKC commented Oct 20, 2017

I feel like this comment,

Inside of the foreach, a new scope is implicitly
created and we must reference $parent or $root to
access other scopes. In contrast, Vue maintains
the root scope and allows you to explicitly name
any nested scopes, so that it's always clear
what data you're accessing.

is unfair, knockout does have a way of maintaining scope, and will use it frequently,
<ul data-bind="foreach: { data: todos, as: "todo" }">

@stephenlb
Copy link

Nice! 👍

@artlung
Copy link

artlung commented May 31, 2019

As I'm new to Vue and a rather old hand at Knockout this was highly useful to me. Thanks so much for putting this together!

@forgaoqiang
Copy link

  data: {
    todos: []
    newTodoText: '',
  },

There should be a coma between todos:[] and netTodoText

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment