Messages Component

    Messages React component represents Messages component.

    Messages Components

    There are following components included:

    • Messages / F7Messages - main Messages container
    • Message / F7Message - single message element
    • MessagesTitle / F7MessagesTitle - single messages title element

    Messages Properties

    Prop Type Default Description
    <Messages> properties
    init boolean true Initializes Messages component
    newMessagesFirst boolean false Enable if you want to use new messages on top, instead of having them on bottom
    scrollMessages boolean true Enable/disable messages autoscrolling when adding new message
    scrollMessagesOnEdge boolean true If enabled then messages autoscrolling will happen only when user is on top/bottom of the messages view
    <Message> properties
    type string sent Message type: sent (default) or received
    text string Message text
    avatar string Message user's avatar URL
    name string Message user's name
    image string Message image URL
    header string Message header
    footer string Message footer
    textHeader string Message text header
    textFooter string Message text footer
    first boolean false Defines that the message is first in the conversation
    last boolean false Defines that the message is last in the conversation
    tail boolean false Defines that the message has visual "tail". Usually last message in conversation
    sameName boolean false Defines that this message sender name is the same as on previous message
    sameHeader boolean false Defines that this message header text is the same as on previous message
    sameFooter boolean false Defines that this message footer text is the same as on previous message
    sameAvatar boolean false Defines that this message user's avatar URL is the same as on previous message

    Messages Methods

    <Messages> methods
    .scroll(duration, position); Scroll messages to top/bottom depending on newMessagesFirst parameter
    • duration - number scroll duration in ms
    • position - number scroll position in px
    .showTyping(message); Show typing message indicator
    .hideTyping() Hide typing message indicator

    Messages Events

    Event Description
    <Message> events
    click Event will be triggered when user clicks on message bubble
    clickName Event will be triggered when user clicks on message user's name
    clickText Event will be triggered when user clicks on message text
    clickAvatar Event will be triggered when user clicks on message user's avatar
    clickHeader Event will be triggered when user clicks on message header
    clickFooter Event will be triggered when user clicks on message footer
    clickBubble Event will be triggered when user clicks on message bubble

    Messages Slots

    Single message React component (<Message>) has additional slots for custom elements:

    • default - element will be inserted as a child of <div class="message-bubble"> element in the end
    • start - element will be inserted in the beginning and direct child of main message element <div class="message">
    • end - element will be inserted in the end and direct child of main message element <div class="message">
    • content-start - element will be inserted in the beginning and direct child of the <div class="message-content"> element
    • content-end - element will be inserted in the end and direct child of the <div class="message-content"> element
    • bubble-start - element will be inserted in the beginning and direct child of the <div class="message-bubble"> element
    • bubble-end - element will be inserted in the end and direct child of the <div class="message-bubble"> element. Same as default slot

    The following slots can be used inside of single message instead of same props if you need to pass there more complext layout:

    • header - element will be inserted in message header
    • footer - element will be inserted in message footer
    • text - element will be inserted in message text
    • name - element will be inserted in message name
    • image - element will be inserted in message image (supposed tp be an <img> element)
    • text-header - element will be inserted in message text header
    • text-footer - element will be inserted in message text footer
    <Message
      type="sent"
      text="Hello World"
      name="John Doe"
      avatar="path/to/image.jpg"
    >
      <div slot="start">Start</div>
      <div slot="end">End</div>
      <div slot="content-start">Content Start</div>
      <div slot="content-end">Content End</div>
      <div slot="bubble-start">Bubble Start</div>
      <div slot="bubble-end">Bubble End</div>
    </Message>
    
    {/* Renders to: */}
    
    <div class="message message-sent">
      <div>Start</div>
      <div class="message-avatar" style="background-image: url(path/to/image.jpg);"></div>
      <div class="message-content">
        <div>Content Start</div>
        <div class="message-name">John Doe</div>
        <div class="message-bubble">
          <div>Bubble Start</div>
          <div class="message-text">Hello World</div>
          <div>Bubble End</div>
        </div>
        <div>Content End</div>
      </div>
      <div>End</div>
    </div>
    

    Access To Messages Instance

    If you use automatic initalization to init Messages (with init={true} prop) and need to use Messages API you can access its initialized instance by accessing .f7Messages component's property.

    Examples

    Here is how the full example of Messages page where it can be used together with Messagebar:

    export default class extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          attachments: [],
          sheetVisible: false,
          typingMessage: null,
          messagesData: [
            {
              type: 'sent',
              text: 'Hi, Kate',
            },
            {
              type: 'sent',
              text: 'How are you?',
            },
            {
              name: 'Kate',
              type: 'received',
              text: 'Hi, I am good!',
              avatar: 'http://lorempixel.com/100/100/people/9',
            },
            {
              name: 'Blue Ninja',
              type: 'received',
              text: 'Hi there, I am also fine, thanks! And how are you?',
              avatar: 'http://lorempixel.com/100/100/people/7',
            },
            {
              type: 'sent',
              text: 'Hey, Blue Ninja! Glad to see you ;)',
            },
            {
              type: 'sent',
              text: 'Hey, look, cutest kitten ever!',
            },
            {
              type: 'sent',
              image: 'http://lorempixel.com/200/260/cats/4/',
    
            },
            {
              name: 'Kate',
              type: 'received',
              text: 'Nice!',
              avatar: 'http://lorempixel.com/100/100/people/9',
            },
            {
              name: 'Kate',
              type: 'received',
              text: 'Like it very much!',
              avatar: 'http://lorempixel.com/100/100/people/9',
            },
            {
              name: 'Blue Ninja',
              type: 'received',
              text: 'Awesome!',
              avatar: 'http://lorempixel.com/100/100/people/7',
            },
          ],
          images: [
            'http://lorempixel.com/300/300/cats/1/',
            'http://lorempixel.com/200/300/cats/2/',
            'http://lorempixel.com/400/300/cats/3/',
            'http://lorempixel.com/300/150/cats/4/',
            'http://lorempixel.com/150/300/cats/5/',
            'http://lorempixel.com/300/300/cats/6/',
            'http://lorempixel.com/300/300/cats/7/',
            'http://lorempixel.com/200/300/cats/8/',
            'http://lorempixel.com/400/300/cats/9/',
            'http://lorempixel.com/300/150/cats/10/',
          ],
          people: [
            {
              name: 'Kate Johnson',
              avatar: 'http://lorempixel.com/100/100/people/9',
            },
            {
              name: 'Blue Ninja',
              avatar: 'http://lorempixel.com/100/100/people/7',
            },
          ],
          answers: [
            'Yes!',
            'No',
            'Hm...',
            'I am not sure',
            'And what about you?',
            'May be ;)',
            'Lorem ipsum dolor sit amet, consectetur',
            'What?',
            'Are you sure?',
            'Of course',
            'Need to think about it',
            'Amazing!!!',
          ],
          responseInProgress: false,
        }
      }
      render() {
        return (
          <Page>
            <Navbar title="Messsages"></Navbar>
    
            <Messagebar
              placeholder={this.placeholder}
              ref={(el) => {this.messagebarComponent = el}}
              attachmentsVisible={this.attachmentsVisible}
              sheetVisible={this.state.sheetVisible}
            >
              <Link
                iconIos="f7:camera_fill"
                iconMd="material:camera_alt"
                slot="inner-start"
                onClick={() => {this.setState({sheetVisible: !this.state.sheetVisible})}}
              ></Link>
              <Link
                iconIos="f7:arrow_up_fill"
                iconMd="material:send"
                slot="inner-end"
                onClick={this.sendMessage.bind(this)}
              ></Link>
              <MessagebarAttachments>
                {this.state.attachments.map((image, index) => (
                  <MessagebarAttachment
                    key={index}
                    image={image}
                    onAttachmentDelete={() => this.deleteAttachment(image)}
                  ></MessagebarAttachment>
                ))}
              </MessagebarAttachments>
              <MessagebarSheet>
                {this.state.images.map((image, index) => (
                  <MessagebarSheetImage
                    key={index}
                    image={image}
                    checked={this.state.attachments.indexOf(image) >= 0}
                    onChange={this.handleAttachment.bind(this)}
                  ></MessagebarSheetImage>
                ))}
              </MessagebarSheet>
            </Messagebar>
    
            <Messages ref={(el) => {this.messagesComponent = el}}>
              <MessagesTitle><b>Sunday, Feb 9,</b> 12:58</MessagesTitle>
    
              {this.state.messagesData.map((message, index) => (
                <Message
                  key={index}
                  type={message.type}
                  image={message.image}
                  name={message.name}
                  avatar={message.avatar}
                  first={this.isFirstMessage(message, index)}
                  last={this.isLastMessage(message, index)}
                  tail={this.isTailMessage(message, index)}
                >
                  {message.text && (
                    <span slot="text" dangerouslySetInnerHTML={{__html: message.text}} />
                  )}
                </Message>
              ))}
              {this.state.typingMessage && (
                <Message
                  type="received"
                  typing={true}
                  first={true}
                  last={true}
                  tail={true}
                  header={`${this.state.typingMessage.name} is typing`}
                  avatar={this.state.typingMessage.avatar}
                ></Message>
              )}
            </Messages>
          </Page>
        )
      }
    
      get attachmentsVisible() {
        const self = this;
        return self.state.attachments.length > 0;
      }
      get placeholder() {
        const self = this;
        return self.state.attachments.length > 0 ? 'Add comment or Send' : 'Message';
      }
      componentDidMount() {
        const self = this;
        self.$f7ready(() => {
          self.messagebar = self.messagebarComponent.f7Messagebar;
          self.messages = self.messagesComponent.f7Messages;
        });
      }
      isFirstMessage(message, index) {
        const self = this;
        const previousMessage = self.state.messagesData[index - 1];
        if (message.isTitle) return false;
        if (!previousMessage || previousMessage.type !== message.type || previousMessage.name !== message.name) return true;
        return false;
      }
      isLastMessage(message, index) {
        const self = this;
        const nextMessage = self.state.messagesData[index + 1];
        if (message.isTitle) return false;
        if (!nextMessage || nextMessage.type !== message.type || nextMessage.name !== message.name) return true;
        return false;
      }
      isTailMessage(message, index) {
        const self = this;
        const nextMessage = self.state.messagesData[index + 1];
        if (message.isTitle) return false;
        if (!nextMessage || nextMessage.type !== message.type || nextMessage.name !== message.name) return true;
        return false;
      }
      deleteAttachment(image) {
        const self = this;
        const attachments = self.state.attachments;
        const index = attachments.indexOf(image);
        attachments.splice(index, 1);
        self.setState({ attachments });
      }
      handleAttachment(e) {
        const self = this;
        const attachments = self.state.attachments;
        const index = self.$$(e.target).parents('label.checkbox').index();
        const image = self.state.images[index];
        if (e.target.checked) {
          // Add to attachments
          attachments.unshift(image);
        } else {
          // Remove from attachments
          attachments.splice(attachments.indexOf(image), 1);
        }
        self.setState({ attachments });
      }
      sendMessage() {
        const self = this;
        const text = self.messagebar.getValue().replace(/\n/g, '<br>').trim();
        const messagesToSend = [];
        self.state.attachments.forEach((attachment) => {
          messagesToSend.push({
            image: attachment,
          });
        });
        if (text.trim().length) {
          messagesToSend.push({
            text,
          });
        }
        if (messagesToSend.length === 0) {
          return;
        }
    
        self.setState({
          // Reset attachments
          attachments: [],
          // Hide sheet
          sheetVisible: false,
          // Send message
          messagesData: [...self.state.messagesData, ...messagesToSend],
        });
        self.messagebar.clear();
    
        // Focus area
        if (text.length) self.messagebar.focus();
    
        // Mock response
        if (self.state.responseInProgress) return;
        self.setState({
          responseInProgress: true,
        })
        setTimeout(() => {
          const answer = self.state.answers[Math.floor(Math.random() * self.state.answers.length)];
          const person = self.state.people[Math.floor(Math.random() * self.state.people.length)];
          self.setState({
            typingMessage: {
              name: person.name,
              avatar: person.avatar,
            },
          });
          setTimeout(() => {
            self.setState({
              messagesData: [...self.state.messagesData, {
                text: answer,
                type: 'received',
                name: person.name,
                avatar: person.avatar,
              }],
              typingMessage: null,
              responseInProgress: false,
            });
          }, 4000);
        }, 1000);
      }
    };