Пара тапок » jquery http://paratapok.ru Блог о веб-разработке Sun, 18 Dec 2022 12:14:48 +0000 ru-RU hourly 1 https://wordpress.org/?v=4.3.34 Как с помощью Javascript/Jquery установить фокус на произвольный элемент? http://paratapok.ru/frontend/2613_kak-s-pomoshhyu-javascriptjquery-ustanovit-fokus-na-proizvolnyj-element/ http://paratapok.ru/frontend/2613_kak-s-pomoshhyu-javascriptjquery-ustanovit-fokus-na-proizvolnyj-element/#comments Wed, 05 Oct 2016 07:33:57 +0000 http://paratapok.ru/?p=2613 Установить js фокус на элемент достаточно просто:

document.getElementById('element').focus();

Установить focus на блок с id=element с помощью Jquery:

$('#element').focus();

Почему не работает jquery focus?

Важно помнить, атрибут tabindex="-1" позволяет произвольному блоку получить focus через Javascript.

]]>
http://paratapok.ru/frontend/2613_kak-s-pomoshhyu-javascriptjquery-ustanovit-fokus-na-proizvolnyj-element/feed/ 2
Количество элементов (jquery). Как получить? http://paratapok.ru/frontend/2608_kolichestvo-elementov-jquery-kak-poluchit/ http://paratapok.ru/frontend/2608_kolichestvo-elementov-jquery-kak-poluchit/#comments Mon, 03 Oct 2016 13:36:47 +0000 http://paratapok.ru/?p=2608 Читать далее →]]> Для получения количества элементов Jquery существует две функции size() и length(). Обе функции используются для подсчета выбранных элементов через jquery селектор. Приведем пример использования. Пусть у нас есть набор ссылок внутри параграфа:

<p class="b-paragrath">
<a href="#">Ссылка 1</a>
<a href="#">Ссылка 2</a>
<a href="#">Ссылка 3</a>
<a href="#">Ссылка 4</a>
</p>

Посчитаем количество ссылок внутри имеющего параграфа. На jquery это будет выглядеть вот так:

$(document).ready(function(){
    $('.b-paragrath a').length; //4
    $('.b-paragrath a').size(); //4
});

В обоих случаях количество элементов (jquery) в параграфе будет равно четырем.

]]>
http://paratapok.ru/frontend/2608_kolichestvo-elementov-jquery-kak-poluchit/feed/ 0
Как реализовать клик после ajax jquery? http://paratapok.ru/frontend/2604_kak-realizovat-klik-posle-ajax-jquery/ http://paratapok.ru/frontend/2604_kak-realizovat-klik-posle-ajax-jquery/#comments Mon, 03 Oct 2016 06:41:24 +0000 http://paratapok.ru/?p=2604 Читать далее →]]> В jquery есть функция click(), которая позволяет реализовать клик после ajax jquery и на любое другое событие.

Допустим у нас есть какой-то элемент в теле html-документа с классом «b-link» и нам нужно инициировать нажатие на этот элемент после выполнения некоторого ajax запроса:

	
$.ajax({
	type: 'POST',
	url: 'send.php',
	success: function(data)
	{				
		if(data)
		{
			$('.b-link').click();
		}
	}
});

Опишем, что тут происходит. После удачного выполнения ajax запроса выполняется функция success, в которой проверяются возвращенные данные. Если данные не пустые, то можем выполнять нужное нам действие.

Показанный пример помогает реализовать клик после ajax jquery.

]]>
http://paratapok.ru/frontend/2604_kak-realizovat-klik-posle-ajax-jquery/feed/ 1
Drag-and-drop перемещение элементов по категориям на jQuery http://paratapok.ru/frontend/2555_drag-and-drop-peremeshhenie-elementov-po-kategoriyam-na-jquery/ http://paratapok.ru/frontend/2555_drag-and-drop-peremeshhenie-elementov-po-kategoriyam-na-jquery/#comments Thu, 04 Feb 2016 13:09:43 +0000 http://paratapok.ru/?p=2555 Читать далее →]]> Пожалуй, самым простым интерфейсным решением сортировки элементов по категориям является механизм перетаскивания: перетащить – бросить – сохранить. Такое решение является стандартным и в операционных системах, когда вы перекладываете папки или файлы из одного места в другое. Поэтому и на сайте грех не использовать уже известную пользователю логику. К слову, в gmail также письма можно перетаскивать на ярлыки.

Итак, рассмотрим, как решить задачу drag-and-drop сортировки с использованием javascript. Предположим, что у пользователя есть список из нескольких фотографий, и ему необходимо разложить их по соответствующим папкам/альбомам. Приведенный ниже пример может быть легко адаптирован под ваши нужды.

Пример работы скрипта перетаскивания элементов

Пример работы скрипта перетаскивания элементов

Важно! Сразу следует отметить, что будем рассматривать не drag events, а стандартные mouse events для десктопных устройств и touch events для тач-устройств. Т.е. по сути будем эмулировать drag-and-drop события.

Макет

Интерфейс состоит из двух частей: списка папок (левая колонка) и списка элементов (правая колонка). Все тривиально и стандартно.

Для минимизации трудозатрат макет в примере сверстан на основе весьма популярного фреймворка bootstrap, однако стили можно легко написать самому, не прибегая к сторонним css-фреймворкам. В представленном примере были использованы плагины:

  1. underscore.js – для шаблонизации данных, приходящих от сервера;
  2. nanoScroller.js – для стилизации скроллбара для случая, когда список папок занимает по высоте больше места, чем высота окна.

Алгоритм сортировки фотографий по альбомам

Не углубляясь в подробности алгоритм будет следующим:

  1. Вешаем обработчик событий mousedown/touchstart на перетаскиваемые элементы.
  2. При возникновении события mousedown/touchstart вешаем:
    • Событие mousemove/touchmove.
    • Событие mouseup/touchend.
  3. При возникновении события mousemove/touchmove:
    • Определяем dom-элемент, над которым находится курсор мыши.
    • Если активный элемент drop-областью, то подсвечиваем его.
  4. При возникновении события mouseup/touchend:
    • Проверяем было ли событие типа drag-and-drop и является ли элемент, над которым закончилось действие пользователя, drop-областью.
    • В случае успешного прохождения проверок инициируем ajax-запрос на перемещение элемента в папку. Для этого потребуется передать id элемента и id папки.
    • После того, как запрос на перемещение элемента исполнен, в ответ будет получен список папок с обновленным количеством элементов. Рендерим полученные данные.

Исходя из приведенного выше следует, что в рамках реализации необходимо решить задачи перемещения блоков на jquery и отлавливания нахождения курсора над нужными dom-элементами.

Список drop-областей

При верстке данного блока следует обратить внимание, что каждую папку, в которую у пользователя будет возможность переносить фотографии, необходимо обернуть в div с вспомогательным классом b-folders__item и указанием id в data-атрибуте. С помощью этого класса мы будем узнавать, является ли данная область целевой, т.е. отлавливать «бросок» фотографии на эту область. И при успешном выполнении также следует отправлять ajax-запрос, для которого потребуется указать id папки, в которую перемещается элемент.

Функционал находится в файле 30-category.js. Непосредственно к теме статьи этот код не относится. Он является вспомогательным и обеспечивает «плавание» меню с папками вслед за скроллом страницы, а также применяет кастомный скроллбар для случая, когда все элементы не влезают в видимую область.

Список drag-элементов

Функционал находится в файле 40-folders.js. Нет смысла подробно описывать последовательность действий – весь код хорошо прокомментирован. Приведем основной код, которому посвящена статья, и остановимся на некоторых моментах.

// обработчик на нажатие мыши
body.on('mousedown touchstart', '.b-photos__item .b-photos__link', function(e) {
    var self = $(this),
        offerId = self.data('id'),
        pos = {},
        drop,
        touchElem,
        currentTarget,
        timeMouseDown = new Date().getTime(),
        moveInitFlag = false,
        moveInit = function() {

            // курсор
            body.addClass('drag');

            // создаем фейк элемент
            body.append('<div id="drag-drop" class="b-photos__touch">' + self.find('.b-photos__media').html() + '</div>');
            touchElem = $('#drag-drop');
            touchElem.css({
                left: pos.x + 2,
                top: pos.y + w.scrollTop() + 2
            });

            // отменяем клик
            self.one('mouseup touchend click tap', function(e) {
                e.preventDefault();
            });

            // помечаем, что пользователь тащит
            moveInitFlag = true;
        };

    // выбираем все дроп-зоны
    dropFolders = parent.find('.' + baseClass + '__item');

    // отмена перетаскивания элемента
    e.preventDefault();

    // сохранение позиции курсора
    pos.x = e.clientX;
    pos.y = e.clientY;

    // вешаем на элемент обработчик мув
    body.on('mousemove touchmove', function(e) {
        var isDropZone,
            currentPos = {},
            touches = e.originalEvent.touches;

        currentTarget = $(e.target);

        // bugfix Chrome https://code.google.com/p/chromium/issues/detail?id=161464
        // на событие mousedown хром генерирует событие touchmove
        if (new Date().getTime() < timeMouseDown + 10) {
            return;
        }

        // сохраняем позицию курсора
        currentPos.x = e.clientX;
        currentPos.y = e.clientY;

        // инициализация перетаскивания
        if (!moveInitFlag) moveInit();

        // обновляем позицию курсора и target, если тач устройство
        if (touches !== undefined) {
            currentPos.x = touches[0].clientX;
            currentPos.y = touches[0].clientY;
            currentTarget = $(document.elementFromPoint(currentPos.x, currentPos.y));
        }

        // обновление позиции превьюшки
        touchElem.css({
            left: currentPos.x + 2,
            top: currentPos.y + w.scrollTop() + 2
        });

        // находится ли курсор над drop-зоной ?
        isDropZone = person.testDropZone(currentTarget);

        // если target является папкой
        if (isDropZone) {
            dropFolders.removeClass(baseClass + '__item--drop');
            isDropZone.addClass(baseClass + '__item--drop');
        } else {
            dropFolders.removeClass(baseClass + '__item--drop');
        }
    });

    // отжимаем кнопку мыши
    body.one('mouseup touchend', function(e) {
        var elem = $(e.target),
            isDropZone,
            folderId = 0,
            folderName = '',
            folderCurrent = null,
            touches = e.originalEvent.changedTouches;

        // отвязываем события перемещения
        body.off('mousemove touchmove');

        // проверям какое событие произошло
        if (!moveInitFlag) return;

        // отменяем действие по умолчанию
        e.preventDefault();
        e.stopPropagation();

        // сбрасываем курсор
        body.removeClass('drag');

        // удаляем превьюшку 
        if (touchElem) touchElem.remove();

        // обновляем target, если тач устройство
        if (touches !== undefined) {
            // не работает в Safari
            // elem = $(document.elementFromPoint(touches[0].clientX, touches[0].clientY));

            // поэтому берем последнее значение из события touchmove
            elem = currentTarget;
        }

        // находится ли курсор над drop-зоной ?
        isDropZone = person.testDropZone(elem);

        // если target является папкой
        if (isDropZone) {

            folderId = isDropZone.data('id');
            folderName = isDropZone.find('.b-category-list__val').text();

            if (folderId === 0) folderId = '';

            // id текущей папки
            folderCurrent = parent.data('folderCurrent');

            if (folderCurrent === folderId) {
                // показываем сообщение
                DND.alert.show({
                    text: DND.CONST.MESSAGES.FOLDERS.INFOLDER,
                    time: 2000
                });

            } else {

                // запрос на перемещение папки
                $.ajax({
                    type: 'POST',
                    url: parent.data('folderMove'),
                    data: {
                        folder_id: folderId,
                        offer_id: offerId
                    },
                    dataType: 'json',
                    beforeSend: function() {
                        // блокируем блок 
                        self.parent().append('<div class="b-photos__load"></div>');
                    }
                }).done(function(data) {
                    var currentElem,
                        allCount,
                        alert;

                    // получен список папок

                    // прогоняем данные по шаблону и обновляем код блока
                    parent.find('.' + baseClass + '__list').html(DND.tmpl.other['tmpl__folders-list']({
                        folders: data,
                        current: folderCurrent
                    }));

                    // меняем счетчик количества объектов в папке Мое портфолио
                    (data[0].cnt_offers > 0) 
                        ? allCount = '(' + data[0].cnt_offers + ')'
                        : allCount = '';
                    parent.find('.' + baseClass + '__parent .b-category-list__count').text(allCount);

                    // удаляем итем со страницы
                    currentElem = self.closest('.b-photos__item');
                    currentElem.hide(500, function() {
                        currentElem.remove();

                        // выраниванием список
                        // DND.visual.valignListPortfolio();

                        // инициируем событие перемещения презентации в другу папку
                        body.trigger('folder.move');
                    });

                    // показываем сообщение
                    alert = DND.alert.show({
                        text: DND.CONST.MESSAGES.FOLDERS.AFTER_MOVE + ' «' + folderName + '».',
                        time: 2000
                    });

                }).fail(function() {
                    self.parent().find('.b-photos__load').fadeOut(300);

                    DND.alert.show({
                        text: DND.CONST.MESSAGES.ERROR_DEFAULT,
                        time: 2000
                    });
                });
            }
        }

        // убираем стили перетаскивания с папок
        dropFolders.removeClass(baseClass + '__item--drop');

        return false;
    });
});

Как получать координаты для touch-устройств?

Для десктопных устройств в объекте события event есть свойства clientX и clientY, однако в touch-устройствах они отсутствуют. В то же время в объекте события есть свойство touches. Следует всего лишь взять первый элемент массива и так же получить значения свойств clientX и clientY.

[event.touches[0].clientX, event.touches[0].clientY]

Как получить dom-элемент по координатам мыши?

Чтобы получить элемент, над которым находится курсор необходимо использовать метод elementFromPoint:

document.elementFromPoint(event.clientX, event.clientY)

Как определить, что dom-элемент является drop-областью?

Всё просто, используем метод closest, который ищем ближайшего предка с указанным классом. Если такого не нашлось, значит элемент не является drop-областью.

Баг с определением элемента по координатам в safari на touch-устройствах

На момент написания статьи в Safari был замечен баг, заключающийся в том, что метод elementFromPoint отрабатывал неверно, и возвращался указатель на родителя на несколько уровней выше. Этот момент обозначен в коде, соответствующая строка закомментирована. Поэтому в качестве альтернативы было решено брать последнее запомненное значение из события touchmove.

Итак, как видите нет ничего сложного в том, чтобы реализовать на javascript перемещение элементов (в нашем случае картинок) мышью. Необходимо лишь в нужном порядке отслеживать соответствующие события, которые в совокупности дают эмуляцию нативных drag-and-drop событий.

]]>
http://paratapok.ru/frontend/2555_drag-and-drop-peremeshhenie-elementov-po-kategoriyam-na-jquery/feed/ 0
Автоматическое обрезание длинного текста в спойлер на jQuery http://paratapok.ru/frontend/2541_avtomaticheskoe-obrezanie-dlinnogo-teksta-v-spojler-na-jquery/ http://paratapok.ru/frontend/2541_avtomaticheskoe-obrezanie-dlinnogo-teksta-v-spojler-na-jquery/#comments Sat, 16 Jan 2016 20:42:48 +0000 http://paratapok.ru/?p=2541 Читать далее →]]> На днях возникла задача реализовать своеобразный спойлер на jquery. Суть сводится к тому, чтобы если в блок выводится слишком длинный текст, например, превышающий 2000 символов, тогда текст должен обрезаться, а в конец вставляться многоточие. К тому же после блока необходимо выводить ссылку «Читать далее», которая будет раскрывать текст полностью. Следовало также не забывать и про функционал обратного сворачивания блока в формат анонса.

Задача достаточно стандартная, однако поиск подходящего jquery-плагина не увенчался успехом, поскольку во всех плагинах, которые удалось найти, ограничения задаются не количеством символов, а высотой блока. То есть указывается высота контейнера, и плагин обрезает текст, выходящий за его пределы. В интернете можно найти весьма удобные варианты таких плагинов, например, Readmore.js и dotdotdot. Причем последний даже может отслеживать изменение размера окна и автоматически обновлять результат.

Однако проблема была в том, что сайт адаптивный и блок принимает разную ширину в зависимости от ширины окна браузера. В итоге могла получиться ситуация, когда в блоке окажется совсем мало символов. Конечно же, можно по какому-то алгоритму менять высоту блока при ресайзе окна, однако было принято решение не делать какую-то надстройку над каким-то готовым решением, а написать свой небольшой плагин, который будет выполнять обрезку текста на jquery исходя из заданного количества символов.

Пример работы плагина readmore

Можно выделить несколько преимуществ данного подхода.

  1. Можно быть уверенным, что в блоке будет отображаться объем текста, несущий смысловую нагрузку даже в свернутом состоянии.
  2. Не требуются лишние обработчики на изменение размера окна, которые бы постоянно выполняли проверку того, сколько текста влезает в блок.

При реализации также необходимо было учесть две вещи, влияющих на красоту результата. Во-первых, нужно чтобы текст обрезался по целому слову. Во-вторых, избежать ситуации «раскрывать слишком мало» – может произойти в случае, когда общее количество символов в блоке чуть больше заданного значения, по которому следует производить обрезку, например, на 50-100 символов. Если параметры не будет указаны, то модуль будет использовать дефолтные значения.

Итак, алгоритм задачи достаточно прост:

  1. Вырезаем требуемое количество символов – формируем анонс.
  2. Дополняем текст анонса многоточием и добавляем html-обвязку в виде ссылки «Читать далее».
  3. Навешиваем обработчик на ссылку, которая будет менять текст в блоке на анонс и полный в зависимости от состояния.

Самая трудоемкая часть реализации скрипта html-спойлера – получение анонса из полного текста блока, поскольку в блоке может быть не просто текст, а отформатированная с помощью html-тегов разметка.

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

// формирование анонса
person.cutBrief = function() {
    var tmp,
        i = 0,                          // счетчик циклов
        j = 0,                          // счетчик циклов
        html = data.html,               // html блока
        htmlLength = html.length,       // количество символов html блока
        count = 0,                      // счетчик текстовых символов
        countFlag = true,               // текущий символ не является html-разметкой
        endCharsLen = ENDCHARS.length,  // размер массива символов, указывающих на окончание слова
        end = htmlLength,               // позиция конца анонса при поиске
        resultLimit = data.limit.total - data.limit.delta,    // требуемое количество символов
        tagName,                        // название тега
        tagStack = [];                  // стек тегов, которые необходимо закрыть в конце анонса

    if (data.count > data.limit.total) {
        // формируем анонс

        for (; i < htmlLength; i++) {
            // если открывается тег
            if (html[i] === '<') {
                countFlag = false;

                // символ не последний
                if (i < htmlLength - 1) {
                    // тег является закрывающим
                    if (html[i+1] === '/') {
                        tmp = html.indexOf('>', i+1);
                        if (tmp > 0) {
                            // верный формат закрытия тега
                            tagName = html.substr(i+2, tmp-i-2);
                            // обнаруженный тег должен иметь закрывающую часть ?
                            if ($.inArray(tagName, TAGDIC) >= 0) {
                                tagStack.pop();
                            }
                        }
                    } else {
                        // тег является открывающим
                        // следующий символ - любая латинская буква ?
                        if (/\w/gi.test(html[i+1])) {
                            // получение имени тега и опредение его на необходимость закрытия
                            tmp = html.indexOf('>', i+1);
                            if (tmp > 0) {
                                tagName = html.substr(i+1, tmp-i-1);
                                // тег должен иметь закрывающую часть
                                if ($.inArray(tagName, TAGDIC) >= 0) {
                                    tagStack.push(tagName);
                                }
                            } else {
                                // не является тегом
                                countFlag = true;
                            }
                        } else {
                            // не является тегом
                            countFlag = true;
                        }
                    }
                }
            }

            // инкрементим счетчик текстовых символов
            if (countFlag) {
                count++;
            }

            // если закрывается тег
            if (html[i] === '>') {
                countFlag = true;
            }

            // дошли до конца требуемого размера анонса
            if (count >= resultLimit) {
                // текущий символ не является концом слова
                if ($.inArray(html[i], ENDCHARS) < 0) {
                    // символ не последний
                    if (i < htmlLength - 1) {
                        // следующий символ тоже не конец слова
                        if ($.inArray(html[i+1], ENDCHARS) < 0) {
                            // ищем первое вхождение каждого символа из набора и выбираем ближайший
                            for (; j < endCharsLen; j++) {
                                tmp = html.indexOf(ENDCHARS[j], i+1);
                                if ((tmp > 0) && (tmp < end)) {
                                    end = tmp;
                                }
                            };
                            i = end;
                        }
                    }
                } else {
                    // слово закончилось целиком
                    count--;
                }
                break;
            }
        };

        // вырезаем кусок html
        data.brief = html.substr(0, i);

        // добавляем точки
        data.brief += opt.ellipsis;

        // закрываем открытые теги
        for (i = tagStack.length - 1; i >= 0; i--) {
            data.brief += '</' + tagStack[i] + '>';
        };
    } else {
        // не обрезаем
        data.brief = html;
    }
};

Применяется плагин стандартно:

$('.b-block--first').readmore();

В плагине предусмотрены следующие параметры:

  • ellipsis {string} – символы, которые будут выводиться в конце анонса;
  • textOpen {string} – текст ссылки в свернутом состоянии;
  • textClose {string} – текст ссылки в развернутом состоянии;
  • callback {function} – функция, исполняющаяся после раскрытия/закрытия блока;
  • brief {integer} – максимальное количество символов анонса, уменьшенное на величину addition;
  • addition {integer} – минимальное количество символов раскрываемой части текста;
  • smoothly {integer} – время плавного раскрытия/закрытия блока в миллисекундах.

Следует заметить, что callback-функция срабатывает только после окончания анимации текста, которая выполняется на jquery с помощью метода animate, и принимает два входных параметра: ссылку блок и текущее состояние.

Ниже приведен пример того, как можно в зависимости от состояния блока, выполнять какие-либо свои действия:

$('.b-block--second').readmore({
    ellipsis: '[...]',
    textOpen: 'Открыть',
    textClose: 'Закрыть',
    callback: function(self, state) {
        state
            ? self.css('background', '#e74c3c')
            : self.css('background', '#3498db');
    },
    brief: 500,
    addition: 100
});

CSS в примере по минимуму, можно и вовсе без него обойтись (код приведен на SCSS):

.b-readmore {
    padding: 15px 0 0 0;
    &__link {
        color: #000;
        text-decoration: underline;
        &:hover,
        &:focus,
        &:active {
            color: #000;
            text-decoration: none;
        }
    }
    &__open {
        display: inline-block;
    }
    &__close {
        display: none;
    }
    &--opened & {
        &__open {
            display: none;
        }
        &__close {
            display: inline-block;
        }
    }
}

Оцениваем и комментируем, как лучше сделать спойлер для сайта по задаваемому количеству символов.

]]>
http://paratapok.ru/frontend/2541_avtomaticheskoe-obrezanie-dlinnogo-teksta-v-spojler-na-jquery/feed/ 6