Building iWisdom: completing the editor 2

In the last post, I wrote about adding functionality to editor like ability to apply styles. I was also able to get the editor state out to the top-level component so that I can save it.

However, to edit or view a wisdom, I need to render its description fetched from the database, in the WisdomEditor. So, I need to pass the description in the editor and render its text accordingly.

For this, first I passed the description to the editor through a prop:
In App.js:

<Wisdom description= {this.state.addWisdom.description}..

In Wisdom.js:

<WisdomEditor description= {this.props.description}..

Now in the WisdomEditor, I need to create the editor state from this description. Instead of creating an empty editor state in the constructor, I will use createWithContent method of EditorState to create its state. In the constructor of WisdomEditor:

constructor (props) {
  super(props);

  var hashtagDecorator = [
    {
      strategy: hashtagStrategy,
      component: HashtagSpan
    }
  ];

  var description = this.props.description;
  if (description) {
    var jsonDescription = JSON.parse(description);
    var currentContent = convertFromRaw(jsonDescription);

    this.state = {
      editorState: EditorState.createWithContent(currentContent, hashtagDecorator)
    }
  } else {
    this.state = {
      editorState: EditorState.createEmpty(hashtagDecorator)
    }
  }
}

This code requires convertFromRaw, which can be imported from draft-js:

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

This code basically takes description(which is a JSON string created from the ContentState of editor), parses it to JSON and convert it to a ContentState object(currentContent). This ContentState object is then passed to EditorState.createWithContent function to create the editor state.

In this way, whenever the editor is mounted, it will have editor state based on the description passed to it.

As the code of creating the editor state from the description is in the constructor, if I try to change the state in description while the editor state is already mounted on the page, it will not render the new description. To fix this, add a key attribute to the editor div via props, so that it is re-mounted every time the description changes:
In App.js:

<Wisdom key = {this.state.addWisdom.key}..

In Wisdom.js:

<WisdomEditor key={this.props.key}...

In WisdomEditor…

render () {
   return ( <div key={this.props.key}...

Now, I have the description coming from the top-level component. This means my editor works with saving, adding and editing wisdom.

The next thing to work on is hashtag links. I have already added functionality to add a hashtag link in the editor. Now, I should be able to click the link to open another Wisdom. For this, I have used the convention of making hashtags, by camel-casing the title of wisdom. To give the link the ability to be clicked and pass the hashtag in click handler, add this code to HashtagSpan:

<span onClick={() => this.props.hashtagClick(this.props.children[0].props.text)} > this.props.children</span>

Now, I need to pass props into the HashtagSpan. This is done via props property in hashtagDecorator:

var hashtagDecorator = [
  {
    strategy: hashtagStrategy,
    component: HashtagSpan,
    props: { hashtagClick: this.props.hashtagClick }
  }
]

Then add hashtagClick to WisdomEditor via the top-level component:
In App.js:

<Wisdom hashtagClick={(hashtag) => this.hashtagClick(hashtag)}...

In Wisdom.js:

<WisdomEditor hashtagClick ={(hashtag) => this.props.hashtagClick(hashtag)}...

Also, the hashtag is passed to this event by HashtagSpan using this code as above:

this.props.children[0].props.text

Now, in App.js, hashtagClick is handled in this function:

hashtagClick (hashtag) {

}

In this function, I can write code to navigate to the wisdom corresponding to the hashtag. For that, first I need to create a function to convert title of a wisdom to hashtag. Create a new module called Utilities.js and write that function in it:
vi Utilities.js

In that file;

export const createHashtag = function(value) {
  var array = value.split(" ");
	var hashtag = '#' + array.reduce(function(acc, value){
		var characters = value.split('');
		var camelValue = characters.map(function(char, index){
			return index === 0 ? char.toUpperCase() : char;
		}).join("");
		return  acc + camelValue;
	}, "");

	return hashtag;
} 

Now, I can import this function in App.js to use it:

hashtagClick(hashtag) {
  var itemToEdit = this.state.wisdom.filter(item => {
    return createHashtag(item.title) === hashtag;
  })[0];

  if (itemToEdit && itemToEdit.key) {
    this.setState({addWisdom: itemToEdit, screen: 'edit', lookups: []});
  }
}

This will open a Wisdom item on clicking its hashtag.

Now, I need to insert the hashtag into the editor from the Lookup. For that first add a button with text ‘Insert’ in Lookup.js render method. Then import createHashtag from Utilities to create a hashtag and handle its click event:
In Lookup.js:

 var hashtag = createHashtag(item.title);
<button onClick={() => this.props.insertLink(hashtag)}...

In Wisdom.js:

<Lookup insertLink={(hashtag) => this.props.insertLink(hashtag)}..

In App.js:

<Wisdom insertLink = {(hashtag) => this.insertLink(hashtag)}..

Now, add the function insertLink in App.js:

insertLink(hashtag) {

}

To add a hashtag to the editor, I need to set hashtag in the state of App.js and then pass it to the editor via props:

insertLink(hashtag) {
  this.setState({hashtag});
}

Now, pass it to WisdomEditor via Wisdom:

Wisdom hashtag={this.state.hashtag}..

In Wisdom.js:

WisdomEditor hashtag = {this.props.hashtag}

Now in WisdomEditor, I will use this prop to add it to the editor. For this, I need to use the Modifier module of draftJS, which lets you edit the editor state.

import { Editor..., Modifier } from 'draft-js';

Now in render method of WisdomEditor, write this code:

var hashtag = this.props.hashtag;
var editorState = this.state.editorState;
var currentContent = editorState.getCurrentContent();
var selection = editorState.getSelection();

if (selection.getStartOffset() === selection.getEndOffset()) {
  var newContentState = Modifier.insertText(currentContent, selection, hashtag);
  var editorState = EditorState.push(this.state.editorState, newContentState, 'insert-fragmant');
}

<Editor editorState={editorState}...

This will put the hashtag in the editor. However, as soon as it is put in the editor, it should be removed from top- level component state. So in App.js in descChange method and titleChange method, set it to empty:

descChange (description, plainText) {
  ...
  this.setState({...., hashtag: ''});
}

This gives the application the ability to insert a hashtag link from Lookup.

The WisdomEditor is complete now, as it can do all the things that it is required to do. However, when I click a hashtag to move to a new Wisdom item, I should have the ability to come back to it, otherwise my edits in the first wisdom item will be lost. I will write about it in my next post.