Ответы пользователя по тегу JavaScript
  • Существуют ли сниппеты комментариев для PhpStorm как в SublimeText3?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Вводите над определением функции /**, нажимаете Enter - вуаля, DocBlock готов!
    Документация
    Ответ написан
  • Douglas Crockford или Flanagan D для изучения JavaScript?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Все зависит от того, что вы вкладываете в понятие "разобраться". Я бы рекомендовал прочитать учебник Ильи Кантора, скорее всего, этого будет достаточно.
    Ответ написан
    5 комментариев
  • Как понять эту матрицу?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    You may also pass in a matrix while instantiating the PF.Grid class. It will initiate all the nodes in the grid with the same walkability indicated by the matrix. 0 for walkable while 1 for blocked.

    Как обычно, ответ на ваш вопрос находится в документации.

    Эта матрица, по сути - карта вашего поля, где 0 означает, что по клетке можно пройти, а 1 - нельзя.
    Если вам нужно получить информацию о проходимости какой-то конкретной клетки, то это сделать довольно легко:
    matrix [координата Y (номер строки)] [координата X (номер колонки)]
    Ответ написан
    3 комментария
  • Что не так в Javascript?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    document.querySelector("#nav-toggle")
    На странице нет такого элемента, поэтому querySelector возваращает null.
    Об этом в ошибке и написано - у null нет метода addEventListener.
    addEventListener вызывается только в одном месте - правда, разобраться совсем не сложно.

    А вообще, если уж используете jquery, так и используйте на полную катушку:
    if ($(window).width() > 991) {
        $("#nav-toggle").on("click", function() {
            $(this).toggleClass("active");
        });
    }
    Ответ написан
    3 комментария
  • Как можно улучшить код на JQuery?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Я же вам здесь очень подробно ответил.
    Принципы, которые там описаны, применяются одинаково хорошо и для ванильного js и для jquery.

    var classes = ['item-id', 'item-title', 'item-price', 'delete-btn'];
    
    $('#sf tr').each(function () {
        var $cell = $('<td>').append('<a class="add_item">Добавить в корзину</a>');
        
        $(this).append($cell);
    
        $(this).find('td').each(function (index, element) {
            $(element).addClass(classes[index]);
        });    
    });


    Или вот такой вариант, без итерации по строкам:
    var classes = ['item-id', 'item-title', 'item-price', 'delete-btn'],
        $rows = $('#sf tr').filter(':gt(0)'),
        $link = $('<a>').addClass('add_item').text('Добавить в корзину'),
        $cell = $('<td>').append($link),
        $cells;
    
    $rows.append($cell);
    
    $cells = $rows.find('td');
    
    $cells.each(function (index, cell) {
        $(cell).addClass(classes[index % classes.length]);
    });


    Интересные, кстати, получаются результаты сравнения скорости работы этих двух вариантов.
    Ответ написан
    9 комментариев
  • Почему постоянно лезет undefined?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    На сколько я поля это фишка асинхронности !?
    Правильно поняли.

    Это получается нужно код выстраивать цепочкой из калбэков ?
    Необязательно. (для nodejs, понятно, своя специфика, но общие принципы такие же, естественно).
    Ответ написан
    Комментировать
  • Как выполнять функцию по завершении часто повторяющегося события, а не много раз подряд?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Дело в том, что за время вращения колеса мыши, событие mousewheel срабатывает много раз. Соотвественно и обработчик события срабатывает столько же раз.
    Чтобы этого избежать, нужно применить debounce обработчика, то есть сделать так, чтобы он выполнился один раз через n миллисекунд после его последнего вызова. Если с момента последнего вызова не прошло n миллисекунд, а обработчик был вызван еще раз, его выполнение откладывается еще на n миллисекунд.
    В данном случае, обработчик выполнится через 100 миллисекунд после того, как перестанут поступать события скролла (100 - это просто пример, реальное значение нужно подбирать исходя из ситуации):
    $(document).ready(function () {
        var owl = $(".sldr").owlCarousel().data('owlCarousel');
    
        $(window).on('mousewheel', debounce(function (event) {
            var direction = event.originalEvent.deltaY < 0 ? 'next' : 'prev';
            owl[direction]();
        }, 100));   
    });
    Код функции debounce
    function debounce(func, wait, immediate) {
        var timeout, args, context, timestamp, result;
    
        var later = function () {
            var last = new Date().getTime() - timestamp;
    
            if (last < wait && last >= 0) {
                timeout = setTimeout(later, wait - last);
            } else {
                timeout = null;
                if (!immediate) {
                    result = func.apply(context, args);
                    if (!timeout) context = args = null;
                }
            }
        };
    
        return function () {
            context = this;
            args = arguments;
            timestamp = new Date().getTime();
            var callNow = immediate && !timeout;
            if (!timeout) timeout = setTimeout(later, wait);
            if (callNow) {
                result = func.apply(context, args);
                context = args = null;
            }
    
            return result;
        };
    }

    Реализацию функции debounce я беззастенчиво позаимствовал из библиотеки underscore.
    Интерактивный пример
    Ответ написан
    Комментировать
  • Как оптимизировать данный код (открытие/закрытие всплывающих окон)?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    У вас по сути N систем из трех взаимосвязанных элементов.
    Сами системы между собой никак не связаны, а вот элементы внутри системы между собой нужно как-то связать.
    Самый простой вариант - использовать индексы (интерактивный пример):
    var buttons = document.querySelectorAll('.js-btn-item'),
        popups = document.querySelectorAll('.js-products-popup'),
        closers = document.querySelectorAll('.js-btn-close'),
        index, button, popup, closer;
    
    for (index = 0; index < buttons.length; index++) {
        button = buttons[index];
        popup = popups[index];
        closer = closers[index];
    
        button.addEventListener('click', buttonHandler(popup));
        closer.addEventListener('click', closerHandler(popup))
    }
    
    function buttonHandler(popup) {
        return function (event) {
            event.preventDefault();
            popup.classList.add('js-popup-show');
        }
    }
    
    function closerHandler(popup) {
        return function (event) {
            event.preventDefault();
            popup.classList.remove('js-popup-show');
        }
    }

    Объяснение, зачем мы делаем такие сложные обработчики событий из двух вложенных функций, находится здесь. В современных браузерах можно сделать проще, но при этом внутри обработчика события this будет ссылаться уже не на элемент, а на что-то другое (скорее всего, window), но в данном случае это не имеет значения.

    Для того, чтобы этот вариант работал, естественно, элементы каждого типа должны идти в строго одинаковом порядке.
    Чтобы этого избежать, можно использовать родителя-обертку, внутри которого содержатся все 3 элемента системы (пример). Такой подход, помимо всего прочего, за счет делегирования позволяет устанавливать N обработчиков, а не N*2, как в первом случае. Но код обработчика слегка усложняется - добавляется проверка целевого элемента.

    Третий способ показал Алексей Зуев - он более декларативный, как видите, javascript-кода там совсем немного - одна универсальная функция. Но этот вариант требует использования id вместо классов, это не всегда возможно.
    Ответ написан
    Комментировать
  • Подключение/отключение CSS стилей по кнопке, с прямым изменением в файле css?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега PHP
    Как вам уже сказали, это серверная логика, для этого придется написать php-скрипт.
    При нажатии на кнопку отправлять ajax-запрос на этот скрипт (ну или просто переходить на него, а в конце редиректить куда нужно), а в самом скрипте изменять css-файл и перезаписывать его. Проще всего, разумеется, воспользоваться готовыми библиотеками.
    Ответ написан
    1 комментарий
  • Как отменить событие после его выполнения?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    $(e).one('click', function () {
      в следующий раз не вызывать эту функцию при клике на $(e)
    })

    Документация
    Ответ написан
    Комментировать
  • Owlcarousel 1.3. Как получить индекс активного слайда?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Ведь прямо на той странице написано:
    updateResult(".currentItem", this.owl.currentItem);
    Ответ написан
  • Как навесить обработчик на коллекцию элементов?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Проблема в том, что вы пытаетесь навесить обработчик на коллекцию элементов, которая возвращается функцией querySelectorAll. Такой возможности нет, нужно обходить эту коллекцию и навешивать обработчик на каждый элемент отдельно:
    var buttonItems = document.querySelectorAll('.button-item'),
        index, button;
    
    for (index = 0; index < buttonItems.length; index++) {
        button = buttonItems[index];
        button.addEventListener('click', function (event) {
            console.log('click');
            event.preventDefault();
        });
    }

    Пример

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

    Лучше делать так:
    var buttons = document.querySelectorAll('.button-item'),
        index, button;
    
    for (index = 0; index < buttons.length; index++) {
        button = buttons[index];
        button.addEventListener('click', clickHandler);
        button.addEventListener('dblclick', doubleClickHandler);
    }
    
    function clickHandler(event) {
        console.log('click', this.innerText);
        event.preventDefault();
    }
    
    function doubleClickHandler(event) {
        console.log('doubleclick', this.innerText);
        this.removeEventListener('click', clickHandler);    
        this.removeEventListener('dblclick', doubleClickHandler);
    }
    Ответ написан
  • Как сделать так, чтобы функция выполнялась только после того, как другая завершится?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Дисклеймер
    Кому не нравится название "обещания", мысленно заменяйте его на то, которое считаете подходящим. Я выбрал именно его, чтобы концепция, лежащая в их основе, была интуитивно понятна.

    Если функция асинхронная, то лучше всего использовать обещания, что вы и попытались сделать (интерактивный пример).
    one().done(two);
    
    function one() {
        var dfd = new $.Deferred();
    
        // Запускаем асинхронную задачу. Например, ajax-запрос.
        setTimeout(function () {
            var foo = 'bar';
    
            // "Выполняем обещание", передавая в него какую-то информацию.
            // Передавать аргументы, разумеется, не обязательно.
            dfd.resolve(foo);
        }, 2000);
    
        // Возвращаем из функции обещание, на которое могут подписаться другие функции.
        // Обратите внимание, этот код выполнится до того, как завершится асинхронная задача.
        return dfd.promise();
    }
    
    function two(foo) {
        // Обрабатываем данные, полученные внутри асинхронной функции one.
        console.log('two', foo);
    }

    Для трех функций расклад немного сложнее, но принцип такой же.
    Есть и более элегантный способ запуска цепочки из трех функций:
    код
    one().then(two, onOneError).then(three, onTwoError);
    
    function one() {
        var dfd = new $.Deferred();
    
        setTimeout(function () {
            console.log('one');
            
            if (Math.round(Math.random() * 10) >= 5)
            {
                dfd.resolve();
            }
            else
            {
                dfd.reject();
            }
        }, 1000);
    
        return dfd.promise();
    }
    
    function two() {
        var dfd = new $.Deferred();
    
        setTimeout(function () {
            console.log('two');
            
            if (Math.round(Math.random() * 10) >= 5)
            {
                dfd.resolve();
            }
            else
            {
                dfd.reject();
            }
        }, 1000);
    
        return dfd.promise();
    }
    
    function three() {
        setTimeout(function () {
            console.log('three');
        }, 1000);
    }
    
    function onTwoError() {
        console.log('twoError', arguments);
    }
    
    function onOneError() {
        console.log('oneError', arguments);
    }

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


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

    Другой вариант - передавать callback, но это прямой путь в callback hell. Для запуска трех и более функций подряд я его не рекомендую - смотрите сами, на что становится похож код:
    one(function () {
        two(three)
    });
    
    function one(callback) {
        console.log('one');
        setTimeout(callback, 1000);
    }
    
    function two(callback) {
        console.log('two');
        setTimeout(callback, 1000);
    }
    
    function three() {
        console.log('three');
    }


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

    Если внутри функций ничего асинхронного нет, то можно просто вызвать их друг за другом - следующая и так запустится после предыдущей (пример).
    Ответ написан
    5 комментариев
  • JS API и REST API - это разные понятия?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Вы путаете теплое с мягким.
    REST - это методология, работа с таким апи может быть реализована на любых технологиях (включая js), реализация на стороне сервера - тоже.
    JS API - это уже конкретная реализация клиентской библиотеки. На сервере, с которым работает эта библиотека, при этом может быть REST апи.
    Ответ написан
    2 комментария
  • Как правильно отлаживать JS?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Как узнать, в каком месте вылезла ошибка?
    console.error выводит полный стек вызовов со ссылками на конкретные строки конкретных скриптов.
    Еще очень удобно включить режим "Pause on exceptions", в нем при выпадении ошибки автоматически включается дебагер. Но для этого Developer Tools должны быть открыты (хотя, если вы веб-разработчик, то они в принципе никогда не закрываются).
    Ну а все остальные проблемы легко решаются при помощи дебагера. Там и значения переменных и стек вызовов и логику можно пошагово посмотреть.
    Ответ написан
    2 комментария
  • Почему такой вариант работы с DOM считается темной стороной силы?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Потому что операции работы с DOM затратны по ресурсам и времени. Если элемент в DOM еще не добавлен, изменять его гораздо дешевле.

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

    Я бы переписал так:
    var classes = ['item-id', 'item-title', 'item-price'],
        table = document.getElementById('table'),
        rows = table.getElementsByTagName('tr'),
        rowIndex, cellIndex, cells, currentRow, currentCell, addToCartCell;
    
    // Пропускаем первую строку, потому что в ней находится заголовок таблицы
    for (rowIndex = 1; rowIndex < rows.length; rowIndex++) {
        currentRow = rows[rowIndex];
        cells = currentRow.getElementsByTagName('td');
    
        for (cellIndex = 0; cellIndex < cells.length; cellIndex++) {
            currentCell = cells[cellIndex];
            currentCell.setAttribute('class', classes[cellIndex]);
        };
    
        addToCartCell = document.createElement("td");
        addToCartCell.innerHTML = '<a class="add_item">Добавить в корзину</a>';
        currentRow.appendChild(addToCartCell);
    };

    Обратите внимание, что упростив код и дав понятные названия переменным, мы избавились от необходимости его комментировать.
    Многие разработчики (и я в их числе) считают необходимость в комментариях признаком плохого кода. Комментировать можно и нужно зачем мы что-то делаем, но что код делает, должно быть понятно из него самого.
    Я оставил один полезный комментарий в качестве примера.

    Помимо этого, мы перенесли объявления переменных в самое начало условной функции, поскольку это широкораспространенное соглашение, облегчающее понимание кода.

    Можно было бы пойти чуть дальше и разбить код на маленькие независимые функции:
    function getTable() {
        return document.getElementById('table')
    }
    
    function getRows(table) {
        var allTableRows = table.getElementsByTagName('tr'),
            rows = [],
            index;
    
        // Пропускаем первую строку, потому что в ней находится заголовок таблицы
        for (index = 1; index < allTableRows.length; index++) {
            rows.push(allTableRows[index]);
        }
    
        return rows;
    }
    
    function appendAddToCartButton(row) {
        var cell = document.createElement("td");
        cell.innerHTML = '<a class="add_item">Добавить в корзину</a>';
        row.appendChild(cell);
    }
    
    function setCellsClasses(row) {
        var classes = ['item-id', 'item-title', 'item-price'],
            cells = row.getElementsByTagName('td'),
            index;
    
        // Обратите внимание, что здесь мы итерируем по классам, а не по ячейкам
        // В том случае, если в функции prepareTable кто-то по ошибке
        // поменяет местами строки добавления классов и кнопки добавления в корзину
        // код не сломается
        for (index = 0; index < classes.length; index++) {
            cells[index].setAttribute('class', classes[index]);
        }
    }
    
    function prepareTable() {
        var table = getTable(),
            rows = getRows(table),
            rowIndex, currentRow;
    
        for (rowIndex = 0; rowIndex < rows.length; rowIndex++) {
            currentRow = rows[rowIndex];
            setCellsClasses(currentRow);
            appendAddToCartButton(currentRow);
        }
    }
    
    prepareTable();

    Но в данном случае, скорее всего, это уже излишне.
    Ответ написан
    Комментировать
  • Как изменить данные с помощью цикла в jquery?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Вам нужно сделать замыкание, чтобы работать с копией переменной, а не с ней самой, поскольку код внутри цикла выполняется после его завершения, когда переменная i имеет максимальное значение.
    https://learn.javascript.ru/closures-usage#армия-ф...
    Ответ написан
    Комментировать
  • Как сделать что бы баннер открывались в новой вкладке?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    var glob_tag = "<a href='" + glob_lnk + "' target='_blank'>";
    Ответ написан
    Комментировать
  • Как в Javascript определить что в инпут был введен именно вопрос?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Вы не указали, что именно у вас не работает.
    Разве что, проверка регистрозависимая. Отвязать от регистра можно так.
    Ответ написан
    Комментировать
  • Как ограничить кол-во dots в slick слайдере?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега CSS
    Количество точек = общее количество слайдов / количество показываемых за раз слайдов
    А за зацикливание отвечает опция infinite.
    Ответ написан
    4 комментария