Exemplo n.º 1
0
        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();
        }
Exemplo n.º 2
0
        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()));
        }
Exemplo n.º 3
0
        /// <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);
                }
            }
        }
Exemplo n.º 4
0
        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()));
        }
Exemplo n.º 5
0
        public override void BuildOutput(TypeScriptCodeBuilder b)
        {
            using (b.Block($"module {ViewModelModuleName}"))
            {
                b.Line();
                WriteViewModelClass(b);

                b.Line();
                WriteEnumNamespace(b);
            }
        }
Exemplo n.º 6
0
        public override void BuildOutput(TypeScriptCodeBuilder b)
        {
            using (b.Block($"module {ListViewModelModuleName}"))
            {
                b.Line();
                WriteDataSources(b);

                b.Line();
                WriteListViewModelClass(b);
            }
        }
Exemplo n.º 7
0
 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();
             }
         }
     }
 }
Exemplo n.º 8
0
        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();
        }
Exemplo n.º 9
0
        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();
        }
Exemplo n.º 10
0
        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()));
        }
Exemplo n.º 11
0
        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("]),");
            }
        }
Exemplo n.º 12
0
 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} }}, ");
     }
 }
Exemplo n.º 13
0
        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;");
            }
        }
Exemplo n.º 14
0
        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};");
                    }
                }
            }
        }
Exemplo n.º 15
0
        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);
                }
            }
        }
Exemplo n.º 16
0
        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();
        }
Exemplo n.º 17
0
        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();
        }
Exemplo n.º 18
0
        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);
                    }
                }
            }
        }
Exemplo n.º 19
0
 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},");
                 }
             }
         }
     }
 }
Exemplo n.º 20
0
        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);
            }
        }
Exemplo n.º 21
0
        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,");
                }
            }
        }
Exemplo n.º 22
0
        /// <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);
                    }
                }
            }
        }
Exemplo n.º 23
0
        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()));
        }
Exemplo n.º 24
0
        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();
        }
Exemplo n.º 25
0
        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();");
            }
        }
Exemplo n.º 26
0
        private void WriteViewModelClass(TypeScriptCodeBuilder b)
        {
            using (b.Block($"export class {Model.ViewModelGeneratedClassName} extends Coalesce.BaseViewModel"))
            {
                b.Line($"public readonly modelName = \"{Model.ClientTypeName}\";");
                b.Line($"public readonly primaryKeyName = \"{Model.PrimaryKey.JsVariable}\";");
                b.Line($"public readonly modelDisplayName = \"{Model.DisplayName}\";");
                b.Line($"public readonly apiController = \"/{Model.ApiRouteControllerPart}\";");
                b.Line($"public readonly viewController = \"/{Model.ControllerName}\";");

                b.DocComment($"Configuration for all instances of {Model.ViewModelClassName}. Can be overidden on each instance via instance.coalesceConfig.");
                b.Line($"public static coalesceConfig: Coalesce.ViewModelConfiguration<{Model.ViewModelClassName}>");
                b.Indented($"= new Coalesce.ViewModelConfiguration<{Model.ViewModelClassName}>(Coalesce.GlobalConfiguration.viewModel);");

                b.DocComment($"Configuration for the current {Model.ViewModelClassName} instance.");
                b.Line("public coalesceConfig: Coalesce.ViewModelConfiguration<this>");
                b.Indented($"= new Coalesce.ViewModelConfiguration<{Model.ViewModelGeneratedClassName}>({Model.ViewModelClassName}.coalesceConfig);");

                b.DocComment("The namespace containing all possible values of this.dataSource.");
                b.Line($"public dataSources: typeof ListViewModels.{Model.ClientTypeName}DataSources = ListViewModels.{Model.ClientTypeName}DataSources;");

                b.Line();
                b.Line();
                foreach (PropertyViewModel prop in Model.ClientProperties)
                {
                    b.DocComment(prop.Comment);
                    b.Line($"public {prop.JsVariable}: {prop.Type.TsKnockoutType(true)} = {prop.Type.ObservableConstructorCall()};");
                    if (prop.Type.IsEnum)
                    {
                        b.DocComment($"Text value for enumeration {prop.Name}");
                        b.Line($"public {prop.JsTextPropertyName}: KnockoutComputed<string | null> = ko.pureComputed(() => {{");
                        b.Line($"    for(var i = 0; i < this.{prop.JsVariable}Values.length; i++){{");
                        b.Line($"        if (this.{prop.JsVariable}Values[i].id == this.{prop.JsVariable}()){{");
                        b.Line($"            return this.{prop.JsVariable}Values[i].value;");
                        b.Line("        }");
                        b.Line("    }");
                        b.Line("    return null;");
                        b.Line("});");
                    }
                    if (prop.IsManytoManyCollection)
                    {
                        if (prop.Comment.Length > 0)
                        {
                            b.DocComment($"Collection of related objects for many-to-many relationship {prop.ManyToManyCollectionName} via {prop.Name}");
                        }
                        b.Line($"public {prop.ManyToManyCollectionName.ToCamelCase()}: KnockoutObservableArray<ViewModels.{prop.ManyToManyCollectionProperty.Object.ViewModelClassName}> = ko.observableArray([]);");
                    }
                }

                b.Line();
                foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.IsPOCO))
                {
                    b.DocComment($"Display text for {prop.Name}");
                    b.Line($"public {prop.JsTextPropertyName}: KnockoutComputed<string>;");
                }

                b.Line();
                foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.Role == PropertyRole.CollectionNavigation && !f.IsManytoManyCollection))
                {
                    b.DocComment($"Add object to {prop.JsVariable}");
                    using (b.Block($"public addTo{prop.Name} = (autoSave?: boolean | null): {prop.Object.ViewModelClassName} =>", ';'))
                    {
                        b.Line($"var newItem = new {prop.Object.ViewModelClassName}();");
                        b.Line($"if (typeof(autoSave) == 'boolean'){{");
                        b.Line($"    newItem.coalesceConfig.autoSaveEnabled(autoSave);");
                        b.Line($"}}");
                        b.Line($"newItem.parent = this;");
                        b.Line($"newItem.parentCollection = this.{prop.JsVariable};");
                        b.Line($"newItem.isExpanded(true);");
                        if (prop.InverseIdProperty != null)
                        {
                            b.Line($"newItem.{prop.InverseIdProperty.JsVariable}(this.{Model.PrimaryKey.JsVariable}());");
                        }
                        else if (prop.Object.PropertyByName(Model.PrimaryKey.JsVariable) != null)
                        {
                            // TODO: why do we do this? Causes weird behavior simply because key names line up.
                            // If all primary keys are just named "Id", this will copy the PK of the parent to the PK of the child,
                            // which doesn't make sense.
                            b.Line($"newItem.{Model.PrimaryKey.JsVariable}(this.{Model.PrimaryKey.JsVariable}());");
                        }
                        b.Line($"this.{prop.JsVariable}.push(newItem);");
                        b.Line($"return newItem;");
                    }

                    b.DocComment($"ListViewModel for {prop.Name}. Allows for loading subsets of data.");
                    b.Line($"public {prop.JsVariable}List: (loadImmediate?: boolean) => {ListViewModelModuleName}.{prop.Object.ListViewModelClassName};");
                }

                b.Line();
                foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.Role == PropertyRole.CollectionNavigation))
                {
                    b.DocComment($"Url for a table view of all members of collection {prop.Name} for the current object.");
                    b.Line($"public {prop.ListEditorUrlName()}: KnockoutComputed<string> = ko.computed(");
                    if (prop.ListEditorUrl() == null)
                    {
                        b.Indented($"() => \"Inverse property not set on {Model.ClientTypeName} for property {prop.Name}\",");
                    }
                    else
                    {
                        b.Indented($"() => this.coalesceConfig.baseViewUrl() + '/{prop.ListEditorUrl()}' + this.{Model.PrimaryKey.JsVariable}(),");
                    }
                    b.Indented("null, { deferEvaluation: true }");
                    b.Line(");");
                }

                b.Line();
                foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.Role == PropertyRole.ReferenceNavigation))
                {
                    b.DocComment($"Pops up a stock editor for object {prop.JsVariable}");
                    b.Line($"public show{prop.Name}Editor: (callback?: any) => void;");
                }

                b.Line();
                foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.Type.IsEnum))
                {
                    b.DocComment($"Array of all possible names & values of enum {prop.JsVariable}");
                    b.Line($"public {prop.JsVariable}Values: Coalesce.EnumValue[] = [ ");
                    foreach (var kvp in prop.Type.EnumValues)
                    {
                        b.Indented($"{{ id: {kvp.Key}, value: '{kvp.Value.ToProperCase()}' }},");
                    }
                    b.Line("];");
                }

                b.Line();
                foreach (var method in Model.ClientMethods.Where(m => !m.IsStatic || m.ResultType.EqualsType(Model.Type)))
                {
                    WriteClientMethodDeclaration(b, method, Model.ViewModelGeneratedClassName, true, true);
                }

                WriteMethod_LoadFromDto(b);

                WriteMethod_SaveToDto(b);

                b.DocComment(new[]
                {
                    "Loads any child objects that have an ID set, but not the full object.",
                    "This is useful when creating an object that has a parent object and the ID is set on the new child."
                });
                using (b.Block("public loadChildren = (callback?: () => void): void =>", ';'))
                {
                    b.Line("var loadingCount = 0;");
                    // AES 4/14/18 - unsure of the reason for the !IsReadOnly check here. Perhaps just redundant?
                    foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.Role == PropertyRole.ReferenceNavigation && !f.IsReadOnly))
                    {
                        b.Line($"// See if this.{prop.JsVariable} needs to be loaded.");
                        using (b.Block($"if (this.{prop.JsVariable}() == null && this.{prop.ForeignKeyProperty.JsVariable}() != null)"))
                        {
                            b.Line("loadingCount++;");
                            b.Line($"var {prop.JsVariable}Obj = new {prop.Object.ViewModelClassName}();");
                            b.Line($"{prop.JsVariable}Obj.load(this.{prop.ForeignKeyProperty.JsVariable}(), () => {{");
                            b.Indented("loadingCount--;");
                            b.Indented($"this.{prop.JsVariable}({prop.JsVariable}Obj);");
                            b.Indented("if (loadingCount == 0 && typeof(callback) == \"function\") { callback(); }");
                            b.Line("});");
                        }
                    }
                    b.Line("if (loadingCount == 0 && typeof(callback) == \"function\") { callback(); }");
                }


                b.Line();
                using (b.Block("public setupValidation(): void"))
                {
                    b.Line("if (this.errors !== null) return;");

                    var validatableProps = Model.ClientProperties.Where(p => !string.IsNullOrWhiteSpace(p.ClientValidationKnockoutJs()));

                    b.Line("this.errors = ko.validation.group([");
                    foreach (PropertyViewModel prop in validatableProps.Where(p => !p.ClientValidationAllowSave))
                    {
                        b.Indented($"this.{prop.JsVariable}.extend({{ {prop.ClientValidationKnockoutJs()} }}),");
                    }
                    b.Line("]);");

                    b.Line("this.warnings = ko.validation.group([");
                    foreach (PropertyViewModel prop in validatableProps.Where(p => p.ClientValidationAllowSave))
                    {
                        b.Indented($"this.{prop.JsVariable}.extend({{ {prop.ClientValidationKnockoutJs()} }}),");
                    }
                    b.Line("]);");
                }



                b.Line();
                using (b.Block($"constructor(newItem?: object, parent?: Coalesce.BaseViewModel | {ListViewModelModuleName}.{Model.ListViewModelClassName})"))
                {
                    b.Line("super(parent);");
                    b.Line("this.baseInitialize();");
                    b.Line("const self = this;");

                    // Create computeds for display for objects
                    b.Line();
                    foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.IsPOCO))
                    {
                        // If the object exists, use the text value. Otherwise show 'None'
                        using (b.Block($"this.{prop.JsTextPropertyName} = ko.pureComputed(function()", ");"))
                        {
                            b.Line($"if (self.{prop.JsVariable}() && self.{prop.JsVariable}()!.{prop.Object.ListTextProperty.JsVariable}()) {{");
                            b.Indented($"return self.{prop.JsVariable}()!.{prop.Object.ListTextProperty.JsVariable}()!.toString();");
                            b.Line("} else {");
                            b.Indented("return \"None\";");
                            b.Line("}");
                        }
                    }

                    b.Line();
                    b.Line();
                    foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.Role == PropertyRole.CollectionNavigation && !f.IsManytoManyCollection))
                    {
                        b.Line($"// List Object model for {prop.Name}. Allows for loading subsets of data.");
                        var childListVar = $"_{prop.JsVariable}List";
                        b.Line($"var {childListVar}: {ListViewModelModuleName}.{prop.Object.ListViewModelClassName};");

                        using (b.Block($"this.{prop.JsVariable}List = function(loadImmediate = true)"))
                        {
                            using (b.Block($"if (!{childListVar})"))
                            {
                                b.Line($"{childListVar} = new {ListViewModelModuleName}.{prop.Object.ListViewModelClassName}();");
                                b.Line($"if (loadImmediate) load{prop.Name}List();");
                                b.Line($"self.{prop.Parent.PrimaryKey.JsVariable}.subscribe(load{prop.Name}List)");
                            }
                            b.Line($"return {childListVar};");
                        }

                        using (b.Block($"function load{prop.Name}List()"))
                        {
                            using (b.Block($"if (self.{prop.Parent.PrimaryKey.JsVariable}())"))
                            {
                                if (prop.InverseIdProperty != null)
                                {
                                    b.Line($"{childListVar}.queryString = \"filter.{prop.InverseIdProperty.Name}=\" + self.{prop.Parent.PrimaryKey.JsVariable}();");
                                }
                                else
                                {
                                    b.Line($"{childListVar}.queryString = \"filter.{Model.PrimaryKey.Name}=\" + self.{prop.Parent.PrimaryKey.JsVariable}();");
                                }
                                b.Line($"{childListVar}.load();");
                            }
                        }
                    }

                    b.Line();
                    b.Line();
                    foreach (PropertyViewModel prop in Model.ClientProperties.Where(f => f.Role == PropertyRole.ReferenceNavigation))
                    {
                        using (b.Block($"this.show{prop.Name}Editor = function(callback: any)", ';'))
                        {
                            b.Line($"if (!self.{prop.JsVariable}()) {{");
                            b.Indented($"self.{prop.JsVariable}(new {prop.Object.ViewModelClassName}());");
                            b.Line("}");
                            b.Line($"self.{prop.JsVariable}()!.showEditor(callback)");
                        }
                    }

                    // Register autosave subscriptions on all autosavable properties.
                    // Must be done after everything else in the ctor.
                    b.Line();
                    foreach (PropertyViewModel prop in Model.ClientProperties.Where(p => p.IsClientWritable && !p.Type.IsCollection))
                    {
                        b.Line($"self.{prop.JsVariable}.subscribe(self.autoSave);");
                    }

                    foreach (PropertyViewModel prop in Model.ClientProperties.Where(p => p.IsClientWritable && p.IsManytoManyCollection))
                    {
                        b.Line();
                        b.Line($"self.{prop.ManyToManyCollectionName.ToCamelCase()}.subscribe<KnockoutArrayChange<{prop.ManyToManyCollectionProperty.Object.ViewModelClassName}>[]>(changes => {{");
                        using (b.Indented())
                        {
                            using (b.Block("for (var i in changes)"))
                            {
                                b.Line("var change = changes[i];");
                                b.Line("self.autoSaveCollection(");
                                b.Indented("change.status, ");
                                b.Indented($"this.{prop.JsVariable}, ");
                                b.Indented($"{prop.Object.ViewModelClassName}, ");
                                b.Indented($"'{prop.Object.ClientProperties.First(f => f.Type.EqualsType(Model.Type)).ForeignKeyProperty.JsVariable}',");
                                b.Indented($"'{prop.ManyToManyCollectionProperty.ForeignKeyProperty.JsVariable}',");
                                b.Indented($"change.value.{prop.ManyToManyCollectionProperty.Object.PrimaryKey.JsVariable}()");
                                b.Line(");");
                            }
                        }
                        b.Line("}, null, \"arrayChange\");");
                    }

                    b.Line();
                    b.Line("if (newItem) {");
                    b.Indented("self.loadFromDto(newItem, true);");
                    b.Line("}");
                }
            }
        }
Exemplo n.º 27
0
        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("}");
                }
            }
        }
Exemplo n.º 28
0
        private void WriteMethod_LoadFromDto(TypeScriptCodeBuilder b)
        {
            b.DocComment(new[] {
                "Load the object from the DTO.",
                "@param data: The incoming data object to load.",
            });
            using (b.Block("public loadFromDto = (data: any) =>", ';'))
            {
                b.Line("if (!data) return;");

                if (Model.PrimaryKey != null)
                {
                    b.Line("// Set the ID");
                    b.Line($"this.myId = data.{Model.PrimaryKey.JsonName};");
                }

                b.Line();
                b.Line("// Load the properties.");
                foreach (PropertyViewModel prop in Model.ClientProperties)
                {
                    // DB-mapped viewmodels can't have an external type as a parent.
                    // There's no strong reason for this restriction other than that
                    // the code just doesn't support it at the moment.
                    var parentVar = prop.PureTypeOnContext ? "undefined" : "this";

                    if (prop.Type.IsCollection && prop.Object != null)
                    {
                        b.Line($"if (data.{prop.JsonName} != null) {{");
                        b.Line("// Merge the incoming array");
                        if (prop.Object.PrimaryKey != null)
                        {
                            b.Indented($"Coalesce.KnockoutUtilities.RebuildArray(this.{prop.JsVariable}, data.{prop.JsonName}, \'{prop.Object.PrimaryKey.JsVariable}\', ViewModels.{prop.Object.ViewModelClassName}, {parentVar}, true);");
                        }
                        else
                        {
                            b.Indented($"Coalesce.KnockoutUtilities.RebuildArray(this.{prop.JsVariable}, data.{prop.JsonName}, null, ViewModels.{prop.Object.ViewModelClassName}, {parentVar}, true);");
                        }
                        b.Line("}");
                    }
                    else if (prop.Type.IsDate)
                    {
                        b.Line($"if (data.{prop.JsVariable} == null) this.{prop.JsVariable}(null);");
                        b.Line($"else if (this.{prop.JsVariable}() == null || !this.{prop.JsVariable}()!.isSame(moment(data.{prop.JsVariable}))) {{");
                        b.Indented($"this.{prop.JsVariable}(moment(data.{prop.JsVariable}));");
                        b.Line("}");
                    }
                    else if (prop.IsPOCO)
                    {
                        b.Line($"if (!this.{prop.JsVariable}()){{");
                        b.Indented($"this.{prop.JsVariable}(new {prop.Object.ViewModelClassName}(data.{prop.JsonName}, {parentVar}));");
                        b.Line("} else {");
                        b.Indented($"this.{prop.JsVariable}()!.loadFromDto(data.{prop.JsonName});");
                        b.Line("}");
                    }
                    else
                    {
                        b.Line($"this.{prop.JsVariable}(data.{prop.JsVariable});");
                    }
                }
                b.Line();
            }
        }
Exemplo n.º 29
0
        public override Task <string> BuildOutputAsync()
        {
            var b = new TypeScriptCodeBuilder(indentSize: 2);

            using (b.Block("import", " from 'coalesce-vue/lib/metadata'"))
            {
                b.Line("Domain, getEnumMeta, ModelType, ObjectType,");
                b.Line("PrimitiveProperty, ModelReferenceNavigationProperty, ForeignKeyProperty, PrimaryKeyProperty");
            }
            b.Line();
            b.Line();

            // Assigning each property as a member of domain ensures we don't break type contracts.
            // Exporting each model individually offers easier usage in imports.
            b.Line("const domain: Domain = { enums: {}, types: {}, services: {} }");



            foreach (var model in Model.ClientEnums.OrderBy(e => e.Name))
            {
                WriteEnumMetadata(b, model);
            }

            foreach (var model in Model.ApiBackedClasses.OrderBy(e => e.ClientTypeName))
            {
                WriteApiBackedTypeMetadata(b, model);
            }

            foreach (var model in Model.ExternalTypes.OrderBy(e => e.ClientTypeName))
            {
                WriteExternalTypeMetadata(b, model);
            }

            foreach (var model in Model.Services.OrderBy(e => e.ClientTypeName))
            {
                WriteServiceMetadata(b, model);
            }

            // Create an enhanced Domain definition for deep intellisense.
            b.Line();
            using (b.Block("interface AppDomain extends Domain"))
            {
                using (b.Block("enums:"))
                {
                    foreach (var model in Model.ClientEnums.OrderBy(e => e.Name))
                    {
                        b.Line($"{model.Name}: typeof {model.Name}");
                    }
                }
                using (b.Block("types:"))
                {
                    foreach (var model in Model.ClientClasses.OrderBy(e => e.ClientTypeName))
                    {
                        b.Line($"{model.ClientTypeName}: typeof {model.ClientTypeName}");
                    }
                }
                using (b.Block("services:"))
                {
                    foreach (var model in Model.Services.OrderBy(e => e.ClientTypeName))
                    {
                        b.Line($"{model.ClientTypeName}: typeof {model.ClientTypeName}");
                    }
                }
            }

            b.Line();
            b.Line("export default domain as AppDomain");


            return(Task.FromResult(b.ToString()));
        }
Exemplo n.º 30
0
        /// <summary>
        /// Writes the class for invoking the given client method,
        /// as well as the property for the default instance of this class.
        /// </summary>
        /// <param name="b"></param>
        /// <param name="method"></param>
        /// <param name="parentClassName"></param>
        /// <param name="methodLoadsParent">
        /// If true, code emitted for methods that load their parents' type should
        /// load the method's parent with the results of the method when called.
        /// </param>
        /// <param name="parentLoadHasIdParameter">
        /// If true, calls to this.parent.load() will pass null as the first arg and a callback as the second.
        /// </param>
        public void WriteClientMethodDeclaration(
            TypeScriptCodeBuilder b, MethodViewModel method, string parentClassName,
            bool methodLoadsParent = false, bool parentLoadHasIdParameter = false
            )
        {
            var model              = this.Model;
            var isService          = method.Parent.IsService;
            var returnIsListResult = method.ReturnsListResult;

            string reloadArg              = !isService ? ", reload" : "";
            string reloadParam            = !isService ? ", reload: boolean = true" : "";
            string callbackAndReloadParam = $"callback?: (result: {method.ResultType.TsType}) => void{reloadParam}";

            // Default instance of the method class.
            b.Line("/**");
            b.Indented($"Methods and properties for invoking server method {method.Name}.");
            if (method.Comment.Length > 0)
            {
                b.Indented(method.Comment);
            }
            b.Line($"*/");

            b.Line($"public readonly {method.JsVariable} = new {parentClassName}.{method.Name}(this);");

            string methodBaseClass = returnIsListResult
                ? "ClientListMethod"
                : "ClientMethod";

            // Not wrapping this in a using since it is used by nearly this entire method. Will manually dispose.
            var classBlock = b.Block(
                $"public static {method.Name} = class {method.Name} extends Coalesce.{methodBaseClass}<{parentClassName}, {method.ResultType.TsType}>", ';');

            b.Line($"public readonly name = '{method.Name}';");
            b.Line($"public readonly verb = '{method.ApiActionHttpMethodName}';");

            if (method.ResultType.IsCollection || method.ResultType.IsArray)
            {
                b.Line($"public result: {method.ResultType.TsKnockoutType()} = {method.ResultType.ObservableConstructorCall()};");
            }

            // ----------------------
            // Standard invoke method - all CS method parameters as TS method parameters.
            // ----------------------
            b.Line();
            b.Line($"/** Calls server method ({method.Name}) with the given arguments */");

            string parameters = "";

            parameters = string.Join(", ", method.ClientParameters.Select(f => f.Type.TsDeclarationPlain(f.Name) + " | null"));
            if (!string.IsNullOrWhiteSpace(parameters))
            {
                parameters = parameters + ", ";
            }
            parameters = parameters + callbackAndReloadParam;

            using (b.Block($"public invoke = ({parameters}): JQueryPromise<any> =>", ';'))
            {
                string jsPostObject = "{ ";
                if (method.IsModelInstanceMethod)
                {
                    jsPostObject = jsPostObject + "id: this.parent[this.parent.primaryKeyName]()";
                    if (method.Parameters.Any())
                    {
                        jsPostObject = jsPostObject + ", ";
                    }
                }

                string TsConversion(ParameterViewModel param)
                {
                    string argument = param.JsVariable;

                    if (param.Type.HasClassViewModel)
                    {
                        return($"{argument} ? {argument}.saveToDto() : null");
                    }
                    if (param.Type.IsDate)
                    {
                        return($"{argument} ? {argument}.format() : null");
                    }
                    return(argument);
                }

                jsPostObject += string.Join(", ", method.ClientParameters.Select(f => $"{f.JsVariable}: {TsConversion(f)}"));
                jsPostObject += " }";

                b.Line($"return this.invokeWithData({jsPostObject}, callback{reloadArg});");
            }


            // ----------------------
            // Members for methods with parameters only.
            if (method.ClientParameters.Any())
            {
                b.Line();


                // ----------------------
                // Args class, and default instance
                b.Line($"/** Object that can be easily bound to fields to allow data entry for the method's parameters */");
                b.Line($"public args = new {method.Name}.Args(); ");

                using (b.Block("public static Args = class Args", ';'))
                {
                    foreach (var arg in method.ClientParameters)
                    {
                        b.Line($@"public {arg.JsVariable}: {arg.Type.TsKnockoutType(true)} = {arg.Type.ObservableConstructorCall()};");
                    }
                }



                // Gets the js arguments to pass to a call to this.invoke(...)
                string JsArguments(string obj = "")
                {
                    string result;

                    if (obj != "")
                    {
                        result = string.Join(", ", method.ClientParameters.Select(f => $"{obj}.{f.JsVariable}()"));
                    }
                    else
                    {
                        result = string.Join(", ", method.ClientParameters.Select(f => obj + f.JsVariable));
                    }

                    if (!string.IsNullOrEmpty(result))
                    {
                        result = result + ", ";
                    }
                    result = result + "callback";

                    return(result);
                }

                // ----------------------
                // invokeWithArgs method
                // ----------------------
                b.Line();
                b.Line($"/** Calls server method ({method.Name}) with an instance of {method.Name}.Args, or the value of this.args if not specified. */");
                // We can't explicitly declare the type of the args parameter here - TypeScript doesn't allow it.
                // Thankfully, we can implicitly type using the default.
                using (b.Block($"public invokeWithArgs = (args = this.args, {callbackAndReloadParam}): JQueryPromise<any> =>"))
                {
                    b.Line($"return this.invoke({JsArguments("args")}{reloadArg});");
                }


                // ----------------------
                // invokeWithPrompts method
                // ----------------------
                b.Line();
                b.Line("/** Invokes the method after displaying a browser-native prompt for each argument. */");
                using (b.Block($"public invokeWithPrompts = ({callbackAndReloadParam}): JQueryPromise<any> | undefined =>", ';'))
                {
                    b.Line($"var $promptVal: string | null = null;");
                    foreach (var param in method.ClientParameters.Where(f => f.ConvertsFromJsString))
                    {
                        b.Line($"$promptVal = {$"prompt('{param.Name.ToProperCase()}')"};");
                        // AES 1/23/2018 - why do we do this? what about optional params where no value is desired?
                        b.Line($"if ($promptVal === null) return;");
                        b.Line($"var {param.Name}: {param.Type.TsType} = {param.Type.TsConvertFromString("$promptVal")};");
                    }

                    // For all parameters that can't convert from a string (is this even possible with what we support for method parameters now?),
                    // pass null as the value. I guess we just let happen what's going to happen? Again, not sure when this case would ever be hit.
                    foreach (var param in method.ClientParameters.Where(f => !f.ConvertsFromJsString))
                    {
                        b.Line($"var {param.Name}: null = null;");
                    }
                    b.Line($"return this.invoke({JsArguments("")}{reloadArg});");
                }
            }



            // ----------------------
            // Method response handler - highly dependent on what the response type actually is.
            // ----------------------
            b.Line();
            using (b.Block($"protected loadResponse = (data: Coalesce.{(returnIsListResult ? "List" : "Item")}Result, {callbackAndReloadParam}) =>", ';'))
            {
                var incomingMainData = returnIsListResult
                    ? "data.list || []"
                    : "data.object";

                if (method.ResultType.IsCollection && method.ResultType.PureType.HasClassViewModel)
                {
                    // Collection of objects that have TypeScript ViewModels. This could be an entity or an external type.

                    // If the models have a key, rebuild our collection using that key so that we can reuse objects when the data matches.
                    var keyNameArg = method.ResultType.PureType.ClassViewModel.PrimaryKey != null
                        ? $"'{method.ResultType.PureType.ClassViewModel.PrimaryKey.JsVariable}'"
                        : "null";

                    b.Line($"Coalesce.KnockoutUtilities.RebuildArray(this.result, {incomingMainData}, {keyNameArg}, ViewModels.{method.ResultType.PureType.ClassViewModel.ClientTypeName}, this, true);");
                }
                else if (method.ResultType.HasClassViewModel)
                {
                    // Single view model return type.

                    b.Line("if (!this.result()) {");
                    b.Indented($"this.result(new ViewModels.{method.ResultType.PureType.ClassViewModel.ClientTypeName}({incomingMainData}));");
                    b.Line("} else {");
                    b.Indented($"this.result().loadFromDto({incomingMainData});");
                    b.Line("}");
                }
                else
                {
                    // Uninteresting return type. Either void, a primitive, or a collection of primitives.
                    // In any case, regardless of the type of the 'result' observable, this is how we set the results.
                    b.Line($"this.result({incomingMainData});");
                }

                if (isService)
                {
                    b.Line("if (typeof(callback) == 'function')");
                    b.Indented("callback(this.result());");
                }
                else if (method.ResultType.EqualsType(method.Parent.Type) && methodLoadsParent)
                {
                    // The return type is the type of the method's parent. Load the parent with the results of the method.
                    // Parameter 'reload' has no meaning here, since we're reloading the object with the result of the method.
                    b.Line($"this.parent.loadFromDto({incomingMainData}, true)");
                    using (b.Block("if (typeof(callback) == 'function')"))
                    {
                        b.Line($"callback(this.result());");
                    }
                }
                else
                {
                    // We're not loading the results into the method's parent. This is by far the more common case.
                    b.Line("if (reload) {");
                    // If reload is true, invoke the load function on the method's parent to reload its latest state from the server.
                    // Only after that's done do we call the method-completion callback.
                    // Store the result locally in case it somehow gets changed by us reloading the parent.
                    b.Indented("var result = this.result();");
                    b.Indented($"this.parent.load({(parentLoadHasIdParameter ? "null, " : "")}typeof(callback) == 'function' ? () => callback(result) : undefined);");
                    b.Line("} else if (typeof(callback) == 'function') {");
                    // If we're not reloading, just call the callback now.
                    b.Indented("callback(this.result());");
                    b.Line("}");
                }
            }


            // End of the method class declaration.
            classBlock.Dispose();
        }