7

讓我們先從一個在此先感謝:)映射層次JSON到打字稿,KnockoutJS類型的對象

OK,所以我試圖加載/使用淘汰賽匹配JSON數據映射層次打字稿/ KnockoutJS類型類。映射插件,層次結構可以達到N級。

我知道我可以執行以下操作來從JSON數據映射/加載頂級類。

var qry = ko.mapping.fromJS(jsData, {}, new Query()); 

但我無法弄清楚是如何映射/負載複雜,N級,分級JSON數據到一組打字稿/ KnockoutJS類和構建父/子關係。

我已經讀過無數的articals,但是當涉及到簡單的父/子範例之外的層次關係時,它們都不足,而且我也找不到任何使用knockout.mapping插件的東西。

下面是我希望映射/加載的TypeScript類的定義。我是一個C++/C#開發人員,所以這種性質的JavaScript對我來說是非常新的。

打字稿對象

的JSON會是這個樣子:

{ 
    "ID": 2, 
    "Name": "Northwind 2", 
    "RootTargetID": 2, 
    "RootTarget": { 
     "ID": 2, 
     "Name": "Customers", 
     "ParentID": null, 
     "FilterID": 2, 
     "Queries": [], 
     "Children": [], 
     "Parent": null, 
     "Selects": [ 
      { 
       "ID": 3, 
       "Name": "CompanyName", 
       "Aggregation": "None", 
       "TargetID": 2, 
       "Target": null 
      }, 
      { 
       "ID": 4, 
       "Name": "ContactName", 
       "Aggregation": "None", 
       "TargetID": 2, 
       "Target": null 
      } 
     ], 
     "Filter": { 
      "FilterClauseID": 2, 
      "Type": "AND", 
      "Left": null, 
      "Right": null, 
      "ParentID": null, 
      "QueryTargets": [], 
      "Parent": null, 
      "Children": [ 
       { 
        "FilterClauseID": 3, 
        "Type": "NE", 
        "Left": "Country", 
        "Right": "Germany", 
        "ParentID": 2, 
        "QueryTargets": [], 
        "Parent": null, 
        "Children": [] 
       }, 
       { 
        "FilterClauseID": 4, 
        "Type": "NE", 
        "Left": "Country", 
        "Right": "Mexico", 
        "ParentID": 2, 
        "QueryTargets": [], 
        "Parent": null, 
        "Children": [] 
       } 
      ] 
     } 
    } 
} 
+0

解決以下 – DIGGIDY

回答

6

OK,所以我進一步有點向下行,現在,大量的頭髮拉和numerious試驗後。

下面是我試圖存檔的一個幾乎可行的例子,唯一的問題是它似乎沒有正確映射,即使單步執行代碼似乎表明它正確加載。只有當我將它與綁定一起使用時,它會在RootTaget.Filter.Type上引發null未引用的綁定,該值應填充爲值。

我仍然試圖弄清楚爲什麼,但我會歡迎有關可能的錯誤的建議。 :)

已經得到解決和工作

半工作打字稿

///<reference path="Scripts/typings/jquery/jquery.d.ts"/> 
///<reference path="Scripts/typings/knockout/knockout.d.ts"/> 
///<reference path="Scripts/typings/knockout.mapping/knockout.mapping.d.ts"/> 

module ViewModel 
{ 
    export class Query { 
     public ID: KnockoutObservable<number>; 
     public Name: KnockoutObservable<string>; 
     public RootTargetID: KnockoutObservable<number>; 
     public RootTarget: KnockoutObservable<QueryTarget>; 

     constructor(json: any) { 
      this.ID = ko.observable<number>(0); 
      this.Name = ko.observable<string>(); 
      this.RootTargetID = ko.observable<number>(); 
      this.RootTarget = ko.observable<QueryTarget>(); 

      var mapping = { 
       'RootTarget': { 
        create: function (args) { 
         return new QueryTarget(args.data, null); 
        } 
       } 
      }; 

      ko.mapping.fromJS(json, mapping, this); 

     } 
    } 

    export class QueryTarget { 
     public ID: KnockoutObservable<number>; 
     public Name: KnockoutObservable<string>; 
     public ParentID: KnockoutObservable<number>; 
     public Children: KnockoutObservableArray<QueryTarget>; 
     public Parent: KnockoutObservable<QueryTarget>; 
     public Selects: KnockoutObservableArray<QuerySelect>; 
     public FilterID: KnockoutObservable<number>; 
     public Filter: KnockoutObservable<FilterClause>; 

     constructor(json: any, parent: QueryTarget) { 
      this.ID = ko.observable<number>(0); 
      this.Name = ko.observable<string>(); 
      this.ParentID = ko.observable<number>(0); 
      this.Children = ko.observableArray<QueryTarget>(); 
      this.Parent = ko.observable<QueryTarget>(parent); 
      this.Selects = ko.observableArray<QuerySelect>(); 
      this.FilterID = ko.observable<number>(0); 
      this.Filter = ko.observable<FilterClause>(); 

      var mapping = { 
       'Children': { 
        create: function (args) { 
         return new QueryTarget(args.data, this); 
        } 
       }, 
       'Selects': { 
        create: function (args) { 
         return new QuerySelect(args.data, this); 
        } 
       }, 
       'Filter': { 
        create: function (args) { 
         return new FilterClause(args.data, null); 
        } 
       } 
      }; 

      ko.mapping.fromJS(json, mapping, this); 
     } 
    } 

    export class QuerySelect { 
     public ID: KnockoutObservable<number>; 
     public Name: KnockoutObservable<string>; 
     public Aggregation: KnockoutObservable<string>; 
     public TargetID: KnockoutObservable<number>; 
     public Target: KnockoutObservable<QueryTarget>; 

     constructor(json: any, parent: QueryTarget) { 
      this.ID = ko.observable<number>(); 
      this.Name = ko.observable<string>(); 
      this.Aggregation = ko.observable<string>(); 
      this.TargetID = ko.observable<number>(); 
      this.Target = ko.observable<QueryTarget>(parent); 

      ko.mapping.fromJS(json, {}, this); 
     } 
    } 

    export class FilterClause { 
     public FilterClauseID: KnockoutObservable<number>; 
     public Type: KnockoutObservable<string>; 
     public Left: KnockoutObservable<string>; 
     public Right: KnockoutObservable<string>; 
     public ParentID: KnockoutObservable<number>; 
     public Parent: KnockoutObservable<FilterClause>; 
     public Children: KnockoutObservableArray<FilterClause>; 

     constructor(json: any, parent: FilterClause) { 
      this.FilterClauseID = ko.observable<number>(); 
      this.Type = ko.observable<string>(); 
      this.Left = ko.observable<string>(); 
      this.Right = ko.observable<string>(); 
      this.ParentID = ko.observable<number>(); 
      this.Parent = ko.observable<FilterClause>(parent); 
      this.Children = ko.observableArray<FilterClause>(); 

      var mapping = { 
       'Children': { 
        create: function (args) { 
         return new FilterClause(args.data, this); 
        } 
       } 
      }; 

      ko.mapping.fromJS(json, mapping, this); 
     } 
    } 

    export class QueryModuleViewModel 
    { 
     public QueryObj: Query; 

     constructor() { 

      var json = { 
       "ID": 2, 
       "Name": "Northwind 2", 
       "RootTargetID": 2, 
       "RootTarget": { 
        "ID": 2, 
        "Name": "Customers", 
        "ParentID": null, 
        "FilterID": 2, 
        "Queries": [], 
        "Children": [], 
        "Parent": null, 
        "Selects": [ 
         { 
          "ID": 3, 
          "Name": "CompanyName", 
          "Aggregation": "None", 
          "TargetID": 2, 
          "Target": null 
         }, 
         { 
          "ID": 4, 
          "Name": "ContactName", 
          "Aggregation": "None", 
          "TargetID": 2, 
          "Target": null 
         } 
        ], 
        "Filter": { 
         "FilterClauseID": 2, 
         "Type": "AND", 
         "Left": null, 
         "Right": null, 
         "ParentID": null, 
         "QueryTargets": [], 
         "Parent": null, 
         "Children": [ 
          { 
           "FilterClauseID": 3, 
           "Type": "NE", 
           "Left": "Country", 
           "Right": "Germany", 
           "ParentID": 2, 
           "QueryTargets": [], 
           "Parent": null, 
           "Children": [] 
          }, 
          { 
           "FilterClauseID": 4, 
           "Type": "NE", 
           "Left": "Country", 
           "Right": "Mexico", 
           "ParentID": 2, 
           "QueryTargets": [], 
           "Parent": null, 
           "Children": [] 
          } 
         ] 
        } 
       } 
      } 

      //$.getJSON("/api/query/2", null, 
      // d => { 
      //  this.QueryObj = new Query(d); 
      // }) 

      this.QueryObj = new Query(json); 
     } 
    } 
} 

window.onload =() => { 
    ko.applyBindings(new ViewModel.QueryModuleViewModel()); 
}; 

HTML結合試驗

<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <meta charset="utf-8" /> 
    <title>TypeScript Knockout Mapping Query Test</title> 
    <link rel="stylesheet" href="app.css" type="text/css" /> 

    <script src="Scripts/jquery-2.0.2.js" type="text/javascript"></script> 
    <script src="Scripts/knockout-2.2.1.debug.js" type="text/javascript"></script> 
    <script src="Scripts/knockout.mapping-latest.debug.js" type="text/javascript"></script> 
    <script src="query.js"></script> 
    <!--<script src="my_js_query_test_all.js"></script>--> 

</head> 
<body> 
    <h1>TypeScript Knockout Mapping Query Test</h1> 
    <div data-bind="with: QueryObj"> 
     <span data-bind="blah: console.log($context)"></span> 

     <p>Query Name: <input data-bind="value: Name" /></p> 

     <hr /> 
     <p>Quick test of RootTarget and Filter data</p> 
     <p>RootTarget.ID: <input data-bind="value: RootTarget().ID" /></p> 
     <p>RootTarget.Name: <input data-bind="value: RootTarget().Name" /></p> 

     <p>TYPE: <input data-bind="value: RootTarget().Filter().Type" /></p> 

     <hr /> 
     <p>RootTarget.FilterClause Hierarcy</p> 
     <div data-bind="with: RootTarget().Filter"> 
      <div data-bind="template: { name: 'QueryListClauseTemplate' }"></div> 
     </div> 

     <hr /> 
     <p>RootTarget.Selects</p> 
     <div data-bind="foreach: { data: RootTarget().Selects }"> 
      <div data-bind="template: { name: 'QueryListSelectsTemplate' }"></div> 
     </div> 

    </div> 

    <script type="text/template" id="QueryListClauseTemplate"> 

     <a title="FilterClause.Type" href="#" data-bind="text: Type" /> 

     <div data-bind="foreach: { data: Children }"> 
      <div data-bind="template: { name: 'QueryListClauseTemplate' }"></div> 
     </div> 
    </script> 

    <script type="text/template" id="QueryListSelectsTemplate"> 
     <a title="Select.Name" href="#" data-bind="text: Name" /> 
    </script> 

</body> 
</html> 
+0

添加添加HTML測試代碼 – DIGGIDY

+0

修正了幾個小的疏忽,但預期仍然沒有工作。 – DIGGIDY

+0

這個問題似乎沒有太大的意義,我猜測TypeScript可能會讓問題的根源複雜化。我已經問了另一個問題的根本原因,[見鏈接](http://stackoverflow.com/questions/17612550/mapping-json-with-knockout-fails-to-populate-type-defined-object-properties)我當我得到答案時,將使用soild解決方案更新這一個。 – DIGGIDY

1

另一種方法是創建一個.d.ts文件,該文件定義了TypeScript接口,該接口描述由給定C#類的挖空映射插件生成的可觀察類型的嵌套集合。

然後,您將使用.d.ts文件獲取所需的類型檢查(與使用絕對類型的github項目中的.d.ts文件獲取對現有的javaScript庫的類型檢查相同的方式)。

我創建了一個控制檯應用程序來檢查我的c#dll使用反射。我使用了一個自定義屬性來標記要爲其創建TypeScript接口的類型。(我還必須創建一個自定義屬性來標記哪些屬性不被創建爲可觀察的,因爲映射插件只會將嵌套集合的葉節點設置爲可觀察對象)。

這對我很好,因爲我能夠在C#模型更改時快速重新生成.d.ts文件。我能夠對我的淘汰賽ViewModel的所有部分進行類型檢查。

//the custom attributes to use on your classes 
    public class GenerateTypeScript : Attribute 
    { 
     public override string ToString() 
     { 
      return "TypeScriptKnockout.GenerateTypeScript"; 
     } 
    } 

    public class NotObservable : Attribute 
    { 
     public override string ToString() 
     { 
      return "TypeScriptKnockout.NotObservable"; 
     } 
    } 


    //example of using the attributes 
    namespace JF.Models.Dtos 
    { 
     [TypeScriptKnockout.GenerateTypeScript] 
     public class ForeclosureDetails : IValidatableObject, IQtipErrorBindable 
     { 
      [TypeScriptKnockout.NotObservable] 
      public Foreclosure Foreclosure { get; set; } 

      //strings used for form input and validation 
      public string SaleDateInput { get; set; } 
      public string SaleTimeInput { get; set; }  
      ....etc. 



    //the console app to generate the .d.ts interfaces 
    void Main() 
    { 
     string dllPath = @"binFolder"; 
     string dllFileName = "JF.dll"; 
     Assembly assembly = Assembly.LoadFrom(Path.Combine(dllPath,dllFileName)); 
     List<string> interfacesToIgnore = new List<string>{"IValidatableObject"}; //stuff that won't exist on the client-side, Microsoft Interfaces 

     var types = from t in assembly.GetTypes() 
       where (t.IsClass || t.IsInterface) 
       && t.GetCustomAttributes(true).Any(a => ((Attribute)a).ToString() == "TypeScriptKnockout.GenerateTypeScript") 
       orderby t.IsClass, t.Name 
       select t; 

     Console.WriteLine("/// <reference path=\"..\\Scripts\\typings\\knockout\\knockout.d.ts\" />"); 

     foreach (var t in types) 
     { 

      //type 
      Console.Write("{0} {1}", " interface", t.Name); 

      //base class 
      if(t.BaseType != null && t.BaseType.Name != "Object"){ 
       Console.Write(" extends {0}", t.BaseType.Name); 
      }  

      //interfaces 
      var interfacesImplemented = t.GetInterfaces().Where (i => !interfacesToIgnore.Contains(i.Name)).ToList(); 
      if(interfacesImplemented.Count() > 0){ 
       Console.Write(" extends"); 
       var icounter = 0; 
       foreach (var i in interfacesImplemented) 
       { 
        if(icounter > 0) 
         Console.Write(","); 
        Console.Write(" {0}", i.Name); 
        icounter++; 
       } 
      } 
      Console.WriteLine(" {"); 

      //properties 
      foreach (var p in t.GetProperties()) 
      { 
       var NotObservable = p.GetCustomAttributes(true).Any(pa => ((Attribute)pa).ToString() == "TypeScriptKnockout.NotObservable"); 
       Console.WriteLine("  {0}: {1};", p.Name, GetKnockoutType(p, NotObservable)); 
      } 
      Console.WriteLine(" }\n");   

     } 
    } 


    public string GetKnockoutType(PropertyInfo p, bool NotObservable){ 

     if(p.PropertyType.Name.StartsWith("ICollection") 
     || p.PropertyType.Name.StartsWith("IEnumerable") 
     || p.PropertyType.Name.StartsWith("Dictionary") 
     || p.PropertyType.Name.StartsWith("List")) 
     {  
      return String.Format("KnockoutObservableArray<{0}>", p.PropertyType.GenericTypeArguments[0].Name); 
     } 
     var typeName = p.PropertyType.Name; 
     if(typeName.StartsWith("Nullable")) 
      typeName = p.PropertyType.GenericTypeArguments[0].Name; 


     switch (typeName) 
     { 
      case "Int32" : 
      case "Decimal" : 
       return NotObservable ? "number" : "KnockoutObservable<number>"; 

      case "String" : 
       return NotObservable ? "string" : "KnockoutObservable<string>"; 

      case "DateTime" :  
       return NotObservable ? "Date" : "KnockoutObservable<Date>"; 

      case "Boolean": 
       return NotObservable ? "boolean" : "KnockoutObservable<boolean>"; 

      case "Byte[]": 
       return NotObservable ? "any" : String.Format("KnockoutObservableAny; //{0}", typeName); 

      default: 
       if(NotObservable) 
        return typeName; 

       bool isObservableObject = true; 
       var subProperties = p.PropertyType.GetProperties(); 
       foreach (var subProp in subProperties) 
       { 
        if(
         subProp.PropertyType.IsClass 
         && !subProp.PropertyType.Name.StartsWith("String") 
         && !subProp.PropertyType.Name.StartsWith("ICollection") 
         && !subProp.PropertyType.Name.StartsWith("IEnumerable") 
         && !subProp.PropertyType.Name.StartsWith("Dictionary") 
         && !subProp.PropertyType.Name.StartsWith("List")    
        ) 
        { 
         isObservableObject = false; 
        }    
       } 

       return isObservableObject ? String.Format("KnockoutObservable<{0}>", typeName) : typeName;        
     } 
    } 

    //example of the interfaces generated 

    interface ForeclosureDetails extends IQtipErrorBindable { 
     Foreclosure: Foreclosure; 
     SaleDateInput: KnockoutObservable<string>; 
     SaleTimeInput: KnockoutObservable<string>; 
     ...etc.