Содержание

Сателлиты

Традиционная модель DOM хорошо описывает списки, любой узел может содержать произвольное количество (не ограниченное сверху) узлов определенного класса или группы классов. Однако не описывает случаи единичного вхождения зависимого объекта, который может существовать максимум в единственном экземпляре. Поэтому в basis.js вводится собственный паттерн для решения этой задачи – сателлиты (спутники).

Если посмотреть на HTML, то можно заметить ряд тегов, которые допускают единственное вхождение определенного тега. Например, тег <base> может присутствовать в единственном экземпляре, если их несколько, обрабатывается первый в документе, второй и последующие игнорируются. У <table> может быть только один <thead> и <tfoot>, которые вне зависимости от места указания в разметке "прибиваются" в начало и конец таблицы соответственно, все остальные воспринимаются как обычные <tbody>. В любом сложном интерфейсе гораздо больше ситуаций, когда появляется необходимость в единичных именованных вхождениях дочерних объектов. Паттерн сателитов – попытка унифицировать такой подход.

Принцип работы

Сателлиты хранятся в свойстве satellite, которое представляет собой объект. Ключи – это имена сателлитов, а значения – ссылки на объекты. Задать сателлит можно в конфиге (свойство satellite) или методом setSatellite, которому передаются два параметра:

  • name – имя сателлита;

  • satellite – новое значение сателлита, это должен быть экземпляр basis.dom.wrapper.AbstractNode, объект конфигурации или null.

Сателлиты используют паттерн "владелец" и могут передаваться от одного владельца другому. У сателлита может быть лишь один владелец. Если сателлитом завладевает другой узел (меняется владелец), то у предыдущего владельца удаляется ссылка на него.

basis.require('basis.dom.wrapper');

var node1 = new basis.dom.wrapper.Node({
  satellite: {
    example: new basis.dom.wrapper.Node({
      name: 'example'
    })
  }
});
var node2 = new basis.dom.wrapper.Node();

console.log(node1.satellite.example);
// console> basis.dom.wrapper.Node { name: 'example', ... }
console.log(node1.satellite.example.owner === node1);
// console> true

// назначаем объект, сателлитом другого узла под другим именем
node2.setSatellite('somename', node1.satellite.example);

// после этого объект меняет владельца, а старый владелец теряет
// связь с ним
console.log(node1.satellite.example);
// console> null
console.log(node2.satellite.somename);
// console> basis.dom.wrapper.Node { name: 'example', ... }
console.log(node2.satellite.somename.owner === node2);
// console> true

Когда добавляется или удаляется сателлит, у его владельца срабатывает событие satelliteChanged с двумя параметрами: name – имя сателлита, и oldSatellite – значение owner.satellite[name] до изменения. При изменении владельца срабатывает событие ownerChanged, которое передает обработчикам предыдущее значение owner.

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

var keepAliveNode = new basis.dom.wrapper.Node({
  listen: {                 // слушаем
    owner: {                // владельца
      destroy: function(){  // если он разрушается
        this.setOwner();    // сбрасываем владельца
      }                     // сброс владельца при его разрушении,
    }                       // убережет узел от собственного разрушения
  }
});

Сателлитам можно добавлять обработчик событий используя свойство listen, при этом всем сателлитам задается один и тот же обработчик.

Автоматические сателлиты

Свойство satellite позволяет автоматизировать создание сателлитов и настроить условия, при которых он должен существовать (создаваться и разрушаться).

basis.require('basis.dom.wrapper');

var NodeWithSatellite = basis.dom.wrapper.Node.subclass({
  satellite: {
    example: {
      instanceOf: basis.dom.wrapper.Node.subclass({
        className: 'DemoSatellite',
        caption: 'Static caption'
      })
    }
  }
});

var foo = new NodeWithSatellite();
var bar = new NodeWithSatellite();

console.log(foo.satellite.example);
// console> [object DemoSatellite]
console.log(bar.satellite.example);
// console> [object DemoSatellite]
console.log(foo.satellite.example === bar.satellite.example);
// console> false

При определении сателлита через satellite можно указать следующие настройки:

  • instance – сам сателлит, экземпляр basis.dom.wrapper.AbstractNode;

  • instanceOf – класс экземпляра сателлита, в случае авто-создания; по умолчанию basis.dom.wrapper.AbstractNode; если задано свойство instance, то значение игнорируется;

  • events – список событий (строка или массив), когда должны перевычисляться existsIf, delegate и dataSource; значение по умолчанию 'update', то есть функции перевычисляются при выбрасывании события update у владельца;

  • existsIf – функция, определяющая должен ли существовать сателлит, на вход получает ссылку на владельца; если значение не задано, то сателлит существует всегда, пока существует владелец;

  • config – объект или функция возвращающая объект, конфиг для создания сателлита; функции передается ссылка на владельца;

  • delegate – функция для определения делегата, который должен быть назначен сателлиту (автоматизация); функции передается ссылка на владельца;

  • dataSource – функция для определения набора, который должен быть назначен сателлиту в качестве dataSource; функции передается ссылка на владельца.

Все параметры являются опциональными. Если задается только instanceOf, можно использовать более короткую запись.

// полная запись
var NodeWithSatellite = basis.dom.wrapper.Node.subclass({
  satellite: {
    example: {
      instanceOf: basis.dom.wrapper.Node.subclass({ .. })
    }
  }
});

// эквивалент, сокращенная запись
var NodeWithSatellite = basis.dom.wrapper.Node.subclass({
  satellite: {
    example: basis.dom.wrapper.Node.subclass({ .. })
  }
});

Следующий пример демонстрирует представление, отображающее информацию о пользователе. Список групп создается только тогда, когда у владельца в данных есть свойство groups, содержащее набор.

basis.require('basis.ui');

var view = new basis.ui.Node({
  template: basis.resource('./path/to/template.tmpl'),
  binding: {
    name: 'data:',
    groups: 'satellite:'
  },
  satellite: {
    groups: {
      existsIf: function(owner){
        return owner.data.groups instanceof basis.data.ReadOnlyDataset;
      },
      dataSource: function(owner){
        return owner.data.groups;
      },
      instanceOf: basis.ui.Node.subclass({
        template: basis.resource('./path/to/group-list.tmpl'),
        childClass: {
          template: basis.resource('./path/to/group.tmpl'),
          binding: {
            name: 'data:'
          }
        }
      })
    }
  },
  data: {
    name: 'basis.js',
    groups: new basis.data.Dataset()
  }
});

console.log(view.satellite.groups != null);
// console> true

view.update({ groups: null });
console.log(view.satellite.groups != null)
// console> false