JSFiddle - React, Tailwind, and code Playground

by kwon

HTML

<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.js"></script>
<script src="https://raw.github.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js"></script>
<table border="1" width="100%">
    <thead>
        <tr>
            <th colspan="2">Day(s)</th>
            <th>(optional)</th>
            <th colspan="3">Time</th>
            <th></th>
        </tr>
    </thead>
    <tbody data-bind="foreach: hours, visible: hours().length > 0">
        <tr>
            <td data-bind="css: {'cell-error': from_error()}">
                <select data-bind="options: $parent.days_of_week, optionsCaption: 'From...', value: from"></select>
            </td>
            <td style="text-align: center">-</td>
            <td>
                <select data-bind="options: $parent.days_of_week, optionsCaption: 'To...', value: to"></select>
            </td>
            <td data-bind="css: {'cell-error': from_time_error()}">
                <select data-bind="options: $parent.available_hours, value: from_hour, optionsCaption: 'From'"></select>
                <select data-bind="options: $parent.am_pm, value: from_am_pm, optionsCaption: ' '"></select>
            </td>
            <td style="text-align: center">-</td>
            <td data-bind="css: {'cell-error': to_time_error()}">
                <select data-bind="options: $parent.available_hours, value: to_hour, optionsCaption: 'To'"></select>
                <select data-bind="options: $parent.am_pm, value: to_am_pm, optionsCaption: ' '"></select>
            </td>
            <td style="text-align: center">
                <nav>
                    <a href="#" data-bind="click: $parent.remove_hours">-</a>
                </nav>
            </td>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <td colspan="6">
                <button data-bind="click: save, visible: hours().length > 0">Save hours</button>
            </td>
            <td...

SCSS

table {
    th, td {
        padding: 5px;
    }
}

.cell-error {
    background-color: red;
}

JavaScript

var days_of_week    = ['Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat', 'Sun'];
var am_pm           = ['AM', 'PM'];
var available_hours = ['1:00', '1:30', '2:00', '2:30', '3:00', '3:30'];

var hoursLineView = function(data) {
    var self = this;
    
    self.id         = ko.observable();
    self.from       = ko.observable();
    self.from_hour  = ko.observable();
    self.from_am_pm = ko.observable();
    self.to         = ko.observable();
    self.to_hour    = ko.observable();
    self.to_am_pm   = ko.observable();

    ko.mapping.fromJS(data, {}, this);
    
    self.from_error      = ko.observable();
    self.from_time_error = ko.observable();
    self.to_time_error   = ko.observable();
    self.validate = function() {
        console.log('item validate');
        if (!self.from()) {
            self.from_error("can't be blank");
        }
        if (!self.from_hour() || !self.from_am_pm()) {
            self.from_time_error("can't be blank");
        }
        if (!self.to_hour() || !self.to_am_pm()) {
            self.to_time_error("can't be blank");
        }
    };
    self.from.subscribe(function() {
        if (self.from()) {
            self.from_error("");
        }
    });
    self.from_hour.subscribe(function() {
        if (self.from_hour() &&  self.from_am_pm()) {
            self.from_time_error("");
        }
    });
    self.from_am_pm.subscribe(function() {
        if (self.from_hour() &&  self.from_am_pm()) {
            self.from_time_error("");
        }
    });
    self.to_hour.subscribe(function() {
        if (self.to_hour() &&  self.to_am_pm()) {
            self.to_time_error("");
        }
    });
    self.to_am_pm.subscribe(function() {
        if (self.to_hour() &&  self.to_am_pm()) {
            self.to_time_error("");
        }
    });
    self.valid = function() {
        self.validate();
        
        if (self.from_error() || self.from_time_error() || 
             self.to_time_error()) {
            return false;
        }...