Пример #1
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};");
                    }
                }
            }
        }
Пример #2
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();
        }
Пример #3
0
        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("}");
            }
        }
Пример #4
0
 public override void BuildOutput(TypeScriptCodeBuilder b)
 {
     using (b.Block($"module {ViewModelModuleName}"))
     {
         b.DocComment($"External Type {Model.ViewModelClassName}");
         WriteViewModelClass(b);
     }
 }
Пример #5
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);
                    }
                }
            }
        }
Пример #6
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;");
            }
        }
Пример #7
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();
        }
Пример #8
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();");
            }
        }
Пример #9
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("}");
                }
            }
        }
Пример #10
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();
            }
        }
Пример #11
0
        public override Task <string> BuildOutputAsync()
        {
            var b = new TypeScriptCodeBuilder(indentSize: 2);

            b.Line("import * as metadata from './metadata.g'");
            b.Line("import { Model, DataSource, convertToModel, mapToModel } from 'coalesce-vue/lib/model'");
            //   b.Line("import { Domain, getEnumMeta, ModelType, ExternalType } from './coalesce/core/metadata' ");
            b.Line();

            foreach (var model in Model.ClientEnums.OrderBy(e => e.Name))
            {
                using (b.Block($"export enum {model.Name}"))
                {
                    foreach (var value in model.EnumValues)
                    {
                        b.Line($"{value.Value} = {value.Key},");
                    }
                }

                b.Line();
                b.Line();
            }

            foreach (var model in Model.ClientClasses
                     .OrderByDescending(c => c.IsDbMappedType) // Entity types first
                     .ThenBy(e => e.ClientTypeName))
            {
                var name = model.ViewModelClassName;

                using (b.Block($"export interface {name} extends Model<typeof metadata.{name}>"))
                {
                    foreach (var prop in model.ClientProperties)
                    {
                        b.DocComment(prop.Comment);
                        var typeString = new VueType(prop.Type).TsType();
                        b.Line($"{prop.JsVariable}: {typeString} | null");
                    }
                }

                using (b.Block($"export class {name}"))
                {
                    b.DocComment($"Mutates the input object and its descendents into a valid {name} implementation.");
                    using (b.Block($"static convert(data?: Partial<{name}>): {name}"))
                    {
                        b.Line($"return convertToModel(data || {{}}, metadata.{name}) ");
                    }

                    b.DocComment($"Maps the input object and its descendents to a new, valid {name} implementation.");
                    using (b.Block($"static map(data?: Partial<{name}>): {name}"))
                    {
                        b.Line($"return mapToModel(data || {{}}, metadata.{name}) ");
                    }

                    b.DocComment($"Instantiate a new {name}, optionally basing it on the given data.");
                    using (b.Block($"constructor(data?: Partial<{name}> | {{[k: string]: any}})"))
                    {
                        b.Indented($"Object.assign(this, {name}.map(data || {{}}));");
                    }
                }

                var dataSources = model.ClientDataSources(Model);
                if (model.IsDbMappedType && dataSources.Any())
                {
                    using (b.Block($"export namespace {name}"))
                    {
                        using (b.Block("export namespace DataSources"))
                        {
                            foreach (var source in dataSources)
                            {
                                b.DocComment(source.Comment, true);
                                var sourceMeta = $"metadata.{name}.dataSources.{source.ClientTypeName.ToCamelCase()}";

                                using (b.Block($"export class {source.ClientTypeName} implements DataSource<typeof {sourceMeta}>"))
                                {
                                    b.Line($"readonly $metadata = {sourceMeta}");
                                    foreach (var param in source.DataSourceParameters)
                                    {
                                        b.DocComment(param.Comment);
                                        var typeString = new VueType(param.Type).TsType();
                                        b.Line($"{param.JsVariable}: {typeString} | null = null");
                                    }
                                }
                            }
                        }
                    }
                }

                b.Line();
                b.Line();
            }

            return(Task.FromResult(b.ToString()));
        }