On a recent project, I and my teammates found ourselves presenting a dropdown in a ColorBox (https://www.nuget.org/packages/colorbox/) to the user on more than one occasion. Of course, if you use it more than once that means it is a candidate for a generic tool. But, how to do this with Knockout and Typescript, that was the problem.
First, the html itself:
1 2 3 4 5 6 7 8 9 10 11 12 | <div id="DropDownSelector" data-bind="with: DropDownSelector"> <div data-bind="visible: Show"> <p>Select a <span data-bind="text: TypeOfChoice"></span> to proceed:</p><br /> <div> <select data-bind="options: Choices, optionsText: 'ChoiceText', value: SelectedChoice"></select> </div> <br /> <button type="button" data-bind="click: Ok">Ok</button> <button type="button" data-bind="click: Cancel">Cancel</button> </div> </div> |
What does this do? Let's analyze it a bit.
- TypeOfChoice - A simple name of the choice object
- Choices - An observable array of Choice objects (a simple view model that contains the choice object (typically a view model) and the choice text).
- ChoicesText - The text that is displayed in the dropdown (if passing a list of view models, this would be the property name to use).
- SelectedChoice - The choice from the dropdown that the user selected.
- Show - Whether or not to display the dropdown.
- Deferrer - A reference to the jQueryDeferred object which allows the dropdown to use the promise pattern.
- DropDownSelectorHref - This is the html page name of the page I displayed above that the ColorBox control uses.
Alright, that details the properties of the object. But, what about actually using it? Well, first, here's how you would call the DropDownSelector observable that we defined:
1 2 3 4 | this.DropDownSelector().PrompUserToMakeAChoice("Page", "PageName", this.Pages()) .done((page) => { // do something here }); |
In this example, we're passing in an observable array of view models. It assumes that there is a property on the objects called PageName. Then when the user selects a value from the dropdown we'll get the Page object and be able to react to the selection.
Notice we called a method on the dropdownselector called PromptUserToMakeAChoice. Well, that method looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | PromptUserToMakeAChoice = (typeOfChoice: string, choicesText: string, choices: Array<any>) => { this.SelectedChoice(null); this.TypeOfChoice(typeOfChoice); var choicesArray = new Array(); ko.utils.arrayForEach(choices, choice => { choicesArray.push(new ChoiceVm(choice, choicesText)); }); this.Choices(choicesArray); this.ChoicesText(choicesText); this.Deferrer = $.Deferred(); this.Show(true); this.ShowDialog(); return this.Deferrer; }; |
This method sets up the properties that the dropdown control uses including an observable array of ChoiceVm objects and then returns the jquery promise. What is this ChoiceVm you ask? Well, its a very simple view model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | constructor() { this.TypeOfChoice = ko.observable(null); this.Choices = ko.observableArray([]); this.ChoicesText = ko.observable(null); this.SelectedChoice = ko.observable(null); this.Show = ko.observable(false); this.Deferrer = null; $(document).bind("cbox_complete", () => { $.colorbox.resize(); }); } Ok = () => { if (this.SelectedChoice() == null) { alert("You must select a " + this.TypeOfChoice() + "."); return; } this.Deferrer.resolve(this.SelectedChoice().Choice()); this.Close(); }; Cancel = () => { this.Deferrer.reject(); this.Close(); this.Deferrer = null; }; Close = () => { this.Show(false); $.colorbox.close(); }; ShowDialog = () => { var height = $(this.DropDownSelectorHref).height(); var colorBoxOptions = { href: this.DropDownSelectorHref, height: height, innerWidth: 300, minHeight: 100, }; $.colorbox(colorBoxOptions); $.colorbox.resize(); }; |
Some straight-forward functions that the HTML itself binds to. That about wraps it up. I would like to give credit to former co-worker, Drew, for his work on this as he did the heavy lifting. I didn't want such a great piece to not be shown to the world. Its a great piece of code and I hope it proves useful to others. Happy coding!
One thing I dislike about typescript and knockout is that I feel its clunky. It feels like you add an additional three layers on top of the three layers you already have with MVC for instance. Interesting read though!
ReplyDelete