Search code examples
javascriptcssvue.jschatbot

BotUI: Show loading animation before actions


Using BotUI (Docs) I'd like to have the loading animation, which is displayed before messages appear, also displayed before actions (e.g. buttons) appear.

var botui = new BotUI('hello-world');

botui.message.add({
  content: 'Hello world from bot!'
}).then(function() {
  return botui.message.add({
    delay: 3000,
    human: true,
    content: 'Hello world from human!'
  });
}).then(function() {
  return botui.message.add({
    delay: 3000,
    content: 'Another hello world from bot!'
  });
}).then(function() {
  return botui.message.add({
    delay: 3000,
    human: true,
    content: 'Another hello world from human!'
  });
}).then(function () {
  return botui.action.button({
    delay: 3000,
    action: [{
      text: 'Some button',
      value: 'button1'
    }, {
      text: 'Another button',
      value: 'button2'
    }]
  });
}).then(function (res) {
  if(res.value == 'button1') {
    console.log('Some button was clicked.');
  }
  if(res.value == 'button2') {
    console.log('Another button was clicked.');
  }
});
@import url(https://fonts.googleapis.com/css?family=Open+Sans);
.botui-container {
  font-size: 14px;
  background-color: #fff;
  font-family: "Open Sans", sans-serif;
}

.botui-messages-container {
  padding: 10px 20px;
}

.botui-actions-container {
  padding: 10px 20px;
}

.botui-message {
  min-height: 30px;
}

.botui-message-content {
  padding: 7px 13px 17px 13px;
  border-radius: 15px;
  color: #595a5a;
  background-color: #ebebeb;
}

.botui-message-content.human {
  color: #f7f8f8;
  background-color: #919292;
}

.botui-message-content.text {
  line-height: 1.3;
}

.botui-message-content.loading {
  background-color: rgba(206, 206, 206, 0.5);
  line-height: 1.3;
  text-align: center;
}

.botui-message-content.embed {
  padding: 5px;
  border-radius: 5px;
}

.botui-message-content-link {
  color: #919292;
}

.botui-actions-text-input {
  border: 0;
  outline: 0;
  border-radius: 0;
  padding: 5px 7px;
  font-family: "Open Sans", sans-serif;
  background-color: transparent;
  color: #595a5a;
  border-bottom: 1px solid #919292;
}

.botui-actions-text-submit {
  color: #fff;
  width: 30px;
  padding: 5px;
  height: 30px;
  line-height: 1;
  border-radius: 50%;
  border: 1px solid #919292;
  background: #777979;
}

.botui-actions-buttons-button {
  border: 0;
  color: #fff;
  line-height: 1;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  padding: 7px 15px;
  border-radius: 4px;
  font-family: "Open Sans", sans-serif;
  background: #777979;
  box-shadow: 2px 3px 4px 0 rgba(0, 0, 0, 0.25);
}

.botui-actions-text-select {
  border: 0;
  outline: 0;
  border-radius: 0;
  padding: 5px 7px;
  font-family: "Open Sans", sans-serif;
  background-color: transparent;
  color: #595a5a;
  border-bottom: 1px solid #919292;
}

.botui-actions-text-searchselect {
  border: 0;
  outline: 0;
  border-radius: 0;
  padding: 5px 7px;
  font-family: "Open Sans", sans-serif;
  background-color: transparent;
  color: #595a5a;
  border-bottom: 1px solid #919292;
}

.botui-actions-text-searchselect .dropdown-toggle {
  border: none !important;
}

.botui-actions-text-searchselect .selected-tag {
  background-color: transparent !important;
  border: 0 !important;
}

.slide-fade-enter-active {
  transition: all 0.3s ease;
}

.slide-fade-enter,
.slide-fade-leave-to {
  opacity: 0;
  transform: translateX(-10px);
}

.dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 0.5rem;
  display: inline-block;
  background-color: #919292;
}

.dot:nth-last-child(1) {
  margin-left: 0.3rem;
  animation: loading 0.6s 0.3s linear infinite;
}

.dot:nth-last-child(2) {
  margin-left: 0.3rem;
  animation: loading 0.6s 0.2s linear infinite;
}

.dot:nth-last-child(3) {
  animation: loading 0.6s 0.1s linear infinite;
}

@keyframes loading {
  0% {
    transform: translate(0, 0);
    background-color: #ababab;
  }
  25% {
    transform: translate(0, -3px);
  }
  50% {
    transform: translate(0, 0);
    background-color: #ababab;
  }
  75% {
    transform: translate(0, 3px);
  }
  100% {
    transform: translate(0, 0);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/botui/build/botui.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/botui/build/botui.min.css">
<div class="botui-app-container" id="hello-world">
  <bot-ui></bot-ui>
</div>

At least I managed to identify the relevant template part in botui.js.

<div v-if=\"msg.loading\" class=\"botui-message-content loading\"><i class=\"dot\"></i><i class=\"dot\"></i><i class=\"dot\"></i></div>

But since the contents of the botui-actions-container are loaded only after the delay I am unable to display the loading animation before the buttons appear.


Solution

  • This can be done without changing the inner working of BotUI, by using botui.message.add (to use the loading feature) immediately followed by botui.message.remove using the index parameter of the last message. Then use the action you want. This makes the loading appears before the action as requested, despite botui.action not having a loading parameter.

    Here is the code that I use, with botui.action.button, but you can use any action. If you use delay: 0 for the action, the action appears instead of the loading, without any blank message that would appear if you do not remove the initial message.

    // add an empty message to use the "loading" feature not available with action
    botui.message.add({
        delay: 2000,
        loading: true,
        content: ''
    }).then(function (index) {
        // get the index of the empty message and delete it
        botui.message.remove(index);
        // display action with 0 delay
        botui.action.button({
            delay: 0,
            action: [{
                text: 'Action 1',
                value: 'a1'
            }, {
                text: 'Action 2',
                value: 'a2'
            }]
        }).then(function (res) {
            botui.message.bot({
                delay: 1000,
                content: 'action chosen: ' + res.value
            });
        });
    });