diff --git a/Definitions/knockout-2.2.d.ts b/Definitions/knockout-2.2.d.ts index 639aa4159..cf346a778 100644 --- a/Definitions/knockout-2.2.d.ts +++ b/Definitions/knockout-2.2.d.ts @@ -1,6 +1,12 @@ -// Type definitions for Knockout 2.1.0 +// Type definitions for Knockout 2.2 +// Project: http://knockoutjs.com // https://github.com/borisyankov/DefinitelyTyped + +interface KnockoutSubscription { + dispose(): void; +} + interface KnockoutObservableArrayFunctions { // General Array functions indexOf(searchElement, fromIndex?: number): number; @@ -26,23 +32,33 @@ interface KnockoutObservableArrayFunctions { } interface KnockoutObservableArray extends KnockoutObservableArrayFunctions { - (): any[]; - (value: any[]): void; -} -interface KnockoutObservableArrayStatic { - (): KnockoutObservableArray; fn: KnockoutObservableArrayFunctions; + + (): KnockoutObservableArray; + (value: any[]): KnockoutObservableArray; } interface KnockoutObservable { + + fn; + (): any; (value): void; - subscribe(func: Function): void; + extend(source); + subscribe(func: Function): KnockoutSubscription; } interface KnockoutComputed extends KnockoutObservable { + (): KnockoutComputed; + (func: Function, context?: any): KnockoutComputed; + (def: KnockoutComputedDefine): KnockoutComputed; + (options?: any): KnockoutComputed; + + subscribe(callback: (newValue: number) => void ): KnockoutSubscription; + getDependenciesCount(): number; + hasWriteFunction(): bool; } interface KnockoutComputedDefine { @@ -50,12 +66,6 @@ interface KnockoutComputedDefine { write(any); } -interface KnockoutComputedStatic { - (): KnockoutComputed; - (func: Function): KnockoutComputed; - (def: KnockoutComputedDefine) : KnockoutComputed; -} - interface KnockoutBindingContext { $parent: any; $parents: any[]; @@ -72,55 +82,102 @@ interface KnockoutBindingHandler { //update(element: any, valueAccessor: any, allBindingsAccessor: any, viewModel: any, bindingContext: KnockoutBindingContext) : void; init: any; update: any; + options: any; } interface KnockoutBindingHandlers { value: KnockoutBindingHandler; } -interface KnockoutStatic { - utils: KnockoutUtilsStatic; - bindingHandlers: KnockoutBindingHandlers; - applyBindings(viewModel, rootNode?); - computed : KnockoutComputedStatic; - observableArray: KnockoutObservableArrayStatic; - observable(intial?): KnockoutObservable; +interface KnockoutMemoization { + memoize(callback); + unmemoize(memoId, callbackParams); + unmemoizeDomNodeAndDescendants(domNode, extraCallbackParamsArray); + parseMemoText(memoText); } -interface KnockoutUtilsStatic { - arrayForEach(array: any[], action); - arrayIndexOf(array: any[], item); - arrayFirst(array: any[], predicate, predicateOwner?); - arrayRemoveItem(array: any[], itemToRemove); - arrayGetDistinctValues(array: any[]); - arrayMap(array: any[], mapping); - arrayFilter(array: any[], predicate); - arrayPushAll(array: any[], valuesToPush); +interface KnockoutVirtualElements { + allowedBindings; + emptyNode; + firstChild; + insertAfter; + nextSibling; + prepend; + setDomNodeChildren; +} + +interface KnockoutExtenders { + throttle(target: any, timeout: number): KnockoutComputed; + notify(target: any, notifyWhen: string): any; +} + +interface KnockoutUtils { + + fieldsIncludedWithJsonPost: any[]; + + arrayForEach(array: any[], action: (any) => void ): void; + arrayIndexOf(array: any[], item: any): number; + arrayFirst(array: any[], predicate: (item) => bool, predicateOwner?: any): any; + arrayRemoveItem(array: any[], itemToRemove: any): void; + arrayGetDistinctValues(array: any[]): any[]; + arrayMap(array: any[], mapping: (item) => any): any[]; + arrayFilter(array: any[], predicate: (item) => bool): any[]; + arrayPushAll(array: any[], valuesToPush: any[]): any[]; + extend(target, source); - emptyDomNode(domNode); - moveCleanedNodesToContainerElement(nodes); - setDomNodeChildren(domNode, childNodes); - replaceDomNodes(nodeToReplaceOrNodeArray, newNodesArray); - setOptionNodeSelectionState(optionNode, isSelected); - stringTrim(str: string); - stringTokenize(str: string, delimiter); - stringStartsWith(str: string, startsWith); - buildEvalWithinScopeFunction(expression, scopeLevels); - domNodeIsContainedBy(node, containedByNode); - domNodeIsAttachedToDocument(node); - tagNameLower(element); - registerEventHandler(element, eventType, handler); - triggerEvent(element, eventType); - unwrapObservable(value); - toggleDomNodeCssClass(node, className, shouldHaveClass); - setTextContent(element, textContent); + + emptyDomNode(domNode): void; + moveCleanedNodesToContainerElement(nodes: any[]): HTMLElement; + cloneNodes(nodesArray: any[], shouldCleanNodes: bool): any[]; + setDomNodeChildren(domNode: any, childNodes: any[]): void; + replaceDomNodes(nodeToReplaceOrNodeArray: any, newNodesArray: any[]): void; + setOptionNodeSelectionState(optionNode: any, isSelected: bool): void; + stringTrim(str: string): string; + stringTokenize(str: string, delimiter: string): string; + stringStartsWith(str: string, startsWith: string): string; + domNodeIsContainedBy(node: any, containedByNode: any): bool; + domNodeIsAttachedToDocument(node: any): bool; + tagNameLower(element: any): string; + registerEventHandler(element: any, eventType: any, handler: Function): void; + triggerEvent(element: any, eventType: any): void; + unwrapObservable(value: any): any; + toggleDomNodeCssClass(node: any, className: string, shouldHaveClass: bool): void; + setTextContent(element: any, textContent: string): void; + setElementName(element: any, name: string): void; ensureSelectElementIsRenderedCorrectly(selectElement); - range(min, max); - makeArray(arrayLikeObject); - getFormFields(form, fieldName); - parseJson(jsonString); - stringifyJson(data, replacer, space); - postJson(urlOrForm, data, options); + forceRefresh(node: any): void; + ensureSelectElementIsRenderedCorrectly(selectElement: any): void; + range(min: any, max: any): any; + makeArray(arrayLikeObject: any): any[]; + getFormFields(form: any, fieldName: string): any[]; + parseJson(jsonString: string): any; + stringifyJson(data: any, replacer: Function, space: string): string; + postJson(urlOrForm: any, data: any, options: any): void; + + domNodeDisposal; +} + + +interface KnockoutStatic { + utils: KnockoutUtils; + memoization: KnockoutMemoization; + bindingHandlers: KnockoutBindingHandlers; + + computed: KnockoutComputed; + observableArray: KnockoutObservableArray; + virtualElements: KnockoutVirtualElements; + extenders: KnockoutExtenders; + + applyBindings(viewModel: any, rootNode?: any): void; + applyBindingsToDescendants(viewModel: any, rootNode: any): void; + observable(intial? ): KnockoutObservable; + contextFor(node: any): any; + isSubscribable(instance: any): bool; + subscribable(): void; + toJSON(viewModel: any, replacer?: Function, space?: any): string; + toJS(viewModel: any): any; + isObservable(instance: any): bool; + dataFor(node: any): any; } declare var ko: KnockoutStatic; \ No newline at end of file diff --git a/Tests/knockout-tests.ts b/Tests/knockout-tests.ts new file mode 100644 index 000000000..a2c7e8d8d --- /dev/null +++ b/Tests/knockout-tests.ts @@ -0,0 +1,517 @@ +/// +/// + +declare var $; + +function test_creatingVMs() { + var myViewModel = { + personName: ko.observable('Bob'), + personAge: ko.observable(123) + }; + ko.applyBindings(myViewModel); + ko.applyBindings(myViewModel, document.getElementById('someElementId')); + + myViewModel.personName(); + myViewModel.personName('Mary'); + myViewModel.personName('Mary').personAge(50); + + myViewModel.personName.subscribe(function (newValue) { + alert("The person's new name is " + newValue); + }); + + var subscription = myViewModel.personName.subscribe(function (newValue) { }); + subscription.dispose(); +} + +function test_computed() { + function AppViewModel() { + var self = this; + + self.firstName = ko.observable('Bob'); + self.lastName = ko.observable('Smith'); + self.fullName = ko.computed(function () { + return self.firstName() + " " + self.lastName(); + }); + } + + function MyViewModel() { + this.firstName = ko.observable('Planet'); + this.lastName = ko.observable('Earth'); + + this.fullName = ko.computed({ + read: function () { + return this.firstName() + " " + this.lastName(); + }, + write: function (value) { + var lastSpacePos = value.lastIndexOf(" "); + if (lastSpacePos > 0) { + this.firstName(value.substring(0, lastSpacePos)); + this.lastName(value.substring(lastSpacePos + 1)); + } + }, + owner: this + }); + } + + function MyViewModel() { + this.price = ko.observable(25.99); + + this.formattedPrice = ko.computed({ + read: function () { + return '$' + this.price().toFixed(2); + }, + write: function (value) { + value = parseFloat(value.replace(/[^\.\d]/g, "")); + this.price(isNaN(value) ? 0 : value); + }, + owner: this + }); + } + + function MyViewModel() { + this.acceptedNumericValue = ko.observable(123); + this.lastInputWasValid = ko.observable(true); + + this.attemptedValue = ko.computed({ + read: this.acceptedNumericValue, + write: function (value) { + if (isNaN(value)) + this.lastInputWasValid(false); + else { + this.lastInputWasValid(true); + this.acceptedNumericValue(value); + } + }, + owner: this + }); + } + + ko.applyBindings(new MyViewModel()); +} + + +function test_observableArrays() { + var myObservableArray = ko.observableArray(); + myObservableArray.push('Some value'); + var anotherObservableArray = ko.observableArray([ + { name: "Bungle", type: "Bear" }, + { name: "George", type: "Hippo" }, + { name: "Zippy", type: "Unknown" } + ]); + + myObservableArray().length; + myObservableArray()[0]; + + myObservableArray.indexOf('Blah'); + myObservableArray.push('Some new value'); + myObservableArray.pop(); + myObservableArray.unshift('Some new value'); + myObservableArray.shift(); + myObservableArray.reverse(); + myObservableArray.sort(function (left, right) { return left.lastName == right.lastName ? 0 : (left.lastName < right.lastName ? -1 : 1) }); + myObservableArray.splice(1, 3); + + var someItem: KnockoutObservableArray; + myObservableArray.remove(someItem); + myObservableArray.remove(function (item) { return item.age < 18 }); + myObservableArray.removeAll(['Chad', 132, undefined]); + myObservableArray.removeAll(); + myObservableArray.destroy(someItem); + myObservableArray.destroy(function (someItem) { return someItem.age < 18 }); + myObservableArray.destroyAll(['Chad', 132, undefined]); + myObservableArray.destroyAll(); +} + +function test_bindings() { + var currentProfit = ko.observable(150000); + ko.applyBindings({ + people: [ + { firstName: 'Bert', lastName: 'Bertington' }, + { firstName: 'Charles', lastName: 'Charlesforth' }, + { firstName: 'Denise', lastName: 'Dentiste' } + ] + }); + var viewModel = { availableCountries: ko.observableArray(['France', 'Germany', 'Spain']) }; + viewModel.availableCountries.push('China'); + + ko.bindingHandlers.yourBindingName = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + }, + update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + } + }; + ko.bindingHandlers.slideVisible = { + update: function (element, valueAccessor, allBindingsAccessor) { + var value = valueAccessor(), allBindings = allBindingsAccessor(); + var valueUnwrapped = ko.utils.unwrapObservable(value); + var duration = allBindings.slideDuration || 400; + if (valueUnwrapped == true) + $(element).slideDown(duration); + else + $(element).slideUp(duration); + }, + init: function (element, valueAccessor) { + var value = ko.utils.unwrapObservable(valueAccessor()); + $(element).toggle(value); + }, + update: function (element, valueAccessor, allBindingsAccessor) { + } + }; + ko.bindingHandlers.hasFocus = { + init: function (element, valueAccessor) { + $(element).focus(function () { + var value = valueAccessor(); + value(true); + }); + $(element).blur(function () { + var value = valueAccessor(); + value(false); + }); + }, + update: function (element, valueAccessor) { + var value = valueAccessor(); + if (ko.utils.unwrapObservable(value)) + element.focus(); + else + element.blur(); + } + }; + ko.bindingHandlers.allowBindings = { + init: function (elem, valueAccessor) { + var shouldAllowBindings = ko.utils.unwrapObservable(valueAccessor()); + return { controlsDescendantBindings: !shouldAllowBindings }; + } + }; + ko.bindingHandlers.withProperties = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + var newProperties = valueAccessor(), + innerBindingContext = bindingContext.extend(newProperties); + ko.applyBindingsToDescendants(innerBindingContext, element); + return { controlsDescendantBindings: true }; + } + }; + ko.bindingHandlers.withProperties = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + var newProperties = valueAccessor(), + childBindingContext = bindingContext.createChildContext(viewModel); + ko.utils.extend(childBindingContext, newProperties); + ko.applyBindingsToDescendants(childBindingContext, element); + return { controlsDescendantBindings: true }; + } + }; + ko.bindingHandlers.randomOrder = { + init: function (elem, valueAccessor) { + var child = ko.virtualElements.firstChild(elem), + childElems = []; + while (child) { + childElems.push(child); + child = ko.virtualElements.nextSibling(child); + } + ko.virtualElements.emptyNode(elem); + while (childElems.length) { + var randomIndex = Math.floor(Math.random() * childElems.length), + chosenChild = childElems.splice(randomIndex, 1); + ko.virtualElements.prepend(elem, chosenChild[0]); + } + } + }; + + var node, containerElem, nodeToInsert, insertAfter, nodeToPrepend, arrayOfNodes; + ko.virtualElements.allowedBindings.mySuperBinding = true; + ko.virtualElements.emptyNode(containerElem); + ko.virtualElements.firstChild(containerElem); + ko.virtualElements.insertAfter(containerElem, nodeToInsert, insertAfter); + ko.virtualElements.nextSibling(node); + ko.virtualElements.prepend(containerElem, nodeToPrepend); + ko.virtualElements.setDomNodeChildren(containerElem, arrayOfNodes); +} + +function test_more() { + var viewModel = { + firstName: ko.observable("Bert"), + lastName: ko.observable("Smith"), + pets: ko.observableArray(["Cat", "Dog", "Fish"]), + type: "Customer", + hasALotOfPets: any + }; + viewModel.hasALotOfPets = ko.computed(function () { + return this.pets().length > 2 + }, viewModel); + var plainJs = ko.toJS(viewModel); + + ko.extenders.logChange = function (target, option) { + target.subscribe(function (newValue) { + console.log(option + ": " + newValue); + }); + return target; + }; + + ko.extenders.numeric = function (target, precision) { + var result = ko.computed({ + read: target, + write: function (newValue) { + var current = target(), + roundingMultiplier = Math.pow(10, precision), + newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue), + valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier; + + if (valueToWrite !== current) { + target(valueToWrite); + } else { + if (newValue !== current) { + target.notifySubscribers(valueToWrite); + } + } + } + }); + + result(target()); + + return result; + }; + + function AppViewModel(one, two) { + this.myNumberOne = ko.observable(one).extend({ numeric: 0 }); + this.myNumberTwo = ko.observable(two).extend({ numeric: 2 }); + } + + ko.applyBindings(new AppViewModel(221.2234, 123.4525)); + + ko.extenders.required = function (target, overrideMessage) { + + target.hasError = ko.observable(); + target.validationMessage = ko.observable(); + + function validate(newValue) { + target.hasError(newValue ? false : true); + target.validationMessage(newValue ? "" : overrideMessage || "This field is required"); + } + + validate(target()); + + target.subscribe(validate); + + return target; + }; + + function AppViewModel(first, last) { + this.firstName = ko.observable(first).extend({ required: "Please enter a first name" }); + this.lastName = ko.observable(last).extend({ required: "" }); + } + + ko.applyBindings(new AppViewModel("Bob", "Smith")); + + var first; + this.firstName = ko.observable(first).extend({ required: "Please enter a first name", logChange: "first name" }); + + var upperCaseName = ko.computed(function () { + return name().toUpperCase(); + }).extend({ throttle: 500 }); + + function AppViewModel() { + this.instantaneousValue = ko.observable(); + this.throttledValue = ko.computed(this.instantaneousValue) + .extend({ throttle: 400 }); + + this.loggedValues = ko.observableArray([]); + this.throttledValue.subscribe(function (val) { + if (val !== '') + this.loggedValues.push(val); + }, this); + } + + function GridViewModel() { + this.pageSize = ko.observable(20); + this.pageIndex = ko.observable(1); + this.currentPageData = ko.observableArray(); + + ko.computed(function () { + var params = { page: this.pageIndex(), size: this.pageSize() }; + $.getJSON('/Some/Json/Service', params, this.currentPageData); + }, this); + } + this.setPageSize = function (newPageSize) { + this.pageSize(newPageSize); + this.pageIndex(1); + } + + ko.computed(function () { + var params = { page: this.pageIndex(), size: this.pageSize() }; + $.getJSON('/Some/Json/Service', params, this.currentPageData); + }, this).extend({ throttle: 1 }); + + $(".remove").click(function () { + viewModel.items.remove(ko.dataFor(this)); + }); + $(".remove").live("click", function () { + viewModel.items.remove(ko.dataFor(this)); + }); + + $("#people").delegate(".remove", "click", function () { + + var context = ko.contextFor(this), + parentArray = context.$parent.people || context.$parent.children; + + parentArray.remove(context.$data); + + return false; + }); + $("#people").delegate(".add", "click", function () { + var context = ko.contextFor(this), + childName = context.$data.name() + " child", + parentArray = context.$data.people || context.$data.children; + + context.$root.addChild(childName, parentArray); + + return false; + }); + ko.observableArray.fn.filterByProperty = function (propName, matchValue) { + return ko.computed(function () { + var allItems = this(), matchingItems = []; + for (var i = 0; i < allItems.length; i++) { + var current = allItems[i]; + if (ko.utils.unwrapObservable(current[propName]) === matchValue) + matchingItems.push(current); + } + return matchingItems; + }, this); + } + function Task(title, done) { + this.title = ko.observable(title); + this.done = ko.observable(done); + } + + function AppViewModel() { + this.tasks = ko.observableArray([ + new Task('Find new desktop background', true), + new Task('Put shiny stickers on laptop', false), + new Task('Request more reggae music in the office', true) + ]); + + this.doneTasks = this.tasks.filterByProperty("done", true); + } + + ko.applyBindings(new AppViewModel()); + this.doneTasks = ko.computed(function () { + var all = this.tasks(), done = []; + for (var i = 0; i < all.length; i++) + if (all[i].done()) + done.push(all[i]); + return done; + }, this); +} + +function test_mappingplugin() { + var viewModel = { + serverTime: ko.observable(), + numUsers: ko.observable() + } + var data = { + serverTime: '2010-01-07', + numUsers: 3 + }; + viewModel.serverTime(data.serverTime); + viewModel.numUsers(data.numUsers); + + var viewModel = ko.mapping.fromJS(data); + ko.mapping.fromJS(data, viewModel); + var unmapped = ko.mapping.toJS(viewModel); + + var viewModel = ko.mapping.fromJS(data); + ko.mapping.fromJS(data, viewModel); + + var myChildModel = function (data) { + ko.mapping.fromJS(data, {}, this); + + this.nameLength = ko.computed(function () { + return this.name().length; + }, this); + } + + var oldOptions = ko.mapping.defaultOptions().include; + ko.mapping.defaultOptions().include = ["alwaysIncludeThis"]; + + var oldOptions = ko.mapping.defaultOptions().copy; + ko.mapping.defaultOptions().copy = ["alwaysCopyThis"]; + + var someObject; + ko.mapping.fromJS(data, {}, someObject); + ko.mapping.fromJS(data, {}, this); + + var alice, aliceMappingOptions, bob, bobMappingOptions; + var viewModel = ko.mapping.fromJS(alice, aliceMappingOptions); + ko.mapping.fromJS(bob, bobMappingOptions, viewModel); + + var obj; + var result = ko.mapping.fromJS(obj, { + key: function (item) { + return ko.utils.unwrapObservable(item.id); + } + }); + + result.mappedRemove({ id: 2 }); + var newItem = result.mappedCreate({ id: 3 }); +} + +function test_misc() { + var postbox = new ko.subscribable(); + postbox.subscribe(callback, target, topic); + + postbox.subscribe(function (newValue) { + this.latestTopic(newValue); + }, vm, "mytopic"); + postbox.notifySubscribers(value, "mytopic"); + + ko.subscribable.fn.publishOn = function (topic) { + this.subscribe(function (newValue) { + postbox.notifySubscribers(newValue, topic); + }); + + return this; + }; + + this.myObservable = ko.observable("myValue").publishOn("myTopic"); + + ko.subscribable.fn.subscribeTo = function (topic) { + postbox.subscribe(this, null, topic); + + return this; + }; + + this.observableFromAnotherVM = ko.observable().subscribeTo("myTopic"); + + postbox.subscribe(function (newValue) { + this(newValue); + }, this, topic); + + ko.bindingHandlers.isolatedOptions = { + init: function (element, valueAccessor) { + var args = arguments; + ko.computed({ + read: function () { + ko.utils.unwrapObservable(valueAccessor()); + ko.bindingHandlers.options.update.apply(this, args); + }, + owner: this, + disposeWhenNodeIsRemoved: element + }); + } + }; + + ko.subscribable.fn.publishOn = function (topic) { + this.subscribe(function (newValue) { + postbox.notifySubscribers(newValue, topic); + }); + + return this; + }; + + this.myObservable = ko.observable("myValue").publishOn("myTopic"); + + var x = ko.observableArray([1, 2, 3]); + + var element; + ko.utils.domNodeDisposal.addDisposeCallback(element, function () { + $(element).datepicker("destroy"); + }); +} \ No newline at end of file