JSFiddle - React, Tailwind, and code Playground

by lamarant

HTML

<script src="https://raw.github.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.debug.js"></script>
Solution #2 to: <a href="http://stackoverflow.com/questions/16137887" target="_blank">http://stackoverflow.com/questions/16137887</a>
<hr />
<h1>All Foo's</h1>
<div data-bind="template: { name: 'MyTemplate', data: { data: Foo } }"></div>
<hr />
<h1>All Poo's</h1>
<div data-bind="template: { name: 'MyTemplate', data: { data: Poo } }"></div>
<hr />

<script type="text/html" id="MyTemplate">
    <div class="addnew">
        Add Member &raquo; 
        <select data-bind="options: $root.filterObjects(data), optionsText: 'ID', value: data.Selected"></select>
        <button data-bind="click: $root.addSelected(data, data.Selected) ">+</button>
    </div>
    <div data-bind="foreach: data.Objects">
        <div>
            Member #<span data-bind="text: ID"></span> &raquo;
            <input type="text" data-bind="value: Title, valueUpdate: 'afterkeydown'" />
            <button data-bind="click: function () { $parent.data.removeObject($data); }">x</button>
        </div>
    </div>
</script>
<h1 class="Debug">Debug</h1>
<strong>viewModel._AllObjects</strong>
<pre data-bind="text: ko.toJSON($data._AllObjects, null, 2)"></pre>
<strong>viewModel.Foo</strong>
<pre data-bind="text: ko.toJSON($data.Foo, null, 2)"></pre>
<strong>viewModel.Poo</strong>
<pre data-bind="text: ko.toJSON($data.Poo, null, 2)"></pre>

CSS

body{color: #444; font-family: Arial; font-size: 14px;}
input{width: 50%; padding: 5px;}
button{cursor: pointer;}
select{padding: 5px;}
h1{font-size: 16px;}
h1.Debug{font-size: 14px; background: red; color: #fff;padding: 5px;}
pre, strong{font-size: 12px;}
.addnew{background: #eee;padding: 5px;margin-bottom: 10px;}

JavaScript

var model = {
    _AllObjects: [
        { ID: 100, Title: 'This is item number 100' },
        { ID: 776, Title: 'This is item number 776' },
        { ID: 456, Title: 'This is item number 456' },
        { ID: 999, Title: 'This is item number 999' }
    ],
    _FooRefs: [100],
    _PooRefs: [100, 776]
};

ko.extenders.references = function (target, mappedObjects) {
    target.Objects = ko.computed(function () {
        var refs = ko.utils.unwrapObservable(target);
        return ko.utils.arrayMap(refs, function (id) {
            return mappedObjects.mappedGet({ ID: id });
        });
    });
    target.addObject = function (obj) {
        if (obj) {
            var id = ko.utils.unwrapObservable(obj.ID);            
            if (mappedObjects.mappedIndexOf({ ID: id }) === -1) {
                mappedObjects.mappedCreate(obj);
            }
            target.push(id);
        }
    }.bind(target);
    target.removeObject = function (obj) {
        if (obj) {
            var id = ko.utils.unwrapObservable(obj.ID);
            target.remove(id);
        }
    }.bind(target);
    return target;
};

function ViewModel(data) {
    ko.mapping.fromJS(data, {
        ignore: ['_AllObjects', '_FooRefs', '_PooRefs']
    }, this);
    
    var _AllObjects = ko.mapping.fromJS(data._AllObjects, {
        key: function (obj) {
            return obj.ID;
        }
    });
    
    this.Foo = ko.observableArray(data._FooRefs).extend({ references: _AllObjects });
    this.Poo = ko.observableArray(data._PooRefs).extend({ references: _AllObjects });
    
    this.Foo.Selected = ko.observable();
    this.Poo.Selected = ko.observable();
    
    this.filterObjects = function (references) {
        return ko.computed(function () {
            var allObjects = ko.utils.unwrapObservable(_AllObjects);
            return ko.utils.arrayFilter(allObjects, function (obj) {
                var refs = ko.utils.unwrapObservable(references),
                    id =...