2012-05-14 22 views
7

Piszę klienta WWW JS. Użytkownik może edytować listę/drzewo elementów tekstowych (np. Listę rzeczy do zrobienia lub notatki). Często manipuluję DOM przy użyciu jQuery.Jak uniknąć duplikowania kodu podczas działania w DOM w kierunku "w górę" i "w dół"?

Użytkownik może poruszać się po liście w górę iw dół za pomocą klawiatury (podobnie jak w przypadku klawiszy J/K w Gmailu) i wykonywać kilka innych operacji. Wiele z tych operacji ma funkcję "up"/"down" lustrzanego odbicia, np.

$.fn.moveItemUp = function() { 
    var prev = this.getPreviousItem(); 
    prev && this.insertBefore(prev); 
    // there's a bit more code in here, but the idea is pretty simple, 
    // i.e. move the item up if there's a previous item in the list 
} 

$.fn.moveItemDown = function() { 
    var next = this.getNextItem(); 
    next && this.insertAfter(next); 
    // .... 
} 

teraz ten wzór ma dwa niemal identyczne funkcje powtarza się w kilku miejscach w moim kodu, ponieważ istnieje wiele operacji na liście/drzewie elementów, które są prawie symetryczne.

PYTANIE: Jak mogę to elegancko zmienić, aby uniknąć powielania kodu?

trywialny sposób wpadłem do tej pory jest .apply użyć() ...

$.fn.moveItem = function(direction) { 
     var up = direction === 'up', 
      sibling = up ? this.getPreviousItem() : this.getNextItem(), 
      func = up ? $.fn.insertBefore : $.fn.insertAfter; 

     // ... 

     if (! sibling) { return false; } 

     func.apply(this, [ sibling ]); 

     // ... 
    }; 

Korzyścią jest łatwiejsze utrzymanie gdy struktura innych elementów kodu wymaga zmiany moveUp/moveDown. Już kilka razy musiałem trochę zmienić kod i zawsze muszę pamiętać, że muszę to zrobić w dwóch miejscach ...

Ale nie jestem zadowolony z wersji "zunifikowanej", ponieważ:

  • Implementacja jest bardziej skomplikowana niż proste moveUp/moveDown, dlatego w przyszłości NIE może być tak łatwo utrzymać się później. Lubię prosty kod. Wszystkie te parametry, operacje trójskładnikowe, .apply() robią sztuczki w każdej funkcji, która ma iść "w górę" i "w dół" ...
  • Nie wiem, czy można bezpiecznie odwoływać się do funkcji jQuery.fn. * (W końcu powinny być używane przez zastosowanie ich do obiektu jQuery $ ("..."). *)

W jaki sposób rozwiązujesz te "prawie identyczne kody" podczas pracy z DOM lub podobną strukturą?

+1

BTW hugomg

Odpowiedz

4

Jedno, co można zrobić, to użyć zamknięć ukryć parametr konfiguracyjny przechodzącego od użytkownika:

var mk_mover = function(direction){ 
    return function(){ 
     //stuff 
    }; 
}; 

var move_up = mk_mover("up"); 
var move_down = mk_mover("down"); 

Jeśli chcesz kontynuować w tym kierunku, staje się w zasadzie kwestia decydowania jaki jest najlepszy sposób przekazania parametrów do wspólnej implementacji i nie ma na to jednego najlepszego rozwiązania.


Możliwym kierunku iść jest zastosowanie podejścia w stylu OO, przekazując konfiguracji „strategia” obiektu do funkcji realizacji.

var mk_mover = function(args){ 
    var get_item = args.getItem, 
     ins_next = args.insNext; 
    return function(){ 
     var next = get_item.call(this); 
     next && ins_next.call(this, next); 
    }; 
}; 

var move_up = mk_mover({ 
    get_item : function(){ return this.getPrevious(); }, 
    get_next : function(x){ return this.insertBefore(x); } 
}); 

var move_down = mk_mover({/**/}); 

Wolę robić to, gdy interfejs strategii (metody, które różnią się między kierunkach) jest niewielki i stosunkowo stała i kiedy chcesz otworzyć możliwość dodawania nowych rodzajów kierunku w przyszłości.

Używam tego również wtedy, gdy wiem, że ani zestaw wskazówek, ani rodzaje metod nie będą musiały się zbytnio zmieniać, ponieważ JS ma lepsze wsparcie dla OO niż dla instrukcji switch.


Drugi możliwy kierunek, aby przejść jest podejście enum, który został użyty:

var mk_mover = function(direction){ 
    return function(){ 
     var next = (
      direction === 'up' ? this.getNextItem() : 
      direction === 'down' ? this.getPreviousItem() : 
      null); 

     if(next){ 
      if(direction === 'up'){ 
       this.insertAfter(next); 
      }else if(direction === 'down'){ 
       this.insertBefore(next); 
      } 
     } 

     //... 
    }; 
} 

Czasami można użyć schludne obiekty lub tablice zamiast sprawozdania przełącznika, aby rzeczy ładniejsze:

var something = ({ 
    up : 17, 
    down: 42 
}[direction]); 

Chociaż muszę przyznać, że jest to trochę niezgrabne, ma tę zaletę, że jeśli twój zestaw wskazówek zostanie wcześnie ustalony, masz teraz dużą elastyczność, aby dodać nowy element if-else lub przełącznik oświadczenie h, kiedy trzeba, w powściągliwy sposób (bez konieczności iść dodać kilka metod do obiektu strategii gdzieś indziej ...)


BTW, myślę, że podejście zasugerował siedzi w niewygodny pośredni teren między dwiema wersjami, które zasugerowałem.

Jeśli wszystko, co robisz, to przełączanie wewnątrz funkcji w zależności od przekazywanego znacznika kierunku wartości, lepiej po prostu przełączniki bezpośrednio w miejscach, które potrzebujesz, bez konieczności przekształcania rzeczy w oddzielne funkcje, a następnie konieczności wykonywania partii denerwujących .call i .apply rzeczy.

Z drugiej strony, jeśli masz problemy z zdefiniowaniem i rozłączeniem funkcji, możesz je otrzymać po prostu od osoby dzwoniącej (w postaci obiektu strategii) zamiast przeprowadzania wysyłki przez podaj się.

+0

Thx za dobre sugestie, zwłaszcza pomysł "fabryki" metody. –

2

Już wcześniej spotkałem się z podobnymi problemami i nie ma naprawdę świetnego wzoru, który znalazłem, aby poradzić sobie z wykonywaniem pozornie podobnych zadań na odwrót.

Podczas gdy ma to dla ciebie znaczenie jako człowieka, że ​​są one podobne, tak naprawdę nazywasz tylko arbitralne metody w odniesieniu do kompilatora. Jedynym oczywistym powielanie jest kolejność, w której rzeczy są nazywane:

  1. znaleźć element, który chcielibyśmy, aby wstawić przed lub po
  2. czeku, że przedmiot istnieje
  3. wstawić bieżącą pozycję przed lub po to

Rozwiązałeś już problem w jeden sposób, który w zasadzie przeprowadza te kontrole. Twoje rozwiązanie jest jednak trochę nieczytelne. Oto w jaki sposób mogę rozwiązać, że:

$.fn.moveItem = function(dir){ 
    var index = dir == 'up' ? 0:1, 
     item = this['getPreviousItem','getNextItem'][index](); 
    if(item.length){ 
    this['insertBefore','insertAfter'][index].call(this,item); 
    } 
} 

Za pomocą indeksu tablicy, możemy zobaczyć, co się nazywa, kiedy to miano trochę łatwiej niż w przykładzie. Ponadto zastosowanie opcji apply jest niepotrzebne, ponieważ wiesz, ile argumentów przekazujesz, więc wywołanie jest lepiej dostosowane do tego przypadku.

Nie jestem pewien, czy to coś lepszego, ale jest trochę krótszy (czy zwrot jest niepotrzebny?) I nieco czytelniejszy IMO.

Moim zdaniem, centralizacja działań jest nieco ważniejsza niż czytelność - jeśli chcesz zoptymalizować, zmienić lub dołączyć do akcji przenoszenia czegoś, musisz zrobić to tylko raz. Zawsze możesz dodawać komentarze, aby rozwiązać problem z czytelnością.

+0

apply vs call - g2k (więcej na: http://stackoverflow.com/questions/ 1986896/jaka jest różnica między wezwaniem i zgłoszeniem). –

1

Uważam, że podejście jednofunkcyjne jest najlepsze, ale odetnę je od tego, co się dzieje. Wykonaj jedną funkcję, aby przenieść element do indeksu lub przed inną pozycją (wolę tę ostatnią) i obsłużyć wszystkie zdarzenia "przenieś" ogólnie, nie zależnie od kierunku.

function moveBefore(node) { 
    this.parentNode.insertBefore(this, node); // node may be null, then it equals append() 

    // do everything else you need to handle when moving items 
} 
function moveUp() { 
    var prev = this.previousSibling; 
    return !!prev && moveBefore.call(this, prev); 
} 
function moveDown() { 
    var next = this.nextSibling; 
    return !!next && moveBefore.call(this, next.nextSibling); 
} 
+0

Dzięki za dobrą sugestię, aby oddzielić wspólny od różnych! –

Powiązane problemy