D´n´D-TAGS v3 ASG (based on: knockout-sortable - simple list)

https://github.com/rniemeyer/knockout-sortable

by Asle Gundersby

HTML

<script src="http://knockoutjs.com/downloads/knockout-3.2.0.js"></script>
<script src="https://rawgithub.com/rniemeyer/knockout-sortable/master/build/knockout-sortable.js"></script>
<div class="tag-container">
    <input class="taginput"  placeholder="&lt;add a tag&gt;" data-bind="
        autoComplete: { selected: selectedOption, options: options },
        textInput: tagname, 
        event: {keyup: inputTagASG}
    "/>
    <h3>Selected Tags ('sceneTags')</h3>
    <div class="tag-table">
        <div class="tag-list" data-bind="sortable:{template:'tagsTmpl',data:sceneTags, allowDrop:true,options:{update:$root.onUpdate}}"></div>
    </div>
</div>
     <!--  helper: 'clone',revert: true, -->
<br/>
<div class="tag-container">
    <h3>Available Tags ('projectSceneTags')</h3>
    <div class="tag-table">
        <div class="tag-list" data-bind="
            sortable:{template:'tagsTmpl',data:tags, allowDrop:true, options:{ update:$root.onUpdate}}"></div> 
    </div>
</div>

<script type="text/html" id="tagsTmpl">
    <div class="tag" data-bind="css : {}">
        <span class="tag-action" data-bind="
            visible: !isAllocated() && source() === 'external' && sceneID  !== null,
            click: function(){$root.applyTagsAllocation($data)}
        ">+</span> 
        <span class="tag-action remove" data-bind="
            visible: isAllocated() && source() === 'external' && sceneID  !== null, 
            click: function(){$root.undoTagsAllocation($data)}
        ">x</span>
        <span class="tag-action remove" data-bind="
            css: {'internal': (source() === 'internal')},
            visible: source() === 'internal' && sceneID  !== null, 
            click: function(){$root.undoTagsAllocation($data)}
        ">x</span> 
        
        <span class="tag-text" data-bind="text: text"></span>
</div></script>





<!--
<script type="text/html" id="template-tag">
    <div class="tag" data-bind="css: {selected: isSelected}">
        <span class="tag-action"...

CSS

body {
    font-family: arial;
}
p, a, input {
    font-size: .8em;
}
.ko_container {
    width: auto;
    min-width: 25px;
    min-height: 25px;
    border: 1px dotted;
    padding: 2px;
}
.tag-container {
    width: 350px;
    margin-top: 5px;
    margin-left: 5px;
    padding: 2px;
    border: black 1px solid;
}
.tag-container h3 {
    text-align: left;
    font-weight: bold;
    margin-bottom: 4px;
}
.tag-container input.taginput {
    float: right;
    position: relative;
    background: lightyellow;
    font-size: 10px;
    min-width: 20px;
    width: auto;
    max-width: 80px;
    margin: 0px 1px 2px 10px;
    border: 1px solid grey;
    padding: 1px;
}
.tag-table{
    background-color: lightblue ;
    margin: -2px;
    padding: 1px;
    border-radius: 0px;
}
.tag-list {
    height: auto;
    margin: 1px;
    padding: 1px;
    overflow-y: auto;
    clear: both;
}
.tag {
    float: left;
    background-color: #444;
    color: #fff;
    border-radius: 3px;
    margin: 1px;
    padding: 2px 3px;
    cursor: move;
    white-space: nowrap;
}
.tag .tag-action {
    display: inline-block;
    /* width: 11px;
    height: 11px; */
    width: 0.5em;
    cursor: pointer;
    text-align: center;
    vertical-align: middle;
    font-size: 12x;
}
.tag .tag-action {
    color: lime;
    font-size: 11px;
    font-weight:400;
}
.tag.selected .tag-action { /* Tomalak css */
    color: red;
    font-weight:400;
    vertical-align: top;
}

.tag-action.remove { /* ASG 2015 css */
    color: yellow;
    font-weight:600;
}
.tag-action.remove.internal{ /* ASG 2015 css */
    color: red;
    font-weight:600;
}
.tag-action.textbatch { /* ASG 2015 css */
    color: white;
    font-weight:600;
}

/*-----------------------------------------------------------------------------------------------------------------*/

/* ASG 2015 coloring of tabs -- not implemented yet */
  div.type0 .tag  {
       font-size: 11px;
        background-color: #6797ed; /* blue-ish */
        background:...

JavaScript

ko.bindingHandlers.autoComplete = {
    // Only using init event because the Jquery.UI.AutoComplete widget will take care of the update callbacks
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var settings = valueAccessor();
        var selectedOption = settings.selected;
        var options = settings.options;

        var updateElementValueWithLabel = function (event, ui) {
            // Stop the default behavior
            event.preventDefault();

            // Update the value of the html element with the label of the activated option (ui.item)
            //$(element).val(ui.item.label);
            bindingContext.$root.tagname(ui.item.label);
            // Update our SelectedOption observable
            if(typeof ui.item !== "undefined") {
                selectedOption(ui.item);    // ui.item - label|value|...
            }
        };

        $(element).autocomplete({
            source: options,
            select: function (event, ui) {
                updateElementValueWithLabel(event, ui);
            },
            focus: function (event, ui) {
                updateElementValueWithLabel(event, ui);
            },
            change: function (event, ui) {
                updateElementValueWithLabel(event, ui);
            }
        });
    }
};
//----------------------------------------- bindingHandlers above….

var Tag = function (data) {
    this.ID = data.ID || -1;
    this.sceneID = data.sceneID || null;
    this.textbatchID = data.textbatchID || null;
    this.text = ko.observable( (data.text || 'new').trim() );
    this.tagType = ko.observable( data.tagType || 0 );
    this.isAllocated = ko.observable(data.isAllocated); 
    
    this.source = ko.observable(data.source || 'external');  // default source is 'external', cannot be deleted!

    this.isSelected = ko.observable( data.isSelected || false );    
}; 

Tag.create = function (data) {
    return new...