var Conditions = { 'Number': ['=', '!=', '>', '<'], 'String': ['Contains', '=', '!='], findByType: function(type) { for (p in this) { if (typeof this[p] === "object" && p == type) { return this[p]; } } return []; } }; var _columns = [ { Column: 'DeveloperName', Display: 'Developer', Type: 'String'}, { Column: 'IssueID', Display: 'Issue ID', Type: 'Number'}, { Column: 'IssuetTitle', Display: 'Issue Title', Type: 'String'}, { Column: 'IssuetDescription', Display: 'Issue Description', Type: 'String'} ]; function findFilterColumn(column) { for (var i = 0; i < _columns.length; i++) { if (_columns[i].Column == column) { return _columns[i]; } } return null; }; var Filter = function(column, condition, nextFilterBinaryCondition, type, value) { this.ColumnObj = ko.observable(findFilterColumn(column || "")); this.ColumnObj.subscribe( function(value) { this.Value(null); }, this); this.Column = ko.dependentObservable( function() { return (this.ColumnObj() != null) ? this.ColumnObj().Column : ""; }, this); this.Condition = ko.observable(condition || null); this.NextFilterBinaryCondition = ko.observable(nextFilterBinaryCondition || "AND"); this.Type = ko.dependentObservable( function() { return (this.ColumnObj() != null) ? this.ColumnObj().Type : null; }, this); this.AvailableConditions = ko.dependentObservable( function() { var conditions = Conditions.findByType(this.Type()); return conditions; }, this); this.Value = ko.observable(value || ""); this.IsValid = ko.dependentObservable( function () { var valueAndColumnAreValid = (this.ColumnObj() != null && this.Value() != null && this.Value().length > 0); return valueAndColumnAreValid; }, this); }; function ViewModel(columnsSource) { this.nextFilterBinaryConditions = ['AND', 'OR']; this.columns = ko.observableArray(columnsSource); this.filters = ko.observableArray([new Filter()]); this.addFilter = function(filterObj) { var invalidFilter = ko.utils.arrayFirst(this.filters(), function (filter) { return filter.IsValid() === false; }); if (invalidFilter == null) { var currentIndex = ko.utils.arrayIndexOf(this.filters(), filterObj); this.filters.splice(currentIndex + 1, 0, new Filter()); } }; this.removeFilter = function(filterObj) { this.filters.remove(filterObj); if (this.filters().length == 0) { this.addFilter(); } }; this.postFilters = function() { var items = ko.toJS(this.filters()); var mappedItems = ko.utils.arrayMap(items, function(filter) { delete filter.ColumnObj; delete filter.AvailableConditions; return filter; }); alert(ko.toJSON(mappedItems)); }; }; ko.applyBindings(new ViewModel(_columns));
<div id="holder"> <ul id="filtersholder" data-bind='foreach: filters'> <li> <div class="filter-template"> <input type="button" class="delete-filter" value="-" data-bind="click: function() { $root.removeFilter($data); }" /> <select data-bind="options: $root.columns, optionsValue: Column, optionsText: 'Display', value: ColumnObj"> </select> <select class="opperator" data-bind="options: AvailableConditions, value: Condition"> </select> <input type="text" data-bind="value: Value" /> <select style="width: 54px;" data-bind="options: $root.nextFilterBinaryConditions, value: NextFilterBinaryCondition"> </select> <input type="button" class="add-filter" value="+" data-bind="click: function() { $root.addFilter($data); }" /> </div> </li> </ul> <p> <input type="button" id="submitFilters" value="Submit" data-bind="click: postFilters" /> </p> </div>
#filtersholder, #submitFilters { margin: 10px; } .delete-filter, .add-filter { width: 24px; height: 24px; } .opperator { width: 80px; }