Building iWisdom: Improving wisdom component

In the last post, I wrote about making the iWisdom application work with data. Instead of a static application, it is now a fully functional application, that talks to a server and database. However, instead of releasing at this point, I want to make a few more changes.

Some of the changes that I wanted to make are:
1. Improving wisdom component
2. Code Refactor
3. Improving UI

In this post, I will write about the first point, that is improving wisdom component. I will write about the other two points in the upcoming posts.

Why did I want to improve the Wisdom component? It’s because the Wisdom is the core component of my application. It is the essence of iWisdom. The whole concept of iWisdom is explained by this component. Why would somebody use iWisdom for managing knowledge? This question is answered by iWisdom component largely. Yes, there are other parts of the application that also support iWisdom, but Wisdom is the most important one.

As Wisdom is the most important component of iWisdom, I wanted to make it better so that when I show iWisdom to others, I should be able to convey the concept of iWisdom.

What changes did I want to make in this component? Wisdom component consists of three parts- title, description and Lookup. Title in an input field, description is a ‘textarea’ and Lookup is a component in itself. First, I wanted to make description accept much more than just plain text. The user should be able to add various effects to the text like bold, underline, etc. They should be able to add links to it. This would be required if I want to add links to other wisdom in my wisdom. This will also be required if I want to enhance the lookup component to support adding other Wisdom links into the Wisdom.

So, I started researching on various ways to implement a rich text editor for description field. As I was looking for a React way, I came across draftjs library for creating rich text editors for React applications. I started implementing my editor with it.

My new rich text component is not complete yet, and I will write more about it in an upcoming post. So far, I am able to create an editor that takes commands for making the text bold, italic, underline and also add hashtag based links in it. Let’s see how I created it.

First, I created a new branch so that I can play with draftjs without changing existing code. On master branch:
git branch richtext

Then, I switched to the newly created ‘richtext’ branch:
git checkout richtext

Now, I started implementing it. First, install draftjs in your front-end application:
npm install --save draft-js

Once, it is installed, I created a new file called WisdomEditor.js, which is the editor that I will be creating.
vi WisdomEditor.js

Then, I imported React and modules from draftjs.

import React, { Component } from 'react';
import { Editor, EditorState } from 'draft-js';

Editor and EditorState are required for Editor UI and saving the state of an editor. As the state of the editor is not a simple text, it is saved in form of an object called EditorState.

Now, I created my new component with render function:

class WisdomEditor extends Component {
  render () {
    return (
<div className="wisdomEditor">
      <Editor />
    </div>

  }
}

The Editor component should have value in form of EditorState, which is passed to it by an ‘editorState’ property. It should also have an ‘onChange’ event to update the EditorState.

<Editor editorState={this.state.editorState} onChange={(editorState) => this.onChange(editorState)} />

Now, let’s implement constructor to give component the state.

constructor (props) {
  super(props);
  
  this.state = {
    editorState: EditorState.createEmpty()
  };
}

Now, we need to implement the ‘onChange’ function of class for state changes. Add it just below the constructor:

onChange (editorState) {
  this.setState({editorState});
}

Now export the WisdomEditor class, so that we can use it in other components:

export default WisdomEditor; 

This is the most basic Editor from draftjs. To use the editor, I added this code to App.js(for testing that it works):

import WisdomEditor from './WisdomEditor';

Then, add it to render method of App.js:

<WisdomEditor />

On running the application, I was not able to see an editor. Yet, I was able to type text at the location where I added the editor. To fix the visibility, I need to add some borders to my editor.

For this, first create a file called WisdomEditor.css:
vi WisdomEditor.css

Then, add the following code to it to add a border and some width and height to the WisdomEditor:

.wisdomEditor {
  border: 1px solid #dedede;
  min-height: 400px;
  width: 600px;
}

Now, import the CSS file to WisdomEditor.js to use it there:

import './WisdomEditor.css';

Now, on refreshing the page, I was able to see the Editor and I was able to type into it.

However, there are a few issues in it. First, it should take focus on clicking anywhere on the editor. To fix this, I used the focus() method on Editor.

First, add a ref attribute to the editor to reference it in my code:

<Editor ref="editor" ..

Now create an onClick event handler on WisdomEditor:


<div className="wisdomEditor" onClick={() => this.focus()}>
      <Editor />
    </div>

Now, let’s implement the ‘focus’ method in WisdomEditor class to bring focus on editor(via ref):

focus () {
  this.refs.editor.focus();
}

This brings focus on the editor on clicking anywhere in it.

Now, we need to implement some rich text into the editor, as right now it only takes the plain text.

First, I implemented keyboard commands for bold(Cmd+b), italic(Cmd+i) and underline(Cmd+u). For this, I imported a module called RichUtils from draftjs, in WisdomEditor.js.

import { Editor, EditorState, RichUtils } from draft-js;

Draftjs Editor has an event called handleKeyCommand which can be used for this functionality. Let’s implement it on the Editor:

<Editor handleKeyCommand= {(command) => this.handleKeyCommand(command)}...

Now, add handleKeyCommand function to WisdomEditor class. It uses the handleKeyCommand method of RichUtils to calculate new editor state based on the command:

handleKeyCommand (command) {
  var newState = RichUtils.handleKeyCommad(this.state.editorState, command);
  if (newState) {
    this.setState({editorState: newState});
    return 'handled';
  }

  return 'not-handled';
}

This adds bold, underline and italic commands to the editor.

Another thing I need is to add links to the editor. I will be using hashtag based links as I want to make it user-friendly. The user should be able to add a link without click too many buttons. The links should get added in normal keyboard typing flow. So, I decided to use hashtags as links. In fact, this is the reason, I decided to not give buttons on my editor to execute commands for bold, italic and underline. I want to make it easy to add Wisdom, and keyboard commands can do the job without disturbing the typing flow.

To add hashtag links to my editor, I need to add a decorator class to my editor:

import { Editor, EditorState, RichUtils, CompositeDecorator } from 'draft-js';

Then in the constructor, I need to define the decorator with a strategy and component. The strategy is what actions are taken by the user and the component is the UI that will be rendered as a result of that action:

constructor () {
  super();
  this.compositeDecorator = new CompositeDecorator([
    {
      strategy: hashtagStrategy,
      component: HastagSpan
    }
  ]);

  this.state = {
      editorState: EditorState.createEmpty(compositeDecorator)
  }
}

Now, I need to define hashtagStrategy and HashtagSpan functions. Just below the WisdomEditor class:

const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;

function hashtagStrategy(contentBlock, callback, contentState) {
        findWithRegex(HASHTAG_REGEX, contentBlock, callback);
}

function findWithRegex(regex, contentBlock, callback) {
	const text = contentBlock.getText();
	let matchArr, start;
	while ((matchArr = regex.exec(text)) !== null) {
  		start = matchArr.index;
  		callback(start, start + matchArr[0].length);
	}
}

This defines the hashtagStrategy. I can also add it to a separate file and import it in WisdomEditor.js. Now, let’s define the component HashtagSpan:

const HashtagSpan = (props) => {
	return (
	  <span style={styles.hashtag} data-offset-key={props.offsetKey} >
	    {props.children}
	  </span>
	);
};

const styles = {
	hashtag: {
		color: 'rgba(95, 184, 138, 1.0)',
		cursor: 'pointer'
	}
}

Now, run the application. In the editor, if I type something starting with a hash, it becomes a different colored span. I can add a click event to this component also to perform some action:

<span onClick={() => alert('from hashtag')}
 style={styles.hashtag}
 data-offset-key={props.offsetKey}
	  >

Clicking on the hashtag will give an alert in this case.

That’s it. I have created a large part of my editor. Now I can add more functionality to it. I also need to integrate it in my Wisdom component. I will write about it in an upcoming post.