Knockoutjs.com - Twitter client example
http://knockoutjs.com/examples/twitter.html
by rniemeyer
HTML
<link rel="stylesheet" href="http://knockoutjs.com/examples/resources/twitterExample.css">
<script src="http://knockoutjs.com/examples/resources/twitterApi.js"></script>
<script src="http://knockoutjs.com/downloads/knockout-3.0.0.js"></script>
<div class='liveExample'>
<div class='configuration'>
<div class='listChooser'>
<button data-bind='click: deleteList, enable: editingList.name'>Delete</button>
<button data-bind='click: saveChanges, enable: hasUnsavedChanges'>Save</button>
<select data-bind='options: savedLists, optionsValue: "name", value: editingList.name'> </select>
</div>
<p>Currently viewing <span data-bind='text: editingList.userNames().length'> </span> user(s):</p>
<div class='currentUsers' data-bind='with: editingList'>
<ul data-bind='foreach: userNames'>
<li>
<button data-bind='click: $root.removeUser'>Remove</button>
<div data-bind="text: $data"> </div>
</li>
</ul>
</div>
<form data-bind='submit: addUser'>
<label>Add user:</label>
<input data-bind='value: userNameToAdd, valueUpdate: "keyup", css: { invalid: !userNameToAddIsValid() }' />
<button data-bind='enable: canAddUserName' type='submit'>Add</button>
</form>
</div>
<div class='tweets'>
<div class='loadingIndicator'>Loading...</div>
<table width='100%' data-bind="foreach: currentTweets">
<tr>
<td><img data-bind='attr: { src: profile_image_url }' /></td>
<td>
<a class='twitterUser' data-bind='attr: { href: "http://twitter.com/" + from_user }, text: from_user' href='http://twitter.com/${ from_user }' > </a>
<span data-bind="text: text"> </span>
<div class='tweetInfo' data-bind='text: created_at'> </div>
</td>
...
CSS
body { font-family: arial; font-size: 14px; }
.liveExample { padding: 1em; background-color: #EEEEDD; border: 1px solid #CCC; max-width: 655px; }
.liveExample input { font-family: Arial; }
.liveExample b { font-weight: bold; }
.liveExample p { margin-top: 0.9em; margin-bottom: 0.9em; }
.liveExample select[multiple] { width: 100%; height: 8em; }
.liveExample h2 { margin-top: 0.4em; font-weight: bold; font-size: 1.2em; }
.liveExample select { height: 1.7em; }
.liveExample button { height: 2em; }
li { list-style-type: disc; margin-left: 20px; }
JavaScript
// The view model holds all the state we're working with. It also has methods that can edit it, and it uses
// computed observables to calculate more state in terms of the underlying data
// --
// The view (i.e., the HTML UI) binds to this using data-bind attributes, so it always stays up-to-date with
// the view model, even though the view model does not know or care about any view that binds to it
var savedLists = [
{ name: "Celebrities", userNames: ['JohnCleese', 'MCHammer', 'StephenFry', 'algore', 'StevenSanderson']},
{ name: "Microsoft people", userNames: ['BillGates', 'shanselman', 'ScottGu']},
{ name: "Tech pundits", userNames: ['Scobleizer', 'LeoLaporte', 'techcrunch', 'BoingBoing', 'timoreilly', 'codinghorror']}
];
var TwitterListModel = function(lists, selectedList) {
this.savedLists = ko.observableArray(lists);
this.editingList = {
name: ko.observable(selectedList),
userNames: ko.observableArray()
};
this.userNameToAdd = ko.observable("");
this.currentTweets = ko.observableArray([])
this.findSavedList = function(name) {
var lists = this.savedLists();
return ko.utils.arrayFirst(lists, function(list) {
return list.name === name;
});
};
this.addUser = function() {
if (this.userNameToAdd() && this.userNameToAddIsValid()) {
this.editingList.userNames.push(this.userNameToAdd());
this.userNameToAdd("");
}
};
this.removeUser = function(userName) {
this.editingList.userNames.remove(userName)
}.bind(this);
this.saveChanges = function() {
var saveAs = prompt("Save as", this.editingList.name());
if (saveAs) {
var dataToSave = this.editingList.userNames().slice(0);
var existingSavedList = this.findSavedList(saveAs);
if (existingSavedList) existingSavedList.userNames = dataToSave; // Overwrite existing list
else...