Lecaw

Учимся создавать Web-сайты

Создание расширенной формы поиска

June 27, 2013 282hits

В последнее время нас часто стали спрашивать про различные формы поиска и сегодня я хочу поделиться с вами одним из способов создания формы поиска с нуля, для начала взгляните на демонстрационную страницу, чтобы понять, с чем мы сегодня будем работать. Цель состоит в том, чтобы получить максимальную совместимость с мобильными устройствами и старыми браузерами (до IE8). На первый взгляд, кажется, что данная форма поиска очень проста в реализации, но мы должны применить некоторые хитрости для того, чтобы заставить ее работать должным образом.

ДЕМО Исходные файлы

 

Итак, что нам нужно:

  • Изначально мы хотим отображать только иконку поиска.
  • При нажатии, поле поиска должно выскользнуть из под иконки.
  • Компонент должен быть плавающим, это означает, что мы сможем использовать его в адаптивном макете.
  • После ввода необходимого запроса, должна быть возможность запуска поиска по нажатию клавиши Enter, либо нажав на иконку поиска.
  • Если поле ввода пустое, то при нажатии на иконку поиска, поле должно исчезнуть.
  • Мы также хотим, чтобы поле ввода исчезало, когда мы щелкаем за пределами панели поиска, не зависимо от того пустое поле ввода или нет.
  • Для улучшенной работы на сенсорных устройствах, мы добавим поддержку сенсорных событий.

HTML разметка

Создадим основной контейнер, форму, текст, добавим несколько элементов ввода и иконку:

<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>

Обычно, мы использовали псевдо-элемент для создания иконки, но этот способ не подходит для замены элементов, например замена на форму поиска, поэтому для иконки мы будем использовать в данном случае элемент span.

CSS

Следуя нашим требованиям, мы должны убедиться, что у нас видна кнопка с иконкой поиска. Все остальное должны быть скрыто. Давайте зададимся вопросом - что происходит, когда мы расширяем строку поиска (основную оболочку). Как мы это делаем? Давайте использовать overflow: hidden и увеличение ширины sb-search оболочки.

Итак, начнем мы со стиля sb-search оболочки. Мы сделаем, её плавающей с правой стороны и установим overflow со значением hidden. Ширина, естественно должна быть 60px но так как нам нужно будет анимировать ширину до 100%, это может быть проблематично на мобильных (IOS) браузерах. Переход от пиксельной ширины до процентной плохо реализован в мобильных браузерах. Они просто пропускают сам момент перехода. Поэтому вместо этого мы определим min-width со значением 60px и ширину 0%. Это блестящее решение от @julienknebel, он написал целую статью на эту тему: CSS transition from a fixed px width to an auto width.

Мы также установим переход для ширины и -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;
}

Все что выходит за пределы этого контейнера, будет скрыто.

Теперь, давайте настроим позиционирование строки поиска. Мы установим процентную ширину так, чтобы когда мы расширяем родителя, ввод будет расширяться вместе с ним. Установка нужной высоты, размера шрифта и отступов будет гарантировать, что текст будет находиться по центру (использование line-height не будет работать в IE8, так что давайте установим вместо этого отступы).
Установка поля ввода с абсолютной позицией, казалось бы, не так уж и необходимо, но это решает малоприятный момент - иногда случается, что при закрытии поиска: поле ввода остается видимым на правой стороне на небольшой промежуток времени.

.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 браузеров.

Давайте определим цвет текста:

.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;
}

Теперь, перейдем к кнопке отправить и иконке поиска. Мы знаем, что необходимо разместить их в одном месте, так что давайте поместим их в правом углу и установим одинаковые размеры. Так как они будут находиться поверх друг друга, установим свойство 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;
}

Изначально, мы хотим иметь активируемую по щелчку кнопку с иконкой. Затем, когда мы открываем поле ввода, нам нужно чтобы он также был активируемым по щелчку. Поэтому установим z-index входа в -1 и сделать его прозрачным, так что сначала мы всегда будем видеть только иконку поиска:

.sb-search-submit {
    background: #fff; 
    -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, потому что элемент будет не активен. Поэтому вместо этого мы используем сплошной цвет фона и непрозрачность в 0.

У элемента span будет высокий 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";
}

Теперь перейдем к стилю ссылки:

@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%, когда мы добавляем класс sb-search-open.

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

Давайте изменим цвет иконки поиска и поместим её под полем поиска, для этого установим z-index с наименьшим значением, точно также как на сайте - программа диспетчер такси:

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

И наконец, мы установим z-index кнопки отправки в более высокое значение, чтобы мы могли щелкнуть по ней:

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

JavaScript

Давайте начнем с переключения класса sb-search-open. Когда мы нажимаем на основную оболочку (sb-search), мы добавляем класс sb-search-open и удаляем его, когда мы нажимаем на кнопку отправки, только если поле поиска пустое. В противном случае мы отправляем форму. Чтобы не вызывать удаление класса, когда мы нажимаем на поле поиска (так как наш триггер - целая оболочка), мы должны предотвратить распространение события щелчка на том элементе. Это означает, что при нажатии на поле поиска не будет происходить вызов события щелчка на его родительском элементе.

;( 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, когда мы нажимаем где-то в другом месте, за пределами нашей строки поиска. Для этого мы также должны, позаботится о цепочке событий на основной оболочке.

;( 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' );
            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 );

Кроме того, когда мы нажимаем на иконку поиска, нам нужно чтобы поле поиска сфокусировалось. Так как это вызывает некоторый судорожный переход на мобильных (iOS) браузерах (клавиатура открывается одновременно), мы должны избежать этого. Когда мы закрываем строку поиска, мы будем размывать вход. Это позволит решить некоторые проблемы на некоторых устройствах, которые показывают мигающий курсор даже если поле поиска скрыто.

;( 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;
    }
 
    !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();
                    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' );
            if( !mobilecheck() ) {
                this.inputEl.focus();
            }
            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 );

Чтобы все работало гладко на мобильных устройствах, нам нужно добавить соответствующие сенсорные события. Добавление 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();
                    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' );
            if( !mobilecheck() ) {
                this.inputEl.focus();
            }
            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 мы используем EventListener Polyfill от Jonathan Neal.

// 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);
                }
            }
        }
    });
})();

ДЕМО Исходные файлы

 

Дополнительная информация

Влерий Аликин - веб-разработчик & дизайнер. Соучредитель и член команды Lecaw.

Эл. почта
RATTING:
(2 голосов)

Оставить комментарий