Arr.js: события для стандартного массива

    Arr.js — это «класс», унаследованный от стандартного Array. Отличительными особенностями являются: наличие события change для отслеживания любых изменений в массиве, и методы insert(), update(), remove(), set(), get() для упрощенной работы с массивом. Доступны все «родные» методы стандартного Array.

    var fruits = new Arr('apple', 'orange', 'pineapple');
    
    fruits.on('change', function(event) {
      alert('I changed fruits: ' + fruits.join(', '));
    });
    
    fruits.push('banana');
    


    Код: Примеры работы основных методов
    var fruits = new Arr('apple', 'orange', 'pineapple');
    
    fruits.get(0);
    // apple
    
    fruits.get(10, 'lime'); // trying to get undefined element - return defaultValue
    // lime
    
    fruits.get(20); // trying to get undefined element
    // null
    
    fruits.set(1, 'nut');
    // ['nut', 'orange', 'pineapple']
    
    fruits.insert(['lime', 'banana', 'kivi']);
    // ['nut', 'orange', 'pineapple', 'lime', 'banana', 'kivi']
    
    fruits.remove(function(item, index) {
      if (item.indexOf('apple') !== -1) { // remove only items where word "apple" is founded
        return true;
      }
    });
    // ['nut', 'orange', 'lime', 'banana', 'kivi']
    
    fruits.update(function(item, index) {
      if (item.indexOf('nut') !== -1) { // update "nut" to "apple"
        return 'apple';
      }
    });
    // ['apple', 'orange', 'lime', 'banana', 'kivi']
    



    Зачем событие change и как с ними работать


    Наличие события позволяет сделать:
    • подобие FRP: когда изменение одних данных должно повлечь за собой изменение других данных и так далее
    • отложенный рендеринг: что то изменилось в массиве — обновили HTML (ala angular)
    • автоматическое сохранение данных на сервер при любых изменениях

    Поддерживается одно событие — change.

    var fruits = new Arr('apple', 'orange', 'pineapple');
    
    fruits.on('change', function(event) { // handler
      console.log(event);
    });
    
    fruits.push('banana');
    // { "type": "insert", "items": ["banana"] }
    
    fruits.remove(function(item) { return item == 'banana'; });
    // { "type": "remove", "items": ['banana"] }
    

    Понять что произошло в массиве можно по передаваемому в handler объекту, event. Свойства объекта event: type может принимать значения: insert, update, remove. Свойство items позволяет узнать какие элементы массива были затронуты.

    Наглядный пример


    // Массив в котором планируем хранить данные о погоде
    var weatherList = new Arr;
    
    // При изменении в массиве - перересовываем список
    weatherList.on('change', function() {
      var el = $('#weather');
      var celsius, maxCelsius, minCelsius, weather;
    
      el.html('');
    
      weatherList.forEach(function(item) {
        celsius = Math.floor(item.main.temp - 273);
        maxCelsius = Math.floor(item.main.temp_max - 273);
        minCelsius = Math.floor(item.main.temp_min - 273);
        weather = item.weather.pop().main;
        el.append('<li><b>' + item.name + '</b> ' + ' ' + celsius + ' (max: ' + maxCelsius + ', min: ' + minCelsius + ') ' + weather + '</li>');
      });
    });
    
    // Загрузка погоды из сервиса, обновление массива weatherList
    function loadWeather(lat, lng) {
      $.get('http://api.openweathermap.org/data/2.5/find?lat=' + lat + '&lon=' + lng + '&cnt=10').done(function(data) {
        // clear weather list
        weatherList.remove(function() { return true; });
    
        // insert items
        weatherList.insert(data.list);
      });
    }
    
    // Погода в Киеве
    loadWeather(50.4666537, 30.5844519);
    

    Посмотреть рабочий пример на JSBin.

    В заключении


    Хочу добавить, что идея не нова, есть github://MatthewMueller/array. Но код мне показался слишком перегруженным, что собственно может вылиться в проблемы с производительностью. Поэтому было принято решение «расширить» стандартный Array.

    Планы: есть желание покрыть библиотеку качественными тестами — было бы хорошо, если бы кто ни будь, кто хорошо разбирается в этом — помог.

    Расширять список методов пока не планируется, за исключением метода removeListener().

    Репозиторий Arr.js и документация (en).

    Комментарии по улучшению приветствуются!

    P.S.: В личных целях был разработан компонент https://github.com/jmas/list/blob/master/List.js который использует Arr.js. Компонент используется для создания списков, которые самостоятельно обновляют HTML при изменении массива данных. Компонент использует componentjs для разруливания зависимостей.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 25

      0
      Полезное расширение. Есть маленькое замечание, следующий код избыточен (если defaultValue undefined, то зачем его устанавливать в undefined). Это будет иметь смысл, только если новое значение не будет undefined. Сделать пул-реквест?

      defaultValue = typeof defaultValue === 'undefined' ? undefined: defaultValue;
        0
        Давайте. Просто делал хотфикс.
        +2
        Массивы слишком простой тип данных чтобы прикручивать к нему события.
          0
          И соглашусь, и нет. К примеру, коллекции в Backbone.Js решают подобные задачи, но код раздут.
            0
            Ок, вы можете использовать традиционные массивы и Arr совершенно параллельно, или создание «классов» с интерфейсом похожим на массив запрещено?
              0
              Хозяин, как говорится, барин.
              Пару моментов:
              1. events лучше сделать через объект, а не массив. Во-первых вы не будете бегать по всему массиву евентов при проброске события.
              Во вторых, намного проще будет делать отписку, которой сейчас нету.

              2. Сейчас у объекта есть лишь одно событие, и в каждом передается тип.
              Использование такой конструкции приведет к switch или if else, для разделения событий. Проще сразу бросать событие с нужным именем и обрабатывать его без проверок.

              Вот неплохая реализация работы с событиями github.com/Wolfy87/EventEmitter.
                0
                Отличные замечания. По 1-му: уже в планах. По 2-му посоветуйте как лучше, мне кажется можно выбрасывать change и тут же insert / remove / update.
                  0
                  Если вы таки будете улучшать обработку событий, то сделайте поддержку handleEvent, чтобы можно было избежать использования функции Function#bind
            0
            Работает по типу Object.observe?
            0
            Arr.js — это «класс», унаследованный от стандартного Array
            Только криво (тест), а надо было вот так.
              0
              Да, да. Конструктор инициализируется не совсем корректно. Исправим. Спасибо.
                0
                Ну и если редактировать массив при помощи `length` или менять значения по индексу, никаких событий не будет. Или например `splice` генерирует только `remove`, хотя сам метод не только удаляет.
                  0
                  Да. И при обычном a[0] = value событий так же не будет. Используйте set(), get(). Удаление splice(), remove().

                  Нужно доработать splice(). Спасибо.
                    0
                    splice() теперь умеет генерировать change, update, remove (https://github.com/jmas/arr/issues/5).
                    0
                    Учтите, что этот вариант не работает в IE меньше 11го, т.к. в этих IE не поддерживается __proto__
                      0
                      Кстати, целью было поддерживать библиотеку и в IE8+, поэтому для начала попробую корректно применить конструктор Array, в противном случае прийдется написать «похожее» поведение.
                        0
                        Решением для IE может быть копирование всех методов из прототипа в экземпляр в конструкторе CustomArray и в методах «concat», «reverse», «slice», «splice», «sort», «filter», «map». Из минусов можно выделить то, что не будет работать оператор instanceof.
                          0
                          instanceof критически важен. Спасибо за совет, нужно пробовать.
                    0
                      0
                      Конструктор теперь проходит тест. Правда логика конструктора != Array.prototype.constructor, что пока выглядит несколько странно. Буду иметь ввиду и подбирать более удачное решение.
                      +2
                      // trying to get undefined element
                      // null
                        –1
                        Уже поправлено. Традиционный undefined возвращает get().
                        –1
                        Считаю, что проще использовать такую конструкцию, где нужно:

                        var fruits = new Arr('apple', 'orange', 'pineapple');

                        function refresh(massive) {
                        // изменение других данных и так далее
                        // отложенный рендеринг
                        // автоматическое сохранение данных на сервер
                        }

                        fruits.push('banana');
                        refresh(fruits);

                        Т.к. в данном случае не нужна постоянно сидящая в памяти «прослушка».
                          0
                          Пример #1: компонент List для пере-рендеринга представления должен знать только о: данных, шаблоне, контейнере (куда вывыодить данные) и когда обновляться. Поэтому я посчитал логичным: инициализировать компонент этими минимально-необходимыми данными, дать интерфейс через который я смогу проще общаться с компонентом (только добавление / обновление / удаление) — проще говоря почти обычный массив. Интервейс работы с более чем одним компонентом теперь черезвычайно прост — добавилось-изменилось-удалилось: прошло обновление во всех компонентах-слушателях.

                          Пример #2: компонент ServerSync. Использует тот же интерфейс. Выполняет совершенно другие задачи.

                          В противном случае, необходимо знать какие методы (у каждого компонента) выведены наружу, например List.render(massive), ServerSync.update(massive) создают необходимость каждый раз подсматривать в реализацию (документацию).

                          Но такой подход — это всегда дело вкуса.

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