Unobtrusive KnockoutJS Bindings

An example of using KnockoutJS without the default data-bind HTML5 markup. Uses a JS-based bindings object and model binder, both illustrated in the JavaScript page

HTML

<script src="http://knockoutjs.com/js/jquery.tmpl.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-debug.js"></script>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>Create</title>
        <meta name="author" content="Brandon Satrom" />
        <meta name="viewport" content="width=device-width; initial-scale=1.0" />     
    </head>
    <body>
        <div id="site-wrapper">          
            For a full writeup of this example, <a href="http://userinexperience.com/?p=633">click here</a>
            <h2>Create a Speaker</h2>
            <form data-bind="submit: addSpeaker">
                <fieldset>
                    <legend>Speaker Info</legend>
                    Name: <input type="text" id="name" /> <br />                                                       Bio: <input type="text" id="bio" /> <br />
                    Twitter Handle: <input id="twitterHandle" type="text" /> <br />
                    Photo Url: <input id="photoUrl" type="text" />    
                </fieldset>
                <fieldset>
                    <legend>Favorite Languages</legend>
                    New item:
                    <input type="text" id="languageToAdd" />
                    <button type="submit" id="addLanguage">Add</button>
                    <p>Your choices:</p>
                    <select multiple="multiple" width="50" id="languages"> </select>
                </fieldset>
                <input type="submit" value="Create Speaker" />
                <!-- Profile Preview -->
                <article id="profilePreview">
                    <header>
                        <div><img id="image" /></div>
                        <div>
                            <h1>
                                <span id="displayName"></span> :: 
                                <a id="twitterUrl">
                                    @<span id="displayTwitterHandle"></span>
             ...

CSS

/* 
   Reset
------------------------------------------------------------------- */

html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, textarea, input, select {margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline;}
table {border-collapse: collapse; border-spacing: 0;}
caption, th, td {text-align: left; font-weight: normal;}
table, td, th {vertical-align: middle;}
blockquote:before, blockquote:after, q:before, q:after {content: "";}
blockquote, q {quotes: "" "";}
a img {border: none;}
:focus {outline: 0;}


/* 
   General 
------------------------------------------------------------------- */

html {
    height: 100%;
    padding-bottom: 1px; /* force scrollbars */
}

body {
    background: #FFF;
    color: #444;
    font: normal 75% sans-serif;
    line-height: 1.5;
}

/* 
   Typography 
------------------------------------------------------------------- */

/* Headings */

h1,h2,h3,h4,h5,h6 {
    color: #444;
    font-weight: normal;
    line-height: 1;
    margin-bottom: 0.3em;
}
h4,h5,h6 {font-weight: bold;}

h1 {font-size: 2.6em;}
h2 {font-size: 2em;}
h3 {font-size: 1.5em;}
h4 {font-size: 1.25em;}
h5 {font-size: 1.1em;}
h6 {font-size: 1em;}

h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin: 0;}


/* Links */

a:focus,a:hover {color: #039;}
a {
    color: #456;
    text-decoration: none;
}
a:hover {text-decoration: underline;}

a.feed {
    background: url('img/icon-feed.gif') no-repeat left center;
    padding-left: 18px;
}
a.more {
    color: #579;
    font-weight: bold;
}
a.more:hover {color: #234;}


/* Text elements */

p {margin-bottom: 1em;}

abbr, acronym {border-bottom: 1px dotted #666;}
address {margin-bottom: 1.5em;}
blockquote {margin: 1.5em;}
del, blockquote {...

JavaScript

var speakerView = {};
speakerView.createBindings = function(bindlist) {
    function setBinding(id, value) {
        var el = document.getElementById(id);
        if (el) {
            el.setAttribute('data-bind', value);
        }
    }

    for (var inputsKey in bindlist.inputs) {
        if (bindlist.inputs.hasOwnProperty(inputsKey)) {
            setBinding(bindlist.inputs[inputsKey], 'value: ' + bindlist.inputs[inputsKey]);
        }
    }

    for (var optionsKey in bindlist.options) {
        if (bindlist.inputs.hasOwnProperty(optionsKey)) {
            setBinding(bindlist.options[optionsKey], 'options: ' + bindlist.options[optionsKey]);
        }
    }

    for (var key in bindlist.custom) {
        if (bindlist.custom.hasOwnProperty(key)) {
            setBinding(key, bindlist.custom[key]);
        }
    }
};

var viewModel = {
    id: '',
    name: ko.observable(''),
    email: ko.observable(''),
    bio: ko.observable(''),
    twitterHandle: ko.observable(''),
    state: ko.observable(''),
    photoUrl: ko.observable(''),
    languages: ko.observableArray([]),
    favoriteTopics: ko.observableArray([]),
    averageRating: ko.observable(''),
    languageToAdd: null,//ko.observable(''),
    topicToAdd: null//ko.observable('')
};
viewModel.twitterUrl = ko.dependentObservable(function() {
    return "http://www.twitter.com/" + viewModel.twitterHandle();
});
viewModel.addLanguage = function() {
    if (viewModel.languageToAdd() !== '') {
        viewModel.languages.push(viewModel.languageToAdd());
        viewModel.languageToAdd('');
    }
};
viewModel.addTopic = function() {
    if (viewModel.topicToAdd() !== '') {
        viewModel.favoriteTopics.push(viewModel.topicToAdd());
        viewModel.topicToAdd('');
    }
};
viewModel.addSpeaker = function() {
    $.ajax({
        url: "/speakers/create/",
        type: 'post',
        data: ko.toJSON(this),
        contentType: 'application/json',
        success: function(result) {
            alert(result);
 ...