تخفیف ویژه

اعتبارسنجی Form ها (جلسه 14) : ساخت Polyfill برای مرورگرهای قدیمی - قسمت پایانی

دسته بندی: آموزش
زمان مطالعه: 11 دقیقه
۲۹ مهر ۱۳۹۶

همونطور که اطلاع دارید در جلسه گذشته ساخت Polyfill‌ها رو ادامه دادیم و شروع به نوشتن تست‌های مختلف برای ویژگی‌های Validity کردیم. در این قسمت میخوایم این تست‌ها رو تموم کنیم و آموزش مربوط به اعتبارسنجی Form‌ها رو به پایان برسونیم.

در جلسه گذشته دو تست badInput و patternMismatch رو پیاده سازی کردیم. در این جلسه میخوایم دیگر تست‌ها رو نیز پیاده سازی کنیم.

تست rangeOverflow

برای اینکه تست rangeOverflow خطا داشته باشه و true رو برگشت بده، باید شرایط زیر همزمان برقرار باشند:

  • فیلد مورد نظر ویژگی max داشته باشد
  • فیلد عددی باشد
  • حداقل یک کاراکتر داشته باشد
  • مقدار فیلد بزرگتر از مقدار ویژگی max باشد

کد Javascript مربوط به این تست بصورت زیر میشه:

rangeOverflow: (field.hasAttribute('max') && isNum && field.value > 1 && parseInt(field.value, 10) > parseInt(field.getAttribute('max'), 10))

همونطور که میبینید تمام شروط بیان شده، پیاده سازی شده اند. برای تبدیل رشته مقادیر به عدد از تابع parseInt استفاده کردیم.

تست rangeUnderflow

همه موارد مانند تست بالا است بغیر از دو مورد زیر:

  • فیلد مورد نظر باید ویژگی min داشته باشد
  • مقدار فیلد از مقدار ویژگی min کمتر باشد

کد مربوط به این تست:

rangeUnderflow: (field.hasAttribute('min') && isNum && field.value > 1 && parseInt(field.value, 10) < parseInt(field.getAttribute('min'), 10))

میبینید که اینجا هم از parseInt استفاده شده است.

تست stepMismatch

برای این تست شروط زیر برای true شدن لازم می‌باشد:

  • فیلد عددی باشد
  • ویژگی step داشته باشد
  • مقدار ویژگی step برابر با any نباشد
  • باقیمانده مقدار فیلد تقسیم بر مقدار step صفر نباشد (برای اینکار از عملگر % یا remainder استفاده میکنیم)

کد مربوط به این تست:

stepMismatch: (field.hasAttribute('step') && field.getAttribute('step') !== 'any' && isNum && Number(field.value) % parseFloat(field.getAttribute('step')) !== 0)

تست toLong

این تست با برقرار شدن همزمان شروط زیر، true رو برگشت میده:

  • فیلد مورد نظر ویژگی maxLength داشته باشد
  • مقدار ویژگی maxLength بزرگتر از 0 باشد
  • تعداد کاراکترهای مقدار ورودی فیلد از مقدار ویژگی maxLength بیشتر باشد

کدهای مربوط به این تست:

tooLong: (field.hasAttribute('maxLength') && field.getAttribute('maxLength') > 0 && length > parseInt(field.getAttribute('maxLength'), 10))

تست tooShort

در این حالت همه چیز تقریبا برعکس تست قبل می‌باشد و باید وجود ویژگی minLength رو مورد بررسی قرار بدیم. کد رو در نظر بگیرید:

tooShort: (field.hasAttribute('minLength') && field.getAttribute('minLength') > 0 && length > 0 && length < parseInt(field.getAttribute('minLength'), 10))

تست typeMismatch

این تست یکی از پیچیده‌ترین حالت‌ها برای اعتبارسنجی است. باید شرایط زیر رو برای این تست بررسی کنیم:

  • فیلد مورد نظر نباید خالی باشد
  • باید دو عبارت منظم رو قرار بدیم و بررسی کنیم که url و email وارد شده از الگو پیروی می‌کنند یا خیر.

در صورتی که مقادیر وارد شده از الگوی مورد نظر پیروی نکنند، مقدار true برگشت داده میشه. کدهای بصورت زیر خواهند بود:

typeMismatch: (length > 0 && ((type === 'email' && !/^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/.test(field.value)) || (type === 'url' && !/^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*)(?::\d{2,5})?(?:[\/?#]\S*)?$/.test(field.value))))

تست valueMissing

این تست نیز مقداری پیچیده است. در ابتدا باید بررسی کنیم که فیلد مورد نظر ویژگی required دارد یا خیر. اگر داشته باشد باید متناسب با نوع فیلد، سه تست متفاوت رو انجام بدیم.

  • اگر چک باکس یا رادیو باشد، باید مطمئن بشیم که checked باشد و تیک خورده باشد
  • اگر Select menu باشد باید مطمئن بشیم که مقدار برابر با selected باشد
  • فیلد مورد نظر هر نوع دیگری که باشد، باید بررسی کنیم که مقداری دارد یا خیر

کد مربوط به این تست بصورت زیر خواهد شد:

valueMissing: (field.hasAttribute('required') && (((type === 'checkbox' || type === 'radio') && !field.checked) || (type === 'select' && field.options[field.selectedIndex].value < 1) || (type !=='checkbox' && type !== 'radio' && type !=='select' && length < 1)))

به همین راحتی.

مجموعه کامل تست ها

اگر تست‌های بالا رو سر جای خودشون در شئ checkValidity قرار بدیم، کدها بصورت زیر خواهند شد:

// Run validity checks
var checkValidity = {
    badInput: (isNum && length > 0 && !/[-+]?[0-9]/.test(field.value)), // value of a number field is not a number
    patternMismatch: (field.hasAttribute('pattern') && length > 0 && new RegExp(field.getAttribute('pattern')).test(field.value) === false), // value does not conform to the pattern
    rangeOverflow: (field.hasAttribute('max') && isNum && field.value > 1 && parseInt(field.value, 10) > parseInt(field.getAttribute('max'), 10)), // value of a number field is higher than the max attribute
    rangeUnderflow: (field.hasAttribute('min') && isNum && field.value > 1 && parseInt(field.value, 10) < parseInt(field.getAttribute('min'), 10)), // value of a number field is lower than the min attribute
    stepMismatch: (field.hasAttribute('step') && field.getAttribute('step') !== 'any' && isNum && Number(field.value) % parseFloat(field.getAttribute('step')) !== 0), // value of a number field does not conform to the stepattribute
    tooLong: (field.hasAttribute('maxLength') && field.getAttribute('maxLength') > 0 && length > parseInt(field.getAttribute('maxLength'), 10)), // the user has edited a too-long value in a field with maxlength
    tooShort: (field.hasAttribute('minLength') && field.getAttribute('minLength') > 0 && length > 0 && length < parseInt(field.getAttribute('minLength'), 10)), // the user has edited a too-short value in a field with minlength
    typeMismatch: (length > 0 && ((type === 'email' && !/^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/.test(field.value)) || (type === 'url' && !/^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*)(?::\d{2,5})?(?:[\/?#]\S*)?$/.test(field.value)))), // value of a email or URL field is not an email address or URL
    valueMissing: (field.hasAttribute('required') && (((type === 'checkbox' || type === 'radio') && !field.checked) || (type === 'select' && field.options[field.selectedIndex].value < 1) || (type !=='checkbox' && type !== 'radio' && type !=='select' && length < 1))) // required field without a value
};

ملاحظات مخصوص برای دکمه‌های رادیو

در مرورگرهای تحت پوشش، ویژگی required فقط زمانی برای رادیو‌ها با شکست مواجه میشه که هیچ کدام از المنتهای درون گروه رادیو، checked نباشند. تا اینجای کار و درون Polyfill ما اگر رادیو مورد نظر unchecked باشد و حتی اگر المنتهای دیگر موجود در گروه رادیو checked باشند، باز هم مقدار true برای valueMissing برگشت داده میشه و تست مورد نظر با شکست مواجه میشه.

برای برطرف کردن این مشکل، در ابتدا نیاز داریم که به همه المنتهای موجود در گروه رادیو دسترسی داشته باشیم. اگر یکی از اونا checked باشد، ما رادیوی مورد نظر رو checked در نظر میگیریم و از نظر ما معتبر است. کد زیر رو درون تابع getValidityState در کنار دیگر کدها قرار میدیم:

if (field.type === 'radio' && field.name) {
    var group = document.getElementsByName(field.name);
    if (group.length > 0) {
        for (var i = 0; i < group.length; i++) {
            if (group[i].form === field.form && field.checked) {
                field = group[i];
                break;
            }
        }
    }
}

به همین راحتی.

اضافه کردن ویژگی validity به فیلدهای فرم

در پایان اگر Validity State API در مرورگر پشتیبانی نشود، ما validity ساخته شده توسط Polyfill رو اضافه یا بازنویسی میکنیم. ما برای اینکار از Object.defineProperty() استفاده میکنیم. کد زیر رو ببینید:

if (!supported()) {
    Object.defineProperty(HTMLInputElement.prototype, 'validity', {
        get: function ValidityState() {
            return getValidityState(this);
        },
        configurable: true,
    });
}

قرار دادن همه کدها در کنار هم

در اینجا همه کدهای مربوط به polyfill رو قرار میدیم. برای اینکه توابع ما در دامنه سراسری یا Global قرار نگیرند، از IIFE یا immediately invoked function expression استفاده کردیم. بصورت زیر:

;(function (window, document, undefined) {

    'use strict';

    // Make sure that ValidityState is supported in full (all features)
    var supported = function () {
        var input = document.createElement('input');
        return ('validity' in input && 'badInput' in input.validity && 'patternMismatch' in input.validity && 'rangeOverflow' in input.validity && 'rangeUnderflow' in input.validity && 'stepMismatch' in input.validity && 'tooLong' in input.validity && 'tooShort' in input.validity && 'typeMismatch' in input.validity && 'valid' in input.validity && 'valueMissing' in input.validity);
    };

    /**
     * Generate the field validity object
     * @param  {Node]} field The field to validate
     * @return {Object}      The validity object
     */
    var getValidityState = function (field) {

        // Variables
        var type = field.getAttribute('type') || input.nodeName.toLowerCase();
        var isNum = type === 'number' || type === 'range';
        var length = field.value.length;
        var valid = true;

        // Run validity checks
        var checkValidity = {
            badInput: (isNum && length > 0 && !/[-+]?[0-9]/.test(field.value)), // value of a number field is not a number
            patternMismatch: (field.hasAttribute('pattern') && length > 0 && new RegExp(field.getAttribute('pattern')).test(field.value) === false), // value does not conform to the pattern
            rangeOverflow: (field.hasAttribute('max') && isNum && field.value > 1 && parseInt(field.value, 10) > parseInt(field.getAttribute('max'), 10)), // value of a number field is higher than the max attribute
            rangeUnderflow: (field.hasAttribute('min') && isNum && field.value > 1 && parseInt(field.value, 10) < parseInt(field.getAttribute('min'), 10)), // value of a number field is lower than the min attribute
            stepMismatch: (field.hasAttribute('step') && field.getAttribute('step') !== 'any' && isNum && Number(field.value) % parseFloat(field.getAttribute('step')) !== 0), // value of a number field does not conform to the stepattribute
            tooLong: (field.hasAttribute('maxLength') && field.getAttribute('maxLength') > 0 && length > parseInt(field.getAttribute('maxLength'), 10)), // the user has edited a too-long value in a field with maxlength
            tooShort: (field.hasAttribute('minLength') && field.getAttribute('minLength') > 0 && length > 0 && length < parseInt(field.getAttribute('minLength'), 10)), // the user has edited a too-short value in a field with minlength
            typeMismatch: (length > 0 && ((type === 'email' && !/^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/.test(field.value)) || (type === 'url' && !/^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*)(?::\d{2,5})?(?:[\/?#]\S*)?$/.test(field.value)))), // value of a email or URL field is not an email address or URL
            valueMissing: (field.hasAttribute('required') && (((type === 'checkbox' || type === 'radio') && !field.checked) || (type === 'select' && field.options[field.selectedIndex].value < 1) || (type !=='checkbox' && type !== 'radio' && type !=='select' && length < 1))) // required field without a value
        };

        // Check if any errors
        for (var key in checkValidity) {
            if (checkValidity.hasOwnProperty(key)) {
                // If there's an error, change valid value
                if (checkValidity[key]) {
                    valid = false;
                    break;
                }
            }
        }

        // Add valid property to validity object
        checkValidity.valid = valid;

        // Return object
        return checkValidity;

    };

    // If the full set of ValidityState features aren't supported, polyfill
    if (!supported()) {
        Object.defineProperty(HTMLInputElement.prototype, 'validity', {
            get: function ValidityState() {
                return getValidityState(this);
            },
            configurable: true,
        });
    }

})(window, document);

با اضافه کردن این کد یا polyfill به سایتتون، مرورگرهای بیشتر از IE9+ نیز از Validity State پشتیبانی میکنند.

امیدوارم از این مطلب خوشتون اومده باشه.

در جلسات بعدی با ادامه موضوع در خدمتتون هستیم.

موفق و پیروز باشید.

یا علی

Source

چه امتیازی به این مقاله می دید؟
نویسنده محمد اسفندیاری
بسیار به طراحی وب علاقمندم و به سرعت در حال یادگیری تمام مباحث پیشرفته هستم و دوست دارم که به دیگران هم یاد بدهم.

نظرات کاربران

اولین دیدگاه این پست رو تو بنویس !

نیاز به لاگین

برای ارسال دیدگاه و یا پرسیدن سوال خود در این قسمت، باید در سایت لاگین شوید.