7

Zacznijmy a dzięki z góry :)Mapowanie hierarchiczna JSON do maszynopis-KnockoutJS wpisane obiektu

OK, więc próbuję załadować/map hierarchicznej maszynopis/KnockoutJS wpisywanych klas z dopasowania danych JSON przy użyciu nokaut. mapowanie wtyczki, hierarchia może być do N-tego stopnia.

Wiem, że mogę wykonać następujące czynności, aby odwzorować/załadować klasę najwyższego poziomu z danych JSON.

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

Jednak nie mogę dowiedzieć się jak mapować/kompleks obciążenia, n-tej potęgi, hierarchicznych danych JSON do zestawu maszynopis/KnockoutJS klas i budowania relacji rodzic/dziecko.

Czytałem niezliczoną ilość artykułów, ale wszystkie one nie pasują do hierarchicznych relacji, które wykraczają poza proste przykłady dla rodziców/dzieci i nie mogę znaleźć żadnego z nich za pomocą wtyczki knockout.mapping.

Oto moje wycinane definicje klas TypeScript, które chcę zmapować/wczytać. Jestem programistą C++/C#, więc JavaScript tego typu jest dla mnie bardzo nowy.

maszynopis Przedmioty

JSON będzie wyglądać mniej więcej tak:

{ 
    "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

roztwór dodano poniżej – DIGGIDY

Odpowiedz

6

OK, więc jestem trochę dalej w dół linii teraz, po wielu ciągnięcie za włosy i testów numerious .

Poniżej znajduje się prawie działający przykład tego, co staram się osiągnąć, jedynym problemem z tym jest to, że nie wydaje się poprawnie mapować, mimo że przejście przez kod wydaje się sugerować, że ładuje się poprawnie. Tylko wtedy, gdy używam go z powiązaniami, generuje zerowe odwołanie bezwzględne w RootTaget.Filter.Type, które powinno zostać wypełnione wartością.

Wciąż próbuję zrozumieć dlaczego, ale z przyjemnością przyjmuję sugestie co do ewentualnego błędu. :)

już ustalona i pracy

naczepa pracujących maszynopis

///<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()); 
}; 

wiążące html testy

<!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

dodany kod html Test – DIGGIDY

+0

Poprawiono kilka drobnych niedopatrzeń, ale nadal nie działa zgodnie z oczekiwaniami. – DIGGIDY

+0

Wygląda na to, że nie zajmujemy się tym pytaniem. Myślę, że TypeScript może być ponad komplikowaniem źródła problemu. Zadałem inne pytanie z podstawową przyczyną, [SEE LINK] (http://stackoverflow.com/questions/17612550/mapping-json-with-knockout-fails-to-populate-type-defined-object-perties) I uaktualni to, gdy otrzymam odpowiedź. – DIGGIDY

1

Innym podejściem jest utworzyć plik .d.ts, który definiuje interfejsy TypeScript opisujące zagnieżdżone kolekcje obserwowalnych typów, które są generowane przez wtyczkę mapującą knockout, biorąc pod uwagę Twoje klasy C#.

Następnie otrzymujesz sprawdzanie typów za pomocą pliku .d.ts (w ten sam sposób, jak przy użyciu pliku .d.ts z wyraźnie napisanego projektu github, uzyskasz typ sprawdzający istniejące biblioteki javaScript).

Stworzyłem aplikację konsolową do zbadania mojego C# dll za pomocą refleksji. Użyłem niestandardowego atrybutu do oznaczenia typów, dla których zostały utworzone interfejsy TypeScript.(Musiałem również utworzyć niestandardowy atrybut, aby oznaczyć, które właściwości NIE były tworzone jako obserwowalne, ponieważ wtyczka odwzorowująca sprawia, że ​​węzły liścia z zagnieżdżonych kolekcji są widoczne jako obserwowalne).

To zadziałało dobrze, ponieważ mogłem szybko zregenerować plik .d.ts, gdy zmienił się mój model C#. I mogłem mieć sprawdzanie typów dla wszystkich części mojego nokautu 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. 
Powiązane problemy