Minimal WYSIWYG Web Editor With Document.designMode

Category: Javascript , Text | January 19, 2018
Author: mdotz
Views Total: 1,272
Official Page: Go to website
Last Update: January 19, 2018
License: MIT

Preview:

Minimal WYSIWYG Web Editor With Document.designMode

Description:

A minimal clean WYSIWYG HTML editor built using Document.designMode and Document.execCommand.

How to use it:

This Web Editor requires Font Awesome to provide the icons for the editor controls.

<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

Create the controls for the editor.

<div class="bar" id="bar">
  <div class="row">
    <a href="#" class="far-left std" data-command='bold'><i class="fa fa-bold" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='italic'><i class="fa fa-italic" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='underline'><i class="fa fa-underline" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='justifyLeft'><i class="fa fa-align-left" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='justifyCenter'><i class="fa fa-align-center" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='justifyRight'><i class="fa fa-align-right" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='justifyFull'><i class="fa fa-align-justify" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='insertOrderedList'><i class="fa fa-list-ol" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='insertUnorderedList'><i class="fa fa-list-ul" aria-hidden="true"></i></a>
    <a href="#" class="std" class="far-right" data-command='src'><i>abc</i></a>
  </div>
  <div class="row">
    <a href="#" class="std" data-command='undo'><i class="fa fa-undo" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='redo'><i class="fa fa-repeat" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='formatBlock'><i class="fa fa-header" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='indent'><i class="fa fa-indent" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='outdent'><i class="fa fa-outdent" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='subscript'><i class="fa fa-subscript" aria-hidden="true"></i></a>
    <a href="#" class="std" data-command='superscript'><i class="fa fa-superscript" aria-hidden="true"></i></a>
    <a href="#" id="font"><i class="fa fa-font" aria-hidden="true"></i></a>
    <a href="#" class="picker" id="fore"><i class="fa fa-font fore" aria-hidden="true"></i></a>
    <a href="#" class="picker" id="back"><i class="fa fa-font back" aria-hidden="true"></i></a>
  </div>
  <div id="color-row" class="hidden active">
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
    <a href="#" class="ext"><i class="fa fa-circle" aria-hidden="true"></i></a>
  </div>
  <div id="font-row" class="hidden active">
    <a href="#" class="fill"></a>
    <a href="#" class="fill"></a>
    <a href="#" class="ext">1</a>
    <a href="#" class="ext">2</a>
    <a href="#" class="ext">3</a>
    <a href="#" class="ext">4</a>
    <a href="#" class="ext">5</a>
    <a href="#" class="ext">6</a>
    <a href="#" class="ext">7</a>
    <a href="#" class="fill"></a>
  </div>
</div>

Create an iframe for the editable content.

<iframe id="editor" name="editor">
</iframe>

The CSS for the editor.

.bar {
  background-color: #9ca2b1;
  flex: 0.15;
  border-radius: 10px 10px 0 0;
  display: flex;
  flex-flow: column
}

.bar .row {
  flex: 1;
  display: flex;
  flex-flow: row;
  background-color: #b5c4ce;
  border-top-left-radius: 10px;
  border-top-right-radius: 10px
}

.bar .row.active a { background-color: #adb4c1 }

.bar .row a {
  color: black;
  flex: 0.1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  text-decoration: none;
  background-color: #b5c4ce
}

.bar .row a.fill { background-color: #b5c4ce }

.bar .row a.fill:hover { background-color: #b5c4ce }

.bar .row a:hover { background-color: #c7dfdd }

.bar .row a.far-left { border-top-left-radius: 10px }

.bar .row a.far-right { border-top-right-radius: 10px }

.bar .row a.ext { font-size: 2vw }

.bar .row a.active {
  background-color: #adb4c1;
  border-top-right-radius: 10px;
  border-top-left-radius: 10px
}

.bar .row a i { font-size: 1.6vw }

.bar .row a i.fa.fa-font.fore {
  color: #ff6961;
  text-decoration: underline
}

.bar .row a i.fa.fa-font.back { background: #779ecb }

#editor {
  background-color: #fff;
  flex: 0.85;
  padding: 1vw;
  border: none
}

.hidden { display: none }

The main script.

editor.document.designMode = 'On';

const colors = ['#ffffff' ,'#d3d3d3' ,'#000000','#c00000','#ff0000',
                '#ffc000','#ffff00','#92d050',
                '#00b050', '#00b0f0'];
let foreColorState = false;
let fontSizeAnchors = document.getElementById('font-row').getElementsByTagName('a');
let colorIcons =
          document.getElementById('color-row').getElementsByTagName('i');
let bar = document.getElementById('bar');
let fontRow = document.getElementById('font-row');
let colorRow = document.getElementById('color-row');
let edit = document.getElementById('editor');
let barStyle = getComputedStyle(bar);
let editStyle = getComputedStyle(edit);
let foreAnchor = document.getElementById('fore');
let backAnchor = document.getElementById('back');
let fontAnchor = document.getElementById('font');
let anchors = [foreAnchor, backAnchor, fontAnchor];

// For determining fore/back color bar option is enabled
foreAnchor.addEventListener('click', function(e){
  foreColorState = true;
});

backAnchor.addEventListener('click', function(e){
  foreColorState = false;
});

The third bar handlers.

for(let i = 0; i < fontSizeAnchors.length; i++){
  fontSizeAnchors[i].addEventListener('click', e => {
    editor.document.execCommand('fontSize', false, i+1);
  });
}

for(let i = 0; i < colors.length; i++){
  colorIcons[i].style.color = colors[i];
  colorIcons[i].style.fontSize = '3vw';
  colorIcons[i].parentNode.addEventListener('click', function(e){
    let command = foreColorState ? 'foreColor' : 'hiliteColor';
    editor.document.execCommand(command, false, colors[i]);
  })
}

The font anchor event handler.

fontAnchor.addEventListener('click', function(e){

  if(colorRow.classList.contains('hidden')){
    toggleBarFlex();
    toggleRow(fontRow);
  }
  else{
    hideRow(colorRow);
    toggleRow(fontRow);
  }

  if(fontRow.classList.contains('hidden')){
    toggleActiveAnchor(null);
  }
  else {
    toggleActiveAnchor(fontAnchor);
  }
});

The color anchor event handler.

let previousTarget;

[].forEach.call(document.getElementsByClassName('picker'), v => {
  v.addEventListener('click', function(e) {


    if((previousTarget == null || previousTarget === e.currentTarget)
        || colorRow.classList.contains('hidden')){
          if(fontRow.classList.contains('hidden')){
            toggleBarFlex();
            toggleRow(colorRow);
          }
          else {
            hideRow(fontRow);
            toggleRow(colorRow);
          }
    }

    if(colorRow.classList.contains('hidden')){
      toggleActiveAnchor(null);
    }
    else{
      if(foreColorState){
        toggleActiveAnchor(foreAnchor);
      }
      else {
        toggleActiveAnchor(backAnchor);
      }
    }

    previousTarget = e.currentTarget;
  });
});

function toggleBarFlex(){
  edit.style.flex = editStyle.flexGrow == 0.85 ? 0.775 : 0.85;
  bar.style.flex = barStyle.flexGrow == 0.15 ? 0.225 : 0.15;
}
function toggleRow(row){
  row.classList.toggle('row');
  row.classList.toggle('hidden');
}
function hideRow(row){
  row.classList.add('hidden');
  row.classList.remove('row');
}

function toggleActiveAnchor(activeAnchor){
  anchors.forEach(a => {
    a.classList.remove('active');
  })
  if(activeAnchor != null){
    activeAnchor.classList.add('active');
  }
}

The static anchor event handler.

[].forEach.call(document.getElementsByClassName('std'), v => {
  v.addEventListener('click', function(e){

    //Using currentTarget instead of target due to propagation
    let command = e.currentTarget.getAttribute('data-command');

    switch(command){
      case 'src':
        let child = e.currentTarget.childNodes[0];
        child.innerHTML = child.textContent === 'abc' ? '&lt/&gt' : 'abc';
        //TODO
        break;
      case 'formatBlock':
        editor.focus();
        editor.document.execCommand(command, false, 'H1');
        editor.focus();
        break;
      default:
        editor.focus();
        editor.document.execCommand(command, false, null);
        editor.focus();
    }
  })
});

You Might Be Interested In:


Leave a Reply