データ駆動UIとVue .JSとクエーサー

65863 ワード

データ駆動UIとVue .JSとクエーサー



説明


2020年3月中旬に、フレームワークVueを使用してJSON(Data Drive UI)のスキーマ定義に基づいて動的UI生成の最初の試みを開始しました.クェーサー.
データ駆動UIの概念は、以下のような興味深い解決を可能にします.
  • 動的にUIを生成するデータベース表とビューに関連するUIモデル定義スキーマを定義する
  • テクノロジやフレームワークに対するUIモデル定義スキーマを作成します(VUE +クエーサー用のジェネレータを開発することができます.
  • アイデアはデータベーススキーマAPI、データベースのテーブルとビューに関連するフォームのUI定義を提供するAPIにリンクすることでした.これらのUI定義はJSON形式で構成され、クライアント側のインタプリタはJSON情報に基づいてUIを生成します(VU . js 2.0 +クエーサーフレームワーク1.0).
    動的に生成されたフォームは、フィールドのためのエディットコントロールコンポーネント(および他の関連プロパティ)の型を持つデータベース内の各対応するエンティティフィールドのフィールド定義スキーマを提示します.これらのコントロールは、他のグループの下で、またはグループ(タブ、カード、展開など)の下に表示されます.このスキームは、お互いの依存関係(EG諸国、州、都市)に関連するルックアップフィールドも提供しました.エディットコントロールは、イベントコミュニケーション用のイベントバスの使用やフォーム、エディットコントロール、ラッパーコンポーネント間のプロパティ通信のスコープスロットのようないくつかの調整を使用してクエーサーフレームワークのフォームコントロールに基づいています.JSONスキーマのスロットを使用するいくつかの複雑なコンポーネントの構成も実装されました.レンダララッパーコンポーネントは、RESTful/GraphSQL APIとの相互作用のために提供され、データベース内の対応するエンティティ/ルックアップのデータと対話します.
    単純さの理由のために、大部分の機能は主要なコンポーネント(すなわちフォーム、グループと編集規制(この記事の焦点である))のダイナミックなレンダリングだけに集中するためにオリジナルのコードから除外されました.私たちは、タブでグループ化された分野でフォームの実装を維持しました.

    事前の要件


    私たちは、Git CLI、JavaScript、Vueの良い知識を持っていると仮定します.JSとクエーサーフレームワーク.Vue CLIとQuasar CLIをシステムにインストールする必要があります.このチュートリアルはLinux環境で実行されていましたが、あなたの好みのオペレーティングシステムについて簡単に調整できます.

    JSONスキーマ構造


    JSON構造はかなり簡単です.各グループ項目のフィールドのグループとリストを定義します.
    しかし、フィールドプロパティを定義するのは、サポートされているクエーサーUIコントロールを許可するように複雑です.
    スキーマのフィールドプロパティを使用すると、フィールドに入力された値、マスクの編集、多くの視覚的側面、および大いに多くの検証規則を定義できます.
    JSON構造は以下の通りです.

  • GroupModel : string =>(タブのみがサポートされています);

  • array::arrayグループの配列
  • 主なグループのプロパティ
  • その他の任意のグループ制御タイプ固有のプロパティ
  • フィールドを定義します.
  • 主なフィールドのプロパティ
  • その他の任意のフィールドコントロールタイプの特定のプロパティ.
  • 以下に、JSONスキーマの例を示します.
    export default {
      /*
       * Group type: Only 'tab' is currently supported
       */
      groupModel: "tab",
      /*
       * List of group itens
       */
      groups: [
        {
          /*
           * Main properties (name, label, icon)
           */
          name: "Group 1",
          label: "Group 1",
          icon: "mail",
    
          /*
           * Control type specific properties
           */
          flat: true,
          "expand-separator": true,
    
          /*
               * Field list: name, id and fieldType 
                 are the main properties, the others are 
                 UI control specific properties.
               */
          fields: [
            {
              /*
               * Main field properties
               */
              name: "id",
              id: "g1_id",
              fieldType: "inputtext",
              /*
               * Control type specific properties
               */
              label: "id",
              dense: false,
              readonly: true,
              hidden: true,
            },
            /*
             * Other fields definitions...
             */
            {
              name: "name",
              id: "g1_name",
              fieldType: "inputtext",
              label: "Name",
              placeholder: "Name...",
              hint: "Inform the name...",
              dense: true,
              clearable: true,
              "clear-icon": "close",
              /*
               * Validation rules can be defined as in the example below
               */
              rules: [
                {
                  params: ["val"],
                  exp: '!!val || "Name is required!"',
                },
              ],
            },
            {
              name: "on",
              id: "g1_on",
              fieldType: "btntoggle",
              label: "On?",
              hint: "Report if ON or OFF...",
              dense: false,
              clearable: true,
              "stack-label": true,
              filled: false,
              options: [
                { label: "On", value: "on" },
                { label: "Off", value: "off" },
              ],
            },
            {
              name: "onoff",
              id: "g1_onoff",
              fieldType: "checkbox",
              "outer-label": "On or Off?",
              label: "On/Off",
              hint: "Report if ON or OFF...",
              "indeterminate-value": null,
              "true-value": "on",
              "false-value": "off",
              dense: false,
              clearable: true,
              "stack-label": true,
              filled: false,
            },
            {
              name: "alive",
              id: "g1_alive",
              fieldType: "radio",
              "outer-label": "Is alive?",
              label: "Alive",
              hint: "let me know if you're alive...",
              val: "alive",
              dense: false,
              clearable: true,
              "stack-label": true,
              filled: false,
            },
            {
              name: "birthday",
              id: "g1_birthday",
              fieldType: "datepicker",
              label: "Birthday",
              hint: "enter your birthday...",
              mask: "YYYY-MM-DD",
              titleFormat: "ddd., DD [de] MMM.",
              dense: false,
              clearable: true,
              "stack-label": true,
              filled: false,
            },
            {
              name: "time",
              id: "g1_time",
              fieldType: "timepicker",
              label: "Time",
              hint: "Inform the time...",
              format24h: true,
              dense: false,
              clearable: true,
              "stack-label": true,
              filled: false,
            },
            {
              name: "date",
              id: "g1_date",
              fieldType: "inputdate",
              label: "Date",
              placeholder: "Date...",
              dateMask: "DD/MM/YYYY",
              mask: "##/##/####",
              hint: "Inform the date...",
              titleFormat: "ddd., DD [de] MMM.",
              dense: true,
              clearable: true,
            },
            {
              name: "time2",
              id: "g1_time2",
              fieldType: "inputtime",
              label: "Time",
              placeholder: "Time...",
              timeMask: "HH:mm:ss",
              mask: "##:##:##",
              hint: "Inform the time...",
              format24h: true,
              withSeconds: true,
              dense: true,
              clearable: true,
            },
            {
              name: "date_time",
              id: "g1_date_time",
              fieldType: "inputdatetime",
              label: "Date/Time",
              placeholder: "Date/Time...",
              dateMask: "DD/MM/YYYY HH:mm:ss",
              mask: "##/##/#### ##:##:##",
              hint: "Inform the date and time...",
              dateTitleFormat: "ddd., DD [de] MMM.",
              format24h: true,
              withSeconds: true,
              dense: true,
              clearable: true,
            },
            {
              name: "options",
              id: "g1_options",
              fieldType: "select",
              label: "Options",
              hint: "Inform the option...",
              dense: true,
              clearable: true,
              transitionShow: "flip-up",
              transitionHide: "flip-down",
              options: ["Google", "Facebook", "Twitter", "Apple", "Oracle"],
            },
            {
              name: "word",
              id: "g1_word",
              fieldType: "editor",
              label: "Editor",
              hint: "Spills the beans...",
              clearable: true,
              "stack-label": true,
              "min-height": "5rem",
            },
            {
              name: "range",
              id: "g1_range",
              fieldType: "range",
              outerLabel: "Range",
              hint: "Inform the range...",
              clearable: true,
              "stack-label": true,
              min: 0,
              max: 50,
              label: true,
            },
            {
              name: "track",
              id: "g1_track",
              fieldType: "slider",
              outerLabel: "Track",
              hint: "Drag...",
              clearable: true,
              "stack-label": true,
              min: 0,
              max: 50,
              step: 5,
              label: true,
            },
            {
              name: "evaluate",
              id: "g1_evaluate",
              fieldType: "rating",
              label: "Rating",
              hint: "Do the evaluation...",
              clearable: true,
              "stack-label": true,
              max: 5,
              size: "2em",
              color: "primary",
            },
            {
              name: "open_close",
              id: "g1_open_close",
              fieldType: "toggle",
              "outer-label": "Open?",
              label: "Open",
              hint: "Open or closed report...",
              dense: false,
              clearable: true,
              "stack-label": true,
              filled: false,
              color: "primary",
              "true-value": "on",
              "false-value": "off",
            },
            {
              name: "files",
              id: "g1_files",
              fieldType: "uploader",
              "outer-label": "Send files",
              label: "Select the files",
              hint: "Select the files...",
              dense: false,
              clearable: true,
              multiple: true,
              "stack-label": true,
            },
          ],
        },
        {
          name: "Group 2",
          label: "Group 2",
          icon: "alarm",
    
          flat: true,
          "expand-separator": true,
        },
        {
          name: "Group 3",
          label: "Group 3",
          icon: "movie",
    
          flat: true,
          "expand-separator": true,
        },
      ],
    };
    

    魔法はどう


    フレームワークに必要なリソース


    フレームワークを動作させるためには、動的にコンポーネントを作成する可能性をサポートする必要があります.幸運にもVue.JSは、これらのことで非常に良いです!
    VueJSスポートConditional Rendering - (v-if/v-else/v-else-if) , and List Rendering - (v-for) . これらの機能を使用すると、JSONスキーマを反復処理し、条件付きでUIコンポーネントをレンダリングできます.
    条件付きRenenderingはいくつかの種類のコントロールについてはOKですが、あなたがそれらの多くを持っているときに最適なオプションではありません(この記事では、私たちはあなたのためにボーナスコントロールとしてフォームコントロールの約20種類を定義しました!)
    この種の挑戦Vueのために.JSサポートdynamic component creation - (:is) . この機能を参照すると、動的にコンポーネントインスタンスをインポートできます.
    また、各コントロールタイプが異なるプロパティセットを持っていることを述べました.仕事のために、Vue.JSはバッチ内のオブジェクトのすべてのプロパティをリンクできるようにする必要があります.そして再びVue.JSには以下の解決策があります.Passing all properties of an Object - (v-bind) .
    以下のセクションでは、上記のすべての機能がどのように使われますかtemplate 成形機の断面Vue
    問題へのクリーンで簡潔な解決を作成します.

    コンポーネントインフラストラクチャ


    src/componentフォルダには一連のソースコードがあります.全体をどのように実装したかを理解するために分析しましょう.

    <小野寺>js


    このmixin object を注入します.Vueこの関数は、データ名(ComponentMap [])を提供することです.コンポーネント名を動的にインポートし、その名前のコンポーネントインスタンスを返すファクトリに解決します.
    /**
     * A mixin object that mantain a dictionary de components
     */
    
    export default {
      data() {
        return {
          componentMap: {},
        };
      },
      methods: {
        initComponentsMap() {
          this.componentMap = {
            // Group components
            card: () => import("./Card01"),
            tabs: () => import("./Tabs01"),
            tab: () => import("./Tab01"),
            tabpanel: () => import("./TabPanel01"),
            expansion: () => import("./Expansion01"),
    
            // Form component
            form: () => import("./Form01"),
    
            // From field components
            inputtext: () => import("./Input01"),
            inputdate: () => import("./DateInput01"),
            inputtime: () => import("./TimeInput01"),
            inputdatetime: () => import("./DateTimeInput01"),
            select: () => import("./Select01"),
            checkbox: () => import("./CheckBox01"),
            radio: () => import("./Radio01"),
            toggle: () => import("./Toggle01"),
            btntoggle: () => import("./ButtonToggle01"),
            optgroup: () => import("./OptionGroup01"),
            range: () => import("./Range01"),
            slider: () => import("./Slider01"),
            datepicker: () => import("./DatePicker01"),
            timepicker: () => import("./TimePicker01"),
            rating: () => import("./Rating01"),
            uploader: () => import("./Uploader01"),
            editor: () => import("./Editor01"),
    
            // Other
            icon: () => import("./Icon01"),
          };
        },
      },
    };
    
    その後、辞書はtemplate 名前は以下の通り:
    <!-- Create a dynamica TABS type component -->
    <component :is="componentMap['tabs']"></component>
    

    フォーミングVue


    これは、JSONスキーマに基づいてUIを動的に組み立てる作業の大半を行います.
    それは内部のサービスのための一連の機能を持っているので、本当に重要な部分に集中しましょう.
  • 最初に、それがmixinとして注入されることができて、テンプレートにおいて、アクセス可能であるために、コンポーネント・マップをインポートする
  • コンポーネント・エコシステムと通信するためにイベント・バスをつくって、提供する
  • JSONスキーマを受け取るプロパティを定義します
  • 入力データの内容を維持するためにFormDataデータを定義します.
  • 
    ...
    
    import componentMap from "./_componentMap01";
    
    ...
    
    export default {
      name: "FormGenerator",
    
      mixins: [componentMap],
    
      provide() {
        return {
          // The event bus to comunicate with components
          eventBus: this.eventBus,
        };
      },
      props: {
        // The schema placeholder property
        schema: {
          type: Object,
        },
      },
      data() {
        return {
          // The event bus instance
          eventBus: new Vue(),
    ...
          // Form data with input field contents
          formData: {},
    ...
        }
      }
    
    ...
    
    }
    
    そして最後にtemplate これは動的なコンポーネントを作成します-テンプレートのコメントは明らかにVueの方法を説明します.JSの機能は一緒に作品を作るために働く:
    <template>
      <!--
            Dynamic wrapper `form` component
            `fixedSchema` is the ajusted version of property `schema`
          -->
      <component v-if="fixedSchema" :is="componentMap['form']" ref="form">
        <!--
            ==================  
            Groups with fields
            ==================
            -->
        <div v-if="fixedSchema.groups && fixedSchema.groups.length > 0">
          <!--
              ==========
              TAB Model
              ==========
              -->
          <!--
                Dynamic `tabs` component
              -->
          <component
            v-if="fixedSchema.groupModel == 'tab'"
            :is="componentMap['tabs']"
            v-model="selectedGroup"
            dense
          >
            <!--
                  Dynamic `tab itens` components  
                -->
            <component
              v-for="(group, index) in fixedSchema.groups"
              :is="componentMap['tab']"
              :key="index"
              v-bind="group"
            >
            </component>
          </component>
    
          <q-separator />
    
          <!--
                Dynamic `tabpanel` component
              -->
          <component
            v-for="(group, index) in fixedSchema.groups"
            :is="componentMap['tabpanel']"
            :key="index"
            :selected="selectedGroup"
            v-bind="group"
          >
            <div v-if="group.fields && group.fields.length > 0">
              <!--
                    And finally all UI field controls:
                    - Component type specified by `componentMap[field.fieldType]`
                    - Data contents linked to `formData[field.name]` by `v-model`
                    - All `field` properties linked by `v-bind`
                  -->
              <component
                v-for="(field, index) in validFieldComps(group.fields)"
                :key="index"
                :is="componentMap[field.fieldType]"
                v-model="formData[field.name]"
                v-bind="field"
                v-show="!field.hidden"
              >
              </component>
            </div>
          </component>
        </div>
      </component>
    </template>
    

    / src /コンポーネントの他の“vue”ファイル


    他のコンポーネントは、基本的に所望の機能性を提供するためにオリジナルのクエーサー構成要素のうちの1つ以上をカプセル化する.彼らはイベントをformgeneratorに渡します.ITSを通じたVueevent bus を返します.v-on="$listners" and v-bind="$attrs" .
    例として、入力から次のソースコードがあります.Vue
    <template>
      <q-input
        v-bind="$attrs"
        v-on="$listeners"
        @input="onInput"
        @clear="onClear"
        @focus="onFocus"
        @blur="onBlur"
      >
        <template
          v-for="(_, slot) of $scopedSlots"
          v-slot:[slot]="scope"
        >
          <slot
            :name="slot"
            v-bind="scope"
          />
        </template>
      </q-input>
    </template>
    
    <script>
    import compInfo from './_compInfo'
    
    export default {
      mixins: [compInfo],
      inject: ['eventBus'],
      methods: {
        onInput (value) {
          this.eventBus.$emit('input', this, value)
        },
        onClear (value) {
          this.eventBus.$emit('clear', this, value)
        },
        onFocus (evt) {
          this.eventBus.$emit('focus', this, evt)
        },
        onBlur (evt) {
          this.eventBus.$emit('blur', this, evt)
        }
      },
      inheritAttrs: false
    }
    </script>
    

    フォーミングの使い方


    今では簡単な部分はsrc/pages/FormTest.vue JSONスキーマをロードし、Formgeneratorコンポーネントに渡すページがあります.
    <template>
      <form-generator :schema="schema" />
    </template>
    
    <script>
    import FormGenerator from "../components/FormGenerator";
    import jsonSchema from "../data/schema.js";
    
    export default {
      components: { FormGenerator },
      data() {
        return {
          schema: {},
        };
      },
      created() {
        this.schema = jsonSchema;
      },
    };
    </script>
    
    以下のコマンドで例を実行します.
    # Run the Quasar/Vue application
    $ yarn quasar dev
    
    次に、好みのブラウザで次のURLを入力します.
    http://localhost:8080
    あなたはこの印象的な結果を得る

    このチュートリアルからの例の実行


    インストール


    # Clone tutorial repository
    $ git clone https://github.com/maceto2016/VueDataDrivenUI
    
    # access the project folder through the terminal
    $ cd VueDataDrivenUI
    
    # Install dependencies
    $ npm install
    

    アプリケーションを実行する


    # Run the Quasar/Vue application
    $ yarn quasar dev
    

    アプリケーションのテスト


    好みのブラウザで次のURLを入力します
    http://localhost:8080

    結論


    本論文では、定義されたデータに存在する情報に基づいてUIの動的生成にすぎないデータ駆動UIの概念を提示する.この記事では、JSONスキーマを定義し、Vueを使用してインフラストラクチャを作成する方法を簡単に示しました.コンポーネントを動的に作成するJS +クエーサーフレームワーク.ボーナスとして、我々はQuasarフレームワークUIコンポーネントに基づいて約20のUIコンポーネントを提供します.
    ここに提示されたソースコードと考えを自由に使ってください.Vueへの移行を含む改良のための巨大な余地があります.JS 3、クエーサー2とtypescript.今ではあなた次第です!
    読書ありがとうございます.私はあなたのフィードバックを聞いて満足している!