Form

Create ve Edit sayfaları, sırasıyla veri sağlayıcı oluşturma ve güncelleme yöntemlerini kullanarak yeni kaynak öğesinin oluşturulmasına veya mevcut yöntemin değiştirilmesine olanak tanır. Genel olarak bu sayfalar zorunlu olmasa da aynı formu paylaşacaktır. Olobase Admin ile mevcut form bağlamı bilgisine sahip birçok mevcut giriş bileşeni sayesinde form geliştirme minimum kodla yapılabilir.

Form

Layoutlar

Hem create hem de edit layoutları, belirli bağlama dayalı eylemler içeren show sayfasıyla benzer düzeni paylaşır. VaForm asıl işi yapacağından onlar hakkında söylenecek başka bir şey yok.

Create Layout

Veri oluşturma için sayfa layout'u.

Mixinler

  • Resource

Özellikler

Özelikkler Tür Açıklama
title string Üst başlığın solunda gösterilen isteğe bağlı h1 başlığı.

Slotlar

Ad Açıklama
actions Ek özel eylem düğmeleri yer tutucusu.
default Sayfa içeriği yer tutucusu.

Klonlama

Geçerli bir mevcut kimliğe sahip belirli bir kaynak sorgu dizesi mevcut olur olmaz, oluşturma sayfasının diğer mevcut kaynaklardan gelen değerlerin kopyasını (yani klonlamayı) desteklediğini unutmayın. Bu, VaDataTableServer'da varsayılan olarak bulunan VaCloneButton aracılığıyla otomatik olarak yapılır. Create vue bileşeninde onu VaForm'a enjekte etmenize olanak tanıyan bir öğe desteğinin bulunmasının nedeni budur.

<template>
  <va-create-layout>
    <va-form :item="item">
      <!-- Olobase Admin inputs component -->
    </va-form>
  </va-create-layout>
</template>

<script>
export default {
  props: ["item"],
};
</script>

Edit Layout

Veri düzenleme için sayfa layout'u.

Mixinler

  • Resource

Özellikler

Özelikkler Tür Açıklama
title string Üst başlığın solunda gösterilen isteğe bağlı h1 başlığı.

Slotlar

Ad Açıklama
actions Ek özel eylem düğmeleri yer tutucusu.
default Sayfa içeriği yer tutucusu.

ID

Sayfa oluşturmayla karşılaştırıldığında, API tarafında düzenlenecek ve eklenecek kaynağa karşılık gelen yeni bir id özelliği vardır. Başlık altında veri sağlayıcı güncelleme yöntemini kullanmak için id özelliğini forma koymayı unutmayın.

<template>
  <va-edit-layout>
    <va-form :id="id" :item="item">
      <!-- Olobase Admin inputs component -->
    </va-form>
  </va-edit-layout>
</template>

<script>
export default {
  props: ["id", "item"],
};
</script>

Tabbed layout

Layout üzerinde tam bir özgürlüğe sahip olduğunuz için, herhangi bir vuetify veya özel bileşeni kullanarak sekmeli bir detay sayfası oluşturmak gerçekten kolaydır. Aşağıdaki örneğe göz atın.

Show Layout

<template>
  <va-show-layout
    :showList="false"
    :showClone="false"
    :showEdit="false"
    :showDelete="false"
  >
    <va-show :item="item">
      <v-tabs v-model="tabs">
        <v-tab value="1">Tab 1</v-tab>
        <v-tab value="2">Tab 2</v-tab>
        <v-tab value="3">Tab 3</v-tab>
      </v-tabs>
      <v-window v-model="tabs">
        <v-window-item value="1">
          <v-card>
            <v-card-text>
              Tab content 1
            </v-card-text>
          </v-card>
        </v-window-item>
        <v-window-item value="2">
          <v-card>
            <v-card-text>
              Tab content 2
            </v-card-text>
          </v-card>
        </v-window-item>
        <v-window-item value="3">
          <v-card>
            <v-card-text>
              Tab content 3
            </v-card-text>
          </v-card>
        </v-window-item>
      </v-window>
    </va-show>
  </va-show-layout>
</template>

Enjektör

Takip eden örnekte Olobase Admin bileşen alanlarını kullanarak kaynak görüntülemeyi kolaylaştıran bileşen enjektörünü gösteriliyor. Her Olobase Admin alanı için bu öğeyi enjekte edin.

<script>
export default {
  props: ["id", "item"],
}
</script>

Özellikler

Özellikler Tür Açıklama
item object Tüm özelliklerin Olobase Admin alanlarına eklenmesi gereken açık öğe kaynak nesnesi.

Slotlar

Ad Açıklama
default Tüm içerik, tüm iç alanlarla birlikte oluşturulur. Öğe her alana enjekte edilir.

Tahmin edebileceğiniz gibi VaShow'un ana rolü, her Olobase Admin alanı bileşenine tam öğe kaynak nesnesini enjekte etmektir, bu da minimum standart kod demektir. Daha sonra Olobase Admin alanı, kaynak özelllikte belirtilen kaynak özelliğinin değerini yakalayabilecektir.

Input Bileşenleri

Verileri düzenlemek için desteklenen tüm bileşenleri görmek için inputs sayfasına gidin. Eğer burada gösterilenlerin hiçbiri ihtiyaçlarınızı karşılamıyorsa kendi girdi bileşeninizi de oluşturabilirsiniz.

Kaydet ve Yönlendir

Varsayılan kaydet düğmesi olarak VaSaveButton'u kullanmanız tavsiye edilir. Bu tuş API'ye kaydederken ana formla ve form state verileri ile otomatik olarak senkronize olacaktır.

<template>
  <va-form :id="id" :item="item" redirect="show">
    <!-- Olobase Admin inputs component -->
    <va-save-button></va-save-button>
  </va-form>
</template>
<script>
export default {
  props: ["id", "title", "item"],
};
</script>

VaForm'a yukarıdaki gibi açık bir yönlendirme ayarlamadığınız sürece başarılı bir kaydetme, varsayılan olarak kaynak listesi sayfasına yönlendirilir. Bu desteğin yalnızca kaydet tuşu için etkili olduğunu unutmayın. Ayrıca VaSaveButton üzerinden yönlendirmeyi ayarlayabilirsiniz.

Takip eden örnek birden fazla yönlendirme işlemine ihtiyacınız olduğunda kullanışlı olabilir:

<template>
  <va-form :id="id" :item="item">
    <!-- Olobase Admin inputs component -->
    <va-save-button class="mr-2"></va-save-button>
    <va-save-button
      text
      redirect="create"
      color="secondary"
    ></va-save-button>
  </va-form>
</template>

<script>
export default {
  props: ["id", "title", "item"],
};
</script>

Görüldüğü üzere yukarıdaki kod iki farklı düğme oluşturur. Varsayılan olan mevcut kaydetme davranışı tetiklerken diğer düğme ise kaydet ve listeye yönlendir eylemini tetikler.

Save and Create

Varsayılan Yönlendirmeyi Kapatmak

Varsayılan gönderme yönlendirmesini önlemek için VaForm'da disable-redirect özelliğini kullanın. Bu eylemin yönlendirmeli kaydetme tuşları üzerinde hiçbir etkisi yoktur.

<template>
  <va-form 
    :id="id" 
    :item="item" 
    disable-redirect 
    v-model="model"
  >
 </va-form>
</template>

Snackbar Mesajını Devre Dışı Bırakma

Varsayılan snackbar çubuğu mesajını önlemek için VaForm'daki disable-save-message özelliğini kullanabilirsiniz.

<template>
  <va-form 
    :id="id" 
    :item="item" 
    disable-redirect 
    disable-save-message
    v-model="model"
  >
 </va-form>
</template>

Form Olayları

Form olayları form kaydetme aşamadından sonra sunucudan dönen yanıtları alarak buna göre aksiyon almanızı kolaylaştırır.

<template>
  <va-form 
    :id="id" 
    :item="item" 
    @saved="afterSaveAction($event)"
    v-model="model"
  >
 </va-form>
</template>
Olay Adı Açıklama
model Sunucuya gönderilen veri nesnesine erişmenizi sağlar.
saved Form kayıt edildikten sonra sunucu tarafından dönen yanıt nesnesine erişmenizi sağlar.
error Varsa sunucu tarafından dönen istisnai hatalara erişmenizi sağlar.

Form Model

Form içerisindeki girdilerin v-model özelliği tümüyle va-form içerisinde kontrol edilebilmesi için form tagında v-model niteliğini kullanın. Böylece her bir girdi için v-model yazamk zorunda kalmayacaksınız. Eğer yine de bir girdi içinde v-model kullanmanız gerekiyorsa bunun için bir engel tabiki yok.

Bir örnek:

<template>
  <va-form :id="id" :item="item" v-model="model">
    <va-text-input 
      source="description" 
      multiline
    ></va-text-input>
    <va-boolean-input 
      source="active"
      >
    </va-boolean-input>
    <va-save-button></va-save-button>
  </va-form>
</template>
<script>
export default {
  props: ["id", "title", "item"],
  data() {
    return {
      model: {
        active: null,
        description: null,
      },
    };
  },
};
</script>

Form Doğrulama

VaForm, vuelidate doğrulama kütüphanesini kullanır. Takip eden örnekte Vuelidate kütüphanesi ile bir form doğrulama örneği gösteriliyor:

olobase-demo-ui/src/resources/Companies/Form.vue

Form

<template>
  <va-form 
    :id="id" 
    :item="item" 
    disable-redirect 
    v-model="model"
  >
    <v-row>
      <v-col>
        <va-text-input
          source="companyName"
          :error-messages="companyNameErrors"
        ></va-text-input>
        <va-text-input
          source="companyShortName"
          :error-messages="companyShortNameErrors"
        ></va-text-input>
        <va-text-input
          source="taxOffice"
          :error-messages="taxOfficeErrors"
        ></va-text-input>
        <va-text-input
          source="taxNumber"
          :error-messages="taxNumberErrors"
        ></va-text-input>
        <va-text-input
          source="address"
          :error-messages="addressErrors"
        ></va-text-input>
        <va-save-button></va-save-button>
      </v-col>
    </v-row>
  </va-form>
</template>
<script>
import Utils from "vuetify-admin/src/mixins/utils";
import { useVuelidate } from "@vuelidate/core";
import { required, maxLength, numeric } from "@vuelidate/validators";
import { provide } from 'vue';

export default {
  props: ["id", "item"],
  mixins: [Utils],
  setup() {
    let vuelidate = useVuelidate();
    provide('v$', vuelidate)
    return { v$: vuelidate }
  },
  data() {
    return {
      model: {
        id: null,
        companyName: null,
        companyShortName: null,
        taxOffice: null,
        taxNumber: null,
        address: null,
      },
    };
  },
  validations: {
    model: {
      companyName: {
        required,
        maxLength: maxLength(160),
      },
      companyShortName: {
        required,
        maxLength: maxLength(60),
      },
      taxOffice: {
        maxLength: maxLength(100),
      },
      taxNumber: {
        numeric,
        maxLength: maxLength(60),
      },
      address: {
        maxLength: maxLength(255),
      },
    },
  },
  computed: {
    companyNameErrors() {
      const errors = [];
      const field = "companyName";
      if (!this.v$["model"][field].$dirty) return errors;
      this.v$["model"][field].required.$invalid &&
        errors.push(this.$t("v.text.required"));
      this.v$["model"][field].maxLength.$invalid &&
        errors.push(this.$t("v.string.maxLength", { max: "160" }));
      return errors;
    },
    companyShortNameErrors() {
      const errors = [];
      const field = "companyShortName";
      if (!this.v$["model"][field].$dirty) return errors;
      this.v$["model"][field].required.$invalid &&
        errors.push(this.$t("v.text.required"));
      this.v$["model"][field].maxLength.$invalid &&
        errors.push(this.$t("v.string.maxLength", { max: "60" }));
      return errors;
    },
    taxOfficeErrors() {
      const errors = [];
      const field = "taxOffice";
      if (!this.v$["model"][field].$dirty) return errors;
      this.v$["model"][field].maxLength.$invalid &&
        errors.push(this.$t("v.string.maxLength", { max: "100" }));
      return errors;
    },
    taxNumberErrors() {
      const errors = [];
      const field = "taxNumber";
      if (!this.v$["model"][field].$dirty) return errors;
      this.v$["model"][field].numeric.$invalid &&
        errors.push(this.$t("v.number.numeric"));
      this.v$["model"][field].maxLength.$invalid &&
        errors.push(this.$t("v.string.maxLength", { max: "60" }));
      return errors;
    },
    addressErrors() {
      const errors = [];
      const field = "address";
      if (!this.v$["model"][field].$dirty) return errors;
      this.v$["model"][field].maxLength.$invalid &&
        errors.push(this.$t("v.string.maxLength", { max: "255" }));
      return errors;
    },
  },
  created() {
    this.model.id = this.generateUid();
  }
};
</script>

Liste Görünümü Satır Doğrulama

Liste görünümlü veri güncelleme tablolarında form doğrulama yapabilmek için Vue.js provide metodundan yararlanılır. Doğrulamaya ait validations kuralları ve errors mesajları provide metodunda ilan edilmelidir.

Form

olobase-demo-ui/src/resources/Permissions/List.vue

<template>
  <va-list 
    disable-create
    :fields="fields"
    :filters="filters"
    :items-per-page="50"
  >
    <va-data-table-server
      :group-by="groupBy"
      row-create
      row-clone
      row-edit
      disable-edit
      disable-show
      disable-clone
      disable-create-redirect
    >
    </va-data-table-server>
  </va-list>
</template>
<script>
import { required } from "@vuelidate/validators";

export default {
  props: ["resource"],
  provide() {
    return {
      validations: {
        form: {
          moduleName: {
            required
          },
          resource: {
            required
          },
          route: {
            required
          },
          action: {
            required
          },
          method: {
            required
          }
        }
      },
      errors: {
        moduleNameErrors: (v$) => {
          const errors = [];
          if (!v$['form'].moduleName.$dirty) return errors;
          v$['form'].moduleName.required.$invalid &&
            errors.push(this.$t("v.text.required"));
          return errors;
        },
        resourceErrors: (v$) => {
          const errors = [];
          if (!v$['form'].resource.$dirty) return errors;
          v$['form'].resource.required.$invalid &&
            errors.push(this.$t("v.text.required"));
          return errors;
        },
        actionErrors: (v$) => {
          const errors = [];
          if (!v$['form'].action.$dirty) return errors;
          v$['form'].action.required.$invalid &&
            errors.push(this.$t("v.text.required"));
          return errors;
        },
        routeErrors: (v$) => {
          const errors = [];
          if (!v$['form'].route.$dirty) return errors;
          v$['form'].route.required.$invalid &&
            errors.push(this.$t("v.text.required"));
          return errors;
        },
        methodErrors: (v$) => {
          const errors = [];
          if (!v$['form'].method.$dirty) return errors;
          v$['form'].method.required.$invalid &&
            errors.push(this.$t("v.text.required"));
          return errors;
        },
      }
    };
  },
  data() {
    return {
      groupBy: [{ key: 'moduleName' }],
      selected: [],
      filters: [],
      fields: [
        {
          source: "data-table-group",
          label: this.$t("va.datatable.group"),
          sortable: false,
        },
        {
          source: "moduleName",
          sortable: true,
        },
        {
          source: "resource",
          sortable: true,
        },
        {
          source: "action",
          type: "select",
          sortable: true,
        },
        {
          source: "route",
          sortable: true,
        },
        {
          source: "method",
          type: "select",
          sortable: true,
        },
      ],
    };
  }
};
</script>

Sunucu Tarafında Doğrulama

İstemci tarafında tüm doğrulama alanları tamalandıktan sonra sunucu tarafında doğrulama yapılır. API'niz, doğrulama hatası bulduysa her zaman 400 durum kodu kullanan yanıt gövdesi ile tüm doğrulama alanlarına ait hataları içeren yanıtı aşağıdaki gibi istemciye gönderir.

{
    "data": {
        "error": {
            "firstname": [
                "firstname: Value is required and can't be empty"
            ],
            "lastname": [
                "lastname: Value is required and can't be empty"
            ]
        }
    }
}

Sonrasında useHttp.js eklentisi içerisindeki parseApiErrors adlı fonksiyon sunucudan dönen hataları çözümleyerek bir durum mesajı ile hataları aşağıdaki gibi alt alta ekrana yazdırır.

Form Errors

Tek bir hata gönderilmesi durumunda sunucu yanıtı aşağıdaki gibi olacaktır. İstemci tarafında bu hata yine yukarıdaki gibi görüntülenecektir.

{
    "data": {
        "error": "Example single line error"
    }
}

src/plugins/useHttp.js

/**
 * parse validation errors
 */
function parseApiErrors(error) {
  if (error.response["data"] 
    && error.response["data"]["data"] 
    && error.response["data"]["data"]["error"]) {
    let errorHtml = ""
    let hasError = false
    let errorObject = error.response.data.data.error
    if (errorObject instanceof Object) {
      errorHtml = "<ul>";
      Object.keys(errorObject).forEach(function (k) {
        if (Array.isArray(errorObject[k])) {
          hasError = true;
          errorObject[k].forEach(function (subObject) {
            if (typeof subObject === "string") {
              errorHtml += '<li>' + `${subObject}` + '</li>'
            } else if (typeof subObject === "object") {
              Object.values(subObject).forEach(function (subErrors) {
                if (Array.isArray(subErrors)) {
                  subErrors.forEach(function (strError) {
                    errorHtml += '<li>' + `${strError}` + '</li>'
                  });
                }
              });
            }
          });
        } else {
          hasError = true;
          errorHtml += '<li>' + `${errorObject[k]}` + '</li>'
        }
      })
      errorHtml += "</ul>"; 
    } else if (typeof errorObject === "string") {
      errorHtml = errorObject
      if (errorObject == "Token Expired") {
        store.dispatch("auth/logout");
      } else {
        hasError = true
      }
    }
    if (hasError) {
      store.commit("messages/show", { type: 'error', message: errorHtml })
    }
    return error;
  }
}

Önyüz ve arkayüzün ayrı çalışmaları ilkesi gereğince girdi alanlarına ait etiketlerin çevirilerini tekrar önyüz tarafında yeniden tanımlamamak için bu çeviriler, arka uç uygulamanızdaki label.php adlı php dosyasında tek tek tanımlanmalıdır. Aşağıdaki örnekte firstname ve lastname girdi adlarına ait bir çeviri örneği gösteriliyor.

{
    "data": {
        "error": {
            "firstname": [
                "Firstname: Value is required and can't be empty"
            ],
            "lastname": [
                "Lastname: Value is required and can't be empty"
            ]
        }
    }
}

data/language/en/labels.php

<?php
return [
    // login
    'username' => 'E-Mail',
    'password' => 'Password',
    'email' => 'E-Mail',

    // Account
    // 
    'firstname' => 'Firstname',
    'lastname' => 'Lastname',
];