كيفية إنشاء مربع بحث يظهر عند النقر على أيقونة بحث في CSS


عمر الوريكات

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

searchbar.thumb.png.2100c93e4eaf5f935067

هذه هي المتطلبات التي نريدها مبدئيًا:

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

يمكنك معاينة النتيجة النهائية لهذا الدرس.

بما أننا بتنا الآن نعرف ما نريد فلنقم بذلك.

بنية ملف HTML

كل ما نحتاجه من وسوم HTML هو حاوٍ رئيسي (main container) وسوف يكون عبارة عن وسم <div> وسوف نحتاج إلى وسم <form> وإلى حقلي إدخال (inputs)؛ واحد من نوع "text" والآخر من نوع "submit" وأخيرًا سوف نحتاج إلى وسم <span> ليحتوي على أيقونة البحث:

<div id="sb-search" class="sb-search">
    <form>
        <input class="sb-search-input" placeholder="Enter your search term..." type="search" value="" name="search" id="search">
        <input class="sb-search-submit" type="submit" value="">
        <span class="sb-icon-search"></span>
    </form>
</div>

لنبدأ الآن بتنسيق العناصر باستخدام CSS.

تنسيقات CSS

بناءً على المتطلبات التي ذكرناها سابقًا فإنه يجب في البداية أن يكون لدينا زر يحتوي على أيقونة بحث وباقي العناصر يجب أن تكون مخفية. دعونا الآن نتخيل ما الذي سيحصل عند تمدد مربع البحث وجعله ظاهرًا (الذي هو نفسه سيكون الحاوي الرئيسي الذي ذكرناه سابقًا). كيف نقوم بذلك؟ سوف نستخدم الخاصية overflow: hidden وتكبير العرض الخاص بالعنصر الحاوي (sb-search) يجب أن يقوم بإظهار حقل البحث.

إذًا أول شيء نقوم به هو تنسيق العنصر الحاوي (sb-search) بحيث سوف نجعله يطوف إلى اليمين باستخدام الخاصية float: right ونعطيه الخاصية overflow: hidden، والعرض يجب أن يكون 60px ولكن بما أننا نريد أن يزيد العرض إلى 100% فإننا سنواجه بعض المشاكل في متصفحات iOS فهي لا تقبل التغيير من عرض يعتمد على الـpixels إلى عرض يعتمد على النسب المئوية. لذلك سوف نقوم بتعريف خاصية min-width بالقيمة 60px وخاصية width بقيمة 0%. يمكنك قراءة المزيد عن هذا الحل العبقري من خلال هذا الرابط.

سوف نستعمل أيضًا الخاصية transition والخاصية webkit-backface-visibility: hidden- لتلافي بعض الآثار للحقول في متصفحات الهواتف iOS:

.sb-search {
    position: relative;
    margin-top: 10px;
    width: 0%;
    min-width: 60px;
    height: 60px;
    float: right;
    overflow: hidden;
    -webkit-transition: width 0.3s;
    -moz-transition: width 0.3s;
    transition: width 0.3s;
    -webkit-backface-visibility: hidden;
}

أي شيء يتجاوز/يفيض عن هذا المربع الصغير لن يكون ظاهرًا.

لنقم الآن بموضعة حقل البحث. سوف نستعمل قيمة مئوية بالنسبة للعرض حتى نسمح للحقل بأن يتمدد مع تمدد العنصر الحاوي. ومع إضافة الارتفاع (height) وحجم الخط (font-size) والـpadding المناسبة يمكننا توسيط النص داخل العنصر (استعملنا padding بدل line-height لأن الخاصية line-height لا تعمل بشكل جيد في متصفح IE8).

قد يبدو استعمالنا للخاصية position: absolute غير ضروري، ولكن استعمالها يقوم بحل مشكلة تظهر عند إغلاق مربع البحث بحيث يبدو الحقل وكأنه ظاهر في الجانب الأيمن لفترة قصيرة جدًا.

.sb-search-input {
    position: absolute;
    top: 0;
    right: 0;
    border: none;
    outline: none;
    background: #fff;
    width: 100%;
    height: 60px;
    margin: 0;
    z-index: 10;
    padding: 20px 65px 20px 20px;
    font-family: inherit;
    font-size: 20px;
    color: #2c3e50;
}

input[type="search"].sb-search-input {
    -webkit-appearance: none;
    -webkit-border-radius: 0px;
}

قمنا أيضًا بإزالة التنسيقات الإفتراضية لحقل البحث في متصفحات WebKit.

دعونا نقوم الآن بتعريف لون الخط بالنسبة للـplaceholder (الـplaceholder هو نص يظهر داخل حقل البحث قبل أن يقوم المستخدم بكتابة أي شيء بداخله حتى يُعطي المستخدم لمحة عما يجب عليه كتابته في ذلك الحقل):

.sb-search-input::-webkit-input-placeholder {
    color: #efb480;
}

.sb-search-input:-moz-placeholder {
    color: #efb480;
}

.sb-search-input::-moz-placeholder {
    color: #efb480;
}

.sb-search-input:-ms-input-placeholder {
    color: #efb480;
}

دعونا الآن نقوم بتنسيق زر أيقونة البحث وحقل التأكيد/الإرسال (submit input)، فنحن نريدهما أن يظهرا في نفس المكان لذلك يجب أن نضعهما في الجانب الأيمن ونعطيهما نفس الأبعاد. وبما أنهما سيظهران فوق بعضهما فسوف نعطيهما الخاصية position: absolute:

.sb-icon-search,
.sb-search-submit {
    width: 60px;
    height: 60px;
    display: block;
    position: absolute;
    right: 0;
    top: 0;
    padding: 0;
    margin: 0;
    line-height: 60px;
    text-align: center;
    cursor: pointer;
}

نريد في بداية الأمر أن تكون الأيقونة قابلة للنقر، وعند فتح حقل البحث فإننا نريد لحقل التأكيد/الإرسال (submit input) أن يكون قابلًا للنقر. لذلك سوف نُعطي حقل التأكيد الخاصية z-index: -1 ونجعله شفافًا/مخفيًا حتى يمكننا رؤية أيقونة البحث:

.sb-search-submit {
    background: #fff; /* IE needs this */
    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /* IE 8 */
     filter: alpha(opacity=0); /* IE 5-7 */

    opacity: 0;
    color: transparent;
    border: none;
    outline: none;
    z-index: -1;
}

لاحظ أننا لم نقم بجعل الخلفية شفافة وذلك لأن الأمر لا يعمل بشكل جيد في متصفح IE فالعنصر لا يكون قابلًا للنقر عند ذلك، لذلك قمنا باستعمال لون معين (الأبيض) وجعلنا الشفافية (opacity) تساوي صفر.

وبما أننا نريد لأيقونة البحث أن تظهر فوق كل شيء فإننا سوف نقوم بإعطائها الخاصية z-index بقيمة عالية، وسوف نقوم باستخدام الفئة الزائفة ::before لإضافة الأيقونة:

.sb-icon-search {
    color: #fff;
    background: #e67e22;
    z-index: 90;
    font-size: 22px;
    font-family: 'icomoon';
    speak: none;
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    -webkit-font-smoothing: antialiased;
}

.sb-icon-search:before {
    content: "\e000";
}

ودعونا لا ننسى إضافة خط الويب (web font) في بداية ملف الـCSS:

/* Search icon by IcoMoon, made with http://icomoon.io/app/ */

@font-face {
    font-family: 'icomoon';
    src:url('../fonts/icomoon/icomoon.eot');
    src:url('../fonts/icomoon/icomoon.eot?#iefix') format('embedded-opentype'),
    url('../fonts/icomoon/icomoon.woff') format('woff'),
    url('../fonts/icomoon/icomoon.ttf') format('truetype'),
    url('../fonts/icomoon/icomoon.svg#icomoon') format('svg');
    font-weight: normal;
    font-style: normal;
}

يمكننا الآن إعطاء العنصر الحاوي (sb-search) عرضًا بقيمة 100% وذلك في حالتين؛ الأولى عند النقر على أيقونة البحث وذلك عن طريق إضافة فئة (class) بالاسم "sb-search-open" باستخدام الجافاسكربت والثانية عندما لا يكون الجافاسكربت مفعّلًا لدى المستخدم:

.sb-search.sb-search-open,
.no-js .sb-search {
    width: 100%;
}

لنقم الآن بتغيير لون الخط الخاص بعنصر أيقونة البحث ونضعه أسفل حقل التأكيد/الإرسال وذلك عن طريق إعطائه قيمة z-index أقل من 90 (وهي القيمة التي أعطيناها للعنصر "sb-icon-search"):

.sb-search.sb-search-open .sb-icon-search,
.no-js .sb-search .sb-icon-search {
    background: #da6d0d;
    color: #fff;
    z-index: 11;
}

وأخيرًا لنقم بإضافة الخاصية z-index لحقل التأكيد/الإرسال ولكن بقيمة أكبر من 11 حتى يمكننا النقر عليها:

.sb-search.sb-search-open .sb-search-submit,
.no-js .sb-search .sb-search-submit {
    z-index: 90;
}

انتهينا الآن من تنسيقات CSS وبقي علينا الجافاسكربت.

بعض الجافاسكربت

لنبدأ بإضافة وإزالة الفئة "sb-search-open". بحيث سوف يتم إضافة الفئة عند النقر على الحاوي الرئيسي (sb-search) وإزالته عند النقر على حقل التأكيد/الإرسال ولكن فقط إذا كان حقل البحث فارغًا، أمّا إذا لم يكن فارغًا فإننا نريد تأكيد عملية البحث. وحتى لا نقوم بإزالة الفئة عند النقر على الحقل (لأن الحاوي بأكمله قابل للنقر) فإننا نحتاج إلى منع حدث النقر (click event) من الانتشار على ذلك العنصر. هذا يعني أنّ النقر على الحقل لن يؤدي إلى إثارة حدث النقر على العناصر الحاوية له.

;( function( window ) {
    function UISearch( el, options ) {
        this.el = el;
        this.inputEl = el.querySelector( 'form > input.sb-search-input' );
        this._initEvents();
    }

    UISearch.prototype = {
        _initEvents : function() {
            var self = this,
            initSearchFn = function( ev ) {
                if( !classie.has( self.el, 'sb-search-open' ) ) { 
                    ev.preventDefault();
                    self.open();
                }
                else if( classie.has( self.el, 'sb-search-open' ) && /^\s*$/.test( self.inputEl.value ) ) {
                    self.close();
                }
            }
            this.el.addEventListener( 'click', initSearchFn );
            this.inputEl.addEventListener( 'click', function( ev ) { ev.stopPropagation(); });
        },

        open : function() {
            classie.add( this.el, 'sb-search-open' );
        },

        close : function() {
            classie.remove( this.el, 'sb-search-open' );
        }
    }
 
    window.UISearch = UISearch;

} )( window );

سوف نحتاج أيضًا إلى إضافة الأحداث التي تقوم بإزالة الفئة "sb-search-open" عند النقر خارج مربع البحث، وحتى يعمل ذلك فإننا نريد أن نتعامل مع انتشار الأحداث (event bubbling) عند النقر على الحاوي الرئيسي.

;( function( window ) {
    function UISearch( el, options ) {
        this.el = el;
        this.inputEl = el.querySelector( 'form > input.sb-search-input' );
        this._initEvents();
    }

    UISearch.prototype = {
        _initEvents : function() {
            var self = this,
            initSearchFn = function( ev ) {
                ev.stopPropagation();
                if( !classie.has( self.el, 'sb-search-open' ) ) { 
                    ev.preventDefault();
                    self.open();
                }
                else if( classie.has( self.el, 'sb-search-open' ) && /^\s*$/.test( self.inputEl.value ) ) {
                    self.close();
                }
            }
            this.el.addEventListener( 'click', initSearchFn );
            this.inputEl.addEventListener( 'click', function( ev ) { ev.stopPropagation(); });
        },

        open : function() {
            var self = this;
            classie.add( this.el, 'sb-search-open' );
            // close the search input if body is clicked
            var bodyFn = function( ev ) {
                self.close();
                this.removeEventListener( 'click', bodyFn );
            };
            document.addEventListener( 'click', bodyFn );
        },

        close : function() {
            classie.remove( this.el, 'sb-search-open' );
        }
    }

    window.UISearch = UISearch;

} )( window );

وشيء آخر علينا الاهتمام به وهو قصّ مصطلح البحث.

عندما نقوم أيضًا بالنقر على أيقونة البحث فإننا نريد أن يكون الحقل مفعّلًا (focused)، ولأنّ هذا يسبب بعض المشاكل في متصفح iOS (لوحة المفاتيح تظهر في نفس الوقت) فإننا نريد تلافي ذلك في هذه الحالة، وعندما يتم إغلاق مربع البحث فإننا نريد أن يكون مربع البحث غير مفعل (blur). هذا سوف يحل بعض المشاكل في بعض الأجهزة التي تُظهر بأنّ المؤشر يومض حتى بعد أن يكون الحقل مغلقًا.

ملاحظة جانبية: كلمة focus تدل على أنّ مربع البحث مفعّل (أي أنّ المؤشر بداخله) وكلمة blur تدل على عكس ذلك.

;( function( window ) {
    // http://stackoverflow.com/a/11381730/989439
    function mobilecheck() {
        var check = false;
        (function(a){if(/(android|ipad|playbook|silk|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);

        return check;
    }

    // http://www.jonathantneal.com/blog/polyfills-and-prototypes/
    !String.prototype.trim && (String.prototype.trim = function() {
         return this.replace(/^\s+|\s+$/g, '');
     });

    function UISearch( el, options ) {
        this.el = el;
        this.inputEl = el.querySelector( 'form > input.sb-search-input' );
        this._initEvents();
    }

    UISearch.prototype = {
        _initEvents : function() {
            var self = this,
            initSearchFn = function( ev ) {
                ev.stopPropagation();
                // trim its value
                self.inputEl.value = self.inputEl.value.trim();
                if( !classie.has( self.el, 'sb-search-open' ) ) {
                    ev.preventDefault();
                    self.open();
                }
                else if( classie.has( self.el, 'sb-search-open' ) && /^\s*$/.test( self.inputEl.value ) ) {
                    self.close();
                }
            }

            this.el.addEventListener( 'click', initSearchFn );
            this.inputEl.addEventListener( 'click', function( ev ) { ev.stopPropagation(); });
        },

        open : function() {
            var self = this;
            classie.add( this.el, 'sb-search-open' );
            // focus the input
            if( !mobilecheck() ) {
                this.inputEl.focus();
            }

            // close the search input if body is clicked
            var bodyFn = function( ev ) {
                self.close();
                this.removeEventListener( 'click', bodyFn );
            };
            document.addEventListener( 'click', bodyFn );
        },

        close : function() {
            this.inputEl.blur();
            classie.remove( this.el, 'sb-search-open' );
        }
    }

    window.UISearch = UISearch;

} )( window );

وحتى يعمل كل شيء بشكل سلس في أجهزة الهواتف فإننا سوف نحتاج إلى إضافة أحداث اللمس (touch events). كما أنّ إضافة preventDefault في دالّة initSearchFn سوف يمنع حدث النقر واللمس من أن يتفعّلا مع بعضهما في أجهزة اللمس.

;( function( window ) {
    // http://stackoverflow.com/a/11381730/989439
    function mobilecheck() {
        var check = false;
        (function(a){if(/(android|ipad|playbook|silk|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
        return check;
    }

    // http://www.jonathantneal.com/blog/polyfills-and-prototypes/
    !String.prototype.trim && (String.prototype.trim = function() {
         return this.replace(/^\s+|\s+$/g, '');
     });

    function UISearch( el, options ) {
        this.el = el;
        this.inputEl = el.querySelector( 'form > input.sb-search-input' );
        this._initEvents();
    }

    UISearch.prototype = {
        _initEvents : function() {
            var self = this,
            initSearchFn = function( ev ) {
                ev.stopPropagation();
                // trim its value
                self.inputEl.value = self.inputEl.value.trim();
                if( !classie.has( self.el, 'sb-search-open' ) ) {
                    ev.preventDefault();
                    self.open();
                }
                else if( classie.has( self.el, 'sb-search-open' ) && /^\s*$/.test( self.inputEl.value ) ) {
                    ev.preventDefault();
                    self.close();
                }
            }

            this.el.addEventListener( 'click', initSearchFn );
            this.el.addEventListener( 'touchstart', initSearchFn );
            this.inputEl.addEventListener( 'click', function( ev ) { ev.stopPropagation(); });
            this.inputEl.addEventListener( 'touchstart', function( ev ) { ev.stopPropagation(); } );
        },

        open : function() {
            var self = this;
            classie.add( this.el, 'sb-search-open' );
            // focus the input
            if( !mobilecheck() ) {
                this.inputEl.focus();
            }
            // close the search input if body is clicked
            var bodyFn = function( ev ) {
                self.close();
                this.removeEventListener( 'click', bodyFn );
                this.removeEventListener( 'touchstart', bodyFn );
            };
            document.addEventListener( 'click', bodyFn );
            document.addEventListener( 'touchstart', bodyFn );
        },

        close : function() {
            this.inputEl.blur();
            classie.remove( this.el, 'sb-search-open' );
        }
    }

    window.UISearch = UISearch;

} )( window );

وأخيرًا، للأجهزة التي لا تدعم addEventListener وremoveEventListener فإننا سوف نستعمل polyfill.

// EventListener | @jon_neal | //github.com/jonathantneal/EventListener

!window.addEventListener && window.Element && (function () {
    function addToPrototype(name, method) {
         Window.prototype[name] = HTMLDocument.prototype[name] = Element.prototype[name] = method;
    }

    var registry = [];

    addToPrototype("addEventListener", function (type, listener) {
        var target = this;
        registry.unshift({
            __listener: function (event) {
                event.currentTarget = target;
                event.pageX = event.clientX + document.documentElement.scrollLeft;
                event.pageY = event.clientY + document.documentElement.scrollTop;
                event.preventDefault = function () { event.returnValue = false };
                event.relatedTarget = event.fromElement || null;
                event.stopPropagation = function () { event.cancelBubble = true };
                event.relatedTarget = event.fromElement || null;
                event.target = event.srcElement || target;
                event.timeStamp = +new Date;
                listener.call(target, event);
            },

            listener: listener,
            target: target,
            type: type
        });

        this.attachEvent("on" + type, registry[0].__listener);
    });

    addToPrototype("removeEventListener", function (type, listener) {
        for (var index = 0, length = registry.length; index < length; ++index) {
            if (registry[index].target == this && registry[index].type == type && registry[index].listener == listener) {
                return this.detachEvent("on" + type, registry.splice(index, 1)[0].__listener);
            }
        }
    });

    addToPrototype("dispatchEvent", function (eventObject) {
        try {
            return this.fireEvent("on" + eventObject.type, eventObject);
        } catch (error) {
            for (var index = 0, length = registry.length; index < length; ++index) {
                if (registry[index].target == this && registry[index].type == eventObject.type) {
                     registry[index].call(this, eventObject);
                }
            }
        }
    });

})();

خاتمة

هذا كان كل شيء فيما يخص هذا الدرس، أتمنى أن تكون قد استفدت منه وتعلمت منه شيئًا جديدًا.

ترجمة -وبتصرّف- للمقال Expanding Search Bar Deconstructed لصاحبته Mary Lou.



4 اشخاص أعجبوا بهذا


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


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



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

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

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


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

تسجيل الدخول

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


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