From 22dd4e45539507062b70ba368013d68c675faadf Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Fri, 21 Jun 2019 15:30:26 +0200 Subject: [PATCH 1/3] Add knockout.mapping types --- package.json | 3 +- types/knockout.mapping.d.ts | 227 +++++++++++++++++++++++++++++++++ types/knockout.mapping.spec.ts | 110 ++++++++++++++++ types/tsconfig.json | 8 ++ 4 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 types/knockout.mapping.d.ts create mode 100644 types/knockout.mapping.spec.ts create mode 100644 types/tsconfig.json diff --git a/package.json b/package.json index 72e30ad..d3d2dcd 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "2.6.0", "description": "Knockout Mapping plugin", "main": "dist/knockout.mapping.js", + "types": "types/knockout.mapping.d.ts", "files": [ "dist", "HISTORY.md" @@ -47,4 +48,4 @@ "url": "https://github.com/crissdev/knockout.mapping/issues" }, "license": "MIT" -} +} \ No newline at end of file diff --git a/types/knockout.mapping.d.ts b/types/knockout.mapping.d.ts new file mode 100644 index 0000000..38deebf --- /dev/null +++ b/types/knockout.mapping.d.ts @@ -0,0 +1,227 @@ +import * as ko from "knockout"; + +declare module "knockout" { + export module mapping { + export type MappedObservable = { + [P in keyof T]: + T[P] extends Array ? ko.ObservableArray> : + ko.Observable; + } + + export type MappingOptions = MappingOptionsBase & MappingOptionsSpecific; + + export interface MappingOptionsBase { + ignore?: (keyof T)[]; + include?: (keyof T)[]; + copy?: (keyof T)[]; + observe?: (keyof T)[]; + mappedProperties?: (keyof T)[]; + deferEvaluation?: boolean; + } + + export interface MappingOptionsProperty extends MappingOptionsBase { + create?: (options: CreateOptions) => void; + update?: (options: UpdateOptions) => void; + key?: (data: T) => any; + } + + export type MappingOptionsSpecific = { + [P in keyof T]?: + T[P] extends Array ? MappingOptionsProperty : + MappingOptionsProperty; + } + + export interface CreateOptions { + data: T; + parent: any; + } + + export interface UpdateOptions { + data: T; + parent: any; + target: any; + observable?: ko.Observable; + } + + export interface VisitModelOptions { + visitedObjects?: any; + parentName?: string; + ignore?: string[]; + copy?: string[]; + include?: string[]; + } + + /** + * Checks if an object was created using `knockout.mapping`. + * @param viewModel View model object to be checked. + */ + export function isMapped(viewModel: any): boolean; + + /** + * Updates target observable with value from the source. + * + * @param source Plain JavaScript value to be mapped. + * @param target Observable to be updated. + */ + export function fromJS(source: string, target: ko.Observable): ko.Observable; + /** + * Creates an observable wrapping source's value. + * If 'target' is supplied, instead, target observable is updated. + * + * @param source Plain JavaScript value to be mapped. + * @param options The mapping options. + * @param target Observable to be updated. + */ + export function fromJS(source: string, inputOptions?: MappingOptions, target?: ko.Observable): ko.Observable; + + /** + * Updates target observable with value from the source. + * + * @param source Plain JavaScript value to be mapped. + * @param target Observable to be updated. + */ + export function fromJS(source: number, target: ko.Observable): ko.Observable; + /** + * Creates an observable wrapping source's value. + * If 'target' is supplied, instead, target observable is updated. + * + * @param source Plain JavaScript value to be mapped. + * @param options The mapping options. + * @param target Observable to be updated. + */ + export function fromJS(source: number, inputOptions?: MappingOptions, target?: ko.Observable): ko.Observable; + + /** + * Updates target observable with value from the source. + * + * @param source Plain JavaScript value to be mapped. + * @param target Observable to be updated. + */ + export function fromJS(source: boolean, target: ko.Observable): ko.Observable; + /** + * Creates an observable wrapping source's value. + * If 'target' is supplied, instead, target observable is updated. + * + * @param source Plain JavaScript value to be mapped. + * @param options The mapping options. + * @param target Observable to be updated. + */ + export function fromJS(source: boolean, inputOptions?: MappingOptions, target?: ko.Observable): ko.Observable; + + /** + * Updates target's observable properties with those of the sources. + * + * @param source Plain JavaScript array to be mapped. + * @param target View model object previously mapped to be updated. + */ + export function fromJS(source: SourceT[], target: ko.ObservableArray): ko.ObservableArray; + /** + * Creates a view model object with observable properties for each of the properties on the source. + * If 'target' is supplied, instead, target's observable properties are updated. + * + * @param source Plain JavaScript array to be mapped. + * @param options The mapping options. + * @param target View model object previously mapped to be updated. + */ + export function fromJS(source: SourceT[], inputOptions?: MappingOptions, target?: ko.ObservableArray): ko.ObservableArray; + + /** + * Updates target's observable properties with those of the sources. + * + * @param source Plain JavaScript object to be mapped. + * @param target View model object previously mapped to be updated. + */ + export function fromJS(source: SourceT, target: MappedT): MappedT; + /** + * Creates a view model object with observable properties for each of the properties on the source. + * If 'target' is supplied, instead, target's observable properties are updated. + * + * @param source Plain JavaScript object to be mapped. + * @param options The mapping options. + * @param target View model object previously mapped to be updated. + */ + export function fromJS(source: SourceT, inputOptions?: MappingOptions, target?: MappedT): MappedT; + + /** + * Updates target's observable properties with those of the sources. + * + * @param jsonString JSON of a JavaScript object to be mapped. + * @param target View model object previously mapped to be updated. + */ + export function fromJSON(jsonString: string, target: MappedT): MappedT; + /** + * Creates a view model object with observable properties for each of the properties on the source. + * If 'target' is supplied, instead, target's observable properties are updated. + * + * @param jsonString JSON of a JavaScript object to be mapped. + * @param options Options on mapping behavior. + * @param target View model object previosly mapped to be updated. + */ + export function fromJSON(jsonString: string, inputOptions?: MappingOptions, target?: MappedT): MappedT; + + /** + * Creates an unmapped object containing only the properties of the mapped object that were part of your original JS object. + * + * @param rootObject Object with observables to be converted. + * @param options The mapping options + */ + export function toJS(rootObject: Object, options?: MappingOptions): MappedT; + + /** + * Creates an unmapped object containing only the properties of the mapped object that were part of your original JS object. + * Stringify the result. + * + * @param rootObject Object with observables to be converted. + * @param options The mapping options. + * @param replacer Same as JSON.stringify + * @param space Sam as JSON.stringify + */ + export function toJSON(rootObject: SourceT, options?: MappingOptions, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; + + /** Get the default mapping options. */ + export function defaultOptions(): MappingOptions; + /** + * Sets the default mapping options. + * + * @param options The new default options. + */ + export function defaultOptions(options: MappingOptions): void; + + /** Undocumented. Reset Mapping default options to the original ones. */ + export function resetDefaultOptions(): void; + + /** + * Undocumented. Custom implementation of JavaScript's typeof. + * + * @param x Object to check type. + */ + export function getType(x: any): string; + + /** + * Undocumented. Visit an object and executes callback on each properties. + * + * @param rootObject The root object to visit. + * @param callback The callback which is executed on each properties. + * @param options The options for the visiting. + */ + export function visitModel(rootObject: Object, callback: (propertyValue: any, parentName: string) => any, options?: VisitModelOptions): T; + } + + export interface ObservableArrayFunctions { + mappedCreate(item: T): T; + + mappedRemove(item: T): T[]; + mappedRemove(removeFunction: (item: T) => boolean): T[]; + + mappedRemoveAll(): T[]; + mappedRemoveAll(items: T[]): T[]; + + mappedDestroy(item: T): void; + mappedDestroy(destroyFunction: (item: T) => boolean): void; + + mappedDestroyAll(): void; + mappedDestroyAll(items: T[]): void; + } +} + +export = ko.mapping; diff --git a/types/knockout.mapping.spec.ts b/types/knockout.mapping.spec.ts new file mode 100644 index 0000000..80ec641 --- /dev/null +++ b/types/knockout.mapping.spec.ts @@ -0,0 +1,110 @@ +import "./knockout.mapping"; +import * as ko from "knockout"; + +/* + * Basic Usage + */ + +interface SimpleObject { + serverTime: string; + numUsers: number; +} + +interface SimpleVM extends ko.mapping.MappedObservable { } + +const simpleData: SimpleObject = { + serverTime: "2010-01-07", + numUsers: 3 +}; + +const simpleVM = ko.mapping.fromJS(simpleData); +simpleVM.serverTime("2010-01-08"); +simpleVM.numUsers(5); + +ko.mapping.fromJS(simpleData, simpleVM); + +/* + * Advanced Usage + */ + +interface AdvancedData { + name: string; + children: AdvancedDataChild[]; +} +interface AdvancedDataChild { + id: number; + name: string; +} + +interface AdvancedVM extends ko.mapping.MappedObservable { +} + +const advancedData: AdvancedData = { + name: "Scott", + children: [ + { id: 1, name: "Alice" } + ] +}; + +const advancedVM = ko.mapping.fromJS(advancedData); +advancedVM.name("test"); +advancedVM.children()[0].id(2); + +const advancedMapping: ko.mapping.MappingOptions = { + children: { + key(data) { + return ko.unwrap(data.id); + } + } +}; + +const advancedVM2 = ko.mapping.fromJS(advancedData, advancedMapping); +advancedVM2.name("test"); +advancedVM2.children()[0].id(2); + +class AdvancedVMChild { + id: ko.Observable; + name: ko.Observable; + + constructor(data: AdvancedDataChild) { + this.id = ko.observable(data.id); + this.name = ko.observable(data.name); + } +} + +interface AdvancedVMWithChildren { + name: ko.Observable; + children: ko.ObservableArray; +} + +const advancedMappingChildren: ko.mapping.MappingOptions = { + children: { + key(data) { + return data.id; + }, + create(opts) { + return new AdvancedVMChild(opts.data); + } + } +}; + +const advancedVMChildren = ko.mapping.fromJS(advancedData, advancedMappingChildren); +advancedVMChildren.name("test"); +advancedVMChildren.children()[0].id(5); + +const advancedMappingUpdate: ko.mapping.MappingOptions = { + name: { + update(opts) { + return opts.data + " updated!"; + } + } +}; + +const advancedVMUpdate = ko.mapping.fromJS(advancedData, advancedMappingUpdate); + +const advancedMappingOptions: ko.mapping.MappingOptions = { + ignore: ["name"], + include: ["children"], + copy: ["children"], + observe: ["name"] +} diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 0000000..f1d627e --- /dev/null +++ b/types/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "umd", + "moduleResolution": "node", + "strict": true + } +} \ No newline at end of file From 86988bcc5a7cfcc516daea90b10b6e54acc3372a Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Wed, 17 Jul 2019 12:54:31 +0200 Subject: [PATCH 2/3] Improve MappedObservable type --- types/knockout.mapping.d.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/types/knockout.mapping.d.ts b/types/knockout.mapping.d.ts index 38deebf..86c5b62 100644 --- a/types/knockout.mapping.d.ts +++ b/types/knockout.mapping.d.ts @@ -4,9 +4,13 @@ declare module "knockout" { export module mapping { export type MappedObservable = { [P in keyof T]: - T[P] extends Array ? ko.ObservableArray> : - ko.Observable; - } + T[P] extends ko.Observable | ko.ObservableArray ? T[P] : + T[P] extends string | boolean | number | Date ? ko.Observable : + T[P] extends Array ? ko.ObservableArray> : + T[P] extends Function ? T[P] : + T[P] extends object ? MappedObservable : + T[P]; + }; export type MappingOptions = MappingOptionsBase & MappingOptionsSpecific; @@ -29,7 +33,7 @@ declare module "knockout" { [P in keyof T]?: T[P] extends Array ? MappingOptionsProperty : MappingOptionsProperty; - } + }; export interface CreateOptions { data: T; From 576a1a34bc85c95a70e5733c00e6e65a89f9d6e4 Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Wed, 17 Jul 2019 15:09:52 +0200 Subject: [PATCH 3/3] Automap to MappedObservable when no options is provided --- types/knockout.mapping.d.ts | 54 ++++++++++++++++++++++++++-------- types/knockout.mapping.spec.ts | 27 ++++++++++++++--- 2 files changed, 65 insertions(+), 16 deletions(-) diff --git a/types/knockout.mapping.d.ts b/types/knockout.mapping.d.ts index 86c5b62..a9ab7d2 100644 --- a/types/knockout.mapping.d.ts +++ b/types/knockout.mapping.d.ts @@ -113,12 +113,20 @@ declare module "knockout" { export function fromJS(source: boolean, inputOptions?: MappingOptions, target?: ko.Observable): ko.Observable; /** - * Updates target's observable properties with those of the sources. + * Creates a view model object with observable properties for each of the properties on the source. * * @param source Plain JavaScript array to be mapped. - * @param target View model object previously mapped to be updated. */ - export function fromJS(source: SourceT[], target: ko.ObservableArray): ko.ObservableArray; + export function fromJS(source: SourceT[]): ko.ObservableArray>; + + /** + * Creates a view model object with observable properties for each of the properties on the source. + * + * @param source Plain JavaScript array to be mapped. + * @param inputOptions The mappings options with no properties. + */ + export function fromJS(source: SourceT[], inputOptions: {}): ko.ObservableArray>; + /** * Creates a view model object with observable properties for each of the properties on the source. * If 'target' is supplied, instead, target's observable properties are updated. @@ -127,15 +135,30 @@ declare module "knockout" { * @param options The mapping options. * @param target View model object previously mapped to be updated. */ - export function fromJS(source: SourceT[], inputOptions?: MappingOptions, target?: ko.ObservableArray): ko.ObservableArray; - + export function fromJS(source: SourceT[], inputOptions: MappingOptions, target?: ko.ObservableArray): ko.ObservableArray; /** * Updates target's observable properties with those of the sources. * - * @param source Plain JavaScript object to be mapped. + * @param source Plain JavaScript array to be mapped. * @param target View model object previously mapped to be updated. */ - export function fromJS(source: SourceT, target: MappedT): MappedT; + export function fromJS(source: SourceT[], target: ko.ObservableArray): ko.ObservableArray; + + /** + * Creates a view model object with observable properties for each of the properties on the source. + * + * @param source Plain JavaScript object to be mapped. + */ + export function fromJS(source: SourceT): MappedObservable; + + /** + * Creates a view model object with observable properties for each of the properties on the source. + * + * @param source Plain JavaScript object to be mapped. + * @param inputOptions The mappings options with no properties. + */ + export function fromJS(source: SourceT, inputOptions: {}): MappedObservable; + /** * Creates a view model object with observable properties for each of the properties on the source. * If 'target' is supplied, instead, target's observable properties are updated. @@ -144,15 +167,15 @@ declare module "knockout" { * @param options The mapping options. * @param target View model object previously mapped to be updated. */ - export function fromJS(source: SourceT, inputOptions?: MappingOptions, target?: MappedT): MappedT; - + export function fromJS(source: SourceT, inputOptions: MappingOptions, target?: MappedT): MappedT; /** * Updates target's observable properties with those of the sources. * - * @param jsonString JSON of a JavaScript object to be mapped. + * @param source Plain JavaScript object to be mapped. * @param target View model object previously mapped to be updated. */ - export function fromJSON(jsonString: string, target: MappedT): MappedT; + export function fromJS(source: SourceT, target: MappedT): MappedT; + /** * Creates a view model object with observable properties for each of the properties on the source. * If 'target' is supplied, instead, target's observable properties are updated. @@ -162,6 +185,13 @@ declare module "knockout" { * @param target View model object previosly mapped to be updated. */ export function fromJSON(jsonString: string, inputOptions?: MappingOptions, target?: MappedT): MappedT; + /** + * Updates target's observable properties with those of the sources. + * + * @param jsonString JSON of a JavaScript object to be mapped. + * @param target View model object previously mapped to be updated. + */ + export function fromJSON(jsonString: string, target: MappedT): MappedT; /** * Creates an unmapped object containing only the properties of the mapped object that were part of your original JS object. @@ -169,7 +199,7 @@ declare module "knockout" { * @param rootObject Object with observables to be converted. * @param options The mapping options */ - export function toJS(rootObject: Object, options?: MappingOptions): MappedT; + export function toJS(rootObject: MappedT, options?: MappingOptions): SourceT; /** * Creates an unmapped object containing only the properties of the mapped object that were part of your original JS object. diff --git a/types/knockout.mapping.spec.ts b/types/knockout.mapping.spec.ts index 80ec641..0a866a2 100644 --- a/types/knockout.mapping.spec.ts +++ b/types/knockout.mapping.spec.ts @@ -10,14 +10,12 @@ interface SimpleObject { numUsers: number; } -interface SimpleVM extends ko.mapping.MappedObservable { } - const simpleData: SimpleObject = { serverTime: "2010-01-07", numUsers: 3 }; -const simpleVM = ko.mapping.fromJS(simpleData); +const simpleVM = ko.mapping.fromJS(simpleData); simpleVM.serverTime("2010-01-08"); simpleVM.numUsers(5); @@ -46,7 +44,7 @@ const advancedData: AdvancedData = { ] }; -const advancedVM = ko.mapping.fromJS(advancedData); +const advancedVM = ko.mapping.fromJS(advancedData); advancedVM.name("test"); advancedVM.children()[0].id(2); @@ -108,3 +106,24 @@ const advancedMappingOptions: ko.mapping.MappingOptions = { copy: ["children"], observe: ["name"] } + +/* + * Automatic Mapping + */ + +const autoMapped = ko.mapping.fromJS(simpleData); +autoMapped.numUsers(); +autoMapped.serverTime(); + +const autoMappedWithOptions = ko.mapping.fromJS(simpleData, {}); +autoMappedWithOptions.numUsers(); +autoMappedWithOptions.serverTime(); + +const simpleDataArray = [simpleData]; +const autoMappedArray = ko.mapping.fromJS(simpleDataArray); +autoMappedArray()[0].numUsers(); +autoMappedArray()[0].serverTime(); + +const autoMappedArrayWithOptions = ko.mapping.fromJS(simpleDataArray, {}); +autoMappedArrayWithOptions()[0].numUsers(); +autoMappedArrayWithOptions()[0].serverTime();