Attempting to add validation to select binding causes strange new behavior.

566 views
Skip to first unread message

Stacey

unread,
Dec 28, 2012, 5:20:30 PM12/28/12
to knock...@googlegroups.com
I have a select list whose contents are passed down through the view and a view model is constructed. It looks like this...

        viewModel = {
			Actor : ko.observable()
		};

And the corresponding HTML (with binding) looks like this.

<select 
	data-bind="options: Actors, optionsText: 'Name', 
	value: Actor, optionsCaption: 'Choose an actor ...'" ></select>

Actors are a key/value pair with an ID and a name. So the default populates with the following two values.

  "Actors": [
    {
      "Id": "actors/1",
      "Name": "Character 1",
      "Tags": null
    },
    {
      "Id": "actors/2625",
      "Name": "Character 2625",
      "Tags": null
    }
  ]

So then, the expected behavior is simple. It creates a dropdown list with a default value ("Choose an actor ...") and then you click the dropdown and pick one. So, after picking a value, the view model JSON looks exactly like this.

{
  "Actor": {
    "Id": "actors/1",
    "Name": "Character 1",
    "Tags": null
  },
  "Actors": [
    {
      "Id": "actors/1",
      "Name": "Character 1",
      "Tags": null
    },
    {
      "Id": "actors/2625",
      "Name": "Character 2625",
      "Tags": null
    }
  ]
}

Simple enough - it has worked fine for me up until now. 

So I had a lot of people that were submitting the form without making a selection. So I attempted to plumb in and add some jQuery Validation and Unobtrusive Validation. I do this with the following code... (This part is fairly useless for the Knockout part, I am just showing you the code in case it might be of some use)

(function ($) {
	$.validator.unobtrusive.adapters.addBool("mustbetrue", "required");
})(jQuery);
(function ($) {
	// http://www.falconwebtech.com/post/2012/04/21/MVC3-Custom-Client-Side-Validation-with-Unobtrusive-Ajax.aspx
	$.validator.addMethod('mustbe', function (value, element, params) {
		var testValue = params['propertyvalue'];
		var condition = params['condition'];
		if ((condition == '0') && (value != testValue))
			return true;
		if ((condition == '1') && (value == testValue))
			return true;
		return false;
	});
	var setValidationValues = function (options, ruleName, value) {
		options.rules[ruleName] = value;
		if (options.message) {
			options.messages[ruleName] = options.message;
		}
	};
	$.validator.unobtrusive.adapters.add("mustbe", ["propertyvalue", "condition"], function (options) {
		var value = {
			propertyvalue: options.params.propertyvalue,
			condition: options.params.condition
		};
		setValidationValues(options, "mustbe", value);
	});
})(jQuery);


Okay. That works fine when I run a test like this ...

	<select data-val="true"
		data-val-mustbe="You must select a value"
		data-val-mustbe-condition="0"
		data-val-mustbe-propertyvalue="default"
		data-val-required="You must select a value"
		id="select_value">
		<option value="default">Choose...</option>
		<option value="1">1</option>
		<option value="2">2</option>
	</select>

Very simple. It works exactly as I expect. So I then go to apply this to my Knockout View Model - I change the code to look like this. I seem to have to have the "optionsValue" in order to have something for the validator to wire into. 

<select 
	data-bind="options: Actors, optionsText: 'Name', 
	value: Actor, optionsCaption: 'Choose an actor ...', optionsValue: 'Id'"
data-val="true"
data-val-mustbe="You must select an actor"
data-val-mustbe-condition="0"
data-val-mustbe-propertyvalue=""
data-val-required="You must select an actor"	></select>

The basic output looks the same - and the validation seems to work, but now there is a change. Whenever the user makes a selection from the select list, the resultant view model looks totally different. It looks like this ...

{
  "Actor": "actors/1",
  "Actors": [
    {
      "Id": "actors/1",
      "Name": "Character 1",
      "Tags": null
    },
    {
      "Id": "actors/2625",
      "Name": "Character 2625",
      "Tags": null
    }
  ]
}

It only passes the actor's id through the field, instead of both the name and id. This is, of course, causing the behavior to not work as I desire. 

Any ideas?

Stacey

unread,
Dec 28, 2012, 5:23:54 PM12/28/12
to knock...@googlegroups.com
And for some reason most of my post was pruned off? What in the world?

Gunnar Liljas

unread,
Dec 30, 2012, 9:26:37 AM12/30/12
to knock...@googlegroups.com
The reason it does that is the optionsValue: 'Id' binding, which makes
the value of the select become only the Id.

Two ways around this.

1. Live with it. The Id is the only thing you should need, after all.
2. Bind value to a "selectedActorId" observable, and convert the Actor
observable into a computed, which returns the correct actor from the
array, based on selectedActorId

/G

2012/12/28 Stacey <stacey.ci...@gmail.com>:
> And for some reason most of my post was pruned off? What in the world?
>
> --
>
>

Stacey

unread,
Jan 14, 2013, 2:02:49 PM1/14/13
to knock...@googlegroups.com
Hey, sorry for being so slow to return to this. I'm a bit confused about how I would do option 2.

Gunnar Liljas

unread,
Jan 14, 2013, 6:26:27 PM1/14/13
to knock...@googlegroups.com
self.SelectedActorId=ko.observable();

self.Actor = ko.computed(function(){
return ko.utils.arrayFirst(self.Actors(),function(item){
return item.Id==self.SelectedActorId();
});
}):

2013/1/14 Stacey <stacey.ci...@gmail.com>:
> --
>
>
Reply all
Reply to author
Forward
0 new messages