ACT search interface in Knockout.js

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>
<div data-bind="visible: !valid()" class="view-model-error">
    <h1>Error!</h1>
</div>
<table border="1" width="100%">
    <tr>
        <th data-bind="text: lp_count(), css: {'column-error': lp_errors()}"></th>
        <th>Fields</th>
        <th>Matchers</th>
        <th>Values</th>
        <th data-bind="text: rp_count(), css: {'column-error': rp_errors()}"></th>
        <th>Operands</th>
    </tr>
    <tbody data-bind="foreach: condition_lines">
        <tr>
            <td data-bind="css: {'column-error': $parent.lp_errors()}">
                <select data-bind="options: lps, optionsCaption: ' ', value: lp"></select>
            </td>
            <td>
                <select data-bind="options: keys, optionsText: 'name', optionsCaption: 'Select a field...', optionsValue: 'value', value: key" class="select-field"></select>
                <p data-bind="visible: key_error(), text: key_error"></p>
            </td>
            <td>
                <select data-bind="options: matchers, optionsText: 'name', optionsValue: 'name', value: match"></select>
            </td>
            <td data-bind="template: {name: 'value_field'}"></td>
            <td data-bind="css: {'column-error': $parent.rp_errors()}">
                <select data-bind="options: rps, optionsCaption: ' ', value: rp"></select>
            </td>
            <td>
                <select data-bind="options: operands, optionsText: 'name', optionsCaption: ' ', optionsValue: 'name', value: operand" class="change-operand"></select>
                <span data-bind="visible: ko.utils.arrayIndexOf($parent.condition_lines(), $data) > 0 || $parent.condition_lines().length > 1">
                    &nbsp;
                    &nbsp;
                    <a href="#" data-bind="click:...

CSS

.view-model-error {
    padding: 10px;
}

.inline-errors {
    color: red;
    text-style: italics;
}

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

th.column-error {
    color: #fff;
}

JavaScript

var conditionLineViewModel = function() {
    var self = this;
    
    self.id              = ko.observable();
    self.saved_search_id = ko.observable();
    
    self.lp      = ko.observable();
    self.key   = ko.observable();
    self.match   = ko.observable();
    self.value   = ko.observable();
    self.rp      = ko.observable();
    self.operand = ko.observable();
    
    self.key_error   = ko.observable();
    self.value_error = ko.observable();
    self.valid       = ko.computed(function() {
        if (self.value() && !self.key()) {
            self.value_error("");
            self.key_error("must be selected");
        } else if (self.key() && !self.value()) {
            self.value_error("can't be blank");
            self.key_error("");
        } else {
            self.value_error("");
            self.key_error("");
        }
    });
    
    self.lps      = ko.observableArray(["(", "((", "((("]);
    self.rps      = ko.observableArray([")", "))", ")))"]);
    self.keys     = ko.observableArray([
        {name: 'Date',       value: "date"},
        {name: 'String',     value: "string"},
        {name: 'Boolean',    value: "boolean"},
        {name: 'Checkboxes', value: "checkbox"}
    ]);
    self.matchers = ko.observableArray([
        {name: "Is"},
        {name: "Contains"},
        {name: "<="},
        {name: ">="}
    ]);
    self.operands = ko.observableArray([
        {name: "AND"},
        {name: "OR"}
    ]);

    self.operanded = ko.observable();
};
var ConditionLineViewModel = ko.mapping.fromJS(conditionLineViewModel);

$(".change-operand").live("change", function() {
    var c_line = ko.dataFor(this);
    if (!c_line.operanded()) {
        ViewModel.condition_lines.push(new ConditionLineViewModel);
        c_line.operanded(c_line.operand());
    } else {
        if (c_line.operand() != undefined && c_line.operand() != c_line.operanded()) {
            c_line.operanded(c_line.operand());
           ...