Layer’s UI Libraries

Layer provides UI libraries to help build apps that work with Layer data. These libraries assume that you are using Layer’s SDKs directly, rather than using your own data-models, or directly using Layer’s Client API (REST API). UI Libraries serve the following goals:

  • Provide a Message List that manages a list of Messages in a Conversation, mark Messages as read when they become visible, page Query data as the user scrolls, and managing the scroll position, especially keeping the view pinned to the bottom to continously view new messages as they arrive.
  • Provide Conversation Lists that let you scroll through and select existing Conversations
  • Provide Lists of users that you can create Conversations with

Details of these components may vary, and other components may make sense depending on which framework you are using. Current UI Libraries are available in:

  • IOS
  • Android
  • Web

Using the Layer UI for Web Library

Using this Widget Library is as simple as adding an html tag to your page. The following would generate a suitable UI assuming some CSS to size and position the widgets:

<html>
  <head>
    <!-- STEP 1: Load the Layer WebSDK and Layer UI for Web -->
    <script src='http://cdn.layer.com/sdk/3.2/layer-websdk.min.js'></script>
    <script src='https://cdn.layer.com/ui/1.0/layer-ui-web.min.js'></script>
    <link rel='stylesheet' href='https://cdn.layer.com/ui/1.0/themes/bubbles-basic.css' />

    <!-- STEP 2: Instantiate a layer.Client and start authentication of it -->
    <script src='my-client-initializer.js'></script>

    <!-- STEP 3: Initialize the Client and UI library -->
    <script>
      // Initialize the client and the UI library
      var client = new layer.Client({
        appId: 'layer:///apps/staging/UUID'
      });
      layerUI.init({ appId: 'layer:///apps/staging/UUID' });

      document.addEventListener('DOMContentLoaded', function() {
        var conversationsList = document.querySelector('layer-conversations-list');
        var conversationPanel = document.querySelector('layer-conversation-panel');

        // Whenever the user selects a conversation in the list,
        // tell the conversation panel that this is the selected conversation
        conversationsList.onConversationSelected = function(evt) {
          conversationPanel.conversation = evt.detail.item;
        });
      });
    </script>
  </head>

  <!-- STEP 4: Use the widgets in your page or by generating DOM nodes -->
  <body>
    <layer-identities-list></layer-identities-list>
    <layer-conversations-list></layer-conversations-list>
    <layer-conversation-panel></layer-conversation-panel>
  </body>
</html>

Using Webcomponents means that you can also create an element simply with:

var conversationWidget = document.createElement('layer-conversation-panel');
document.body.appendChild(conversationWidget);

Initialization Sequence

The Layer UI for Web requires a layer.Client to work. Currently, it expects this Client to exist before you create the widgets. Initialization involves:

  1. Load the Layer WebSDK and Layer UI for Web (via CDN, browserify/webpack build, or other means)
  2. Initialize the layer.Client; this can be authenticated later, but needs to exist for the LayerUI to have something to listen to.
  3. Call layerUI.init() and initialize the UI Library
  4. Use the widgets in your page or generate them via document.createElement or node.innerHTML.

Note that things will still work if you generate your widgets prior to calling layerUI.init().

Connecting the WebSDK

The layerUI.init() method takes the following inputs:

  • appId: This allows the library to lookup your Client, which you may have instantiated before or after the init call. If this property is ommitted it can instead be passed into each of your WebComponents as an input.
  • settings: Initially you can leave this out; but later you will want to use this to customize behaviors. Read the Settings docs for more information.
  • mixins: This lets you customize any of the widgets in this library with new behaviors.

Note that instantiating a layer.Client and authenticating it are still required activities that are documented in the WebSDK Documentation.

Working with Webcomponents

As with any DOM node, a Webcomponent has attributes and properties; we do not define one without the other. What that means:

<layer-conversation-panel conversation-id="layer:///conversations/UUID" />

will mean the same as:

var conversationPanel = document.createElement('layer-conversation-panel');
conversationPanel.conversationId = 'layer:///conversations/UUID';

Any property you see documented in this UI Library can be accessed as an attribute using the dash-separated-name or as a property using its camelCasedName. The key difference between attributes and properties is that attributes will be converted by the browser to a string, making it less than ideal for passing in functions and complex objects.

Why Webcomponents?

The goal of this widget library is to provide a library of widgets that do not depend upon any one UI library, but which will work without conflict in ANY UI Library. To accomplish this, we use Webcomponents, which is a technique for defining new DOM nodes such as <layer-conversation-panel/>. What this means for you:

  • If working on a raw Javascript project, you can directly add the widgets to your html and they will just work.
  • If working with a templating engine, you can directly add the widgets to your html template, and they will add the widget to your page.
    • If your templating engine provides attribute binding, attributes of our widgets will be bound allowing you to manage attributes such as selected-conversation-id and other dynamic properties of the widget.
  • If working with a Flux architecture, these widgets will no more support a flux architecture than your <video /> tag does. Rather, you should think of these widgets as widgets on the page that manage their own state, and to which you can pass properties provided via your Flux architecture, such as selectedConversationId, but one should not expect the interals of any DOM node to receive all of its data only through your flux state. That said, there are some tools provided that can allow you more explicit control over the data and behaviors.
  • If working with React, or other frameworks that manage a Virtual DOM, because these widgets DO manage some state, we have overridden the shouldComponentUpdate method to always return false and to manage updating of properties by applying property changes directly.

Adapters

To simplify these use cases, this library provides a library of UI Framework Adapters that automates much of this for you, as well providing a UI-framework specific View or Component that wraps the Webcomponent.

Note that the layerUI.init() call defines the webcomponents that these adapters are designed to wrap, and as such, must be called before initializing an adapter.

React

The React adapter provides a library of React Components that wraps the Webcomponents, and insures that any property or attribute you set is properly passed onto the widget (property names and attribute names can be used interchangably), and that the shouldComponentUpdate method is correctly implemented.

import LayerUI from 'layer-ui-web'
import React from 'react';
import ReactDom from 'react-dom';
LayerUI.init({
  appId: 'layer:///apps/staging/UUID'
});
const {
  ConversationPanel,
  ConversationsList,
  IdentitiesList,
  Notifier,
  Presence,
  SendButton,
  FileUploadButton
} = layerUI.adapters.react(React, ReactDom);

You can then use:

render: function() {
    return <ConversationsList
      composeButtons={SendButton}
      onConversationSelected={this.mySelectHandler}></ConversationsList>
}

Angular 1.5

The Angular adapter is tested with Angular 1.5, and provides a library of Directives which will allow you to use any layer widget directly in your templates. Add the ng- prefix to attribute names if you need scope to be evaluated; if assigning a literal value, directly use the attribute name.

  <layer-notifier notify-in-foreground="toast"></layer-notifier>
  <layer-conversation-panel ng-query="myscopeProp.query"></layer-conversation-panel>
  <layer-conversations-list ng-conversation-selected="myscope.handleSelectionFunc">
  </layer-conversations-list>

Call this function to initialize angular 1.x Directives which will be part of the “layerUIControllers” controller:

import LayerUI from 'layer-ui-web'
LayerUI.init({ appId: 'layer:///apps/staging/UUID' });

layerUI.adapters.angular(angular); // Creates the layerUIControllers controller
angular.module('MyApp', ['layerUIControllers']);

Backbone

Initialize this adapter for access to Layer UI Backbone Views using:

import LayerUI from 'layer-ui-web'
import Backbone from 'backbone';

LayerUI.init({ appId: 'layer:///apps/staging/UUID' });
const {
  ConversationPanel,
  ConversationsList,
  IdentitiesList,
  Notifier,
  Presence,
  SendButton,
  FileUploadButton
} = layerUI.adapters.backbone(Backbone);

// Now we access the Views
var conversationPanelView = new ConversationPanel(client, {
  conversationId: 'layer:///conversations/UUID'
});
var conversationsListView = new ConversationsList(client);
var identitiesListView = new IdentitiesList(client);
var notifierView = new Notifier(client, {
  notifyInForeground: 'toast'
});
var presenceView = new PresenceView(client);

Any occurances of a layer widget in your html should be associated with these views:

<layer-notifier notify-in-foreground="toast"></layer-notifier>
<layer-conversation-panel conversation-id="layer:///conversations/UUID"></layer-conversation-panel>

Events and Custom Behaviors

The Layer UI for Web Library comes with various events and default behaviors built in. Examples:

  • The user hits the ENTER key and a Message is sent.
  • The user clicks to delete a Converation and a window.confirm dialog pops up and verifies that they really want to do that.
  • The user selects a Conversation in the Conversations List, the list then updates its selectedConversationId property.

All of these actions result in an event that allows your app to cancel the specified result, and optionally provide your own result.

Events and custom behaviors are provided via the Javascript CustomEvent Object. A typical flow would be something like this:

  1. User hits ENTER key
  2. Widget triggers a layer-send-message CustomEvent and passes the layer.Message instance that it created and is about to send.
  3. App listens for the event using node.addEventListener('layer-send-message', handler); note that events bubble up, so the node in question could be document.body.
  4. App receives the event and may modify the Message’s MessageParts, and may call evt.preventDefault()
  5. If evt.preventDefault() was called then the Message will not be sent by the widget (though your handler may have sent it).
  6. If evt.preventDefault() is NOT called by your handler, then the widget will send your Message.
document.body.addEventListener('layer-send-message', function(evt) {
  var message = evt.detail.item;
  if (message.parts[0].mimeType === 'text/plain' &&
      message.parts[0].body.indexOf('/help') === 0) {

    // We aren't going to send this message...
    evt.preventDefault();
    showHelp(message.parts[0].body);
  } else {
    // Not calling evt.preventDefault() so this message will be sent after we finish modifying it.
    message.addPart({
      mimeType: 'application/json+message-metadata',
      body: JSON.stringify({
        someState: someValue
      })
    });
  }
});

All parameters of a CustomEvent are passed in the evt.detail sub-object. Events are documented in detail in the API Reference.

Listeners

Some widgets are designed to work with other widgets. For example, whenever you select a Conversation in a Conversation List, the Conversation Panel would typically be expected to render the selected Conversation. However, this is not automatic; you may have many Conversation Lists and Many Conversation Panels, and may need to control this behavior. So instead, LUI provides the listen-to attribute:

  <layer-notifier id="notifier"></layer-notifier>
  <layer-conversations-list id="conversation-list"></layer-conversations-list>
  <layer-conversation-panel listen-to="conversation-list, notifier"></layer-conversation-panel>

This tells the Conversation Panel to listen to the Conversation List; any selection events will automatically update the Conversation Panel’s conversation property. Compare this to using events to do the same:

var conversationsList = document.querySelector('layer-conversations-list');
var conversationPanel = document.querySelector('layer-conversation-panel');

// Whenever the user selects a conversation in the list,
// tell the conversation panel that this is the selected conversation
conversationsList.onConversationSelected = function(evt) {
  conversationPanel.conversation = evt.detail.item;
});

Both are simple, but one can be easily wired up in html/template files.

Adding the <layer-notifier> into the example means that when a user clicks on a notification, the Conversation Panel will notice and select the correct conversation.

Demos Core Components