JSFiddle - React, Tailwind, and code Playground

by blineberry

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
<script src="https://rawgithub.com/rniemeyer/knockout-sortable/master/build/knockout-sortable.js"></script>
<ul class="questionlist" data-bind="sortable: { data: questions, options: { handle: '.sort-handle', cursor: 'move', placeholder: 'sort-placeholder', axis: 'y', revert: true, start: sortStart } }">
    <li class="questionlist-item">
        <div class="sort questionlist-item-sort">
            <button class="sort-up" data-bind="click: $parent.moveQuestionUp, visible: $index() > 0"></button>
            <div class="sort-handle"></div>
            <button class="sort-down" data-bind="click: $parent.moveQuestionDown, visible: $index() < $parent.questions().length - 1"></button>
        </div>
        
        <div class="question questionlist-question">
            <button data-bind="click: editToggle, text: isEditing() ? 'Save' : 'Edit'"></button> <button data-bind="click: $parent.removeQuestion">Remove</button>
            <div class="view" data-bind="visible: !isEditing()">
                <div class="question-text" data-bind="text: text()"></div>
                <ul class="question-answerlist" data-bind="foreach: choices, visible: visible() === 'multiple'">
                    <li>
                        <div class="question-answer">
                            <input data-bind="attr: { name: $parentContext.$index, id: $parentContext.$index() + '-' + $index(), type: $parent.type() === 'mc-multiple' ? 'checkbox' : 'radio', value: $data }, checked: $parent.value" />
                            <label data-bind="attr: { for: $parentContext.$index() + '-' + $index() }, text: $data"></label>
                        </div>
                    </li>
                </ul>
                <div class="question-answer">
                    <select data-bind="visible: visible() === 'select', options: choices, attr { multiple: type() === 'mc-multiple' , size: choices().length > 15 ?...

CSS

.questionlist {
    margin: 0;
    padding: 0;
    list-style: none;
}
.questionlist-item {
    margin: 1.5em 0;
    padding: 1.5em;
    clear: both;
    
    border-radius: 10px;
    
    background: rgba(255,255,255,0.9);
}
.question-text {
    font-weight: bold;
}
.question-answerlist {
    margin: 0;
    list-style: none;
}
.question-answer select {
    margin-left: 40px;
}



.questionlist-item-sort {
    float: left;
}
.questionlist-question {
    margin-left: 60px;
}


.sort-up, .sort-down {
    border: 0 none;
    background: transparent;
    height: 44px;
    width: 44px;
    position: relative;
    
    cursor: pointer;
}
.sort-up:before, .sort-down:before {
    content: '';
    
    position: absolute;
    left: 0;
    height: 0;
    width: 0;
    
    border-style: solid;
    
    cursor: pointer;    
}
.sort-up {
    margin-bottom: 5px;
}
.sort-up:before {
    bottom: 0;
    
    border-color: transparent transparent #DDD;
    border-width: 0 22px 22px;
}
.sort-down {
    margin-top: 5px;
}
.sort-down:before {
    top: 0;
    border-color: #DDD transparent transparent;
    border-width: 22px 22px 0;
}
.sort-handle {
    width: 44px;
    height: 44px;
    background: #DDD;
    
    cursor: move;
}
.sort-placeholder {
    border: 4px dashed yellow;
    height: 3em;
    padding: 1.5em;
    margin: 1.5em;
    clear: both;

    background: lightyellow;
    
    line-height: 1.5;
}
.ui-sortable-helper {
    background: lightyellow;
    
    opacity: 0.9;
}

JavaScript

function  QuestionListVM() {
    var self = this;
    self.test = "Test";
    
    self.questions = ko.observableArray([
        new Question('Which charities do you give to annually? (check all that apply)', [
            'Unicef',
            'United Way',
            'American Cancer Society',
            'Other Charity'
        ], 'mc-multiple'),
        new Question('What\'s your favorite coffee?', [
            'Espresso',
            'Macchiatto',
            'Cortado',
            'Cappuccino',
            'Latte',
            'Mocha',
            'Drip',
            'Coffee? Blech.'
        ], 'mc-single'),
        new Question('True or false: beer is proof that God loves us and wants us to be happy.', [
            'True',
            'False'], 'mc-single'),
        new Question('If you were a beer, what beer would you be?', [], 'txt-short'),
        new Question('In 100 words or more, please describe a solar eclipse.', [], 'txt-long')
    ]);
    
    for (var i = 0; i < self.questions().length; i++) {
        self.questions()[i].order(i);
    }
    
    self.sortQuestions = function() {
        self.questions.sort(function(a,b) {
            return a.order() - b.order();
        });
    };
    
    self.addQuestion = function() {
        self.questions.push(new Question(null, null, null, true, self.questions().length));
    };
    self.removeQuestion = function(question) {
        self.questions.remove(question);
    };
    self.moveQuestionUp = function(question) {
        curIndex = self.questions().indexOf(question);
        
        if (curIndex > 0) {
            self.questions.splice(curIndex - 1, 2, self.questions()[curIndex], self.questions()[curIndex - 1]);
        }
    }
    self.moveQuestionDown = function(question) {
        curIndex = self.questions().indexOf(question);
        
        if (curIndex + 1 < self.questions().length) {
            self.questions.splice(curIndex, 2, self.questions()[curIndex + 1],...