whole_func_string: function(walk) {
      var i, item;
      var ctl = this.createCtl('associativeGroup');
      ctl.items = [];
      // Tweak selector to use the generated id
      this.selector.id = ctl.id;
      // Also append `associativeGroup` class
      if (!Array.isArray(this.selector.classes))
        this.selector.classes = [];
      this.selector.classes.push('associativeGroup');
      // Emit root element
      this.out.push('<fieldset');
      this.emitSelector();
      this.out.push('>');
      // Parse categories and items
      var startIdx = walk.position;
      while(walk.hasCurrent()) {
        walk.scrollToEol().skipWhitespaces();
        if (walk.at('@ ') || walk.at('* ')) {
          this.parseAssociativeItem(ctl, new SubWalker(walk, startIdx, walk.position));
          startIdx = walk.position;
        }
      }
      // Parse the last one
      this.parseAssociativeItem(ctl, new SubWalker(walk, startIdx, walk.position));
      // Emit categories
      this.out.push('<div class="categories">');
      for (i = 0; i < ctl.items.length; i++) {
        item = ctl.items[i];
        if (!item.category)
          continue;
        this.out.push('<div');
        this.emitAttrs({
          'id': 'category-' + item.id,
          'class': 'category'
        });
        this.out.push('>');
        this.out.push('<input');
        this.emitAttrs({
          'id': item.id,
          'name': ctl.id,
          'type': 'hidden',
          'value': item.id
        });
        this.out.push('/>');
        this.out.push('<label>');
        this.out.push(item.label);
        this.out.push('</label>');
        this.out.push('<div class="dropTarget"></div>');
        this.out.push('</div>');
      }
      // Include input for leftover items
      this.out.push('<input');
      this.emitAttrs({
        'id': item.id,
        'name': ctl.id,
        'type': 'hidden',
        'value': 'leftovers'
      });
      this.out.push('/>');
      this.out.push('</div>');
      // Emit select items
      this.out.push('<div class="items">');
      for (i = 0; i < ctl.items.length; i++) {
        item = ctl.items[i];
        if (item.category)
          continue;
        this.out.push('<div');
        this.emitAttrs({
          'id': 'item-' + item.id,
          'class': 'item'
        });
        this.out.push('>');
        this.out.push('<input');
        this.emitAttrs({
          'id': item.id,
          'name': ctl.id,
          'type': 'hidden',
          'value': item.id
        });
        this.out.push('/>');
        this.out.push('<label>');
        this.out.push(item.label);
        this.out.push('</label>');
        this.out.push('</div>');
      }
      this.out.push('</div>');
      // Finish
      this.out.push('</fieldset>');
    }
func_code_string: function(walk) {
      var i, item;
      var ctl = this.createCtl('associativeGroup');
      ctl.items = [];
      // Tweak selector to use the generated id
      this.selector.id = ctl.id;
      // Also append `associativeGroup` class
      if (!Array.isArray(this.selector.classes))
        this.selector.classes = [];
      this.selector.classes.push('associativeGroup');
      // Emit root element
      this.out.push('<fieldset');
      this.emitSelector();
      this.out.push('>');
      // Parse categories and items
      var startIdx = walk.position;
      while(walk.hasCurrent()) {
        walk.scrollToEol().skipWhitespaces();
        if (walk.at('@ ') || walk.at('* ')) {
          this.parseAssociativeItem(ctl, new SubWalker(walk, startIdx, walk.position));
          startIdx = walk.position;
        }
      }
      // Parse the last one
      this.parseAssociativeItem(ctl, new SubWalker(walk, startIdx, walk.position));
      // Emit categories
      this.out.push('<div class="categories">');
      for (i = 0; i < ctl.items.length; i++) {
        item = ctl.items[i];
        if (!item.category)
          continue;
        this.out.push('<div');
        this.emitAttrs({
          'id': 'category-' + item.id,
          'class': 'category'
        });
        this.out.push('>');
        this.out.push('<input');
        this.emitAttrs({
          'id': item.id,
          'name': ctl.id,
          'type': 'hidden',
          'value': item.id
        });
        this.out.push('/>');
        this.out.push('<label>');
        this.out.push(item.label);
        this.out.push('</label>');
        this.out.push('<div class="dropTarget"></div>');
        this.out.push('</div>');
      }
      // Include input for leftover items
      this.out.push('<input');
      this.emitAttrs({
        'id': item.id,
        'name': ctl.id,
        'type': 'hidden',
        'value': 'leftovers'
      });
      this.out.push('/>');
      this.out.push('</div>');
      // Emit select items
      this.out.push('<div class="items">');
      for (i = 0; i < ctl.items.length; i++) {
        item = ctl.items[i];
        if (item.category)
          continue;
        this.out.push('<div');
        this.emitAttrs({
          'id': 'item-' + item.id,
          'class': 'item'
        });
        this.out.push('>');
        this.out.push('<input');
        this.emitAttrs({
          'id': item.id,
          'name': ctl.id,
          'type': 'hidden',
          'value': item.id
        });
        this.out.push('/>');
        this.out.push('<label>');
        this.out.push(item.label);
        this.out.push('</label>');
        this.out.push('</div>');
      }
      this.out.push('</div>');
      // Finish
      this.out.push('</fieldset>');
    }
func_documentation_string: Emits an associative group, e.g.
  
  ```
  <fieldset id="{ctl.id}" class="associativeGroup">
  <div class="categories>
  <div id="category-#{item.id}" class="category">
  <input id="{item.id}"
  type="hidden"
  name="{ctl.id}"
  value="{item.id}"/>
  <label>Category 1</label>
  <div class="dropTarget"></div>
  </div>
  ...
  </div>
  <div class="items">
  <div id="item-{item.id}" class="item">
  <input id="{item.id}"
  type="hidden"
  name="{ctl.id}"
  value="{item.id}"/>
  <label>{item.label}</label>
  </div>
  ...
  </div>
  </fieldset>
  ```