Library for browser based outliner

http://stackoverflow.com/q/257188/154877

by Marcel

HTML

<script src="http://mjsarfatti.com/sandbox/nestedSortable/jquery.mjs.nestedSortable.js"></script>
<div id="wrap">
    <header>
        <h1>Nested Editable Sortables with jQuery</h1>
    </header>
    <section>
        <p>While a field is focused, press <kbd>&uarr;</kbd> to collapse it's children and <kbd>&darr;</kbd> to expand them, or double-click the field to toggle. Use <kbd>Tab</kbd> and <kbd>Shift+Tab</kbd> to move between fields and <kbd>Return</kbd> to tick/untick an item.</p>
        <ol id="outline"></ol>
        <div class="btns">
            <button class="add">Add item</button>
            <button class="sve">Save List</button>
        </div>
        <pre id="output"></pre>
    </section>
    
    <footer>This is a code example for the Stack Overflow question <br><a href="http://stackoverflow.com/q/257188/154877">"Library for browser based outliner"</a></footer>
</div>

CSS

body { color:#222; background:#fafafa; font:13px/1.4 'segoe ui', sans-serif; }
h1, h2 { margin:0 0 .75em; font:700 2.2em/1 'myriad pro', sans-serif; } h2 { color:#999; margin: 1em 0 .25em; font-size:1.6em; }
a { color:#06c; transition:.2s linear; -webkit-transition:.2s linear; } a:hover { color:#39f; }
p { margin:1em 0; } footer { color:#999; margin:3em 0 0; text-align:center; } footer a { color:#777; }
kbd { color:#096; }
#wrap { width:500px; margin:1em auto; padding:1.5em; background:#fff; box-shadow:0 1em 2em rgba(0,0,0,.2); }

.btns { margin:.5em 1em; }

#outline, #outline ol { margin:0 0 0 2em; list-style-type:none; }
#outline { margin:.5em 0; }
#outline li { padding:0; margin:.5em 0 0 0; }
#outline li div  { margin:0; padding:.25em; position:relative; border:1px solid #aaa; }
#outline li div .drg { cursor:move; padding:1px; margin:0 .5em 0 0; border-left:2px dotted #bbb; border-right:2px dotted #bbb; }
#outline li div input.txt { border:0; outline:0; width:90%; background:transparent; }
#outline li div input.chk { top:1px; margin:0 .2em; position:relative; }
#outline li div input.chk:checked + input.txt { color:#999; text-decoration:line-through; }
#outline li div a.del { color:#ccc; top:-5px; right:5px; cursor:pointer; font-size:20px; position:absolute; text-decoration:none; }
#outline li div a.del:hover { color:#c33; }
#outline li.rem > div { border:1px solid #000; }
.placeholder { background:#eee; }
.nesttoodeep { background:#ffdedd; }

JavaScript

$('#outline').nestedSortable({
    revert:250,
    tabSize:25,
    opacity:.6,
    maxLevels:5,
    items:'li',
    handle:'div .drg',
    tolerance:'pointer',
    toleranceElement:'> div',
    errorClass:'nesttoodeep',
    disableNesting:'no-nest',
    placeholder:'placeholder',
    forcePlaceholderSize:true
});

$('button.add').click(function() {
    $('ol#outline').append('<li><div><b class="drg"></b> <input class="chk" type="checkbox" tabindex="-1"> <input class="txt" placeholder="New item"> <a class="del">&times;</a></div></li>');
});

$('button.sve').click(function() {
    alert('TODO: Properly formatted output would help.');
});

$('#outline')
    .delegate('a.del', 'click', function() {
        if (confirm("This item and all of it's children will be deleted. Is that OK?")){
            $(this).closest('li').slideUp('slow', function(){ $(this).remove(); });
        }
    })
    .delegate('input', 'dblclick', function(){
        $(this).closest('li').toggleClass('rem').find('ol').first().slideToggle();
    })
    .delegate('input', 'keydown', function(e) {
        var keyCode = e.keyCode || e.which,
            kbkey = { up: 38, down: 40, right: 39, left: 37, enter: 13 };

        switch (keyCode) {
        case kbkey.up:
            $(this).closest('li').addClass('rem').find('ol').first().slideUp();
            break;
        case kbkey.down:
            $(this).closest('li').removeClass('rem').find('ol').first().slideDown();
            break;
        case kbkey.right:
            alert('TODO: Indent item (maybe)');
            break;
        case kbkey.left:
            alert('TODO: Un-indent item (maybe)');
            break;
        case kbkey.enter:
            $(this).prev().attr('checked', !$(this).prev().attr('checked'));
            break;
        }
    });