/// <summary> /// Write metadata common to all value representations, like properties and method parameters. /// </summary> private void WriteValueCommonMetadata(TypeScriptCodeBuilder b, IValueViewModel value) { b.StringProp("name", value.JsVariable); b.StringProp("displayName", value.DisplayName); WriteTypeCommonMetadata(b, value.Type); }
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(); }
/// <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: {}"); * } * } */ } }
public override Task <string> BuildOutputAsync() { var b = new TypeScriptCodeBuilder(); b.Line(); b.Line(); b.Line("// This file is automatically generated."); b.Line("// It is not in the generated folder for ease-of-use (no relative paths)."); b.Line("// This file must remain in place relative to the generated scripts (<WebProject>/Scripts/Generated)."); b.Line(); b.Line(); b.Line($"/// <reference path=\"coalesce.dependencies.d.ts\" />"); foreach (var referencePath in Model.Select(m => m.EffectiveOutputPath).OrderBy(p => p)) { // https://stackoverflow.com/a/1766773/2465631 Uri referencedFilePath = new Uri(referencePath); Uri referenceFile = new Uri(this.EffectiveOutputPath); Uri diff = referenceFile.MakeRelativeUri(referencedFilePath); string relPath = diff.OriginalString; b.Line($"/// <reference path=\"{relPath}\" />"); } return(Task.FromResult(b.ToString())); }
public override Task <string> BuildOutputAsync() { var b = new TypeScriptCodeBuilder(indentSize: 2); b.Lines(new [] { "import * as $metadata from './metadata.g'", "import * as $models from './models.g'", "import * as qs from 'qs'", "import { AxiosClient, ModelApiClient, ServiceApiClient, ItemResult, ListResult } from 'coalesce-vue/lib/api-client'", "import { AxiosResponse, AxiosRequestConfig } from 'axios'", }); b.Line(); foreach (var model in Model.Entities.OrderBy(e => e.ClientTypeName)) { WriteApiClientClass(b, model, $"ModelApiClient<$models.{model.ClientTypeName}>"); // Lines between classes b.Line(); b.Line(); } foreach (var model in Model.Services.OrderBy(e => e.ClientTypeName)) { WriteApiClientClass(b, model, $"ServiceApiClient<typeof $metadata.{model.ClientTypeName}>"); // Lines between classes b.Line(); b.Line(); } return(Task.FromResult(b.ToString())); }
/// <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"); } }
public override void BuildOutput(TypeScriptCodeBuilder b) { using (b.Block($"module {ViewModelModuleName}")) { b.DocComment($"External Type {Model.ViewModelClassName}"); WriteViewModelClass(b); } }
/// <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 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 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); } } }
private void WriteCommonClassMetadata(TypeScriptCodeBuilder b, ClassViewModel model) { b.StringProp("name", model.ClientTypeName.ToCamelCase()); b.StringProp("displayName", model.DisplayName); if (model.ListTextProperty != null) { // This might not be defined for external types, because sometimes it just doesn't make sense. We'll accommodate on the client. b.Line($"get displayProp() {{ return this.props.{model.ListTextProperty.JsVariable} }}, "); } }
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); } }
public override void BuildOutput(TypeScriptCodeBuilder b) { using (b.Block($"module {ListViewModelModuleName}")) { b.Line(); WriteDataSources(b); b.Line(); WriteListViewModelClass(b); } }
public override void BuildOutput(TypeScriptCodeBuilder b) { using (b.Block($"module {ViewModelModuleName}")) { b.Line(); WriteViewModelClass(b); b.Line(); WriteEnumNamespace(b); } }
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};"); } } } }
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); } }
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); } } }
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(); } } } }
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 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(); }
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("}"); } }
public override Task <string> BuildOutputAsync() { var b = new TypeScriptCodeBuilder(); b.Line(); b.Line("/// <reference path=\"../coalesce.dependencies.d.ts\" />"); if (ShouldWriteGeneratedBy) { b.Line(); b.Line("// Generated by IntelliTect.Coalesce"); } b.Line(); BuildOutput(b); return(Task.FromResult(b.ToString())); }
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("]),"); } }
public override Task <string> BuildOutputAsync() { var b = new TypeScriptCodeBuilder(indentSize: 2); b.Line("import * as $metadata from './metadata.g'"); b.Line("import * as $models from './models.g'"); b.Line("import * as $apiClients from './api-clients.g'"); b.Line("import { ViewModel, ListViewModel, defineProps } from 'coalesce-vue/lib/viewmodel'"); b.Line(); foreach (var model in Model.Entities) { WriteViewModel(b, model); WriteListViewModel(b, model); b.Line(); } return(Task.FromResult(b.ToString())); }
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;"); } }
/// <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("}"); } } }