Categories
Angular JavaScript

How to Add Rich Text Editor to an Angular App with Localization

Adding a rich text editor to your Angular app is easy since there are libraries to do so already. It supports lots of features like changing fonts, adding or removing underline, adding lists, bolding text, changing default fonts, add pictures, adding placeholders, etc. Almost anything you can think of can be added to an editor. CKEditor is the rich text editor with the most comprehensive options available.

To use CKEditor in our Angular app, we use an Angular version of the editor available at https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/angular.html, available as a Node package. This allows us to bind our input content to our model without writing code to do that ourselves.

We install the package by running:

npm i ng2-ckeditor

Then we include the following in the imports array of your module:

import { CKEditorModule } from 'ng2-ckeditor';  
import { FormsModule } from '@angular/forms';  
   
@NgModule({  
  // ...  
  imports: [CKEditorModule, FormsModule],  
  // ...  
})  
export class AppModule {}

In index.html , we add:

<script src="https://cdn.ckeditor.com/4.5.11/full-all/ckeditor.js"></script>

This includes all the plugins for CKEditor.

Then we add the following to our component, we add:

import { Component } from '@angular/core';  
   
@Component({  
  selector: 'sample',  
  template: `  
  <ckeditor  
    [(ngModel)]="content"  
    [config]="config"  
    [readonly]="false"  
    (change)="onChange($event)"  
    (editorChange)="onEditorChange($event)"   
    (ready)="onReady($event)"  
    (focus)="onFocus($event)"  
    (blur)="onBlur($event)"  
    (contentDom)="onContentDom($event)"  
    (fileUploadRequest)="onFileUploadRequest($event)"  
    (fileUploadResponse)="onFileUploadResponse($event)"  
    (paste)="onPaste($event)"  
    (drop)="onDrop($event)"  
    debounce="500">  
  </ckeditor>  
  `,  
})  
export class Editor{  
  content: string = '<p>Some html</p>'; 
  config: any = {  
    allowedContent: true,  
    toolbar: [['Bold', 'Italic', 'Underline', '-', 'NumberedList', 'BulletedList', 'Link', '-', 'CreatePlaceholder']],  
    removePlugins: 'elementspath',  
    resize_enabled: false,  
    extraPlugins: 'font,divarea,placeholder',  
    contentsCss: ["body {font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;}"],  
    autoParagraph: false,  
    enterMode: 2  
  };  
}

In the config , we define the options that are displayed in the editor. We can define the buttons shown in the toolbar with the toolbar property. We put all the options in the array. Full list of controls that you can display in the toolbar is at https://ckeditor.com/latest/samples/old/toolbar/toolbar.html. We have one array per toolbar row. We can also remove plugins that we don’t need with the removePlugins option, and add more plugins with the extraPlugins options. Plugins have to be installed separately if the full installation is not used like I did above.

It can handle model changes with the change handler. You can run code when the editor is loaded with the ready event. paste handler allows you to run code after something is pasted.

Now to change the language of the editor, we can add the language option to the config object. Use the standard language codes listed at https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes. For example, to set the language to Spanish, we put the following in the config object:

language: 'es',

It is very simple.

Another thing you might want to do with your editor is to customize the content of some of the elements. To do this, we can make an Angular directive and add some event handlers to the CKEDITOR object to set the content you want. For example, if you want to set labels of the inputs of the Placeholder plugin that we have above, we run:

ng g directive changeEditorOptions

Then we get:

import { Directive } from '@angular/core';  
@Directive({  
  selector: '[appChangeEditorOptions]'  
})  
export class ChangeEditorOptionsDirective { 
  constructor() {}
}

Next, we reference the CKEDITOR global object we got from putting <script src=”https://cdn.ckeditor.com/4.5.11/full-all/ckeditor.js"></script> in index.html and hook some event handlers to the CKEDITOR object and put that into the constructor. After that we get the following:

import { Directive } from '@angular/core';  
  
declare let CKEDITOR: any;  
declare let $: any;
@Directive({  
  selector: '[appChangeEditorOptions]'  
})  
export class ChangeEditorOptionsDirective{ 

  constructor() { 
    CKEDITOR.on(  
      'instanceReady',  
      (ev) => {  
  
      }  
    ); 

    CKEDITOR.on('dialogDefinition', (event) => {  
      if ('placeholder' === event.data.name) {  
  
      }  
    }); 
}}

instanceReady event is fired when the editor is loaded, and dialogDefinition is loaded when the user opens a dialog box from the editor.

To add a drop down to our Placeholders dialog box and change the placeholders available in the placeholder plugin we can add it into the dialogDefinition event handler. We can make our changes by adding the following:

if ('placeholder' === event.data.name) {  
  const input = event.data.definition.getContents('info').get('name');  
  const dialog = event.data.definition;  
  input.type = 'select';  
  input.items = [  
    ['First Name', 'first_name'],  
    ['Last Name', 'last_name'],  
    ['Link', 'link'],  
  ];}

The code works like this: after opening the dialog, we get the name element in the info section by calling event.data.definition.getContents(‘info’).get(‘name’); Then we change the input to select by setting that to input.type and we populate the input by assigning the array to input.items . The first item in the array is what is displayed and the second is the value.

At the end, we have:

import { Directive } from '@angular/core';  
  
declare let CKEDITOR: any;  
declare let $: any;

@Directive({  
  selector: '[appChangeEditorOptions]'  
})  
export class ChangeEditorOptionsDirective{ 
  constructor() { 
    CKEDITOR.on(  
      'instanceReady',  
      (ev) => {  
  
      }  
    ); 

    CKEDITOR.on('dialogDefinition', (event) => {  
      if ('placeholder' === event.data.name) {  
        const input = event.data.definition.getContents('info').get('name');  
        const dialog = event.data.definition;  
        input.type = 'select';  
        input.items = [  
          ['First Name', 'first_name'],  
          ['Last Name', 'last_name'],  
          ['Link', 'link'],  
        ];  
      }  
    }); }  
}

To run script with the editor loads, we put the following in the instanceReady handler:

const $script = document.createElement('script'),  
$editor_instance = CKEDITOR.instances[ev.editor.name];
$script.src = '//path/to/your/script';  
$script.onload = () => {  
  //run code after load  
}};

$editor_instance.document.getHead().$.appendChild($script);

Now we have:

import { Directive } from '@angular/core';  
  
declare const CKEDITOR: any;
@Directive({  
  selector: '[appChangeEditorOptions]'  
})  
export class ChangeEditorOptionsDirective{ 
  constructor() { 
    CKEDITOR.on(  
      'instanceReady',  
      (ev) => {  
         const $script = document.createElement('script'),  
         $editor_instance = CKEDITOR.instances[ev.editor.name]; 
         $script.src = '//path/to/your/script';  
         $script.onload = () => {  
           //run code after load  
         }}; $editor_instance.document.getHead().$.appendChild($script);  
      }  
    ); 

    CKEDITOR.on('dialogDefinition', (event) => {  
      if ('placeholder' === event.data.name) {  
        const input = event.data.definition.getContents('info').get('name');  
        const dialog = event.data.definition;  
        input.type = 'select';  
        input.items = [  
          ['First Name', 'first_name'],  
          ['Last Name', 'last_name'],  
          ['Link', 'link'],  
        ];  
      }  
    }); }  
}

Since CKEDITOR is global, we need to put declare keyword before it so that the compiler won’t think it’s undefined.

Apply this directive to your editor by adding:

import { Component } from '@angular/core';  
   
@Component({  
  selector: 'sample',  
  template: `  
  <div appChangeEditorOptions>    
    <ckeditor  
      [(ngModel)]="content"  
      [config]="config"  
      [readonly]="false"  
      (change)="onChange($event)"  
      (editorChange)="onEditorChange($event)"   
      (ready)="onReady($event)"  
      (focus)="onFocus($event)"  
      (blur)="onBlur($event)"  
      (fileUploadRequest)="onFileUploadRequest($event)"  
      (fileUploadResponse)="onFileUploadResponse($event)"  
      (paste)="onPaste($event)"  
      (drop)="onDrop($event)"  
      debounce="500">  
    </ckeditor>  
  </div>  
  `,  
})  
export class Editor {  
  content: string = '<p>Some html</p>';
  config: any = {  
    allowedContent: true,  
    toolbar: [['Bold', 'Italic', 'Underline', '-', 'NumberedList', 'BulletedList', 'Link', '-', 'CreatePlaceholder']],  
    removePlugins: 'elementspath',  
    resize_enabled: false,  
    extraPlugins: 'font,divarea,placeholder',  
    contentsCss: ["body {font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;}"],  
    autoParagraph: false,  
    enterMode: 2  
  };  
}

Also include, ChangeEditorOptionsDirective in the app.module.ts into the declaration section of the module if it’s not there already.

Categories
Angular JavaScript

Defining and Using Angular Attribute Directives

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to create and use attribute directives with Angular.

Build a Simple Attribute Directive

We can create a simple directive by using the Angular CLI.

To create one, we run:

ng generate directive highlight

to create the skeleton code for our directive.

Once we did that, we can write some code for our directive. We write the following code:

highlight.directive.ts :

import { Directive, ElementRef } from '@angular/core';

@Directive({  
  selector: '[appHighlight]'  
})  
export class HighlightDirective {  
  constructor(el: ElementRef) {  
    el.nativeElement.style.backgroundColor = 'pink';  
  }  
}

The code above takes an ElementRef of the element that the directive is applied to.

Then we set the backgroundColor of it to 'pink' .

The bracket makes our directive an attribute directive.

We should prefix our directive names with something like app so that they don’t clash with HTML attribute names.

app.module.ts :

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';  
import { AppComponent } from './app.component';  
import { HighlightDirective } from './highlight.directive';

@NgModule({  
  declarations: [  
    AppComponent,  
    HighlightDirective  
  ],  
  imports: [  
    BrowserModule,  
    AppRoutingModule  
  ],  
  providers: [],  
  bootstrap: [AppComponent]  
})  
export class AppModule { }

We added HighlightDirective to our AppModule so we can use it.

Then we can use it in app.component.html as follows:

<div appHighlight>foo</div>

We then should see a pink div with ‘foo’ inside it.

Respond to User-Initiated Events

We can also use directives to detect and act on user-initialed events.

For example, we can write the change the HighlightDirective we had above as follows:

import { Directive, ElementRef, HostListener } from '@angular/core';

@Directive({  
  selector: '[appHighlight]'  
})  
export class HighlightDirective {  
  constructor(private el: ElementRef) {    } 

  @HostListener('mouseenter') onMouseEnter() {  
    this.highlight('pink');  
  } 

  @HostListener('mouseleave') onMouseLeave() {  
    this.highlight(null);  
  } 

  private highlight(color: string){  
    this.el.nativeElement.style.color = color;  
  }  
}

We added the HostListener decorators to listen to the mouseenter and mouseleave events of the element that our highlight directive is applied to.

Therefore, when we hover over our text, we’ll see it turn pink. Otherwise, it’ll go back to its normal color.

Pass Values Into the Directive with an @Input Data Binding

We can use the @Input decorator as we do with components to pass data to directives.

For example, we can write the following:

highlight.directive.ts :

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({  
  selector: '[appHighlight]'  
})  
export class HighlightDirective {  
  @Input() highlightColor: string;  
  constructor(private el: ElementRef) { } 

  ngOnInit() {  
    this.el.nativeElement.style.color = this.highlightColor;  
  }  
}

app.component.html :

<div appHighlight highlightColor='pink'>foo</div>

In the code above, we added the highlightColor input and then pass in the color string in app.component.html to set the color.

We’ll then get pink text since we set highlightColor to pink.

Bind to an @Input Alias

We can make the code above shorter if we change the @Input alias to have the same name as the directive name.

For example, we can write:

highlight.directive.ts :

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({  
  selector: '[appHighlight]'  
})  
export class HighlightDirective {  
  @Input('appHighlight') highlightColor: string;  
  constructor(private el: ElementRef) { } 

  ngOnInit() {  
    this.el.nativeElement.style.color = this.highlightColor;  
  }  
}

Then we can use it as follows in app.component.html ;

<div appHighlight='pink'>foo</div>

As we can see, we don’t have to specify highlightColor separately anymore.

Bind to a Second Property

We can have multiple @Input s to bind to multiple properties.

For example, we can write the following:

highlight.directive.ts :

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({  
  selector: '[appHighlight]'  
})  
export class HighlightDirective {  
  @Input('appHighlight') highlightColor: string;  
  @Input() defaultColor: string;  
  constructor(private el: ElementRef) { } 

  ngOnInit() {  
    this.el.nativeElement.style.color = this.highlightColor || this.defaultColor;  
  }  
}

In the code above, we have 2 @Inputs instead of one.

Now we can use it as follows in app.component.html :

<div appHighlight='pink' defaultColor='yellow'>foo</div>  
<div appHighlight defaultColor='green'>bar</div>

In the code above, we have appHighlight directive applied without any color string set in the second div.

Therefore, since defaultColor is set to 'green' , it’ll be applied as we specified in highlight.directive.ts ‘s ngOnInit method.

Conclusion

We define attribute directives by running ng g directive and name them with brackets outside the name.

Then we can pass in properties with @Input s and listen to events with @HostListener .

Also, we get the element that the directive is applied to and then apply whatever styles we want to it.

Categories
Angular JavaScript

Angular Reactive Forms — Patching Values and Building Forms

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to update values in the form group and use FormBuilder .

Patching the Model Value

There’re 2 ways to update the model value of Reactive forms.

We can either call setValue on an individual control to update the value or call patchValue on a FormGroup to replace values in the form model.

We can call setValue as follows:

app.component.ts :

import { Component } from "@angular/core";  
import { FormControl, FormGroup } from "@angular/forms";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  personForm = new FormGroup({  
    name: new FormControl(""),  
    address: new FormGroup({  
      street: new FormControl("")  
    })  
  }); 

  updateName() {  
    this.personForm.setValue({ ...this.personForm.value, name: "Jane" });  
  }  
}

app.component.html :

<form [formGroup]="personForm">  
  <input type="text" formControlName="name" placeholder="Name" />  
  <div formGroupName="address">  
    <input type="text" formControlName="street" placeholder="Street" />  
  </div>  
</form><button (click)="updateName()">Update Name</button>

setValue strictly adheres to the structure of the form group, so we have to include everything as we did in the updateName method.

If we only want to update some values, we can use the patchValue method as follows:

app.component.ts :

import { Component } from "@angular/core";  
import { FormControl, FormGroup } from "@angular/forms";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  personForm = new FormGroup({  
    name: new FormControl(""),  
    address: new FormGroup({  
      street: new FormControl("")  
    })  
  }); 

  updateName() {  
    this.personForm.patchValue({ name: "Jane" });  
  }  
}

app.component.html :

<form [formGroup]="personForm">  
  <input type="text" formControlName="name" placeholder="Name" />  
  <div formGroupName="address">  
    <input type="text" formControlName="street" placeholder="Street" />  
  </div>  
</form><button (click)="updateName()">Update Name</button>

In the code above, the updateName method changed to:

this.personForm.patchValue({ name: "Jane" });

As we can see, we don’t have to include all the fields. We only included the ones we want to update.

Generating Form Controls with FormBuilder

We can use the FormBuilder class to make creating Reactive forms easier for us.

To create a form with FormBuilder , we can write the following:

app.component.ts :

import { Component } from "@angular/core";  
import { FormBuilder } from "@angular/forms";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  constructor(private fb: FormBuilder) {} 
  personForm = this.fb.group({  
    name: [""],  
    address: this.fb.group({  
      street: [""]  
    })  
  });  
}

app.component.html :

<form [formGroup]="personForm">  
  <input type="text" formControlName="name" placeholder="Name" />  
  <div formGroupName="address">  
    <input type="text" formControlName="street" placeholder="Street" />  
  </div>  
</form>

The code above has the same functionality as the other examples.

We have a personForm with the name field in the root form group and the street field nested in the address form group.

Simple Form Validation

To make sure what’s inputted if complete and correct, we have to add form validation to our form.

With Reactive forms, we can ads a single validator to our form control and also display the overall form status.

We can add validation as follows:

app.component.ts :

import { Component } from "@angular/core";  
import { FormBuilder, Validators } from "@angular/forms";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  constructor(private fb: FormBuilder) {} 
  personForm = this.fb.group({  
    name: ["", Validators.required],  
    address: this.fb.group({  
      street: ["", Validators.required]  
    })  
  });  
}

app.component.html :

<form [formGroup]="personForm">  
  <input type="text" formControlName="name" placeholder="Name" required />  
  <div formGroupName="address">  
    <input type="text" formControlName="street" placeholder="Street" required />  
  </div>  
</form>

In the code above, we made the name and street fields required by importing the Validator and using the required property in each field.

Also, we have to add the required attribute in the inputs to prevent errors when the expression has changed after the template has been checked.

Displaying Form Status

We can reference the status property of our form to get the validator status of our form.

To do this, we just change app.component.html to the following:

<form [formGroup]="personForm">  
  <p>Form Status: {{ personForm.status }}</p>  
  <input type="text" formControlName="name" placeholder="Name" required />  
  <div formGroupName="address">  
    <input type="text" formControlName="street" placeholder="Street" required />  
  </div>  
</form>

Now the form should show the validation status of it. personForm.status returns 'INVALID' if it’s not valid and 'VALID' if it’s valid.

Therefore, we should see that in the first line of the form.

Dynamic Controls Using Form Arrays

To add dynamic controls, we can use the FormArray class.

We can use FormArray as follows:

app.component.ts :

import { Component } from "@angular/core";  
import { FormBuilder, FormArray } from "@angular/forms";

@Componen({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  constructor(private fb: FormBuilder) {}  
  personForm = this.fb.group({  
    names: this.fb.array([this.fb.control("")])  
  }); 

  get names() {  
    return this.personForm.get("names") as FormArray;  
  } 

  addName() {  
    this.names.push(this.fb.control(""));  
  }  
}

app.component.html :

<form [formGroup]="personForm">  
  <div formArrayName="names">  
    <h3>Names</h3>  
    <button (click)="addName()">Add Name</button>  
    <div *ngFor="let n of names.controls; let i=index">  
      <label>  
        Name:  
        <input type="text" [formControlName]="i" />  
      </label>  
    </div>  
  </div>  
</form>

In the code above, we created the names form array in personForm .

Then in the template, we loop through the controls with the *ngFor in the form.

The formControlName is set to the index of the form control.

We also added an Add Name button to add a new form control.

Conclusion

We can use the FormBuilder class to save us time when we want to build forms.

It’s also useful for building dynamic forms since it takes FormArrays .

We can get the status of a form with the status property of the form group.

To update the values of the form, we can call the setValue or patchValue method of a form group.

Categories
JavaScript

JavaScript Events Handlers- onreadystatechange and onselectionchange

In JavaScript, events are actions that happen in an app. They’re triggered by various things like inputs being entered, forms being submitted, and changes in an element like resizing, or errors that happen when an app is running, etc. We can assign an event handler to handle these events.

Events that happen to DOM elements can be handled by assigning an event handler to properties of the DOM object for the corresponding events.

In this article, we will look at the onreadystatechange and the onselectionchange event handlers.

window.document.onreadystatechange

The onreadystatechange lets us attach an event handler to handle the readystatechange event. The readyState is changed as our page loads.

The event will be trigger when the document.readyState changes values, which can be one of 3 values, which is loading , which means that the document is loading, interactive , which means that the document has finished loading and the document has been parsed but sub-resources like images, stylesheets and frames are still loading, or complete , which means that the document and sub-resources have loaded.

For example, we can write the code to get the readyState of document as the page is loading:

document.onreadystatechange = () => {  
  console.log(document.readyState);  
};

If we run the code above, we should get something like:

interactive  
complete

from the console.log output.

We can look at the change of the readyState as the page loads with the code above. To look at it more slowly, we can go to the Network tab of Chrome and change the speed of that the page loads with the drop-down on the top of the tab. We can choose Fast 3G, Slow 3G, or choose ‘Add…’ to set our own speed.

window.document.onselectionchange

The onselectionchange property let us attach an event handler function to process the selectionchange event. The selectionchange event is fired when the current text selection on a document is changed. For example, we can use it like in the following code:

document.onselectionchange = () => {  
  console.log(document.getSelection());  
}

With the code above, we’re logging the Selection object, which represents the text that was selected by the user or the current position of the caret.

A user may make the selection from left to right or right to left. The anchor is where the user starts the selection and the focus is where the user ends the selection.

If the mouse is used for the selection then the anchor is placed where we place the mouse button and the focus is placed where we release the mouse button. The Selection object has the following value properties:

  • anchorNode — is a read-only that returns the Node in which the selection begins. It can return null if selection never existed in the document, for example, a page that was never clicked on will have no selection.
  • anchorOffset — is a read-only that returns a number representing the offset of the selection’s anchorNode within the anchor. If anchorNode is a text node, this is the number of characters within anchor preceding the anchor. If anchorNode is an element, this is the number of child nodes of the anchor preceding the anchor. The anchorNode property is a read-only property that has the Node is which the selection begins. It’ll have the data , textContent , and wholeText properties to get the data DOM Node that’s at the start of the selection.
  • focusNode — is a read-only that returns the node in which the selection ends. It can return null if selection never existed in the document, for example, a page that was never clicked on. It’ll have the data , textContent , and wholeText properties to get the data DOM Node that’s at the end of the selection.
  • focusOffset — is a read-only that returns a number representing the offset of the selection’s anchor within the focus node. If the focus node is a text node, this is the number of characters within the focus node preceding the focus. If focus node is an element, this is the number of child nodes of the focus node preceding the focus.
  • isCollapsed — is a read-only that returns a boolean that indicates whether the selection’s start and endpoints are in the same position.
  • rangeCount — is a read-only that returns the number of ranges in the selection.
  • type — is a read-only that returns a string describing the type of the current selection.

Also, it has a toString method that’ll get us the actual selected text. For example, we can use it below like in the code below:

document.onselectionchange = () => {  
  console.log(document.getSelection().toString());  
}

Then if we have the following HTML code:

<p>  
  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque fermentum erat viverra odio condimentum, vitae feugiat ex condimentum. Maecenas convallis tempus nisl consequat mollis. Nullam tempus ultrices est id accumsan. Duis in dignissim magna. Sed rutrum ac metus ac faucibus. Proin ut ex felis. Donec malesuada eget neque a egestas. Donec vitae aliquet tellus. Suspendisse in egestas nisi. In consequat metus id enim bibendum faucibus. Praesent hendrerit, leo vitae sollicitudin sollicitudin, tellus ante ornare est, nec laoreet augue sem et orci. Etiam tempor pretium faucibus. Etiam eros mauris, hendrerit sed molestie a, eleifend sed eros. Etiam efficitur congue euismod.</p><p>  
  Quisque viverra nibh a ornare fermentum. In viverra, orci sed mattis sodales, nisl eros fermentum lectus, id lobortis ligula diam eu sem. Nulla in tincidunt enim. Donec suscipit tortor ac aliquet pharetra. Donec lobortis nibh in turpis dignissim dapibus. Nunc eu erat quis dui fermentum convallis. Fusce posuere, augue vitae mattis viverra, est velit finibus tellus, non dictum tortor dolor sit amet felis. Aliquam auctor risus urna, non posuere magna dapibus a. Vivamus iaculis lorem massa, eget mollis libero consectetur et. Integer iaculis lorem eget turpis accumsan, nec vulputate sapien accumsan. Donec facilisis arcu nec nisi posuere finibus vel non augue. Donec tempor tortor ac mauris efficitur, nec sollicitudin nisl commodo. Proin tincidunt odio ac turpis bibendum, sed aliquet metus fermentum. In hac habitasse platea dictumst.</p>

We get something like the following logged with the event handler:

us nisl consequat mollis. Nullam tempu

The onreadystatechange gets us the state of the page when it’s loading. It’s handy if we want to do something when the page is loading.

The onselectionchange event handlers let us process the selectchange event, which is triggered when users make selections on the page by highlighting content.

We can get the anchor and focus nodes for the selection inside the event handler by using the document.getSelection() method, and we can get the selected text by calling the toString() method on document.getSelection() .

Categories
JavaScript React

Defining Nested Routes with React Router

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

To build single-page apps, we have to have some way to map URLs to the React component to display.

In this article, we’ll look at how to define nested routes with React Router.

Nested Routes

To define nested routes, first, we define a grandchild route to display the content of the nested routes. We can use the useParams hook to get any route parameters.

Then we can define nested routes by defining a child component to hold our nested routes.

In the child component, we get the url and path properties from the useRouteMatch hook.

Then we use the url variable to prefix the path in the to prop of the Link s. In the Route components, we prefix the path prop with the path property returned from useRouteMatch .

Then we create a parent component that holds the child route that we defined above.

For example, we can define them as follows:

import React from "react";  
import ReactDOM from "react-dom";  
import {  
  BrowserRouter as Router,  
  Switch,  
  Route,  
  Link,  
  useParams,  
  useRouteMatch  
} from "react-router-dom";

function Topic() {  
  let { topicId } = useParams(); return (  
    <div>  
      <h3>{topicId}</h3>  
    </div>  
  );  
}

function Topics() {  
  let { path, url } = useRouteMatch(); return (  
    <div>  
      <h2>Topics</h2>  
      <ul>  
        <li>  
          <Link to={`${url}/foo`}>Foo</Link>  
        </li>  
        <li>  
          <Link to={`${url}/bar`}>Bar</Link>  
        </li>  
        <li>  
          <Link to={`${url}/baz`}>Baz</Link>  
        </li>  
      </ul> <Switch>  
        <Route exact path={path}>  
          <h3>Please select a topic.</h3>  
        </Route>  
        <Route path={`${path}/:topicId`}>  
          <Topic />  
        </Route>  
      </Switch>  
    </div>  
  );  
}

function App() {  
  return (  
    <Router>  
      <div>  
        <ul>  
          <li>  
            <Link to="/">Home</Link>  
          </li>  
          <li>  
            <Link to="/topics">Topics</Link>  
          </li>  
        </ul> <hr /> <Switch>  
          <Route exact path="/">  
            <p>Home</p>  
          </Route>  
          <Route path="/topics">  
            <Topics />  
          </Route>  
        </Switch>  
      </div>  
    </Router>  
  );  
}const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

On the top of the code above, we have the Topics component to display the content of the nested route. We get the id URL parameter with useParams .

The Route that renders Topic has the path of /topics/:topicId . :topicId indicates a placeholder that we can get from useParams .

Then we have the Topics component, which is the parent of the Topic component. It has the child routes as defined in the following code:

<Switch>  
    <Route exact path={path}>  
        <h3>Please select a topic.</h3>  
    </Route>  
    <Route path={`${path}/:topicId`}>  
        <Topic />  
    </Route>  
</Switch>

In the Route we nest Topic in it so we display whatever’s returned there.

The path is set to path={`${path}/:topicId`} so that we call useRouteMatch to prefix the path with what we’ll define in App . That means path will be set to /topics as we’ll do later.

We also have the Link component’s to props prefixed with the url so that they’ll go to the right URL when we click on the links.

Then in App , we have:

<Switch>  
    <Route exact path="/">  
        <p>Home</p>  
    </Route>  
    <Route path="/topics">  
        <Topics />  
    </Route>  
</Switch>

to set the top-level routes. As we mentioned before, we’ll set the top-level route to topics as we did in the second Route component.

In the Link components, we set the relative paths to go to when we click on them.

Then we get something like the following when we click on the links:

Conclusion

To define nested routes, we have to nest Route objects within each other.

The cleanest way to do this is to split each level of nesting into their own components.

We can use the useRouteMatch hook to return the path, and url . The path is for prefix the path prop of Route s, and url is for prefixing the to prop of Links of nested routes.