Typeahead input implementation

Hello 🙂

This post is about the basic typeahead input implementation using ReactJS that can be customised based on the use case you have.

What is typeahead?

Many of you must have seen/used the view where google provides suggestions to your input by showing them as a greyed out text after your input A.K.A Smart compose.

This solution is now used across different tools provided by Google such Gmail, Gsheet, Gdoc etc.

What is the use case?

The use case is pretty straightforward i.e. when you want to offer a suggestion to user. Now the focus is on the word “a suggestion” and not “suggestions”, since in the traditional way when the system isn’t confident on what is the best acceptable suggestion, a dropdown would be shown with a list of possible suggestions. That works fine in most of the use cases, but a thing to remember is that, it is an extra step for user to click on, plus it adds additional time in reading through all suggestions, hence the use case of showing just one in a neat way which is easy to accept with the use of either Tab keypress or Right arrow keypress and saves time.

Implementation

Deciding the Choice of HTML Elements

Since the view requires to render a suggestion as a greyed out text and user should be able to write in the input field, we’re clear that for rendering the suggestion we would need a span element, which can be styled and suggestion can be rendered in it.

Now, we can go with using the HTML input field for the user to be able to write, but to show suggestion you would need to render the suggestion span with position absolute, right after the cursor ends. Having position absolute and deciding the positioning can be challenging which is why we’re going for a different solution which is to use the content-editable property of the div.

Now having span as a child node of this div is a much simpler way than positioning it inside input element. Let’s write down how it would look like

import React from "react";
import "./styles.css";

export default class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
         suggestion: ''
      }
      this.inputRef = React.createRef();
    }

    return (
      <div
        className="chatarea"
        ref={this.inputRef}
        contentEditable="true"
        spellCheck="true"
      >
        {!!this.state.suggestion.length && (
          <span className="suggestion">{this.state.suggestion}</span>
        )}
      </div>
    );
}
// styles.css
.chatarea {
  width: 500px;
  min-height: 50px;
  max-height: 100px;
  height: auto;
  overflow-y: scroll;
  border: 1px solid #ccc;
  border-radius: 3px;
  text-align: left;
  padding: 10px;
}

.chatarea:empty:before {
  content: "Type your message!";
  color: #ccc;
}

.suggestion {
  color: #ccc;
}

JSX Code

You would notice the div that renders the input typed by the user has a state variable to store the suggestion which is rendered conditionally based on whether the suggestion length is greater than 0 or not. To make sure we’re able to retrieve the values for the user typed input a reference has been created and added to div.
Additionally the browser provided spellcheck property has been enabled on the div.

Css code

The styling of the input is pretty standard for the sake of simplicity. To emulate the placeholder behaviour like what you get in an HTML input field, there is a empty:before css selector to render the content of placeholder when input is empty. This is also one of the reason why suggestion span has been rendered conditionally. Other reason being that it makes it easier to clear the input (after submission), as we shouldn’t delete the span node as it’s rendered in virtual DOM by React.

Listening to input

Next step is to listen to user’s input by adding a key down event listener on input div. The events we have to listen to would be
1. Tab press
2. Right arrow press
3. A character that would trigger suggestion fetch (For this post, we’re going to use space as that trigger character, so whenever space key is pressed a suggestion would be pulled and shown to user)

import React from "react";
import "./styles.css";

export default class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
         suggestion: ''
      }
      this.inputRef = React.createRef();
    }

    handleKeyDown(e) {
      const suggestion = "How can I help you?"; // hardcoding the suggestion for this post
      if (e.keyCode === 9 || e.keyCode === 39) { // Tab or right arrow press
        e.preventDefault();
        this.setState({
          suggestion: ""
        });
        const sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
          const range = sel.getRangeAt(0);
          // if the cursor is at the end of input and there is a suggestion
          if (
            range.startOffset === range.startContainer.length &&
            this.state.suggestion.length > 0
          ) {
            range.insertNode(document.createTextNode(suggestion));
            range.collapse(); // Move the cursor after the inserted text node
          }
        }
      } else if (e.keyCode === 32) { // Space character press
        // fetch suggestion
        const sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
          const range = sel.getRangeAt(0);
          // if the cursor is at the end on the input
          if (range.startOffset === range.startContainer.length) {
            this.setState({
              suggestion
            });
          }
        }
      } else {
        // hide suggestion
        this.setState({
          suggestion: ""
        });
      }
    }

    return (
      <div
        className="chatarea"
        ref={this.inputRef}
        contentEditable="true"
        spellCheck="true"
        onKeyDown={this.handleKeyDown}
      >
        {!!this.state.suggestion.length && (
          <span className="suggestion">{this.state.suggestion}</span>
        )}
      </div>
    );
}

JSX code

As you can see there is a method handleKeyDown that listens to keydown events inside the div and the handler has logic to figure out which key was pressed based on the keyCode property of the event.

Key things happening in handleKeyDown are
1. On Tab or Right arrow keypress, a suggestion is accepted by inserting the content of suggestion from state and then setting the suggestion in state to empty.
2. On Space keypress, a suggestion is fetched which has been hardcoded in this implementation and stored in state.
3. If any other key is pressed, the suggestion is cleared out.

To determine the current cursor position we will use the window.getSelection() along with sel.getRangeAt(0). Window’s getSelection method will give the reference to the selected area on the page. Since while typing in user input, the user’s selection would be in the input, window.getSelection should return a reference to that. You can read more about selection property from here.

Selection’s getRange method returns the range of selected area in the DOM. Here we’re trying to retrieve the range existing at index 0. You can read more about Range from here.

Thing to note about the implementation is that the suggestion is only fetched when the cursor is at the end of the already inputted text.
This has been done this way, since there isn’t a prominent use case where user would go to somewhere in middle of already typed text and would want the suggestion to be offered. At that stage they are likely to correct a mistake than to insert something new.

working implementation in codesandbox

Having some troubles in making the code execute with embed from codesandbox into wordpress. Please use https://codesandbox.io/s/hopeful-ritchie-j9zct?file=/src/App.js in the meanwhile

Hope you liked the post. Please do share any feedback you have. 🙂

Infinite Scrolling with recompose and graphql

Hey guys,

It’s been a long time since I posted something.

This post is about implementing infinite scrolling in React with the help of graphql and recompose.

Infinite scroll is UX wise a better way when compared to other approaches like pagination when you don’t know the size of your response data and also when it’s expensive to fetch the number of records in the response.

A prerequisite before going through this post is you should know React.

That was infinite scrolling and now talking about graphql which is an implementation over REST framework where the user can specify what information it needs and graphql on the server side would only send that much information. It’s really useful when your response payload from the server is huge and it also helps when you don’t want to make multiple calls to fetch data from different sources. All the browser needs to do is just make a single call specifying the set of data it needs and server side GraphQL will handle the aggregation of calls and data accordingly.

Speaking about the other tool recompose. Which provides an option to enhance your stateless functional components by providing states, handlers and lifecycle methods without the hassle of creating a stateful component. Recompose is a great tool for creating HOCs which helps in decoupling the logic from rendering. So your component would just concentrate on rendering and the Higher Order Component will handle the state, or event handling or even lifecycle methods as componentDidMount.

1xcf7x

Now coming the main topic which was infinite scrolling. First part is to add event listeners. I am going to add 2 event listeners which are on ‘scroll’ and ‘resize’ events. Now we need to figure out which is our scrolling container in order to add listeners. This depends on the structure of your HTML and CSS. On adding an event listener to scroll you have to be really careful about removing and attaching of event handlers when you’re job is done or underway. So one thing is after attaching the event listener on render it should be removed before unmounting. Also whenever we plan on the making the server query to fetch more data we should remove the event listener and add it again after the component update. So far so good.

import { lifecycle } from 'recompose';
import ListComponent from './ListComponent';

const attachListener = event => {
   scrollEl.addEventListener('scroll', handleLoadOnScoll);
   window.addEventListener('resize', handleLoadOnScoll);
}

const detachListener = event => {
   scrollEl.removeEventListener('scroll', handleLoadOnScoll);
   window.removeEventListener('resize', handleLoadOnScoll);
}

function componentDidUpdate() {
  attachListener();
}

function componentWillUnmount() {
  detachListener();
}

export default lifecycle({
  componentDidUpdate, componentWillUnmount
})(ListComponent);

So here I have a Stateless functional component called ListComponent which is wrapped under the HOC which adds the lifecycle methods on the ListComponent. Now like i said earlier I have add the event listeners for scroll and resize. And i am removing the listener before unmount. But here i am using componentDidUpdate instead of componentDidMount because i want the first set of data to be there before we can initialise the scroller.

Now graphql has a special method known as fetchMore which is made for this very purpose of loading more data. Below I am going to wire the fetchMore functionality. The handleLoadOnScoll will call the fetchMore method.

import { lifecycle, compose, withHandlers } from 'recompose';
import { graphql } from 'react-apollo';
import ListComponent from './ListComponent';
import QUERY_GQL from 'query.gql';

const attachListener = event => {
   scrollEl.addEventListener('scroll', handleLoadOnScoll);
   window.addEventListener('resize', handleLoadOnScoll);
}

const detachListener = event => {
   scrollEl.removeEventListener('scroll', handleLoadOnScoll);
   window.removeEventListener('resize', handleLoadOnScoll);
}

function componentDidUpdate() {
  attachListener();
}

function componentWillUnmount() {
  detachListener();
}

const updateCache = (previousResult, { fetchMoreResult }) => {
  ...previousResult,
  comments: [
     ...previousResult.comments,
     ...fetchMoreResult.comments,
  ]
}

const ListComponentWrapper = compose(
  withHandlers({
    handleLoadOnScoll: (data: {fetchMore, comments, loading}) => event => {
      event.preventDefault();
      const offset = scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight;
      if (offset <= threshold && your_condition_for_cursor && !loading) {
        detachEventListener();
        fetchMore({
          variables: {
             cursor: cursorForNextLoad,
          },
          updateQuery: updateCache,
        });
      }
    }
  }),
  lifecycle({
    componentDidUpdate, componentWillUnmount
  }),
)(ListComponent);

export default graphql(QUERY_GQL, gqlConfig)(ListComponentWrapper);

Wooh. Lots of stuff to digest right. Let's go one by one. First I created a wrapper for graphQl query at the end which takes the query and graphqlconfig object and wraps the result around ListComponentWrapper which will be our enhanced component. Also a thing to note is graphql provides a boolean `loading` inside the data object of response which can be used to figure out whether the query is under processing or has executed completely. So after the query has executed the data object which has fetchMore method, the response data which here i am calling it as comments and the loading boolean is passed down to the enhanced component ListComponentWrapper.
Now lets understand the working of withHandlers HOC. withHandlers is used when you want to attach event handlers to your component. So as I had mentioned earlier that handleLoadOnScroll will be called when scroll event is executed. So now we need to calculate the scroll position at which we need to load the data. For that I have used the properties scrollHeight which is the current height of the container, scrollTop is the top position of scroll bar and clientHeight which the height of the viewframe for the element. Based on that we receive a value which can be compared with the threshold to fire the fetchMore query. I have also added another check as a custom logic to tell when to stop calling the api depending on when you reach the end of data and there is a check for data.loading should be false. So that the fetchMore call is not made multiple times.
So if all conditions are met then we remove the eventListener to avoid unnecessary queuing in the callback queue. You can read about the parameters that can be passed to fetch more here Fetch More documentation. Now there is a parameter called updateQuery which will append the new result to the initial result in the apollo cache. If you are wondering what … are for then you can read about it here. Spread syntax

Now when the call is successful the lifecycle method componentDidUpdate will be called and event listener will be attached again.

Now lets write our ListComponent.

import React from 'react';
import Loader from './Loader';

export default function ListComponent(data: {comments, loading}) {
   if(loading &amp;&amp; !comments.length) return Loader(JSX tag);
   return (
        {
         // rendering a list of comments
         <div>comments.map(comment =&gt; ( {comment.message} ))</div>
         {loading &amp;&amp; comments.length &amp;&amp; Loader(JSX tag)}
        }
   );
}

So here I am checking if loading is true and its the initial load which i am checking if comments are there or not then render the loader which can be any fancy loader that you wish to use otherwise it render the comments. But the catch here is that i have put another loader below the list which will render when loading is true and its not the initial load.
There was a problem that i had faced if I wasn’t checking for the initial load then whole page would render again and the scroll position would be reset to 0 and it will go to top which is definitely a bad experience as you have to scroll down to the bottom to see the newly loaded content.

So this is it. Although I have made this post a bit lengthy. I hope you guys found it useful. If you any suggestions or comments please post it down.

Cheers!

 

Custom grid layout

In here I will try to make a custom grid layout just like bootstrap does. The basic thing before you do that is to keep in mind how many grids do you want to break your page into. You can have 4 or 10 or say twelve as bootstrap does. They do have their reasons for keeping a 12 column grid but the truth is many a times you’d be needing a custom grid layout with maybe 5 items in row rather than multiple of 12. So how do u do that. First of all you have to define a class say

.row {
margin: 0 auto 1em auto;
}
.row:after {
display: table;
content: '';
clear:both;
}

As you can see I have made class with a name of “row”. And i have used a pseudo selector “after” on “row” just to ensure that the parent container takes the height of its child. Normally u might have seen that when you make elements to float. either left or right, they float out of parent’s scope. They kind of take a new z-dimension. So the overall height of the parent reduces to 0. To avoid that, I mean i can’t avoid float to change its behaviour. But with using display table on after the parent takes the height of its child as if they were inline elements.

Now as we have covered this portion quite well, we’ll move onto the grid section. Now i am naming the classes just like bootstrap does. I am using “col” class to signify the column and “s3” to signify the width it would take. So you can do your maths and figure out how much width do u need for your column based on this.

.row .col.s3 {
width: 25%;
}

.row .col {
float: left;
padding: 0 0.5em;
box-sizing: border-box;
}

Now the thing is that you can specify any amount of width in percentage depending on your requirement but to keep them in the same line with the padding mentioned for them is tricky. If you have given float left to all the columns it’s fine but as soon as you throw a padding left or right the grid breaks as the box element will start to expand according to the padding u would have given to it. To keep the div to stay as a fix width div what you need to do is change the box-sizing property of the div. Now when i do box-sizing :border-box. The target element is restricted to its fix width regardless of the padding that you give. The padding will be taken inwards.

So that is it. You can place a few hacks to accomplish this. But the box-sizing property was the thing that surprised me. I’ll be back with more.

Lazy Loading for images Implementation

I m sure many of you aspiring web devs must have come across performance improvement techniques to improve loading time of web page. It is necessary to have non-blocking resources in your web page so that the content is not blocked. By default HTML and CSS are blocking resources. Apart from that JS can be made non-blocking by loading it asynchronously or loading it in the end, when all resources are loaded and rendered. If your website is really interactive, it will be full of images. And these images eat up most of ur loading time. So lazy loading is needed for this purpose, such that only those images have network call that are present in viewport, while others are not. You will notice a fair amount of improvement after implementing this.

I have come up with a lazy load implementation with jquery and some Javascript.

function lazyLoadOnScroll() {

// image array
this.images = [];
this.lazyCount = 0; // keeps the count of elements legible for lazy load

// fetches the elements with lazy class and filters the one that are not to be displayed on mobile
var query = document.querySelectorAll(‘.lazy’);
for( var i=0; i< query.length; i++) {
if (query[i].getAttribute(‘data-background’)) {
images.push(query[i]);
lazyCount++;
}
}

// checks if the image exists inside the viewport and is visible
this.isElementInViewport = function(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
$(el).is(‘:visible’)
)
}

// sets background images of div or src of image tag
this.loadImage = function(el, callback) {
var imgSrc = el.getAttribute(‘data-background’);
if (imgSrc) {
if ($(el).hasClass(‘lazy_parent’)) {
var imageTag = el.querySelector(‘img’);
if (imageTag.getAttribute(‘src’) !== imgSrc) {
imageTag.setAttribute(‘src’, imgSrc);
}
}
else {
el.style.backgroundImage = ‘url(‘ + imgSrc + ‘)’;
}
}
$(el).removeAttr(‘data-background’);
callback ? callback() : null
}

// initializes the lazy load
this.lazyInit = function() {
for (var i = images.length-1; i>=0 ; i–) {
if (isElementInViewport(images[i])) {
loadImage(images[i], function() {
images.splice(i,1);
lazyCount–;
});
}
}
}

if (lazyCount > 0) {
lazyInit();
}

// touchmove for mobile devices and executes lazy load on scroll for other devices
scrollEvent = $(window).on(‘scroll touchmove’, function(event) {
lazyInit();
if (lazyCount == 0) {
$(this).unbind(event);
}
});
}

Now lets go part by part of this function lazyLoadOnScroll().

Here is the working demo for this code..

To load the image using lazy load, add ‘lazy‘ class on the element. If you are using image tag then on the parent div add ‘lazy’ class and along with that add ‘lazy_parent‘ class. Its that simple. Now the key here is we dont specify the background-image property or add image src attribute for images. Coz on adding this property the images network call will be done. The key is to store the image url in data-background tag and load the images after the JS is loaded and load the images using JS. This will help you in avoiding unnecessary network call at the time of page rendering.

First of all I have defined an image array (images) that will contain all the elements containing lazy class. Next variable is lazyCount which stores the count of all elements having data-background set to some url. In the next section I m just fetching the elements having lazy class and pushing those elements in image array while increasing the count for same.

Next up is a function that checks whether the image is present in viewport or not. This function is the key to decide whether to load the image or not to. It uses the bounding rectangle property of Javascript element, to get the height, width, top, bottom, left and right. Along with that it also confirms if the element is visible or not. Coz there are some scenarios in which you would need to show some images dynamically and would not show images in the beginning. Then you would have to trigger the lazyLoadOnScroll function on the show event of image.

Next function loads the image or you could say makes the network call for that image, by adding that image’s src or setting the background-image property if its not an image element. Like I said earlier just add the ‘lazy_parent‘ class on the parent div of image tag. It reads that and changes the src of child image tag. In the end it deletes the data-background property and pops that element from ‘images‘ array, which is provided in the callback function.

Now we just have to iterate through all array elements and check if it exists in the viewport and load images accordingly. On page load the function should be triggered. So call the lazyLoadOnScoll function() on page load event. LazyInit is called to initialize the lazy load. Now to idea was to load images only if they are present in viewport. So on window scroll event i have attached the function to load images. And on completion of loading of images the event is removed.

Now for CSS i would like you add transition property on your element with lazy class just to give it a bit of fade effect.

.lazy {
-webkit-transition: background-image 0.3s ease-in-out;
-moz-transition: background-image 0.3s ease-in-out;
-o-transition: background-image 0.3s ease-in-out;
transition: background-image 0.3s ease-in-out;
background-position: center top;
background-repeat: no-repeat;
}

Thanks for reading this post. If you find it useful do comment.

 

 

Iteration with deletion in Array JS

We often face a basic problem of iteration through an array in Javascript, but recently when i was implementing lazy load in my app, I came across a problem in which i had to delete the element parallely as i was iterating through the array, to avoid running two separate loops for both. So there are 3 approaches that i tried which i will explain below, and only one of them is a correct way to do it.

1.  Running a basic for loop from i=0 to i= array.length

var dummyArr = [1,2,3,4,5,6]

for (var i=0; i< dummyArr.length; i++) {

if ( dummyArr[i] == 2 || dummyArr[i] == 3) {

dummyArr.splice(i,1);

}

}

Now this will give output as dummyArr = [1,3,4,5,6]. It skips the ‘3’ coz after splice is called the indexes in array are modified, just for starters splice is used for deleting element in array (Array Splice). So that element was skipped

2.  Next approach that i tried was using forEach method ( forEach ) in JS. It also does the same thing as basic for loop, but in my conquest towards becoming a better JS dev. I try to use some new and cool things for my code to look better.

var dummyArr = [1,2,3,4,5,6]

dummyArr.forEach(function( elem, index) {

if (elem == 2 || elem == 3) {

dummyArr.splice(index,1);

}

});

So like I said it does the same thing as a basic for loop, nothing fancy. It ignores the elements that are newly appended and skips the indexes that have been deleted.

Output — dummyArr = [1,3,4,5,6]

3.   Last and apt approach was running a reverse for loop through the array.

var dummyArr = [1,2,3,4,5,6]

for (var i=dummyArr.length -1; i>=0;  i–) {

if ( dummyArr[i] == 2 || dummyArr[i] == 3) {

dummyArr.splice(i,1);

}

}

Output — dummyArr = [1,4,5,6]

Now in this case on deletion of element and modification of index the upcoming element’s position and value are not compromised which was happening in previous cases. So the element can be deleted as u desire it to be.

 

Thanks for going through the post.. Will come back with a new post soon.

Adios

REST API on RoR

I decided to learn to implement REST Api.. so i chose Ruby on rails framework for this purpose. In this post i will explain building rest api backend using ruby on rails. I am working on MAC env. I suggest u select a linux env or mac when working with RoR, as it is more convenient and working with console is always cool.

You can follow my previous blog post for ruby on rails installation using the below link.

Ruby on Rails Installation

Follow this link to read more about RESTful API.

I wanted to create a simple dashboard with basic CRUD api calls. To create a new task, update, delete or show all tasks we will have 4 different api calls, i.e. GET,POST,PUT,DELETE.

I will share each api call functionality one by one. Now as ruby on rails is a MVC model. So our API call will hit the controller using the routes.rb file and will modify or fetch the data from database using models and render the view as response to be sent to the caller.

I ran into some gems that were needed for my requirement. Go to your Gemfile and add these lines

# rake gem
gem ‘rake’, ‘~> 10.4.2’

Rake gem is used to experiment with the migrations and do many more things like running cron_tasks. You can read more about rake here.

As i said earlier rails app requires 4 steps that of routes, controller, model and view. So setting the route for our application. Go to routes.rb file and add this line

resources :tasks

‘tasks’ is the name of my application which will be accessed like this ‘localhost:3000/tasks’. This url will hit the controller tasks_controller.rb and will render the index.html.erb inside tasks folder in views directory of app. So we need to generate the appropriate controller and model for this app.

rails generate controller tasks #will generate the controller tasks_controller.rb

rails generate model task #will generate the model task.rb

Now you will be able to see the files created in your app folder. To have a more subtle view of routes run 'rake routes' in your console. This will provide you the path that rails will follow depending on the request.

Ruby on Rails Installation

Ruby on rails is a MVC model framework. which is very popular in recent time and used in many companies. For installation follow the below steps. These are written for MAC env.

  1. Install xcode.

xcode-select –install

2.  Install RVM

curl -SSL https://get.rvm.io | bash

source /Users/<username>/.rvm/scripts/rvm

rvm install 2.0.0

Note: You might have to install bundler gem

command: gem install bundler

3.  Install Git

brew install git

4.   By default rails uses sqlite as its database. But i am using mysql db for this                purpose.

brew install mysql

5.  Now all you need to do is to select a working directory and run the following command.

rails new myapp –d mysql  —- use your appname instead of myapp

gem install bundler

6.  Edit the database.yml file inside myapp/config/ directory

add the username and password for mysql. I suggest you download sequel pro for providing a UI to your database and save the overhead for writing out queries. Choose a db name in the development section in database.yml file. and you are good to go.

7.  Start the mysql server

mysql.server start

8.  Now for creating the database you need to use rake command.

rake db:create

if you receive an error in this step. Go to Gemfile file and replace the line gem ‘mysql2’

with

gem 'mysql2', '~> 0.3.18'

and run the command again

9.  Now all you need to do is to start the rails server.

rails s -p “port_number”

without quotes

Design a site like this with WordPress.com
Get started