Jan 16

Наскоро в проект използвах Sortable от Script.aculo.us и имах следната html структура:

  <ul id="category_products">
    <li id="product_1"><a href="/product/1/edit"><img src="/products/1/thumb.png" /></a></li>
    <li id="product_2"><a href="/product/2/edit"><img src="/products/2/thumb.png" /></a></li>
    <li id="product_3"><a href="/product/2/edit"><img src="/products/3/thumb.png" /></a></li>
    <!-- ... и така нататък ... -->
  </ul>

Това е списък със снимки на продукти, които може да се подреждат с провлачване. Също така при натискане на снимката на продукта, се отваря страница за промяна му. И тук се появява проблем, когато потребителя провлачи някоя снимка за да и смени реда, се отваря страницата за промяна на продукта. Това е меко казано е досадно.

Защо става така?

Много просто. Самото drag&drop работи така – на mousedown събитие върху li. При mouseup събитие li-то се пуска. Но в същият момент се получава и click събитие, което идва от картинката към връзката a, защото тя реално си е била натисната. И затова се получава този неприятен ефект.

Как може да се оправи това?

В Script.aculo.us има един обект Draggables, който е помощен обект грижещ се за Draggable обектите. Каквито са и нашите li-та. Той подъжа обзървъри. Като един обзървър може да има следните методи:

  • onStart – вика се, когато Draggable обект започне да се влачи
  • onDrag – вика се, докато Draggable се мести
  • onEnd – вика се след края на драгването

Вътрешно самия Sortable работи с един клас SortableObserver, който предава промените към Sortable инстанса.

И така, аз си направих един мой обзървър – DisableLinksOnDragObserver (който ползва моя Event.delegate метод).

Това, което DisableLinksOnDragObserver прави е, че когато започне да се драгва някой елемент, се слага маркер drag, и с него индикира, че в момента има влачене. След като се приключи влаченето, маркерът се прави false (тук е важно да се отбележи използването на Function#defer). Същевременно при всяко натискане на a елемент, ако drag е true, спираме събитието.

  // начин на ползване
  Draggables.addObserver(new DisableLinksOnDragObserver('category_products'));

Общо взето нищо сложно. Ако някой има идеи как може да се оптимизира или направи по-добре това, ще се радвам да ги чуя.

2 коментара за "DisableLinksOnDragObserver"

  1. Иван каза:

    И си прав и не си прав. Наскоро ми беше попаднала една статия колко “разстояние” минава мишката преди линка да бъде маркиран като “disabled” (или нещо от рода на).

    Та там убави чиляк обясняваше как всеки браузър е със свое мнение и че разстоянието варира от 7 пиксела до помня ли колко.

    Единствената оптимизация, която ми идва на ум е, че май правиш екстра презастраховане с “this.clearDragMarker = this.clearDragMarker.bind(this);” понеже съм почти сигурен, че this в метода ти е същия като в initialize.

  2. Radoslav Stankov каза:

    за “this.clearDragMarker = this.clearDragMarker.bind(this);” ми трябва защото после при this.clearDragMarker.defer(); ми трябва clearDragMarker да си пази контекста на this.
    В първата версия на “this.clearDragMarker.defer()” беше
    (function(){ this.drag = false; }).bind(this).defer();
    Но не ми харесваше всеки път да си дефинирам нова фунция, да я bind-вам и след това да я отлагам.

Какво мислите по въпроса