Как распутывать лапшу, не впадая в депрессию

    Эта статья не про ваши сладкие интерфейсы на реакте, ангуляре или что вы там используете? Это статья про те ситуации, когда у вас есть кучка jQuery лапши. Нет, пусть это будут горы jQuery лапши, завернутой во вьюшки бэкбона.

    В статье используется библиотека Backbone.View.Elements

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


    Все мы видели лапшу, все мы знаем: лапша в JSе — наверно и в верстке не все хорошо. А раз все так, то код, скорее всего, кишит непонятными манипуляциями с домом. Читать такой код сложно, ведь надо, не упуская мысли автора о том, что здесь вообще происходит, держать в уме кучку невнятных названий для элементов. Итак, давайте придадим коду немножко выразительности:
    _selectors: function () {
        return {
            elemName: '.block__elem-name'
        };
    }
    
    Сложим все селекторы в одном месте и дадим понятное название элементам, для выбора которых они нужны. Выбирать мы их, кстати, будем вот так:
    this._elem('elemName');
    
    вместо
    this.$('.block__elem-name');
    


    В нашем случае вы можете сказать, что это мало добавило выразительности, но не забывайте, что у вас, скорее всего, не проект, использующий БЭМ для именования классов, а сладко пахнущие сверх семантичные селекторы вида
    ‘div > tr.row[data-active=”true”] a.red-Button’
    
    для кнопок “купить”.

    Помимо возможности выбрать элемент внутри нашей вьюшки, мы также получили возможность получить сам селектор по имени:
    this._selector('elemName');
    
    Это тоже бывает нужно.

    Еще одно преимущество — если изменится верстка, то нам надо будет изменить селектор только в одном месте, потому что мы уменьшили дублирование кода.

    Проблема вторая: хранение элементов


    Знаете, бывает вот так:
    $(‘div > tr.row[data-active=”true”] a.red-Button’).blahBlah();
    
    а через 10 строк вот так:
    $(‘div > tr.row[data-active=”true”] a.red-Button’).anotherBlahBlah();
    

    Отодрав ладонь от лица, вы вынесете это в переменную
    var $buyButton = $(‘div > tr.row[data-active=”true”] a.red-Button’);
    
    ой нет, у вас же Backbone — вынесете в свойство
    this._$buyButton = this.$(‘div > tr.row[data-active=”true”] a.red-Button’);
    
    или вы уже подключили Backbone.View.Elements?
    this._$buyButton = this._elem(‘buyButton’);
    

    На самом деле не стоит — _elem и так все кеширует, так что просто
    this._elem(‘buyButton’);
    

    Кеширует, говорите? А что, если все изменится?


    Да, мы тоже слышали, что в программировании две проблемы. Поэтому
    this._findElem('elemName');
    
    ищет без использования кеша
    this._dropElemCache(‘elemName’);
    
    почистит кеш для конкретного элемента, а
    this._dropElemCache();
    
    отчистит весь ваш кеш до блеска, когда вы поймете, что время пришло. Например, после рендеринга.

    Глобальные элементы


    А еще мы завернули в jQuery наиболее часто используемые элементы, чтобы не делать это больше одного раза в приложении. Встречайте:
    this._$window;
    this._$body;
    this._$document;
    


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


    Вроде бы целый язык есть, чтобы стили описывать, но нет — то и дело в лапше можно найти красители:
    $(‘div > tr.row[data-active=”true”] a.red-Button’).css({color: ‘magenta’});
    
    Скорее поперчите все декларативностью и хорошенько перемешайте CSS:
    .button_active {
      color: magenta;
    }
    
    А уж об манипуляции классами мы позаботились. Сначала обозначим все классы в одном месте:
    _classes: function () {
        return {
            activeButton: 'button_active'
        };
    }
    
    А потом, хотите — добавляйте класс
    this._addClass(‘activeButton’, ‘buyButton’);
    
    хотите — удаляйте:
    this._removeClass(‘activeButton’, ‘buyButton’);
    
    хотите — переключайте:
    var condition = !!Math.round(Math.random());
    this._toggleClass(‘activeButton’, ‘buyButton’, condition);
    

    Можно получить селектор, если класс уже описан:
    this._selector(‘activeButton’); // returns ‘.button_active’
    
    а можно и элементы поискать:
    this._elem(‘activeButton’);
    
    Только не забывайте про кеш, ведь активная кнопка наверняка меняется
    this._findElem(‘activeButton’);
    

    Проблема четвертая: когда всё сложно


    Бывает, селекторы и классы формируются динамически:
    var id = 5,
        state = ‘highlighted’;
    $(‘.item[data-id=”’ + id + ’”]’).addClass(‘item_state_’ + state);
    
    Тут в дело вступают сложные селекторы:
    _classes: function () {
        return {
            itemInState: 'item_state_%s'
        };
    },
    
    _selectors: function () {
        return {
            itemById: '.item[data-id=%s]'
        };
    }
    
    Тогда справедливо будет следующее:
    this._class(‘itemInState’, ‘highlighted’); // вернет ‘item_state_highlighted’
    this._selector(‘itemInState’, ‘highlighted’); // вернет ‘.item_state_highlighted’
    this._selector(‘itemById’, 5); // вернет ‘.item[data-id=5]’
    

    А манипуляция описанная выше будет выполняться следующим образом:
    var id = 5,
        state = ‘highlighted’;
    this._addClass([‘itemInState’, state], [‘itemById’, id]);
    
    Класс item_state_highlighted добавится элементу, найденному по селектору .item[data-id=5]

    Терминальная сложность селекторов


    _classes: function () {
        return {
            item: 'item_%(mod)s_%(value)s'
        };
    }
    
    Каждому месту свое имя
    this._elem(‘item’, {
      mod: ‘state’,
      value: ‘focused’
    });
    
    Найдет jQuery коллекцию по селектору ‘.item_state_focused’

    Проблема пятая: получение данных


    Немножко сахара для дата атрибутов.
    this._data;
    
    Хранит данные корневого элемента вью. Так что, если у вас есть div
    <div data-some-ids=”[5,6,7]”></div>
    
    на котором инициализирована вью
    this._data[‘someIds’]; // вернет массив [5,6,7]
    
    А если данные хранятся в конкретном элементе, то вам поможет
    this._getElemData(‘elemName’, ‘someIds’);
    
    Для того, чтобы получить все данные:
    this._getElemData(‘elemName’); // вернет {someIds: [5,6,7]}
    


    Про установку и использование


    GitHub: github.com/backbonex/backbone.view.elements
    todomvc с использованием Backbone.View.Elements и без него

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 30

      0
      Если бы речь была только об элементах, можно было бы проще:
      elements: {
      	name: '.someSelector'
      },
      // и потом обращаться вот так:
      this.$name.blahBlah()
      

      Написать метод _ensureElements, который обходит _.result(this, 'elements') дело пяти минут. Но у вас есть и другие интересные идеи, молодцы.
        0
        Использование подобных деклараций становится не очень удобным, когда вы начинаете наследоваться.
          0
          Всяко веселее, чем писать this.someThing = this.$('.js-someThing') стопицот раз. А что до наследования, то _.result помогает, а то и вот такой метод.
            0
            Я не говорил, что this.someThing = this.$('.js-someThing') это круто.
            Я имел в виду, что предпочитаю метод, возвращающий результат, подобным объектам в прототипе.
            Метод по ссылке — выход, но, на мой взгляд, способ позвать реализацию в родителе должен быть один — что-нибудь вроде backbone-super.
              0
              А вы не в курсе что View#events, Model#defaults, Collection#url и многое другое может быть либо методом, либо объектом? Это очень удобно, потому что иногда проще написать объект, чем городить метод, который возвращает литерал. Такой же принцип и тут.

              Приведенный вами плагин мне не нравится из-за того, как именно он это делает. Лучше уж чуть более многословно, зато без черной магии и с внятным стектрейсом в случае чего.
                0
                Я в курсе, но, повторюсь, предпочитаю всегда использовать метод.
                иногда проще написать объект, чем городить метод

                Вы так говорите, как будто метод вместо объекта — это очень сложно или долго.

                Суть не в том, как именно звать родительскую реализацию, а в том, чтобы всегда делать это одинаково.
                  0
                  > это очень сложно или долго
                  Нет, не очень. Пока у вас 5 вьюх, не проблема:) В подавляющем большинстве случаев нужное свойство статично и его можно написать литералом. Если всегда писать только методы, то получится куча методов, которые ничего не делают, только тупо возвращают строку или хэш. Выглядит это не очень.
                    –1
                    Не понял, как связан выбор между объектом и методом, возвращающим объект, с количеством вьюх.

                    Зато понимаю ваше стремление к красивому коду. Просто предпочитаю однообразие.
                      +1
                      Напрямую. Чем больше кода, тем меньше хочется писать шаблонных вещей типа
                      events: function () {
                          return {
                              'click': 'toggleClicked'
                          } 
                      } 
                      
                      3 раз такое можно написать, на 4 пойдешь и поменяешь поведение в общем предке.
                        –1
                        Вы в каком редакторе код пишите?

                        Я предпочитаю IDE от JetBrains, там есть Live Templates:
                        Набираете несколько символов, нажимаете Tab — и шаблонный код готов. Я даже видео для вас записал.

                        В вашем любимом редакторе, наверняка, тоже есть что-то подобное.

                        Писать легко, единообразие во всех файлах достигается без усилий.
                          0
                          Я в лесу живу и пишу код острой намагниченной иглой, конечно же.
                            +1
                            Серьезно, если для вас основная проблема шаблонного кода — что его трудно напечатать, то я не вижу смысла в дискуссии.
                              0
                              Все, что повторяется — это декларация метода и суперколл. Я не вижу удовлетворительного способа избавиться от шаблонного кода в этой ситуации.

                              Буду рад, если предложите таковой.
                                0
                                Так я же говорю — писать литералы там, где не нужно менять свойство в рантайме.
                                  +1
                                  А когда вы захотите наследоваться и что-то переопределить — вам придется менять литерал на метод в родительском классе или использовать особый способ вызывать родительский метод, умеющий обрабатывать ситуацию, когда выше по цепочке — литерал. Вот этот момент мне кажется важным.
                                    0
                                    Ну, я для себя выработал то самое решение по ссылке. Можно туда еще сахарку добавить, в принципе.
                                      0
                                      Доктор, меня все игнорируют.
                                      0
                                      Код, про который идет речь в статье, используется в нескольких проектах, где есть сотни вьюшек. Каждый раз думать о том свойство где-то или метод реально напрягает.
                                        0
                                        Еще раз приведу ссылку на свое решение: github.com/hogart/skull/blob/master/src/Skull.js#L76
                                          0
                                          Меня не устраивает это решение.
                                          1. Если мы его используем только там, где бывают литералы — то у нас возникает два разных способа вызвать родительский метод. Не круто.
                                          2. Если мы его используем везде — то везде постоянно будет проверяться, функция или нет выше по цепочке. Тоже не круто.

                                          Я думаю, можно прекратить обсуждение. Я вас понимаю, вы меня, наверное, тоже.
                                            0
                                            Да, я понял: вы зачем-то смешиваете соглашение по определению свойств в Backbone и родительские методы.
                                      0
                                      Что же до вызова родительских методов — увы, ждем ES6.
                              –1
                              Иногда статичное свойство может измениться, и тогда придется искать его, и править по всему проекту. А в случае с методом, придется изменить свойство только внутри метода, который его возвращает.
                                0
                                Спасибо за минусы, приму их как знак внимания. Обосную тем кому все таки интересно. Во-первых плохой тон обращаться к свойствам напрямую — это одно из фундаментальных правил программирования. Например, тут или тут тут мнение авторитетов. Во-вторых, работать можно не только в одиночку — и когда работаешь в команде, то сложно отвечать и контролировать чужие действия, а если команда находится в разных городах, а то и часовых поясах, то еще сложнее. Поэтому если ты используешь чье-то свойство, то лучше избегать прямого обращения к нему, а пользоваться геттерами и сеттерами, потому-что «хозяин свойства» в любой момент может что-то изменить или зарефакторить и в этом случае, он не обязано спрашивать чье-то мнение. Сюда же можно добавить то, что даже когда сам начинаешь проект, то должен понимать, что статика может меняться много-много раз, и для того чтобы не править всё и везде, легче править в двух местах — в геттере и сеттере.
                                  +1
                                  А что вы, простите, предлагаете? поменять название свойства, но оставить старые названия акцессоров?
                                  getPrice: function () { return cost; } // srsly, wtf
                                  

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

                                  Я в принципе не против акцессоров, это полезно. Только рефачить одинаково. А чтобы не болело, надо использовать IDE с поддержкой рефакторинга.

                                  P.S.
                                  jQuery — далеко не образец безупречной архитектуры и API, кстати. Если бы это было не так, мы б не писали комменты под статьей под названием «как разгрести говнокод из джейквери».
                  +2
                  Читая про БЭМ, вспомнил свою поделку со смешным названием ))
                    0
                    this._elem(‘item’, {
                      mod: ‘state’,
                      value: ‘focused’
                    });
                    

                    Чтобы найти элементы с несколькими модификаторами придется писать массив?
                    А не проще ли тогда сделать ключ значение?

                    {
                        state: 'focused'
                    }
                    
                      0
                      Это довольно редкая ситуация, а если надо искать элементы по нескольким селекторам, то можно сделать, например, так:
                      _selectors: function () {
                        return {
                          someCollection: '.selector1, .selector2'
                        };
                      }
                      

                      а потом в коде
                      this._elem('someCollection');
                      

                      0
                      Как вариант:

                      //где-нибудь в базовой вьюхе функция которая является и сеттером и геттером. 
                      frg: function (name, el) {
                      	!this.domElements && (this.domElements = {});
                      	return el ? (this.domElements[name] = el, el) : this.domElements[name];
                      }
                      
                      
                      // Использование наследуемых вьюхах: 
                      
                      // в сеттер надо передать два параметра: название и сам элемент. 
                      // сеттер так же возвращает закешированный элемент
                      this.frag('my-block',  this.$('#my-block')).css({display: 'block'});
                      
                      // геттер получает один аргумент и возвращает из кеша элемент
                      this.frag('my-block').css({display: 'block'});
                      

                      Довольно стройно и элегантно
                        0
                        Прощу прощения за опечатку: Надо либо в базовой вьюхе назвать метод frag, либо в наследуемых вызывать как this.frg()

                      Only users with full accounts can post comments. Log in, please.