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">
<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());
...