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. 🙂

Image Compression using Javascript

I have made a plugin that enables you upload image files to the server with a front-end side compression using javascript. So basically the image that you upload gets redrawn on the canvas with new dimension that you specify in the options and a compressed base64 encoded string is obtained from it. Now most of us these days have a phone with a fancy camera, that clicks the images of sizes of around 8-13 MB. So uploading that kind of size onto the server is totally not acceptable. So you wish to either compress on front end side or the server side. Android has some libraries that allows you to compress the files before sending onto the server, but on the other hand there is no such solid lead available on the browser side.

So here’s a plugin that comes to the rescue.

You can find the related files on my github repo

I have listed out the events, methods and options for incorporating the plugin.

Methods

uploadFile(options)

This is the initializer function for upload file plugin. Just call this function on the div on which you want to trigger the upload file plugin, with the options object.

$(‘#divID’).uploadFile(options)

startUpload()

This function starts the upload by making a post call to the server. For this function to work you have to disable autoSubmit property of uploadFile.

$(‘#divID’).startUpload()

stopUpload()

This function stops the upload by setting a flag to disable the form submission.

$(‘#divID’).stopUpload()

remove()

This function as by the name removes the upload area from the DOM.

$(‘#divID’).remove()

Options

url

Specify the url in the option on which to submit your form data.

sizeLimit

Specify the size limit that you want to set for the image file before compression. Default value is 16 MB

maxWidth

Set the maxwidth for image as it gets resized. Default value is 500

maxHeight

Sets the maxheight for image as it gets resized. Default value is 500

quality

Sets the quality that you want the image to be in. Default value is 0.5

outputType

Specify the output file type that you want to submit to server. Default value is png.

isBase64

Its a flag which when true sends the image as a base64 string to server and when false sends a blob object. Default value is false.

allowFileType

In this you have to pass an array of accepted file types for the image. Default value is [‘jpg’, ‘png’, ‘jpeg’]

enablePreview

Flag which when true shows the preview of the image that is going to be sent to server. Default value is false

previewSelector

This option is dependent on the enablePreview option. You have to specify the container element in which you want the preview to be seen. Its also helpful when you have set the autoSubmit property to false.

inputFieldName

The key for the image object which you want to send in the formData. Default value ‘avatar’

autoSubmit

Flag for auto submission of the form on upload. Default value is true

isDragNDrop

Flag for enabling the drag N drop feature on your upload area. Default value is true

appendFileInput

In case you want to handle styling of input type = “file” tag on your own. You can set this option to false. As this option will append the input type = “file” in your upload area div. So when you set it to false, you have to also write the input tag inside the upload area div. Default value is true

fileInputSelector

This is dependent on appendFileInput property. If you have set appendFileInput to false then you have to specify the ID of the input file tag in this option.

allowMultiple

Flag which when true allows to upload multiple images at a time. However be careful to increase the payload limit of your server, as it might get big. Default value is false

maxFiles

Now if you allow multiple input on the plugin, then you can specify the maximum no. of files you want to allow at a time.

Events

onLoad

Event is fired on plugin load

beforeSubmit

Event is fired just before submit of file

onSuccess

Event is fired on successfull upload

onError

Event is fired on error.

I haven’t done anything on the styling part, coz i guess most of you would want to do it on your side, since everybody wants a custom thing. So you can go through the IDs of the element that i have created and style it on your own. This repo only contains a simple JS file.

So feel free to write any suggestions.

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.

 

 

Event Bubbling and Event Capturing in Javascript

Most of us use event handlers in javascripts be it onclick, onmousedown or any other random event handlers.

Behind the scene, how does javascript deal with them. Lets see.

I will go through some examples which will make it easier for you to understand the concept behind event bubbling and event capturing.

Suppose we have a nested div structure.

Event Bubbling: In javascript when an event is triggered on an element inside DOM, the click gets triggered to its next parent until it reaches the topmost element i.e. html.  This is event bubbling. The event bubbles up the DOM. So when we click on the div with id “three” in above code. The onclick event is fired on “three“, “two” and at last “one“. So we will get inside console the output

three

two

one

Now the target element stays the same i.e. div with id “three“. But event pops up to next parent which can be accessed using ‘this‘.

Event Capturing:  Event capturing is just the opposite of event bubbling, the event starts from topmost parent till it reaches the target element. So on clicking the inside most div i.e div#three. We will get the following response.

one

two

three

Now in javascript if you want to specify whether to use event capturing or event bubbling for event handler. We will concentrate on the third parameter inside addEventListener.

addEventListener(“eventName”, EventHandlerfunction(), boolean);

If you specify it as true you will get event capturing for your event handling while if you specify it as false you will get event bubbling.

Thanks for reading this post.

Design a site like this with WordPress.com
Get started