0

I am building an advanced search UI similar to the TFS query builder web interface. Using knockout for the client side implementation and have everything more or less working except the final validation to make certain required items are basically selected. It sort-of works as far as giving me a validation error if I select an item and then de-select the item. Which is fine, but I would like to have the form validate when hitting the search button.

I am pretty sure I need to make use of the ko.validatedobservable method, I'm just not sure exactly how. Anyway, I have a fiddle to look at: http://jsfiddle.net/sstolp/uXBSA/ if anyone has the time or inclination to help me out. I would deeply appreciate it.

Thank you for your time.

scvm.SearchLine = function () {
var self = this;
self.selectedField = ko.observable().extend({ required: true });
self.selectedOperator = ko.observable().extend({ required: true });
self.firstdate = ko.observable(new Date());
self.lastdate = ko.observable(new Date());
self.thedate = ko.observable(new Date());

return self;};

scvm.Criteria = function () {
var self = this,
    lines = ko.observableArray([]),

    // Put one line in by default
    loadInitialData = function () {
        lines.push(new scvm.SearchLine());
    },

    rowcount = ko.computed(function () {
        return lines().length;
    }),

    // Operations
    addLine = function () {
        lines.push(new scvm.SearchLine());
    },

    removeLine = function (line) {
        lines.remove(line);
    },

    search = function () {
        var data = $.map(lines(), function (line) {
            return line.selectedField() ? {
                selectedField: line.selectedField().searchfield,
                selectedOperator: line.selectedOperator().name,
            } : undefined
        });
        alert("Send to server: " + JSON.stringify(data));            
    },

    clear = function () {
        lines.removeAll();
    };

return {
    lines: lines,
    loadInitialData: loadInitialData,
    rowcount: rowcount,
    addLine: addLine,
    removeLine: removeLine,
    search: search,
    clear: clear
};
}();
Steve
  • 78
  • 3

1 Answers1

0

Yes, all your SearchLine objects must be wrapped into ko.validatedObservable. Also you should implement computed property which will check isValid() for each criteria line and return global validity flag.

scvm.SearchLine = function () {
    var self = this;
    self.selectedField = ko.observable().extend({ required: true });
    self.selectedOperator = ko.observable().extend({ required: true });
    self.firstdate = ko.observable(new Date());
    self.lastdate = ko.observable(new Date());
    self.thedate = ko.observable(new Date());

    return ko.validatedObservable(self);
};

scvm.Criteria = function () {

    // ...

    return {
        lines: lines,
        loadInitialData: loadInitialData,
        rowcount: rowcount,
        addLine: addLine,
        removeLine: removeLine,
        search: search,
        clear: clear,
        // new property that indicates validity of all lines
        linesValid: ko.computed(function(){
            var items = lines();
            for (var i = 0, l = items.length; i < l; i++)
                if (!items[i].isValid()) return false;
            return true;
        })
    };
}();

This new property can be used in enable binding of you "Search" button:

<input type="button"
       data-bind="enable: linesValid, click: search"
       title="Clicking this button will run a search."
       value="Search" />

I've modified your fiddle. Take a look: http://jsfiddle.net/ostgals/uXBSA/8/


Update:

Also we should slightly modify Criteria.search method, since our line array contains observables rather than objects:

        //...

        search = function () {
            var data = $.map(lines(), function (line) {
                line = ko.utils.unwrapObservable(line);
                return line.selectedField() ? {
                    selectedField: line.selectedField().searchfield,
                    selectedOperator: line.selectedOperator().name,
                } : undefined
            });
            alert("Send to server: " + JSON.stringify(data));            
        },

        //...
Rango
  • 3,877
  • 2
  • 22
  • 32
  • Hi f_martinez. Thanks for the reply and for the fiddle example. I had continued to work on this after I posted the question, and had actually come up with an implementation that was slightly different than what you posted. The main difference I notice is that your example iterates through the lines object to expose a linesValid property. Also, with your example although I have valid criteria, and the search button is enabled, when I click it I am getting an error that line.selectedField is not a function, whereas I did not receive this message previously, and I don't get that error in mine. – Steve Feb 10 '13 at 14:37
  • The error occures because `line` is observable now and we should use `line()` to get access to its real interface. I've not tested clicking "Search" - my apologies. The workaround in my case is to rewrite `Criteria.search` method with unwrapping `line` before. I've updated my answer. – Rango Feb 10 '13 at 15:36
  • Your method is the first I've thought about, but it seemed to me that this method required more code to rewrite. Maybe I'm wrong. Thanks for interesting question anyway. – Rango Feb 10 '13 at 15:39