Querying

Layer provides a flexible and expressive interface to query through conversations, messages, and announcements.

Note

Queries will load your user’s data from the server. If the client.isTrustedDevice and client.isPersistenceEnabled properties are true, query data may also be loaded from IndexedDB.

To demonstrate a simple example, the following queries Layer for the latest 20 messages in the given conversation:

// Return 20 most recent messages in myConversation
var query = client.createQuery({
    model: layer.Core.Query.Message,
    predicate: 'conversation.id = \'' + myConversation.id + '\'',
    paginationWindow: 20
});

query.on('change', function(evt) {
    var messages = query.data;
    myMessageRenderer(messages);
});

Note

myConversation.id will be of the form layer:///conversations/<UUID>

Similarly, Channel Messages can be queried with:

var channelMessageQuery = client.createQuery({
    model: layer.Core.Query.Message,
    predicate: "channel.id = '" + myChannel.id + "'"
});

Constructing a query

Query objects are initialized with a model parameter, which represents class upon which the query will be performed. Currently, this can be one of three values:

Load all Conversations

var query = client.createQuery({
  model: layer.Core.Query.Conversation
});

Load all Announcements

var query = client.createQuery({
  model: layer.Core.Query.Announcement
});

Query Builder

Alteratively, there is a layer.Core.QueryBuilder class that simplifies the code needed to create queries:

var builder = layer.Core.QueryBuilder
	.messages()
	.forConversation(conversation.id)
	.paginationWindow(50);  // See "Pagination" below

var query = client.createQuery(builder);
query.update(builder.forConversation(otherConversation.id));

The QueryBuilder may suit some developer styles better, and provides an alternate syntax to specifying predicates rather than concatenating strings.

Filtering results

The predicate property allows applications to apply constraints to a query result set. At this time, predicate is only supported for Message queries, and only supports querying for Messages within a specific Conversation. Further, the Messages query must be constrained to a specific Conversation or it will fail. This constraint will be lifted in the future.

The following predicate will constrain the query result to message objects that belong to the provided conversation:

var query = client.createQuery({
    model: layer.Core.Query.Message,
    predicate: "conversation.id = '" + myConversation.id + "'"
});

Sorting results

Currently, there is limited support for sorting directly with queries:

  • Conversations can be sorted by createdAt or lastMessage.sentAt
  • Channels are sorted by when you joined the channel; currently this cannot be changed
  • Memberhsip is sorted by when the user joined the channel; currently this cannot be changed
  • Messages and Announcements are sorted by position; currently this cannot be changed
  • Descending sorts is the only direction currently supported

The following sort descriptor specifies that Conversations be sorted by the sentAt time of the Conversation’s lastMessage:

var query = client.createQuery({
  model: layer.Core.Query.Conversation,
  sortBy: [{'lastMessage.sentAt': 'desc'}]
});

Note

You can apply your own sort after receiving the values (for example, reversing the results to get an ascending sort). However, be aware that queries are live — as new results get synced, the query results are updated, and you will need to keep your copy of the results updated.

Pagination

Data loaded from the server comes in pages; you can control the size of your pages using the paginationWindow property. The default paginationWindow is 100 items of data per page. Once your query has returned data to your application, you can then call update with a new paginationWindow to re-run the query and get additional data.

// Creating this query will load 50 results:
var query = client.createQuery({
    model: layer.Core.Query.Conversation,
    paginationWindow: 50
});

// Updating this query will load the next 10 results:
query.update({
    paginationWindow: 60
});

// Updating this query will remove results until 40 are left:
query.update({
    paginationWindow: 40
});

Note

Currently, no more than 100 results can be requested at a time.

Running a query

Querys run automatically when they are created, and emit a change event when they load a result set. The results are then available via the data property on the Query instance.

var query = client.createQuery({
  model: layer.Core.Query.Message,
  predicate: "conversation.id = '" + myConversation.id + "'",
  dataType: 'instance'
});

query.on('change', function(event) {
  var messages = query.data;
  myMessageListRenderer(messages);
})

Result types

Query results can be returned as object instances, or immutable Javascript objects.

Return Instances; each element of the query’s data array contains Message instances that you can invoke methods on:

var query = client.createQuery({
    model: layer.Core.Query.Message,
    predicate: "conversation.id = '" + myConversation.id + "'",
    dataType: 'instance'
});

query.on('change', function(evt) {
   var firstMessage = query.data[0];
   var conversation = firstMessage.getConversation();
   console.log("New Message in " + conversation.id);
});

Return Objects; each element of the query’s data array will only change if a property within that object has changed. These POJOs are easy to use within React, AngularJS and other frameworks that try to diff objects.

var query = client.createQuery({
    model: layer.Core.Query.Message,
    predicate: "conversation.id = '" + myConversation.id + "'",
    dataType: 'object'
});

query.on('change', function(evt) {
   var firstMessage = query.data[0];
   var firstMessageInstance = client.getMessage(firstMessage.id);
   var conversation = firstMessageInstance.getConversation();
   console.log("New Message in " + conversation.id);
});

Live queries

Querys do not simply return an array of results, a Query object also manages its results by:

  • updating the order of results
  • inserting new results as they are created
  • removing results as they are deleted
  • updating results as their properties change

Querys are your means of detecting changes to user data — new conversations the user has joined, for example, or new messages in conversations. You declare the data you’re interested in by creating a Query for it, and the Query keeps your data synced.

Querys fire events whenever their data changes. There are five types of events; all are received by subscribing to the change event.

Data event

The data event is fired whenever a request is sent to the server for new query results.

This typically happen when first creating the query, when paging for more data, or when changing the predicate which triggers a new request to the server.

The Event object will have an evt.data array of all newly added results. But frequently you may just want to use the query.data array and get ALL results.

query.on('change', function(evt) {
    if (evt.type === 'data') {
       var newData = evt.data;
       var allData = query.data;
    }
});

Insert event

A new resource was created. It may have been created locally by your user, or it may have been remotely created, received via websocket, and added to the Query's results.

The event target property contains the newly inserted object.

query.on('change', function(evt) {
   if (evt.type === 'insert') {
      var newItem = evt.target;
      var allData = query.data;
   }
});

Remove event

A resource was deleted.

This may have been deleted locally by your user, or it may have been remotely deleted, a notification received via websocket, and removed from the Query results.

The event target property contains the removed object.

query.on('change', function(evt) {
    if (evt.type === 'remove') {
        var removedItem = evt.target;
        var allData = query.data;
    }
});

Reset event

Any time your query’s model or predicate properties have been changed the query is reset, and a new request is sent to the server. The reset event informs your UI that the current result set is empty, and that the reason its empty is that it was reset. This helps differentiate it from a data event that returns an empty array (a request to the server that returned []).

query.on('change', function(evt) {
    if (evt.type === 'reset') {
        var allData = query.data; // []
    }
});

// This will cause the above reset to be triggered:
query.update({
    predicate: "conversation.id = '" + anotherConversation.id + "'"
});

Property event

If any properties change in any of the objects in your Query object’s data, a property event will be fired.

The event’s target property contains the object that was modified.

query.on('change', function(evt) {
    if (evt.type === 'property') {
        var changedItem = evt.target;
        var metadataChanges = evt.getChangesFor('metadata');
        var participantChanges = evt.getChangesFor('participant');

        if (metadataChanges.length) {
            ...
        }

        if (participantChanges.length) {
            ...
        }
    }
});

Move event

An existing Conversation has had a property change that impacts how its sorted in a Conversation List Query. For example, a new lastMessage will cause an Conversation to be moved to the top of the list.

The event target property contains the moved object.

 query.on('change', function(evt) {
   if (evt.type === 'move') {
       var changedItem = evt.target;
       var oldIndex = evt.fromIndex;
       var newIndex = evt.newIndex;
       var moveNode = list.childNodes[oldIndex];
       list.removeChild(moveNode);
       list.insertBefore(moveNode, list.childNodes[newIndex]);
   }
 });
Rich Content Typing indicators