Durchsuchbare Dokumentation aufrufen

Zurück zur Dokumentationsübersicht

agorum.tree

Dieses Widget ist eine Erweiterung des BasicTree-Widgets, das dynamisches Nachladen von Knoten erlaubt.

Verwendung


Ein typischer Einsatzzweck für dieses Widget ist die Darstellung eines Ordnerbaums. Ordnerbäume sind potenziell groß, weswegen sie nicht vollständig im Voraus geladen werden sollten:

let objects = require('common/objects');
let aguila = require('common/aguila');

const LOAD_DEPTH = 1;

let byType = (a, b) => (a.isFolder ? 0 : 1) - (b.isFolder ? 0 : 1);
let byName = (a, b) => (a.displayName || '').localeCompare(b.displayName);
let fileSort = (a, b) => byType(a, b) || byName(a, b);

let build = (id, object, depth) => {
  let node = {
    text: object.displayName,
    id: id
  };

  if (object.isFolder) {
    if (depth > 0) {
      node.items = object.items().sort(fileSort).map(item => build(id + '|' + item.ID, item, depth - 1));
    }
    else {
      node.veiled = true;
    }
  }
  else {
    node.icon = 'aguila-icon-page-white-text';
  }

  return node;
};

let tree = aguila.create({
  type: 'agorum.tree',
  width: 500,
  height: 800,
  hideRoot: true,
  data: {
    id: '',
    items: [
      {
        id: '/agorum/roi/Files',
        text: 'Dateien',
        veiled: true
      },
      {
        id: 'home:MyFiles',
        text: 'Eigene Dateien',
        veiled: true
      }
    ]
  }
});

tree.on('reveal', id => aguila.fork(() => build(id, objects.find(id), LOAD_DEPTH)).then(node => tree.update(node)));

tree;

Events


reveal (String)

Löst aus, wenn ein Benutzer einen als veiled markierten Knoten öffnet.

Das System erwartet, dass dieser Knoten danach durch die Funktion update() aktualisiert wird.


Beispiel

tree.on('reveal', id => {
  // TODO: update the tree node referenced by the id
});

Parameter


border (Boolean)

Wert Beschreibung
true Erzwingt die Darstellung eines Rahmens um dieses Widgets, sofern unterstützt.
false Unterdrückt die Darstellung eines Rahmens um dieses Widgets, sofern unterstützt.

data (Propertystruktur)

Die Struktur der data-Property entspricht derjenigen des BasicTree-Widgets, allerdings mit einer Erweiterung.

Statt die Unterknoten eines nicht-Blatt-Elements explizit anzugeben, können Sie den Knoten als veiled markieren. Das signalisiert dem Client, dass dieser Knoten zwar Unterknoten besitzt, diese jedoch noch nicht mitgeschickt wurden.

Wird ein derartig markierter Knoten auf dem Client geöffnet, löst das Event reveal aus, was das Widget dazu auffordert, den Knoten samt seiner Unterknoten zu aktualisieren.


Beispiel

tree.data = {
  text: 'Root',
  id: 'Root',
  items: [
    {
      text: 'Element 1',
      id: 'Element 1',
      items: []
    },
    {
      text: 'Element 2',
      id: 'Element 2',
      veiled: true
    }
  ]
};

Funktionen


update(<node>)

Ersetzt einen bereits im Baum vorhandenen Knoten mit derselben ID durch den übergebenen Knoten. Das kann geschehen:

• nach Aufforderung durch den Client, wenn ein reveal-Event ausgelöst wurde und ein noch nicht vollständig geladener Knoten weiter geladen werden soll
• ohne Aufforderung, etwa, weil sich die Daten geändert haben und der Baum aktualisiert werden soll


Beispiel

Im Beispiel wird als Reaktion auf das reveal-Event asynchron der Unterbaum des gewünschten Knotens nachgeladen und danach der Baum per update() aktualisiert.

tree.update({
  id: '<ID eines vorhandenen Knotens>',
  text: '<Neuer Text>',
  items: [
    <Neue Unterknoten>
  ]
});

Beispiel


Suchergebnisse durch Faceting gruppieren

let objects = require('common/objects');
let aguila = require('common/aguila');

// sample tree level definitions
let levels = [
  {
    categories: [
      { text: '< 1kB', query: 'contentsize:[0 TO 1000}' },
      { text: '1kB - 1MB', query: 'contentsize:[1000 TO 1000000]' },
      { text: '> 1MB', query: 'contentsize:{1000000 TO *]' }
    ]
  },
  {
    categories: [
      { text: 'Dieses Jahr', query: 'createdate_date_range:[NOW/YEAR TO *]' },
      { text: 'älter', query: 'createdate_date_range:[* TO NOW/YEAR}' }
    ]
  },
  {
    terms: 'nameextension'
  },
  {
    categories: [
      { text: 'A-M', query: 'name_ci:[A* TO M*]' },
      { text: 'N-Z', query: 'name_ci:[N* TO Z*]' },
      { text: '0-9', query: 'name_ci:[0* TO 9*]' }
    ]
  }
];

let search = (id, query) => {
  // decode the query path up to this node
  let path = id.split('|').filter(x => x).map(decodeURIComponent);

  // look up the next tree level definition
  let next = levels[path.length];

  // prepare the query object
  let q = objects.query([ query ].concat(path).join(' '));

  if (next) {
    // next tree level is still a faceting level
    if (next.categories) {
      // category faceting (using set queries)
      let f = {};

      next.categories.forEach((category, i) => {
        f[i.toFixed(0)] = {
          type: 'query',
          q: category.query
        };
      });

      let facets = q.facets(f).limit(0).search().facets;

      return next.categories.map((category, i) => {
        let count = (facets[i.toFixed(0)] || {}).count;

        return {
          empty: !count,
          text: category.text + (count ? (' (' + count.toFixed(0) + ')') : ''),
          query: category.query
        };
      });
    }
    else if (next.terms) {
      // term faceting (using a metadata field name)
      let terms = q.facets({
        terms: {
          type: 'terms',
          field: next.terms,
          limit: 100,
          missing: true,
          sort: 'index'
        }
      }).limit(0).search().facets.terms || {};

      let results = (terms.buckets || []).map(bucket => ({
        text: bucket.val + ' (' + bucket.count.toFixed(0) + ')',
        query: next.terms + ':' + bucket.val
      }));

      if (terms.missing && terms.missing.count) {
        results.push({
          text: '- (' + terms.missing.count.toFixed(0) + ')',
          query: '(* NOT ' + next.terms + ':*)'
        });
      }

      return results;
    }
    else {
      throw new Error('Unsupported level definition: ' + JSON.stringify(next));
    }
  }
  else {
    return q.limit(100).sort('name_ci').search('name', 'id').rows.map(row => ({
      text: row.name,
      id: row.id
    }));
  }
};

let build = (id, query) => ({
  id: id,
  items: search(id, query).map(result => {
    let node = {
      text: result.text
    };

    if (result.id) {
      // object result - show as leaf node
      node.id = result.id;
      node.icon = 'aguila-icon-page-white-text';
    }
    else {
      // facet result - show as folder node, encoding the query up to this point inside its ID
      node.id = id + '|' + encodeURIComponent(result.query);

      if (result.empty) {
        node.items = [];
      }
      else {
        node.veiled = true;
      }
    }

    return node;
  })
});

let widget = aguila.create({
  type: 'agorum.vbox',
  width: 500,
  height: 800,
  items: [
    {
      name: 'query',
      type: 'agorum.textInput'
    },
    {
      name: 'tree',
      type: 'agorum.tree',
      border: true,
      flexible: true,
      hideRoot: true
    }
  ]
});

let query = widget.down('query');
let tree = widget.down('tree');

// begin searching immediately and each time the enter key is pressed inside the query field
let current;

let start = () => {
  current = query.value || '*';

  aguila.fork(() => build('', current)).then(node => tree.data = node);
};

start();
query.on('enter', start);

// continue faceting on reveal
tree.on('reveal', id => aguila.fork(() => build(id, current)).then(node => tree.update(node)));

query.focus();

widget;