private static void WriteListViewModel(TypeScriptCodeBuilder b, ClassViewModel model) { string name = model.ViewModelClassName; string viewModelName = $"{model.ListViewModelClassName}ViewModel"; string metadataName = $"$metadata.{name}"; using (b.Block($"export class {viewModelName} extends ListViewModel<$models.{name}, $apiClients.{name}ApiClient>")) { foreach (var method in model.ClientMethods.Where(m => m.IsStatic)) { string signature = string.Concat(method.ClientParameters.Select(f => $", {f.Name}: {new VueType(f.Type).TsType("$models")} | null")); // "item" or "list" var transportTypeSlug = method.TransportType.ToString().Replace("Result", "").ToLower(); b.DocComment(method.Comment, true); b.Line($"public {method.JsVariable} = this.$apiClient.$makeCaller(\"{transportTypeSlug}\", "); b.Indented($"(c{signature}) => c.{method.JsVariable}({string.Join(", ", method.ClientParameters.Select(p => p.Name))}))"); } b.Line(); using (b.Block($"constructor()")) { b.Line($"super({metadataName}, new $apiClients.{name}ApiClient())"); } } b.Line(); }
private static void WriteApiEndpointFunction(TypeScriptCodeBuilder b, ClassViewModel model, MethodViewModel method) { var returnIsListResult = method.ReturnsListResult; string signature = string.Concat(method.ClientParameters.Select(f => $"{f.Name}: {new VueType(f.Type).TsType("$models")} | null, ")) + "$config?: AxiosRequestConfig"; if (method.IsModelInstanceMethod) { signature = $"id: {new VueType(model.PrimaryKey.Type).TsType(null)}, " + signature; } using (b.Block($"public {method.JsVariable}({signature})")) { string resultType = method.TransportTypeGenericParameter.IsVoid ? $"{method.TransportType}<void>" : $"{method.TransportType}<{new VueType(method.TransportTypeGenericParameter).TsType("$models")}>"; b.Line($"const $method = this.$metadata.methods.{method.JsVariable}"); using (b.Block($"const $params = this.$mapParams($method,", ')')) { if (method.IsModelInstanceMethod) { b.Line($"id,"); } foreach (var param in method.ClientParameters) { b.Line($"{param.JsVariable},"); } } b.Line("return AxiosClient"); using (b.Indented()) { b.Line($".{method.ApiActionHttpMethodName.ToLower()}("); b.Indented($"`/${{this.$metadata.controllerRoute}}/{method.Name}`,"); switch (method.ApiActionHttpMethod) { case DataAnnotations.HttpMethod.Get: case DataAnnotations.HttpMethod.Delete: b.Indented($"this.$options(undefined, $config, $params)"); break; default: b.Indented($"qs.stringify($params),"); b.Indented($"this.$options(undefined, $config)"); break; } b.Line(")"); b.Line($".then<AxiosResponse<{resultType}>>(r => this.$hydrate{method.TransportType}(r, $method.return))"); } } // Line between methods b.Line(); }
private void WriteDataSources(TypeScriptCodeBuilder b) { var dataSources = Model.ClientDataSources(Services.ReflectionRepository).ToList(); var defaultSource = dataSources.SingleOrDefault(s => s.IsDefaultDataSource); using (b.Block($"export namespace {Model.ClientTypeName}DataSources")) { if (defaultSource == null) { b.Line($"export class {DataSourceFactory.DefaultSourceName} extends Coalesce.DataSource<{ViewModelFullName}> {{ }}"); } foreach (var source in dataSources) { b.DocComment(source.Comment); using (b.Block($"export class {source.ClientTypeName} extends Coalesce.DataSource<{ViewModelFullName}>")) { if (source.DataSourceParameters.Any()) { foreach (PropertyViewModel prop in source.DataSourceParameters) { b.DocComment(prop.Comment); b.Line($"public {prop.JsVariable}: {prop.Type.TsKnockoutType(true)} = {prop.Type.ObservableConstructorCall()};"); } using (b.Block("public saveToDto = () =>")) { b.Line("var dto: any = {};"); foreach (PropertyViewModel prop in source.DataSourceParameters) { if (prop.Type.IsDate) { b.Line($"if (!this.{prop.JsVariable}()) dto.{prop.JsonName} = null;"); b.Line($"else dto.{prop.JsonName} = this.{prop.JsVariable}()!.format('YYYY-MM-DDTHH:mm:ss{(prop.Type.IsDateTimeOffset ? "ZZ" : "")}');"); } else { b.Line($"dto.{prop.JsonName} = this.{prop.JsVariable}();"); } } b.Line("return dto;"); } } } // Case-sensitive comparison intended here. We always need a data source cased EXACTLY as "Default". if (source == defaultSource && !source.ClientTypeName.Equals(DataSourceFactory.DefaultSourceName, StringComparison.InvariantCulture)) { b.Line($"export const {DataSourceFactory.DefaultSourceName} = {source.ClientTypeName};"); } } } }
public override void BuildOutput(TypeScriptCodeBuilder b) { using (b.Block($"module {ViewModelModuleName}")) { using (b.Block($"export class {Model.ViewModelClassName} extends {Model.ViewModelGeneratedClassName}")) { b.Line(); using (b.Block($"constructor(newItem?: object, parent?: Coalesce.BaseViewModel | {ListViewModelModuleName}.{Model.ListViewModelClassName})")) { b.Line("super(newItem, parent);"); b.Line(); } } } }
/// <summary> /// Write the metadata for all data sources of a class /// </summary> private void WriteDataSourcesMetadata(TypeScriptCodeBuilder b, ClassViewModel model) { using (b.Block("dataSources:", ',')) { var dataSources = model.ClientDataSources(this.Model); foreach (var source in dataSources) { WriteDataSourceMetadata(b, model, source); } // Not sure we need to explicitly declare the default source. // We can just use the absense of a data source to represent the default. /* * var defaultSource = dataSources.SingleOrDefault(s => s.IsDefaultDataSource); * if (defaultSource != null) * { * var name = defaultSource.ClientTypeName.ToCamelCase(); * b.Line($"get default() {{ return this.{name} }},"); * } * else * { * using (b.Block($"default:", ',')) * { * b.StringProp("type", "dataSource"); * b.StringProp("name", "default"); * b.StringProp("displayName", "Default"); * b.Line("params: {}"); * } * } */ } }
private void WriteEnumNamespace(TypeScriptCodeBuilder b) { using (b.Block($"export namespace {Model.ViewModelGeneratedClassName}")) { foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.Type.IsEnum)) { using (b.Block($"export enum {prop.Name}Enum")) { foreach (var kvp in prop.Type.EnumValues) { b.Line($"{kvp.Value} = {kvp.Key},"); } } } } }
private static void WriteListViewModel(TypeScriptCodeBuilder b, ClassViewModel model) { string name = model.ViewModelClassName; string viewModelName = $"{model.ListViewModelClassName}ViewModel"; string metadataName = $"metadata.{name}"; using (b.Block($"export class {viewModelName} extends ListViewModel<models.{name}, apiClients.{name}ApiClient>")) { using (b.Block($"constructor()")) { b.Line($"super({metadataName}, new apiClients.{name}ApiClient())"); } } b.Line(); }
public override void BuildOutput(TypeScriptCodeBuilder b) { using (b.Block($"module {ViewModelModuleName}")) { b.DocComment($"External Type {Model.ViewModelClassName}"); WriteViewModelClass(b); } }
/// <summary> /// Write the metadata for a specific parameter to a specific method /// </summary> private void WriteMethodParameterMetadata(TypeScriptCodeBuilder b, MethodViewModel method, ParameterViewModel parameter) { using (b.Block($"{parameter.JsVariable}:", ',')) { WriteValueCommonMetadata(b, parameter); b.StringProp("role", "value"); } }
/// <summary> /// Write metadata common to all type representations, /// like properties, method parameters, method returns, etc. /// </summary> private void WriteTypeCommonMetadata(TypeScriptCodeBuilder b, TypeViewModel type) { void WriteTypeDiscriminator(string propName, TypeViewModel t) { var kind = t.TsTypeKind; switch (kind) { case TypeDiscriminator.Unknown: // We assume any unknown props are strings. b.Line("// Type not supported natively by Coalesce - falling back to string."); b.StringProp(propName, "string"); break; default: b.StringProp(propName, kind.ToString().ToLowerInvariant()); break; } } void WriteTypeDef(string propName, TypeViewModel t) { var kind = t.TsTypeKind; switch (kind) { case TypeDiscriminator.Enum: b.Line($"get {propName}() {{ return domain.enums.{t.Name} }},"); break; case TypeDiscriminator.Model: case TypeDiscriminator.Object: b.Line($"get {propName}() {{ return {GetClassMetadataRef(t.ClassViewModel)} }},"); break; } } WriteTypeDiscriminator("type", type); WriteTypeDef("typeDef", type); // For collections, write the references to the underlying type. if (type.TsTypeKind == TypeDiscriminator.Collection) { if (type.PureType.TsTypeKind == TypeDiscriminator.Collection) { throw new InvalidOperationException("Collections of collections aren't supported by Coalesce as exposed types"); } using (b.Block($"itemType:", ',')) { b.StringProp("name", "$collectionItem"); b.StringProp("displayName", ""); b.StringProp("role", "value"); WriteTypeCommonMetadata(b, type.PureType); } } }
private void WriteExternalTypeMetadata(TypeScriptCodeBuilder b, ClassViewModel model) { using (b.Block($"export const {model.ViewModelClassName} = domain.types.{model.ViewModelClassName} =")) { WriteCommonClassMetadata(b, model); b.StringProp("type", "object"); WriteClassPropertiesMetadata(b, model); } }
private void WriteClassPropertiesMetadata(TypeScriptCodeBuilder b, ClassViewModel model) { using (b.Block("props:", ',')) { foreach (var prop in model.ClientProperties) { WriteClassPropertyMetadata(b, model, prop); } } }
/// <summary> /// Write the metadata for all methods of a class /// </summary> private void WriteClassMethodMetadata(TypeScriptCodeBuilder b, ClassViewModel model) { using (b.Block("methods:", ',')) { foreach (var method in model.ClientMethods) { WriteClassMethodMetadata(b, model, method); } } }
public void WriteMethod_SaveToDto(TypeScriptCodeBuilder b) { b.DocComment($"Saves this object into a data transfer object to send to the server."); using (b.Block($"public saveToDto = (): any =>")) { b.Line("var dto: any = {};"); if (Model.PrimaryKey != null) { b.Line($"dto.{Model.PrimaryKey.JsonName} = this.{Model.PrimaryKey.JsVariable}();"); } b.Line(); // Objects are skipped in the loop because objects will be examined // at the same time that their corresponding foreign key is examined. foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.IsClientWritable && f.Object == null)) { if (prop.Type.IsDate) { b.Line($"if (!this.{prop.JsVariable}()) dto.{prop.JsonName} = null;"); b.Line($"else dto.{prop.JsonName} = this.{prop.JsVariable}()!.format('YYYY-MM-DDTHH:mm:ss{(prop.Type.IsDateTimeOffset ? "ZZ" : "")}');"); } else if (prop.IsForeignKey) { var navigationProp = prop.ReferenceNavigationProperty; b.Line($"dto.{prop.JsonName} = this.{prop.JsVariable}();"); // If the Id isn't set, use the object and see if that is set. Allows a child to get an Id after the fact. using (b.Block($"if (!dto.{prop.JsonName} && this.{navigationProp.JsVariable}())")) { b.Line($"dto.{prop.JsonName} = this.{navigationProp.JsVariable}()!.{navigationProp.Object.PrimaryKey.JsVariable}();"); } } else { b.Line($"dto.{prop.JsonName} = this.{prop.JsVariable}();"); } } b.Line(); b.Line($"return dto;"); } }
public override void BuildOutput(TypeScriptCodeBuilder b) { using (b.Block($"module {ViewModelModuleName}")) { b.Line(); WriteViewModelClass(b); b.Line(); WriteEnumNamespace(b); } }
public override void BuildOutput(TypeScriptCodeBuilder b) { using (b.Block($"module {ListViewModelModuleName}")) { b.Line(); WriteDataSources(b); b.Line(); WriteListViewModelClass(b); } }
private void WriteServiceMetadata(TypeScriptCodeBuilder b, ClassViewModel model) { using (b.Block($"export const {model.ClientTypeName} = domain.services.{model.ClientTypeName} =")) { b.StringProp("name", model.ClientTypeName.ToCamelCase()); b.StringProp("displayName", model.DisplayName); b.StringProp("type", "service"); b.StringProp("controllerRoute", model.ApiRouteControllerPart); WriteClassMethodMetadata(b, model); } }
/// <summary> /// Write the metadata for an entire method /// </summary> private void WriteClassMethodMetadata(TypeScriptCodeBuilder b, ClassViewModel model, MethodViewModel method) { using (b.Block($"{method.JsVariable}:", ',')) { b.StringProp("name", method.JsVariable); b.StringProp("displayName", method.DisplayName); b.StringProp("transportType", method.TransportType.ToString().Replace("Result", "").ToLower()); b.StringProp("httpMethod", method.ApiActionHttpMethod.ToString().ToUpperInvariant()); using (b.Block("params:", ',')) { // TODO: should we be writing out the implicit 'id' param as metadata? Or handling some other way? // If we do keep it as metadata, should probably add some prop to mark it as what it is. if (method.IsModelInstanceMethod) { using (b.Block($"id:", ',')) { b.StringProp("name", "id"); b.StringProp("displayName", "Primary Key"); // TODO: Is this what we want? Also, i18n. b.StringProp("role", "value"); WriteTypeCommonMetadata(b, model.PrimaryKey.Type); } } foreach (var param in method.ClientParameters) { WriteMethodParameterMetadata(b, method, param); } } using (b.Block("return:", ',')) { b.StringProp("name", "$return"); b.StringProp("displayName", "Result"); // TODO: i18n b.StringProp("role", "value"); WriteTypeCommonMetadata(b, method.ResultType); } } }
private void WriteViewModelClass(TypeScriptCodeBuilder b) { using (b.Block($"export class {Model.ViewModelGeneratedClassName}")) { if (Model.PrimaryKey != null) { // ID of the object. b.Line("public myId: any = 0;"); } b.Line("public parent: any;"); b.Line("public parentCollection: any;"); b.Line(); b.Line("// Observables"); foreach (PropertyViewModel prop in Model.ClientProperties) { b.Line($"public {prop.JsVariable}: {prop.Type.TsKnockoutType(true)} = {prop.Type.ObservableConstructorCall()};"); if (prop.Type.IsEnum) { b.Line($"public {prop.JsTextPropertyName} = {prop.Type.ObservableConstructorCall()};"); } } WriteMethod_LoadFromDto(b); WriteMethod_SaveToDto(b); b.Line(); using (b.Block("constructor(newItem?: any, parent?: any)")) { b.Line("this.parent = parent;"); b.Line(); b.Line("if (newItem) {"); b.Line(" this.loadFromDto(newItem);"); b.Line("}"); } } }
private static void WriteViewModel(TypeScriptCodeBuilder b, ClassViewModel model) { string name = model.ViewModelClassName; string viewModelName = $"{name}ViewModel"; string metadataName = $"$metadata.{name}"; b.Line($"export interface {viewModelName} extends $models.{name} {{}}"); using (b.Block($"export class {viewModelName} extends ViewModel<$models.{name}, $apiClients.{name}ApiClient>")) { // This is an alternative to calling defineProps() on each class that causes larger and more cluttered files: //foreach (var prop in model.ClientProperties) //{ // b.Line($"get {prop.JsVariable}() {{ return this.$data.{prop.JsVariable} }}"); // b.Line($"set {prop.JsVariable}(val) {{ this.$data.{prop.JsVariable} = val }}"); //} foreach (var method in model.ClientMethods.Where(m => !m.IsStatic)) { string signature = string.Concat(method.ClientParameters.Select(f => $", {f.Name}: {new VueType(f.Type).TsType("$models")} | null")); // "item" or "list" var transportTypeSlug = method.TransportType.ToString().Replace("Result", "").ToLower(); b.DocComment(method.Comment, true); b.Line($"public {method.JsVariable} = this.$apiClient.$makeCaller(\"{transportTypeSlug}\", "); b.Indented($"(c{signature}) => c.{method.JsVariable}(this.$primaryKey{string.Concat(method.ClientParameters.Select(p => ", " + p.Name))}))"); } b.Line(); using (b.Block($"constructor(initialData?: $models.{name})")) { b.Line($"super({metadataName}, new $apiClients.{name}ApiClient(), initialData)"); } } b.Line($"defineProps({viewModelName}, $metadata.{name})"); b.Line(); }
private static void WriteApiClientClass(TypeScriptCodeBuilder b, ClassViewModel model, string baseClass) { string clientName = $"{model.ClientTypeName}ApiClient"; using (b.Block($"export class {clientName} extends {baseClass}")) { b.Line($"constructor() {{ super($metadata.{model.ClientTypeName}) }}"); foreach (var method in model.ClientMethods) { WriteApiEndpointFunction(b, model, method); } } }
private static void WriteApiEndpointFunction(TypeScriptCodeBuilder b, ClassViewModel model, MethodViewModel method) { var returnIsListResult = method.ReturnsListResult; string signature = string.Concat(method.ClientParameters.Select(f => $"{f.Name}: {new VueType(f.Type).TsType("$models")} | null, ")) + "$config?: AxiosRequestConfig"; if (method.IsModelInstanceMethod) { signature = $"id: {new VueType(model.PrimaryKey.Type).TsType(null)}, " + signature; } string resultType = method.TransportTypeGenericParameter.IsVoid ? $"{method.TransportType}<void>" : $"{method.TransportType}<{new VueType(method.TransportTypeGenericParameter).TsType("$models")}>"; using (b.Block($"public {method.JsVariable}({signature}): AxiosPromise<{resultType}>")) { b.Line($"const $method = this.$metadata.methods.{method.JsVariable}"); using (b.Block($"const $params = ")) { if (method.IsModelInstanceMethod) { b.Line($"id,"); } foreach (var param in method.ClientParameters) { b.Line($"{param.JsVariable},"); } } b.Line("return this.$invoke($method, $params, $config)"); } // Line between methods b.Line(); }
public override void BuildOutput(TypeScriptCodeBuilder b) { using (b.Block($"module {ModuleName(ModuleKind.Service)}")) { using (b.Block($"export class {Model.ServiceClientClassName}")) { b.Line(); b.Line($"public readonly apiController: string = \"/{Model.ApiRouteControllerPart}\";"); b.DocComment($"Configuration for all instances of {Model.ServiceClientClassName}. Can be overidden on each instance via instance.coalesceConfig."); b.Line($"public static coalesceConfig = new Coalesce.ServiceClientConfiguration<{Model.ServiceClientClassName}>(Coalesce.GlobalConfiguration.serviceClient);"); b.DocComment($"Configuration for this {Model.ServiceClientClassName} instance."); b.Line($"public coalesceConfig: Coalesce.ServiceClientConfiguration<{Model.ServiceClientClassName}>"); b.Indented($"= new Coalesce.ServiceClientConfiguration<{Model.ServiceClientClassName}>({Model.ServiceClientClassName}.coalesceConfig);"); b.Line(); foreach (var method in Model.ClientMethods) { WriteClientMethodDeclaration(b, method, Model.ServiceClientClassName); } } } }
private static void WriteViewModel(TypeScriptCodeBuilder b, ClassViewModel model) { string name = model.ViewModelClassName; string viewModelName = $"{name}ViewModel"; string metadataName = $"metadata.{name}"; b.Line($"export interface {viewModelName} extends models.{name} {{}}"); using (b.Block($"export class {viewModelName} extends ViewModel<models.{name}, apiClients.{name}ApiClient>")) { // This is an alternative to calling defineProps() on each class that causes larger and more cluttered files: //foreach (var prop in model.ClientProperties) //{ // b.Line($"get {prop.JsVariable}() {{ return this.$data.{prop.JsVariable} }}"); // b.Line($"set {prop.JsVariable}(val) {{ this.$data.{prop.JsVariable} = val }}"); //} using (b.Block($"constructor(initialData?: models.{name})")) { b.Line($"super({metadataName}, new apiClients.{name}ApiClient(), initialData)"); } } b.Line($"defineProps({viewModelName}, metadata.{name})"); b.Line(); }
/// <summary> /// Write the metadata for all data sources of a class /// </summary> private void WriteDataSourceMetadata(TypeScriptCodeBuilder b, ClassViewModel model, ClassViewModel source) { // TODO: Should we be camel-casing the names of data sources in the metadata? // TODO: OR, should we be not camel casing the members we place on the domain[key: string] objects? using (b.Block($"{source.ClientTypeName.ToCamelCase()}:", ',')) { b.StringProp("type", "dataSource"); WriteCommonClassMetadata(b, source); if (source.IsDefaultDataSource) { b.Line("isDefault: true,"); } using (b.Block("params:", ',')) { foreach (var prop in source.DataSourceParameters) { WriteClassPropertyMetadata(b, model, prop); } } } }
private void WriteClassPropertyMetadata(TypeScriptCodeBuilder b, ClassViewModel model, PropertyViewModel prop) { using (b.Block($"{prop.JsVariable}:", ',')) { WriteValueCommonMetadata(b, prop); switch (prop.Role) { case PropertyRole.PrimaryKey: // TS Type: "PrimaryKeyProperty" b.StringProp("role", "primaryKey"); break; case PropertyRole.ForeignKey: // TS Type: "ForeignKeyProperty" var navProp = prop.ReferenceNavigationProperty; b.StringProp("role", "foreignKey"); b.Line($"get principalKey() {{ return {GetClassMetadataRef(navProp.Object)}.props.{navProp.Object.PrimaryKey.JsVariable} as PrimaryKeyProperty }},"); b.Line($"get principalType() {{ return {GetClassMetadataRef(navProp.Object)} }},"); b.Line($"get navigationProp() {{ return {GetClassMetadataRef(model)}.props.{navProp.JsVariable} as ModelReferenceNavigationProperty }},"); break; case PropertyRole.ReferenceNavigation: // TS Type: "ModelReferenceNavigationProperty" b.StringProp("role", "referenceNavigation"); b.Line($"get foreignKey() {{ return {GetClassMetadataRef(model)}.props.{prop.ForeignKeyProperty.JsVariable} as ForeignKeyProperty }},"); b.Line($"get principalKey() {{ return {GetClassMetadataRef(prop.Object)}.props.{prop.Object.PrimaryKey.JsVariable} as PrimaryKeyProperty }},"); break; case PropertyRole.CollectionNavigation: // TS Type: "ModelCollectionNavigationProperty" b.StringProp("role", "collectionNavigation"); b.Line($"get foreignKey() {{ return {GetClassMetadataRef(prop.Object)}.props.{prop.InverseProperty.ForeignKeyProperty.JsVariable} as ForeignKeyProperty }},"); break; default: b.StringProp("role", "value"); break; } // We store the negative case instead of the positive // because there are likely going to be more that are serializable than not. if (!prop.IsClientSerializable) { b.Line("dontSerialize: true,"); } } }
private void WriteApiBackedTypeMetadata(TypeScriptCodeBuilder b, ClassViewModel model) { using (b.Block($"export const {model.ViewModelClassName} = domain.types.{model.ViewModelClassName} =")) { WriteCommonClassMetadata(b, model); b.StringProp("type", "model"); b.StringProp("controllerRoute", model.ApiRouteControllerPart); b.Line($"get keyProp() {{ return this.props.{model.PrimaryKey.JsVariable} }}, "); WriteClassPropertiesMetadata(b, model); WriteClassMethodMetadata(b, model); WriteDataSourcesMetadata(b, model); } }
private void WriteListViewModelClass(TypeScriptCodeBuilder b) { using (b.Block($"export class {Model.ListViewModelClassName} extends Coalesce.BaseListViewModel<{ViewModelFullName}>")) { b.Line($"public readonly modelName: string = \"{Model.ClientTypeName}\";"); b.Line($"public readonly apiController: string = \"/{Model.ApiRouteControllerPart}\";"); b.Line($"public modelKeyName: string = \"{Model.PrimaryKey.JsVariable}\";"); b.Line($"public itemClass: new () => {ViewModelFullName} = {ViewModelFullName};"); b.Line(); b.Line("public filter: {"); foreach (var prop in Model.BaseViewModel.ClientProperties.Where(f => f.IsUrlFilterParameter)) { b.Indented($"{prop.JsonName}?: string;"); } b.Line("} | null = null;"); b.DocComment("The namespace containing all possible values of this.dataSource."); b.Line($"public dataSources: typeof {Model.ClientTypeName}DataSources = {Model.ClientTypeName}DataSources;"); b.DocComment("The data source on the server to use when retrieving objects. Valid values are in this.dataSources."); b.Line($"public dataSource: Coalesce.DataSource<{ViewModelFullName}> = new this.dataSources.{DataSourceFactory.DefaultSourceName}();"); b.DocComment($"Configuration for all instances of {Model.ListViewModelClassName}. Can be overidden on each instance via instance.coalesceConfig."); b.Line($"public static coalesceConfig = new Coalesce.ListViewModelConfiguration<{Model.ListViewModelClassName}, {ViewModelFullName}>(Coalesce.GlobalConfiguration.listViewModel);"); b.DocComment($"Configuration for this {Model.ListViewModelClassName} instance."); b.Line($"public coalesceConfig: Coalesce.ListViewModelConfiguration<{Model.ListViewModelClassName}, {ViewModelFullName}>"); b.Indented($"= new Coalesce.ListViewModelConfiguration<{Model.ListViewModelClassName}, {ViewModelFullName}>({Model.ListViewModelClassName}.coalesceConfig);"); // Write client methods b.Line(); foreach (var method in Model.ClientMethods.Where(m => m.IsStatic)) { b.Line(); WriteClientMethodDeclaration(b, method, Model.ListViewModelClassName); } b.Line(); b.Line($"protected createItem = (newItem?: any, parent?: any) => new {ViewModelFullName}(newItem, parent);"); b.Line(); b.Line("constructor() {"); b.Indented("super();"); b.Line("}"); } }
private void WriteEnumMetadata(TypeScriptCodeBuilder b, TypeViewModel model) { using (b.Block($"export const {model.Name} = domain.enums.{model.Name} =")) { b.StringProp("name", model.Name.ToCamelCase()); b.StringProp("displayName", model.DisplayName); b.StringProp("type", "enum"); string enumShape = string.Join("|", model.EnumValues.Select(ev => $"\"{ev.Value}\"")); b.Line($"...getEnumMeta<{enumShape}>(["); foreach (var value in model.EnumValues) { // TODO: allow for localization of displayName b.Indented($"{{ value: {value.Key}, strValue: '{value.Value}', displayName: '{value.Value.ToProperCase()}' }},"); } b.Line("]),"); } }
private void WriteMethod_LoadFromDto(TypeScriptCodeBuilder b) { b.DocComment(new[] { "Load the ViewModel object from the DTO.", "@param data: The incoming data object to load.", "@param force: Will override the check against isLoading that is done to prevent recursion. False is default.", "@param allowCollectionDeletes: Set true when entire collections are loaded. True is the default. ", "In some cases only a partial collection is returned, set to false to only add/update collections.", }); using (b.Block("public loadFromDto = (data: any, force: boolean = false, allowCollectionDeletes: boolean = true): void =>", ';')) { b.Line("if (!data || (!force && this.isLoading())) return;"); b.Line("this.isLoading(true);"); b.Line("// Set the ID "); b.Line($"this.myId = data.{Model.PrimaryKey.JsonName};"); b.Line($"this.{Model.PrimaryKey.JsVariable}(data.{Model.PrimaryKey.JsonName});"); b.Line("// Load the lists of other objects"); foreach (PropertyViewModel prop in Model.ClientProperties.Where(p => p.Type.IsCollection)) { using (b.Block($"if (data.{prop.JsonName} != null)")) { if (prop.Object.PrimaryKey != null) { b.Line("// Merge the incoming array"); b.Line($"Coalesce.KnockoutUtilities.RebuildArray(this.{prop.JsVariable}, data.{prop.JsonName}, '{prop.Object.PrimaryKey.JsonName}', {prop.Object.ViewModelClassName}, this, allowCollectionDeletes);"); if (prop.IsManytoManyCollection) { b.Line("// Add many-to-many collection"); b.Line("let objs: any[] = [];"); using (b.Block($"$.each(data.{prop.JsonName}, (index, item) =>", ");")) { b.Line($"if (item.{prop.ManyToManyCollectionProperty.JsonName}) {{"); b.Indented($"objs.push(item.{prop.ManyToManyCollectionProperty.JsonName});"); b.Line($"}}"); } b.Line($"Coalesce.KnockoutUtilities.RebuildArray(this.{prop.ManyToManyCollectionName.ToCamelCase()}, objs, '{prop.ManyToManyCollectionProperty.ForeignKeyProperty.JsVariable}', {prop.ManyToManyCollectionProperty.Object.ViewModelClassName}, this, allowCollectionDeletes);"); } } else if (prop.PureType.IsPrimitive) { b.Line($"this.{prop.JsVariable}(data.{prop.JsVariable});"); } else { b.Line($"Coalesce.KnockoutUtilities.RebuildArray(this.{prop.JsVariable}, data.{prop.JsonName}, null, {prop.Object.ViewModelClassName}, this, allowCollectionDeletes);"); } } } // Objects are loaded first so that they are available when the IDs get loaded. // This handles the issue with populating select lists with correct data because we now have the object. foreach (PropertyViewModel prop in Model.ClientProperties.Where(p => p.IsPOCO)) { b.Line($"if (!data.{prop.JsonName}) {{ "); using (b.Indented()) { if (prop.ForeignKeyProperty != null) { // Prop is a reference navigation prop. The incoming foreign key doesn't match our existing foreign key, so clear out the property. // If the incoming key and existing key DOES match, we assume that the data just wasn't loaded, but is still valid, so we do nothing. b.Line($"if (data.{prop.ForeignKeyProperty.JsonName} != this.{prop.ForeignKeyProperty.JsVariable}()) {{"); b.Indented($"this.{prop.JsVariable}(null);"); b.Line("}"); } else { b.Line($"this.{prop.JsVariable}(null);"); } } b.Line("} else {"); using (b.Indented()) { b.Line($"if (!this.{prop.JsVariable}()){{"); b.Indented($"this.{prop.JsVariable}(new {prop.Object.ViewModelClassName}(data.{prop.JsonName}, this));"); b.Line("} else {"); b.Indented($"this.{prop.JsVariable}()!.loadFromDto(data.{prop.JsonName});"); b.Line("}"); if (prop.Object.IsDbMappedType) { b.Line($"if (this.parent instanceof {prop.Object.ViewModelClassName} && this.parent !== this.{prop.JsVariable}() && this.parent.{prop.Object.PrimaryKey.JsVariable}() == this.{prop.JsVariable}()!.{prop.Object.PrimaryKey.JsVariable}())"); b.Line("{"); b.Indented($"this.parent.loadFromDto(data.{prop.JsonName}, undefined, false);"); b.Line("}"); } } b.Line("}"); } b.Line(); b.Line("// The rest of the objects are loaded now."); foreach (PropertyViewModel prop in Model.ClientProperties.Where(p => p.Object == null && !p.IsPrimaryKey)) { if (prop.Type.IsDate) { // Using valueOf/getTime here is a 20x performance increase over moment.isSame(). moment(new Date(...)) is also a 10x perf increase. b.Line($"if (data.{prop.JsonName} == null) this.{prop.JsVariable}(null);"); b.Line($"else if (this.{prop.JsVariable}() == null || this.{prop.JsVariable}()!.valueOf() != new Date(data.{prop.JsonName}).getTime()){{"); b.Indented($"this.{prop.JsVariable}(moment(new Date(data.{prop.JsonName})));"); b.Line("}"); } else { b.Line($"this.{prop.JsVariable}(data.{prop.JsonName});"); } } b.Line("if (this.coalesceConfig.onLoadFromDto()){"); b.Indented("this.coalesceConfig.onLoadFromDto()(this as any);"); b.Line("}"); b.Line("this.isLoading(false);"); b.Line("this.isDirty(false);"); b.Line("if (this.coalesceConfig.validateOnLoadFromDto()) this.validate();"); } }