اذهب إلى المحتوى

التعامل مع دخل المستخدم عن طريق نماذج الإدخال في Vue.js


حسام برهان

سنتعلّم في هذا الدرس:

  • بناء هيكل تطبيق بسيط لشرح أفكار الدرس
  • استخدام إطار العمل Bootstrap
  • استخدام بنى معطيات متقدمة مع عناصر الإدخال النصية
  • المعدِّلات (Modifiers) في Vue.js
  • التعامل مع مربعات الاختيار (Checkboxes) وأزرار الانتقاء (Radiobuttons)
  • التعامل مع القائمة المنسدلة <select>
  • إرسال البيانات ‎

لقد تعاملنا في بداية هذه السلسلة مع عناصر الإدخال العادية التي يستخدمها المستخدم لإدخال البيانات ضمن صفحات الويب. سنعمل في هذا الدرس على التوسّع في التعامل مع هذه العناصر بالإضافة إلى الاطلاع على طريقة التعامل مع عناصر إدخال HTML جديدة. كما سنتعلّم كيفية التعامل مع أُطر عمل CSS جاهزة، حيث لم يسبق لنا التعامل معها مسبقًا.

بناء هيكل تطبيق بسيط لشرح أفكار الدرس

كما جرت العادة، سنبني تطبيق بسيط من أجل هذا الدرس بهدف تطبيق الأفكار الواردة فيه. سيتكوّن تطبيقنا من صفحة واحدة فقط تحوي عدد من عناصر HTML التي ننوي التعامل معها. أنشئ مشروع جديد وسمّه input-forms-cli كما فعلنا مسبقًا في الدرس السابق (راجع الفقرة "بناء هيكل تطبيق بسيط لشرح أفكار الدرس"). بعد الانتهاء من عملية الإنشاء، استخدم Visual Studio Code في فتح المشروع (المجلّد) الذي أنشأته توًّا.

لن نحتاج سوى ملف مكوّن واحد وهو App.vue لذلك احذف الملف HelloWorld.vue كما فعلنا في الدرس السابق. ثم افتح الملف App.vue واستبدل المحتوى الحالي بالشيفرة البرمجية التالية:

<template>
    <div class="container">
        <form>
            <div class="row">
                <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
                    <h1 class="text-right">الملف الشخصي للمستخدم</h1>
                    <hr>
                    <div class="form-group">
                        <label class="float-right" for="firstname">الاسم</label>
                        <input type="text" id="firstname" class="form-control" v-model="userMainData.firstname">
                    </div>
                    <div class="form-group">
                        <label class="float-right" for="lastname">الكنية</label>
                        <input type="text" id="lastname" class="form-control" v-model="userMainData.lastname">
                    </div>
                    <div class="form-group">
                        <label class="float-right" for="age">العمر</label>
                        <input type="number" id="age" class="form-control" v-model="userMainData.age">
                    </div>
                    <div class="form-group">
                        <label class="float-right" for="password">كلمة المرور</label>
                        <input type="password" id="password" class="form-control" v-model="userMainData.password">
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 form-group">
                    <label class="float-right" for="description">نبذة</label><br>
                    <textarea id="description" rows="5" class="form-control" v-model="description"></textarea>
                </div>
            </div>
            <div class="row">
                <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
                    <div class="form-group">
                        <label class="float-right" for="graduate">
                            <input type="checkbox" id="graduate" value="متخرج" v-model="status"> متخرج
                        </label>
                        <label class="float-right" for="smoker">
                            <input type="checkbox" id="working" value="أعمل حاليًا" v-model="status"> أعمل حاليًا
                        </label>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 form-group">
                    <label class="float-right" for="male">
                        <input type="radio" id="male" value="ذكر" v-model="gender"> <span>ذكر</span>
                    </label>
                    <label class="float-right" for="female">
                        <input type="radio" id="female" value="أنثى" v-model="gender"> <span>أنثى</span>
                    </label>
                </div>
            </div>
            <div class="row">
                <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 from-group">
                    <label class="float-right" for="subscriptionKind">نوع الاشتراك</label>
                    <select id="subscriptionKind" class="form-control" v-model="selectedSubscription">

                        <option v-for="kind in subscriptionKinds" v-bind:key="kind">
                            {{ kind }}
                        </option>
                    </select>
                </div>
            </div>
            <hr>
            <div class="row">
                <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-1">
                    <button class="btn btn-primary">إرسال
                    </button>
                </div>
            </div>
        </form>
        <hr>
        <div class="row">
            <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
                <div class="card card-info">
                    <div class="card-header text-right">
                        <h4>البيانات المُدخلة</h4>
                    </div>
                    <div class="card-body">
                        <div class="card-text text-right">
                            <p>الاسم:{{ userMainData.firstname }}</p>
                            <p>الكنية:{{ userMainData.lastname }}</p>
                            <p>العمر:{{ userMainData.age }}</p>
                            <p>كلمة المرور:{{ userMainData.password }}</p>
                            <p>نبذة: {{ description }}</p>
                            <p><strong>الوضع الحالي</strong></p>
                            <ul>
                                <li v-for="item in status" v-bind:key="item">{{ item }}</li>
                            </ul>
                            <p>النوع:{{ gender }}</p>
                            <p>نوع الاشتراك: {{ selectedSubscription }}</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                userMainData: {
                    firstname: '',
                    lastname: '',
                    age: 0,
                    password: '',
                },
                description: 'اكتب نبذة قصيرة عنك!',
                status: [],
                gender: 'ذكر',
                selectedSubscription: 'فضي',
                subscriptionKinds: ['ذهبي', 'فضي', 'عادي']
            }
        }
    }
</script>

<style>
    @import "./assets/styles/app.css";
</style>

أضف مجلّدًا جديدًا ضمن المجلّد assets وسمّه styles ثم أضف ملفًا جديدًا ضمن المجلّد الأخير وسمّه app.css كما فعلنا في الدرس السابق.

احرص أن تكون محتويات الملف app.css على الشكل التالي:

@import url(//fonts.googleapis.com/earlyaccess/notonaskharabic.css);

body{
    font-family: 'Noto Naskh Arabic', serif;
}

احفظ جميع التعديلات، ثم افتح موجّه الأوامر في ويندوز، وبعدها انتقل إلى مجلد التطبيق input-forms-cli الذي أنشأته قبل قليل، ونفّذ الأمر:

npm run serve

يعمل هذا الأمر كما نعلم على تشغيل خادوم الويب الخاص بالتطوير، اذهب الآن إلى متصفح الويب لديك، وانتقل إلى العنوان http://localhost:8080/ ليظهر لك شكل شبيه بما يلي:

1.png

استخدام إطار العمل Bootstrap

ربما تكون قد استغربت قليلًا من عدم استخدامنا لأي مكتبة أو إطار عمل CSS في عمليات التنسيق التي كنا نجريها في تطبيقاتنا السابقة. بدلًا عن ذلك، كنا نستخدم شيفرة CSS عادية فحسب في تنسيق التطبيقات.

يعود سبب ذلك إلى أنّه لا يُنصح أبدًا إضافة إطار عمل CSS بالطريقة التقليدية التي تتمثل في استخدام الوسم <link> كما يفعل أغلبنا عند تطوير تطبيقات أمامية (Frontend Applications). يعود السبب في ذلك لأنّ أغلب أُطر عمل CSS تحتوي على شيفرة JavaScript قد لا تكون متوافقة مع Vue.js.

علينا إذًا استخدام إطار عمل CSS نضمن أن يكون متوافقًا مع Vue.js لكي لا تحصل مفاجآت غير مرغوبة! سأتحدث هنا عن استخدام إطار العمل الشهير Bootstrap لكي نُكسب تطبيقاتنا تنسيقات جميلة وقوية وبشكل متوافق مع Vue.js.

افتح نافذة موجه الأوامر في ويندوز، ثم نفّذ الأمر التالي:

npm install bootstrap@4.0.0

بعد الانتهاء وعودة موجه الأوامر إلى حالته الطبيعية، أضف السطر التالي إلى الملف main.js:

import "bootstrap/dist/css/bootstrap.min.css";

ستصبح محتويات الملف main.js مشابهة لما يلي:

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

import "bootstrap/dist/css/bootstrap.min.css";

new Vue({
  render: h => h(App),
}).$mount('#app')

نكون بهذا الشكل قد نصبنا إطار العمل Bootstrap ومن ثمّ أضفناه إلى التطبيق الخاص بنا ليصبح جاهزًا للاستخدام. وهكذا نكون قد استغنينا عن كتابة تنسيقات مخصّصة إلى حد كبير.

استخدام بنى معطيات متقدمة مع عناصر الإدخال النصية

في الحقيقة رغم أنّ عنوان هذه الفقرة مغرٍ بعض الشيء، إلّا أنّنا في الواقع سنستخدم كائن JavaScript عادي لتمثيل بيانات المستخدم الأساسية ضمن تطبيقنا الحالي. إذا راجعت شيفرة التطبيق الأساسية التي أوردتها في الفقرة الأولى، وتحديدًا ضمن القسم <script> ستجد البنية البسيطة التالية:

userMainData:{
            firstname:'',
            lastname:'',
            age:0,
            password:'',
          }

كما أوضحت، سيحمل الحقل userMainData بيانات المستخدم الرئيسية التالية على الترتيب: الاسم، الكنية، العمر، كلمة المرور.

السؤال هنا، كيف سنربط عناصر HTML الموافقة؟ الجواب بسيط، سنستخدم v-model للربط ثنائي الاتجاه. انظر إلى الشيفرة المقتطعة من الشيفرة الموجودة ضمن القسم <template>:

<div class="form-group">
    <label class="float-right" for="firstname">الاسم</label>
    <input
            type="text"
            id="firstname"
            class="form-control"
            v-model="userMainData.firstname">
</div>
<div class="form-group">
    <label class="float-right" for="lastname">الكنية</label>
    <input
            type="text"
            id="lastname"
            class="form-control"
            v-model="userMainData.lastname">
</div>
<div class="form-group">
    <label class="float-right" for="age">العمر</label>
    <input
            type="number"
            id="age"
            class="form-control"
            v-model="userMainData.age">
</div>
<div class="form-group">
    <label class="float-right" for="password">كلمة المرور</label>
    <input
            type="password"
            id="password"
            class="form-control"
            v-model="userMainData.password">
</div>

لاحظ معي كيف استخدمت الحقل userMainData لربط كل خاصية من خصائصه بعنصر HTML الموافق. انظر مثلًا ماذا استخدمت لربط حقل الاسم firstname:

userMainData.firstname

لقد استخدمت النقطة للفصل بين userMainData وبين firstname وهذا جائز تمامًا! يمكنك مراجعة باقي عناصر HTML لترى كيف استخدمت نفس الترميز السابق. الفائدة في استخدام هذا الأسلوب هي في تنظيم وتمثيل البيانات بشكل منطقي في التطبيق.

حاول كتابة أي شيء ضمن الحقول الأربعة السابقة، ستجد أنّ ذلك سينعكس مباشرة على القسم السفلي (البيانات المُدخلة) من الصفحة، والذي جعلته خصيصًا لمشاهدة النتائج التي سنجريها على البيانات في القسم العلوي.

في الحقيقة لقد تعاملنا مسبقًا مع عناصر الإدخال العادية فيما سبق من دروس، وسنتعامل مع أنواع أخرى من عناصر الإدخال في هذا الدرس. أحد العناصر الجديدة التي سنتعامل معها هو العنصر <textarea> والذي نتعامل معه كما نتعامل مع عناصر الإدخال العادية تمامًا. إذ وضعت الموجّه v-model ضمن هذا العنصر ليرتبط مع الحقل description كما هو واضح من الشيفرة الواردة في الفقرة الأولى من هذا الدرس.

المُعدِّلات (Modifiers) في Vue.js

يحتاج المبرمج في بعض الأحيان إلى تعديل سلوك الاستجابة لـ Vue.js. فمثلًا إذا لاحظت في تطبيقنا هذا، أنّ أي شيء تكتبه ضمن أي عنصر من عناصر الإدخال النصية، سينعكس مباشرة ضمن القسم السفلي. قد يكون هذا السلوك غير مرغوب أحيانًا، فقد ترغب ربما بأن لا يستجيب Vue.js مباشرةً لما يكتبه المستخدم، بل بأن تؤجّل هذه الاستجابة حتى ينتهي المستخدم مما يكتبه وينتقل إلى حقل نصي آخر مثلًا.

السيناريو السابق ممكن تمامًا باستخدام تقنية المعدِّلات (modifiers) حيث يمكن أن نستخدم المعدِّل lazy لكي نخبر Vue.js بأن يؤجّل الاستجابة لأي عنصر إدخال مرتبط معه ريثما يعمل المستخدم على مغادرة هذا العنصر (يُفقده التركيز Focus). انتقل إلى الشيفرة البرمجية الخاصة بعنصر الإدخال firstname ضمن <template> ثم أضف المعدّل lazy إلى الموجّه v-model على الشكل التالي:

v-model.lazy="userMainData.firstname"

احفظ التغييرات، ثم حاول كتابة شيء ما ضمن الحقل firstname ولا تحاول مغادرة الحقل، ستجد هذه المرة أنّ ذلك لن ينعكس مباشرة ضمن القسم السفلي. غادر الآن ذلك الحقل إلى حقل آخر، ستجد أنّ بيانات الاسم قد ظهرت دفعة واحدة في القسم السفلي.

توجد معدّلات أخرى مثل number الذي يُستخدم لكي يُفسَّر دخل المستخدم على أنّه رقم بدلًا من النص. وهناك أيضًا المعدّل trim الذي يُستخدم للتخلص من المحارف الفارغة على يمين ويسار النص المُدخَل. يمكن استخدام أي معدّل كما استخدمنا المعدّل lazy قبل قليل، كما ويمكن استخدامها بشكل مركّب. انظر مثلًا كيف يمكن استخدام المعدلين lazy و trim بشكل مركّب:

v-model.lazy.trim = "userMainData.firstname" 

التعامل مع مربعات الاختيار (Checkboxes) وأزرار الانتقاء (Radiobuttons)

سنتعلّم في هذه الفقرة كيفية التعامل مع مربع الاختيار (Checkbox) وزر الانتقاء (Radiobutton). سنبدأ أولًا مع مربع الاختيار.

في تطبيقنا هذا، سنستخدم مربّعي اختيار للتعبير عن كون المستخدم متخرّج أم غير متخرّج. بهدف التعامل مع هذين المربعين، عرّفت حقلًا جديدًا اسمه status على أنّه مصفوفة (انظر القسم <script>). ربطت هذه المصفوفة مع مربعي الاختيار بإسنادها إلى الموجّه v-model لكل من المربعين. انظر الشيفرة المقتطعة من القسم <template>:

<label class="float-right" for="graduate">
    <input
            type="checkbox"
            id="graduate"
            value="متخرج"
            v-model="status"> متخرج
</label>
<label class="float-right" for="smoker">
    <input
            type="checkbox"
            id="working"
            value="أعمل حاليًا"
            v-model="status"> أعمل حاليًا
</label>

لاحظ كيف أنّنا ربطنا مربعي الاختيار بنفس الشكل. الذي سيحدث وراء الكواليس، هو أنّه عندما يختار المستخدم أحد المربّعين سيعمل Vue.js على إدراج قيمة الحقل الذي تمّ اختياره كعنصر في المصفوفة status، فإذا اختار المستخدم كلا المربعين، فسيدرج Vue.js عنصرين ضمن المصفوفة، يعبّر كل منهما عن قيمة مربّع الاختيار. أي أنّ عدد عناصر المصفوفة سيكون مساويًا لعدد المربعات التي اختارها المستخدم، طالما أنّ هذه المربعات قد تمّ ربطها بنفس الشكل.

يمكن تطبيق نفس المبدأ تقريبًا على أزرار الانتقاء، في تطبيقنا هذا لدينا زري انتقاء يُعبّران عن نوع المستخدم (ذكر أم أنثى). عرّفت حقلًا جديدًا أسميته gender يحمل القيمة الافتراضية ذكر. انظر معي إلى الشيفرة المقتطعة من القسم <template> لزري الانتقاء:

<label class="float-right" for="male">
    <input
            type="radio"
            id="male"
            value="ذكر"
            v-model="gender"> <span>ذكر</span>
</label>
<label class="float-right" for="female">
    <input
            type="radio"
            id="female"
            value="أنثى"
            v-model="gender"> <span>أنثى</span>
</label>

لاحظ معي بدايةً أنّني قد وضعت القيمة value لكل من الزرين السابقين لتكونا ذكر و أنثى على الترتيب. لاحظ أيضًا كيف استخدمت نفس أسلوب الربط باستخدام الموجّه v-model لكل من هذين الزرين.

الذي سيحدث عند بدء تشغيل التطبيق أنّ الحقل gender سيحمل القيمة ذكر بشكل افتراضي كما ذكرت قبل قليل، وبالتالي سيختار Vue.js الزر المعبّر عن الـ "ذكر" لأنّ قيمته ستكون مماثلة لقيمة الحقل gender في هذه الحالة. وهذا ما يبرّر الاختيار الافتراضي لهذا الزر عند البدء بتشغيل التطبيق.

جرب أن تجري الآن بعض التجارب على مربعي الاختيار وزري الانتقاء، ولاحظ النتائج التي ستحدث ضمن القسم السفلي المخصّص لعرض البيانات.

التعامل مع القائمة المنسدلة <select>

بقي لنا أن نتعلّم كيفية الربط مع القائمة المنسدلة <select>. سينقسم عملنا هنا إلى قسمين. الأوّل هو تعبئة هذه القائمة، والثاني هو الربط مع Vue.js باستخدام الموجّه v-model.

بالنسبة لتعبئة هذه القائمة بالبيانات الأولية التي سيختار منها المستخدم، فقد عرّفت الحقل subscriptionKinds وهو على شكل مصفوفة تحوي العناصر التالية:

subscriptionKinds: ['ذهبي', 'فضي', 'عادي']

تعبّر هذه المصفوفة عن نوع الاشتراك الذي يرغب المستخدم باعتماده. سيكون لدينا كما هو واضح ثلاثة اشتراكات. الشيفرة البرمجية المسؤولة عن تعبئة عنصر القائمة هي التالية:

<option v-for="kind in subscriptionKinds" v-bind:key="kind">
                          {{ kind }}
                        </option>

هذه الشيفرة مأخوذة من القسم <template> بالطبع. وهي عبارة عن حلقة بسيطة تعمل على تعبئة القائمة من خلال توليد عناصر <option> العمود الفقري لعنصر القائمة <select>.

بالنسبة لعملية الربط فقد عرّفت حقلًا آخرًا أسميته selectedSubscription وأسندت له القيمة الافتراضية فضي. لاحظ معي أنّ هذه القيمة مماثلة للعنصر الثاني من عناصر المصفوفة subscriptionKinds. بعد ذلك، ربطت عنصر القائمة باستخدام v-model على النحو التالي:

v-model="selectedSubscription"

وهكذا وعند تشغيل التطبيق للمرة الأولى، سيظهر العنصر الثاني فضي وقد اختير بشكل افتراضي. جرب الآن أن تغير خياراتك ضمن القائمة وانظر كيف سينعكس ذلك ضمن القسم السفلي المخصّص لعرض البيانات.

إرسال البيانات

كما هو معلوم، عند وضع عنصر الزر button ضمن عنصر النموذج form، فسيؤدي نقر هذا الزر إلى إرسال بيانات النموذج (form) إلى الخادوم. في تطبيقنا البسيط هذا، وضعنا زرًا لإرسال البيانات كما تعلم، ولكن بما أنّه ليس لدينا حاليًا أي تطبيق يعمل على الخادوم لمعالجة لبيانات المرسلة، لذلك سنعمل على معالجة البيانات محليًّا بشكل وهمي، لذلك سنغيّر من سلوك هذا الزر لكي نمنعه من التصرف بالشكل الافتراضي.

سنستخدم لهذه الغاية المعدِّل prevent مع الموجّه v-on:click. اعمل على تعديل شيفرة HTML الخاصة بزر الإرسال لتصبح على النحو التالي:

 <button
    v-on:click.prevent="submitInfo"
    class="btn btn-primary">إرسال
</button>

سنضيف القسم method لكي نتمكّن من تعريف التابع submitInfo. سأضع هنا كامل قسم <script> بعد التعديل:

methods:{
    submitInfo(){
        alert("تمت معالجة البيانات");
    }
}

وضعت رسالة بسيطة تشير إلى أنّ البيانات قد تمت معالجتها بشكل افتراضي محليًّا. يمكن أن تضع بدلًا من هذه الرسالة أن شيفرة قد تجدها مناسبة للبيانات التي أدخلها المستخدم. الهدف هنا هو أن تفهم المبدأ بحيث يمكنك تكييفه فيما بعد بحسب احتياجاتك.

ختامًا

تعلّمنا في هذا الدرس كيفية استخدام أُطر عمل جاهزة لتنسيق المحتوى، حيث استخدمنا في هذا الدرس إطار العمل الشهير Bootstrap. كما تعلّمنا كيف نتعامل مع عدد من عناصر HTML المخصّصة لاستقبال الدخل من المستخدم، حيث تعاملنا مع عناصر الإدخال النصية البسيطة بالإضافة إلى عنصر الإدخال ذو الأسطر المتعدّدة <textarea> وأيضًا مربعات الاختيار وأزرار الانتقاء وعنصر القائمة المنسدلة. في الحقيقة، يمكن أن تبني عناصر إدخال مخصّصة بك وفق احتياجاتك الخاصة وذلك باستخدام المكوّنات كما مرّ معنا في دروس سابقة. أرجو لك الفائدة من هذا الدرس، أراك في الدروس القادمة إن شاء الله.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...