Skip to content

coolshare/NewFramework-SchemaDriven

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

69 Commits
 
 
 
 

Repository files navigation

New Framework: a Schema Driven UI

By Mark Qian 8/2018 (markqian@hotmail.com)

A. Introduction:
A Schema-driven UI: I am not the first one doing this but happened to have the similar idea with what others had:

The "thing" to be driven by schema is view renderers. Many people accept using a renderer for a table but very few develops/architects do the same thing for another majority type of view, form. People keep writing html tags for forms redundantly. We should think about this in a different way: the form info (schema) is the input data of form renderer instead of html codes. In my project, each type of view such as form, table, D3 diagram has a renderer.

The major benefits include:
  • Loading view data automatically
  • Collecting view data for submission automatically
  • For most regular view like regular forms and tables, the views are rendered by "built-in" framework renderers. So no layout coding for the framework users
  • Validation in a systematic way
  • Unit Testing in a systematic way


</br/> Here is the flow of schema driven in my approach:


    1. Models delivered by server (either generated from model-driven server side or given memually to UI).

      Models defined by server contain: a field list where each field has attributes like name, data type and validation info like the following:

          {
            "Student":{
              "fields": [
                {"name":"id", "type":"string", "validate":[]},
                {"name":"name", "type":"string", "validate":["required"]},
                {"name":"age", "type":"int", "validate":["required", "int"]},
                {"name":"campus", "type":"enum", "validate":["required", "int"], "typeMap":{"0":"Hayward", "1":"San Jose", "2":"SF"},
                  ...
              ]  
            },
            "Teacher":{
              "fields": [
                {"name":"id", "type":"string", "validate":[]},
                {"name":"name", "type":"string", "validate":["required"]},
                  ...
              ]  
            },
            "Course":{
              "fields": [
                {"name":"id", "type":"string", "validate":[]},
                {"name":"name", "type":"string", "validate":["required"]},
                  ...
              ]  
            },
            "Campus":{
              "fields": [
                {"name":"id", "type":"enum", "validate":[], "typeMap":{"0":"Hayward", "1":"San Jose", "2":"SF"},
                {"name":"name", "type":"string", "validate":["required"]},
                {"name":"address", "type":"string", "validate":["required"]},  
                {"name":"totalStuden", "type":"int", "validate":[]},  
                  ...
              ]  
            }
      

    2. UI use the models from server as "super" classes to generate "schemas".

      A schema contains more UI related info besides info in its model: formType, tableType, renderStle and so on. This "schema" work as a primitive UI image for that component/field, like the following:

          {
            "Student":{
              "fields": [
                {"name":"id", props:{"formType":"Hidden", "tableType":"Hidden"}},
                {"name":"name", props:{"formType":"Input", "tableType":"text"}},
                {"name":"age", props:{"formType":"Input", "tableType":"text"}},
                  ...
              ]  
            }
      
      The framework will generate a "default" schema for each model while the props specified in schema above overwrites props in generated default props. So if no specified in schema, defaults will be applied like "formType":"Input" and "tableType":"text".

    3. UI defines "views", as instances of schemas, to be used by each individual screen out of "schemas" above.

      A schema can be used in many views and a view may use different fields from different schemas. In many of our screen, a "visual form" could consist of fields from different server model objects. For example, you may want to show couple "forms" on your page with fields like student name, student favor course, student campus address, like the following:

          {
            "StudentView":{
              "fieldList": [
                {"path":"Student.name"},
                {"path":"Student.id"},
                {"path":"Course.name", props:{"label":"Most frequently taking course"}},
                {"path":"Campus.name", props:{"typeList":["1", "2", "0"]}},
                  ...
              ]  
            },
            "TeacherView":{
              "fieldList": [
                {"path":"Teacher.name"},
                {"path":"Student.name", props:{"label":"Favor Student"}},
                {"path":"Course.name", props:{"label":"Most frequently involved course"}},
                {"path":"Campus.name", props:{"label":"Closest campus", "typeList":["0", "2", "1"]}},
                  ...
              ]  
            },
            //Nested View
            "CombinedView": {
              "fields": [
                {"name":"studenView", props:{"schemaName":"StudentView", "showFun":function() {return this.getValue("CurrentView}==="student"}},
                {"name":"teacherView", props:{"schemaName":"TeacherView", "showFun":function() {return this.getValue("CurrentView}==="teacher"}},
                  ...
              ]  
            }
      
      As you can see above, fields in each view definition will overwrite the props with the same name in the "schema" so that each view can have its own characters. For example, "StudentView" and "TeacherView" both contain "Campus.name" but each wants to display the campus names in its own order so they use "typeList" to tell the order differently. View can be nested like "CombinedView" above

    4. UI Renders Views.

      My framework provides a variety of renderers like Form, Table, D3Canvas, LineChart and so on. In this way, it is much easy to apply systematic approaches like validation, submission, data loading. For example, for most "regular" forms, there is not codes are needed to load and submit data since the framework will introspect the "fieldList" in views above to obtain what need to be loaded and submitted: for "TeacherView", the framework needs to load data from model/object Teacher, Student and Campus and similarly when submiting.

    5. UI Handling Application Logic.

      Any application built with my framework is a container containing isolated "Objects" (UI views and service). This objects has no knowledge about others or the "application logic". For example, let's take a look at a navigation view, HeaderTab:

            "HeaderTab":{
              "fields": [
                {"name":"StudentTab", "props":{"formType":"Button", "route":{"viewType":"Screen"}}},
                {"name":"TeacherTab", "props":{"formType":"Button", "route":{"viewType":"Screen"}}},
                {"name":"CampusTab", "props":{"formType":"Button", "route":{"viewType":"Dialog"}}},
                  ...
              ]  
            }
      
      As you can see above, there is only a "route" attribute that includes no info about what to do after "StudentTab" is clicked! This is key of my design: no appliaction logic resides in codes!. The application logic will be loaded as data at runtime in json file as following:
        "Route":{
            "HeaderTab.StudentTab":"StudentView",
            "HeaderTab.TeacherTab":"TeacherView",
            "HeaderTab.CampusTab":"CampusTabView",
              ... 
        }
      
      The key of the map above is the unique id of each field such as "HeaderTab.StudentTab" and the value is the unique name of the view

    6. Each Field Handle Special Interaction.

      Each field may have some special need to send or received event/data. So Pub/Sub is now in place. For example, when the field "Campus.name" in "StudentView" is changed, we want to give "CanpusView" a chance to update its "Canpus.totalStuden":

          {
            "StudentView":{
              "fieldList": [
                {"path":"Student.name"},
                {"path":"Student.id"},
                {"path":"Course.name", props:{"label":"Most frequently taking course"}},
                {"path":"Campus.name", props:{"typeList":["1", "2", "0"], "pub":{"topic":"/Updated/StudentView/Campus.name", {"event":"change"}}},
                  ...
              ]  
            },
            "CanpusView":{
              "fieldList": [
                {"path":"Canpus.name"},
                {"path":"Canpus.totalStuden", props:{"label":"Total Number of Student"}},
                  ...
              ],
              "props":{
                  "sub":{"topic":"/Updated/StudentView/Campus.name", "handler":function() {
                    //make update here
      
              }            
          }
        }
      
      As you can see above, field "Campus.name" register a topic "/Updated/StudentView/Campus.name" for an event of "change" and "CanpusView" subscribe the topic at view level and then handle the update there.

    (More coming soon)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published