JSFiddle - React, Tailwind, and code Playground

by ChangJoo Park

HTML

<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/axios-mock-adapter/dist/axios-mock-adapter.js"></script>

<div id="app">
  <div>
    <message-wrapper :messages="messages" v-on:load-more="loadMore"></message-wrapper>
    <button @click="startPolling">Start</button>
    <button @click="stopPolling">Stop</button>
  </div>
</div>


<template id="message-wrapper">
  <div id="message-wrapper">
    <message-item v-for="message in messages" :message="message"></message-item>
    <div class="messageNotifier" :class="{active: hasNewMessage}">New Message</div>
  </div>
</template>

<template id="message-item">
  <div class="message">
    {{message}}
  </div>
</template>

CSS

#message-wrapper {
  width: 400px;
  height: 400px;
  background-color: tomato;
  overflow-y: auto;
  overflow-x: hidden;
  position: relative;
}

.messageNotifier {
  position: relative;
  width: 400px;
  height: 20px;
  background-color: white;
  z-index: 100;
}

JavaScript

var polling;
var mockAxios = new AxiosMockAdapter(axios);

var mock = [];
for (var i = 0; i < 10; i++) {
  mock.push('NEW TOP HELLO')
}


mockAxios.onGet('/api/msg').reply(200, mock)


var SCROLL_STATE = {
	BOTTOM: 'BOTTOM',
  MIDDLE: 'MIDDLE',
  TOP: 'TOP'
};

Vue.component('message-wrapper', {
  template: '#message-wrapper',
  props: ['messages'],
  data: function() {
    return {
    	scrollState: '',
      offset: 50,
      hasNewMessage: false
    }
  },
  mounted: function() {
    console.log('[#message-wrapper] - mounted');
    this.$el.addEventListener('scroll', this.updateScrollState)
    this.scrollToBottom();
  },
  beforeUpdate: function() {
  	console.log('#beforeUpdate', this.scrollState);
  	this.updateScrollState();
  },
  updated: function () {
  	console.log('#update', this.scrollState, this.scrollState === SCROLL_STATE.BOTTOM);
  	if(this.scrollState === SCROLL_STATE.BOTTOM) {
    	this.scrollToBottom();
    }
    if(this.scrollState === SCROLL_STATE.MIDDLE) {
    	this.hasNewMessage = true;
    }
  },
  watch: {
    scrollState: function (newState, oldState) {
    	console.log(newState);
      if(newState === SCROLL_STATE.TOP) {
        axios.get('/api/msg').then(function (response) {
					this.$emit('load-more', response.data);
        }.bind(this));
			}
    }
	},
  methods: {
  	updateScrollState: function () {   
    	var scrollHeight = this.$el.scrollHeight;
      var boxHeight = this.$el.clientHeight;
      var currentTop = this.$el.scrollTop;
      var isInTop = currentTop < this.offset;
      var isInBottom = currentTop + boxHeight  >= scrollHeight -  this.offset;
      
			if (isInTop) {
      	this.scrollState = SCROLL_STATE.TOP;
      } else if (isInBottom) {
      	this.scrollState = SCROLL_STATE.BOTTOM;
			} else {
      	this.scrollState = SCROLL_STATE.MIDDLE;
			}
      console.log('#updateScrollState : ', this.scrollState)
		},
    scrollToBottom: function() {
      console.log('[#message-wrapper] - scroll to bottom');
     ...