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