تنسيق وتخصيص مدخلات الملفات (file inputs) بالطريقة الصحيحة


عمر الوريكات

سوف نتطرق في هذا الدرس إلى كيفية تنسيق وتخصيص عناصر <"input type="file> بالطريقة الصحيحة والسليمة وباستعمال العنصر <label> وبعض الجافاسكربت.

css-file-input.thumb.png.b606250b62dfec3

معاينة النتيجة النهائية.

يمكنك تحميل الشيفرة المصدرية للأمثلة من هنا.

هناك العديد من الطرق لتخصيص العنصر <"input type="file> وقد جربت العديد منها ولكنها لم تعجبني ولم تُلبّي متطلباتي. لذلك حاولت البحث في Google ولكني لم أجد مبتغاي. وبعد أن فقدت الأمل وظننت أنني لن أجد ما أبحث عنه وقعت عيني بالصدفة على أحد التعليقات الموجودة في موقع StackOverflow، وكان ذلك التعليق يحتوي على كلمة "<label>" وكان ذلك بداية الخيط وأعتقد أنّه ما كنت أبحث عنه.

وكما تعلمون فالنقر على عنصر label يؤدي إلى تفعيل أحد عناصر <input> مرتبطة به، ومما يثير الاهتمام أنّه إذا كان ذلك العنصر عبارة عن  <"input type="file> فإنّ النقر على الـlabel المرتبطة به يؤدي إلى فتح متصفح الملفات وهذا هو الحل المثالي الذي كنت أبحث عنه.

<input type="file" name="file" id="file" class="inputfile" />

<label for="file">Choose a file</label>

أي أنّ النقر على أي واحد من هذين العنصرين (<label> أو <"input type="file>) سوف يعطي نفس النتيجة وهي فتح متصفح الملفات، وهذا يعني أنّ أصعب جزء قد تم حلُّه. لن نحتاج إلى جافاسكربت أو حلول معقدة، كل ما نحتاجه هو السطرين البرمجيين الموجودين في الأعلى. أنظر إلى الصورة في الأسفل.

Figure2-smart-custom-file-input-1.thumb.

 

دعونا الآن نقوم بتنسيق العناصر حتى تبدو وكأنّنا نملك زرا عاديا.

إخفاء عنصر <input>

في البداية يجب علينا إخفاء العنصر <input>، وسوف تتكفل الخاصيتين display: none أو visibility: hidden بذلك. لماذا نريد إخفاءه؟ لأنّ قيمة المُدخل (input) لن يتم ارسالها إلى الخادوم عندما نقوم بعمل تسليم (submit) للنموذج. والسبب الثاني هو أننا لا نريد أن يتم تحديد ذلك العنصر عندما يقوم الزائر بتصفح الموقع باستعمال الزر tab الموجود على لوحة المفاتيح (لأننا نريد لموقعنا أن يكون قابل للوصول accessible). وبناءً على ذلك قمت باستعمال تنسيقات CSS التي تراها في الأسفل التي سوف تعمل على إخفاء العنصر عن أنظارنا ولكنه سيبقى مرئي بالنسبة للمتصفح نفسه:

.inputfile {

    width: 0.1px;
    height: 0.1px;
    opacity: 0;
    overflow: hidden;
    position: absolute;
    z-index: -1;
}

قد تتسائل لماذا وضعنا القيمة 0.1px لكل من العرض والارتفاع وليس 0px. يعود السبب في ذلك إلى أنّه إذا أعطينا عنصر ما عرض وارتفاع بقيمة 0px فإنّنا لن نتمكن من استخدام زر tab على تلك العناصر في بعض المتصفحات. وأمّا بالنسبة للخاصية position: absolute فقد استخدمناها حتى نمنع أن يتداخل العنصر مع العناصر الأخرى.

تنسيق العنصر <label>

بما أنّ العنصر <label> هو الزر افتراضيًا فإننا نستطيع تنسيق هذا العنصر كما نريد. سنقوم بشيء بسيط هنا ولن نجعل التنسيقات معقدة:

.inputfile + label {

    font-size: 1.25em;
    font-weight: 700;
    color: white;
    background-color: black;
    display: inline-block;
}

.inputfile:focus + label,
.inputfile + label:hover {

    background-color: red; 
}

الوصولية (accessibility)

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

.inputfile + label {

    cursor: pointer; /* "hand" cursor */
}

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

Figure3-smart-custom-file-input-2.thumb.

Figure4-smart-custom-file-input-3.thumb.

التصفح/التنقل باستخدام لوحة المفاتيح

إن كان زوار موقعك لا يستطيعون تصفح موقعك باستخدام لوحة مفاتيح فقط فتأكد حينها أنّك تقوم بشيء خاطئ ويجب عليك اصلاحه. وقد كان إخفاء عنصر <input> بطريقة صحيحة هو أحد الأشياء الجيدة لتحسين تجربة المستخدم، وأمّا الشيء الآخر هو أن تُعطي للمستخدم انطباعًا ما بأنّ العنصر قد أصبح في حالة focus (يُصبح العنصر في حالة focus عند التصفح باستخدام زر tab في لوحة المفاتيح، وبالتالي نستطيع استخدام الفئة الزائفة focus: على ذلك العنصر):

.inputfile:focus + label {

    outline: 1px dotted #000;
    outline: -webkit-focus-ring-color auto 5px;
}

تُستخدم القيمة webkit-focus-ring-color auto 5px من أجل الحصول على المظهر الإفتراضي للخط الخارجي (outline) في متصفحات Chrome، Opera وSafari. وبالنسبة للقيمة 1px dotted #00 فهي موجودة فقط للمتصفحات التي لا تفهم -webkit-.

Figure5-smart-custom-file-input-4.thumb.

مشاكل متعلقة باللمس (touch)

إذا كنت تستخدم FastClick (وهي مكتبة للتخلص من الإيقاف المؤقت للنقر والذي مُدته 300ms في الأجهزة التي تعمل باللمس) وكنت تنوي إضافة عناصر إضافية داخل العنصر <label>، فإنّ الزر لن يعمل كما يجب إلا إذا استخدمت الخاصية pointer-events: none:

<label for="file"><strong>Choose a file</strong></label>
.inputfile + label * {

    pointer-events: none;
}

تحسين بعض الأمور باستخدام الجافاسكربت

بقي علينا شيء واحد يجب فعله وهو إظهار إذا ما كان هناك ملفات تم اختيارها أم لا. ومع أنّ العنصر <"input type="file> يُظهر ذلك عادةً إلا أننا قمنا بإخفائه إن كنت تذكر، ولكن لحسن حظنا فهناك طريقة لفعل ذلك باستخدام الجافاسكربت بحيث نجعل نص الـlabel هو اسم الملف المُختار، وإذا كان هناك عدة ملفات فإنّ نص الـlabel يصبح عدد تلك الملفات:

<input type="file" name="file" id="file" class="inputfile" data-multiple-caption="{count} files selected" multiple />
var inputs = document.querySelectorAll( '.inputfile' );

Array.prototype.forEach.call( inputs, function( input )
{

    var label = input.nextElementSibling;
    labelVal = label.innerHTML;

    input.addEventListener( 'change', function( e )
    {
        var fileName = '';

        if( this.files && this.files.length > 1 )
            fileName = ( this.getAttribute( 'data-multiple-caption' ) || '' ).replace( '{count}', this.files.length );
        else
            fileName = e.target.value.split( '\\' ).pop();

        if( fileName )
            label.querySelector( 'span' ).innerHTML = fileName;
        else
            label.innerHTML = labelVal;
    });
});

قمت أيضًا بكتابة أكواد jQuery تقوم بنفس العمل، لذلك تأكد من أن تتصفح الملف المصدري إن كنت تفضل استخدام jQuery.

توضيح بسيط للأكواد الموجودة في الأعلى:

  • وجود الصفة multiple في عنصر <input> يسمح للمستخدم بأن يختار أكثر من ملف مرة واحدة. أمّا الصفة data-multiple-caption فهي تستخدم للتعبير عن الرسالة التي تريد أن تظهر للمستخدم عندما يقوم باختيار عدة ملفات. وبالنسبة للعبارة { count } فهي اختيارية وسوف يتم استبدالها برقم يُعبّر عن عدد الملفات المُختارة.
  • الصفة multiple غير مدعومة في متصفح Internet Explorer 9 أو أقل ولا حتى الخاصية files الخاصة بالجافاسكربت، ولذلك سوف نعتمد على value. وبما أنّها عادةً تحتوي على قيمة بالصيغة C:\fakepath\filename.jpg فإنّ ()split( '\\' ).pop تقوم باستخراج اسم الملف.
  • من المثير للاهتمام أنّه يمكنك إلغاء قيمة من المدخلات عن طريق الضغط على زر ESC عندما تكون نافذة تصفح الملفات مفتوحة، وهذا متاح فقط في متصفحي Chrome وOpera. ولهاذ استخدمنا المتغير labelVal لتخزين القيمة الافتراضية للـlabel وإرجاعها عند الحاجة لذلك.

سوف تكون النتيجة النهائية كما في الصورة:

Figure6-smart-custom-file-input-5.thumb.

 

ولكن ماذا لو كانت الجافاسكربت غير مفعلة؟

بما أنّه لا يوجد طريقة أخرى غير الجافاسكربت لمعرفة إذا ما قام المستخدم باختيار ملف أم لا، فإنّه من الأفضل الاعتماد على المظهر الافتراضي لمُدخِل الملفات من أجل سهولة الاستخدام. لذلك كل ما علينا فعله هو إضافة class باسم "no-js" للعنصر <html> ومن ثم نستخدم الجافاسكربت لاستبداله بالاسم "js" وبهذه الطريقة نعرف إذا كان الجافاسكربت مفعلًا أم لا.

<html class="no-js">

<head>

<!-- remove this if you use Modernizr -->

<script>(function(e,t,n){var r=e.querySelectorAll("html")[0];r.className=r.className.replace(/(^|\s)no-js(\s|$)/,"$1js$2")})(document,window,0);</script>

</head>

</html>

وهذه تنسيقات CSS:

.js .inputfile {

    width: 0.1px;
    height: 0.1px;
    opacity: 0;
    overflow: hidden;
    position: absolute;
    z-index: -1;
}

.no-js .inputfile + label {

    display: none;
}

Figure7-smart-custom-file-input-6.thumb.

خطأ في متصفح Firefox

إنّه لمن المفاجئ معرفة أنّ متصفح Firefox يتجاهل input[type="file"]:focus بينما تعمل :hover و:active بشكل جيد. ولكن لحسن الحظ فإنّ هذا المتصفح يسمح لنا بالتعرف على حالة focus باستخدام الجافاسكربت، لذلك فإنّ الحل هو إضافة class للعنصر <input> ليسمح لنا بالتحكم بحالة الـfocus:

input.addEventListener( 'focus', function(){ input.classList.add( 'has-focus' ); });

input.addEventListener( 'blur', function(){ input.classList.remove( 'has-focus' ); });
.inputfile:focus + label,
.inputfile.has-focus + label {

    outline: 1px dotted #000;
    outline: -webkit-focus-ring-color auto 5px;
}

خاتمة

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

ترجمة -وبتصرّف- للمقال Styling & Customizing File Inputs the Smart Way لصاحبته Osvaldas Valutis.



1 شخص أعجب بهذا


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


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



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن