Click here to Skip to main content
15,885,810 members
Articles / Web Development / XHTML

Fastest & Smallest Vietnamese JavaScript Input Editor

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
22 Sep 2010CPOL2 min read 57.3K   802   11  
JavaScript VietUni version 1.7 Final
/**
 * Initializes a new instance of the VietUni class.
 *
 * @param int mode  Vietnamese typing method: 1=telex, 2=vni, 3=viqr, 4=all(default), otherwise=off
 */
function vietUni(mode) {
  if (document.all || document.getElementById) {
    this.method = (mode == undefined || mode == null) ? 4 : mode;
    return this;
  }
  alert("Xin loi, trinh duyet web cua ban khong cho phep dung VietTyping.");
  return false;
}

/**
 * Sets Vietnamese typing method
 */
vietUni.prototype.setMethod = function(mode) {
  this.method = mode;
  if (this.typer) this.typer.keyMode = this.initKeys();
};

vietUni.prototype.initKeys = function() {
  switch (this.method) {
    case 1:
      if (!this.telexKeys) this.telexKeys = new vietKeysTelex();
      return this.telexKeys;

    case 2:
      if (!this.vniKeys) this.vniKeys = new vietKeysVni();
      return this.vniKeys;

    case 3:
      if (!this.viqrKeys) this.viqrKeys = new vietKeysViqr();
      return this.viqrKeys;

    case 4:
      if (!this.allKeys) this.allKeys = new vietKeysAll();
      return this.allKeys;

    default:
      if (!this.vkOff) this.vkOff = new vietKeysOff();
      return this.vkOff;
  }
};

/**
 * Initializes Vietnamese typer for an editable element (textbox, textarea, iframe.document)
 */
vietUni.prototype.initTyper = function(el) {
  // validates parameters
  if (!el) return;

  if (!this.typer) {
    this.typer = new vietTyper();
    this.typer.keyMode = this.initKeys();
  }

  var self = this;
  if (el.attachEvent) {
    el.attachEvent("onkeypress", function(evt) {
      return vietUni.vietTyping(evt, self, el);
    });
  }
  else if (el.addEventListener) {
    el.addEventListener("keypress", function(evt) {
      return vietUni.vietTyping(evt, self, el);
    }, true);
  }
  else if (el.onkeypress) {
    var oldFunc = el.onkeypress;
    if (typeof oldFunc !== "function") {
      el.onkeypress = function(evt) {
        return vietUni.vietTyping(evt, self, el);
      };
    }
    else {
      el.onkeypress = function(evt) {
        return (!oldFunc(evt)) ? false : vietUni.vietTyping(evt, self, el);
      };
    }
  }
};

/**
 * Handles key-press event of the editable element
 */
vietUni.vietTyping = function(evt, vUni, el) {
  // validates parameters
  if (!vUni || vUni.typer.keyMode.off) return true;

  // event fixing
  if (!evt) evt = event;

  // retrieves pressed key code
  var c = document.all ? evt.keyCode : (evt.which || evt.charCode);
  // out of typeable keys range
  if (c < 49 && c != 16 && c != 20) return true;

  // retrieves the current word
  var len, s = vUni.getCurrentWord(el);
  if (s == null || (len = s.length) < 1 || s.match(/\s+$/)) return true;

  // replaces with Vietnamese word
  vUni.typer.value = s;
  if (c > 32 && vUni.typer.typing(c)) {
    // found an increment of the word length
    if (((s = vUni.typer.value).length == len + 1) && c == s.charCodeAt(len)) {
      // fixs length of the replacement word
      vUni.typer.value = s.substr(0, len);
      len = 0;
    }
    vUni.replaceWord(el, vUni.typer.value);

    // cancels the current key
    if (len > 0) {
      if (typeof evt.cancelBubble !== "undefined") {
        evt.cancelBubble = true;
      }
      if (evt.stopPropagation) {
        evt.preventDefault();
        evt.stopPropagation();
      }
      return false;
    }
  }

  return (!evt.cancelBubble);
};

vietUni.prototype.getCurrentWord = function(el) {
  // for IE / Opera textbox
  if (document.selection && !el.createRange) {
    var caret = (!el.selection) ? el.document.selection.createRange() : el.selection.createRange();
    // is selected ?
    if (caret.text) return null;

    var caret2;
    try {
      caret2 = caret.duplicate();
      caret2.moveStart("word", -1);
    } catch(e) {
      var backward = -10;
      do {
        caret2 = caret.duplicate();
        caret2.moveStart("character", backward++);
      }
      while (!caret2.text && backward < 0);
    }

    el.curWord = caret2.duplicate();

    return caret2.text;
  }
  // for Firefox textbox
  else if (el.setSelectionRange) {
    var p1 = el.selectionStart, p2 = el.selectionEnd;
    // is selected ?
    if (p1 != p2) return null;

    p1 = Math.max(0, p2 - 10);
    el.pos1 = p1;
    el.pos2 = p2;

    return el.value.substr(p1, p2 - p1);
  }
  // for Firefox / Opera iframe.document
  else if (window.getSelection) {
    var sel = el.defaultView.getSelection();
    var rng = sel.getRangeAt(sel.rangeCount - 1).cloneRange();

    // is selected ?
    if (rng.toString()) return null;

    // get neareat word
    var p2 = rng.startOffset, nod = rng.endContainer;
    rng.setEnd(nod, p2);
    rng.setStart(nod, Math.max(0, p2 - 10));

    // store current word
    el.rng1 = rng;
    el.nod1 = nod;
    el.pos1 = rng.startOffset;
    el.pos2 = p2;

    var txt = rng.toString();
    // restore caret position
    rng.setStart(nod, p2);

    return txt;
  }
  else if (typeof el.value !== "undefined") {
    return el.value;
  }

  return null;
};

vietUni.prototype.replaceWord = function(el, newWord) {
  // for IE / Opera textbox
  if (document.selection && !el.createRange && el.curWord) {
    el.curWord.text = newWord;
    el.curWord.collapse(false);
  }
  // for Firefox textbox
  else if (el.setSelectionRange) {
    var p1 = el.pos1, p2 = el.pos2, txt = el.value;
    el.value = txt.substr(0, p1) + newWord + txt.substr(p2);
    //
    el.setSelectionRange(p1 + newWord.length, p1 + newWord.length);
  }
  // for Firefox / Opera iframe.document
  else if (window.getSelection && el.nod1 && el.nod1.insertData) {
    el.rng1.setStart(el.nod1, el.pos1);
    //alert(newWord + ':' + el.pos1 + ':' + el.pos2);
    el.nod1.insertData(el.pos1, newWord);
    el.nod1.deleteData(el.pos1 + newWord.length, el.pos2 - el.pos1);

    // move cursor to the end
    el.rng1.setEnd(el.nod1, el.pos2);
    el.rng1.setStart(el.nod1, el.pos2);
  }
  else if (typeof el.value !== "undefined") {
    el.value = newWord;
  }
};

/*---------- VietTyper class ----------*/
function vietTyper() {
  this.value = "";
  this.charMap = new vietUnicodeMap();
  this.ctrlChar = '-';
  this.changed = 0;
  return this;
}

vietTyper.prototype.typing = function(ctrl) {
  //if (this.keyMode.off) return 0;
  this.changed = 0;
  this.ctrlChar = String.fromCharCode(ctrl);
  this.keyMode.getAction(this);

  this.correct();
  return this.changed;
};

vietTyper.prototype.compose = function(typ) {
  if (!this.value) return;

  var info = this.findCharToChange(typ);
  if (!info || !info[0]) return;

  var telex;
  if (info[0] == '\\') {
    telex = [1, this.ctrlChar, 1];
  }
  else if (typ > 6) {
    telex = this.charMap.getAEOWD(info[0], typ, info[3]);
  }
  else {
    telex = this.charMap.getDau(info[0], typ);
  }
  if (!(this.changed = telex[0])) return;

  // replace a character at info[1]
  this.value = this.value.substr(0, info[1]) + telex[1] + this.value.substr(info[1] + info[2]);

  // spell error
  if (!telex[2]) this.value += this.ctrlChar;
};

vietTyper.prototype.correct = function() {
  //if (!document.all) return 0;
  var val = this.value;
  if ('nNcC'.indexOf(this.ctrlChar) >= 0) val += this.ctrlChar;

  var er = /[^\x01-\x7f](hn|hc|gn)$/i.exec(val);
  if (er) {
    this.value = val.substr(0, val.length - 2) + er[1].charAt(1) + er[1].charAt(0);
    this.changed = 1;
  }
  else if (!this.changed) {
    return 0;
  }

  er = /\w([^\x01-\x7f])(\w*)([^\x01-\x7f])\S*$/.exec(this.value);
  if (!er) return 0;

  var i = this.charMap.isVowel(er[1]);
  var ri = (i - 1) % 24 + 1, ci = (i - ri) / 24;
  var i2 = this.charMap.isVowel(er[3]);
  if (!ci || !i2) return 0;

  var ri2 = (i2 - 1) % 24 + 1;
  var nc = this.charMap.charAt(ri) + er[2] + this.charMap.charAt(ci * 24 + ri2);
  //
  this.value = this.value.replace(new RegExp(er[1] + er[2] + er[3], 'g'), nc);
};

vietTyper.prototype.findCharToChange = function(typ) {
  var lastChars = this.charMap.lastCharsOf(this.value, 5);
  //
  var i = 0, c = lastChars[0][0], chr = 0;
  if (c == '\\') return [c, this.value.length - 1, 1];

  if (typ == 15) {
    while (!(chr = this.charMap.isVD(c))) {
      if ((c < 'A') || (i >= 4) || !(c = lastChars[++i][0])) return null;
    }
  } else {
    while ("cghmnptCGHMNPT".indexOf(c) >= 0) {
      if ((c < 'A') || (i >= 2) || !(c = lastChars[++i][0])) return null;
    }
  }
  c = lastChars[0][0].toLowerCase();

  var pc = lastChars[1][0].toLowerCase();
  var ppc = lastChars[2][0].toLowerCase();
  //
  if (i == 0 && typ != 15) {
    if ((chr = this.charMap.isVowel(lastChars[1][0])) && ("uyoia".indexOf(c) >= 0)
      && !this.charMap.isUO(pc, c) && !((pc == 'o' && c == 'a') || (pc == 'u' && c == 'y'))
      && !((ppc == 'q' && pc == 'u') || (ppc == 'g' && pc == 'i'))) i++;
    //
    if (c == 'a' && (typ == 9 || typ == 7)) i = 0;
  }
  c = lastChars[i][0];

  if ((i == 0 || chr == 0) && typ != 15) chr = this.charMap.isVowel(c);
  if (!chr) return null;

  var clen = lastChars[i][1], isuo = 0;
  //
  if ((i > 0) && (typ == 7 || typ == 8 || typ == 11)) {
    isuo = this.charMap.isUO(lastChars[i + 1][0], c);
    if (isuo) {
      chr = isuo;
      clen += lastChars[++i][1];
      isuo = 1;
    }
  }

  var pos = this.value.length;
  for (var j = 0; j <= i; j++) pos -= lastChars[j][1];

  return [chr, pos, clen, isuo];
};
/*---------- End of VietTyper class ----------*/

/*---------- VietCharMap class ----------*/
function vietCharMap() {
  // properties
  this.vietChars = null;
  this.length = 149;
  this.chrCache = new Array(20);
  this.indCache = new Array(20);
  this.cptr = 0;
  // methods
  this.caching = function(chr, ind) {
    this.chrCache[this.cptr] = chr;
    this.indCache[this.cptr++] = ind;
    this.cptr %= 20;
  };
  // constants
  this.vmap = [[7, 7, 7, 8, 8, 8, 9, 10, 11, 15],
              [0, 3, 6, 0, 6, 9, 0, 3, 6, 0],
              [1, 4, 7, 2, 8, 10, 1, 4, 7, 1]];
  return this;
}

vietCharMap.prototype.charAt = function(ind) {
  var chrCode = this.vietChars[ind];

  return chrCode ? String.fromCharCode(chrCode) : null;
};

vietCharMap.prototype.isVowel = function(chr) {
  var i = 0;
  while ((i < 20) && (chr != this.chrCache[i])) i++;

  if (i < 20) return this.indCache[i];

  i = this.length - 5;
  while ((chr != this.charAt(i)) && i) i--;

  this.caching(chr, i);

  return i;
};

vietCharMap.prototype.isVD = function(chr) {
  var ind = this.length - 5;

  while ((chr != this.charAt(ind)) && (ind < this.length)) ind++;

  return (ind < this.length) ? ind: 0;
};

vietCharMap.prototype.isUO = function(c1, c2) {
  if (!c1 || !c2) return 0;

  var ind1 = this.isVowel(c1);
  var ci = (ind1 - 1) % 12;
  if ((ci != 9) && (ci != 10)) return 0;

  var ind2 = this.isVowel(c2);
  ci = (ind2 - 1) % 12;
  if ((ci != 6) && (ci != 7) && (ci != 8)) return 0;

  return [ind1, ind2];
};

vietCharMap.prototype.getDau = function(ind, typ) {
  var accented = (ind < 25) ? 0: 1;
  var indI = (ind - 1) % 24 + 1;
  var charSet = (typ == 6) ? 0: typ;

  if ((typ == 6) && !accented) return [0];

  var newInd = charSet * 24 + indI;
  if (newInd == ind) newInd = indI;

  var chr = this.charAt(newInd);
  if (!chr) chr = this.lowerCaseOf(0, newInd);

  return [1, chr, newInd > 24 || typ == 6];
};

vietCharMap.prototype.getAEOWD = function(ind, typ, isuo) {
  var c = 0, i1 = isuo ? ind[0] : ind;
  var vc1 = (typ == 15) ? (i1 - 1) % 2 : (i1 - 1) % 12;

  if (isuo) {
    var base = ind[1] - (ind[1] - 1) % 12;

    if (typ == 7 || typ == 11) {
      c = this.charAt(i1 - vc1 + 9) + this.charAt(base + 7);
    }
    else if (typ == 8) {
      c = this.charAt(i1 - vc1 + 10) + this.charAt(base + 8);
    }

    return [c != 0, c, 1];
  }

  var i = -1, shif = 0, del = 0;

  while (shif == 0 && ++i < this.vmap[0].length) {
    if (this.vmap[0][i] == typ) {
      if (this.vmap[1][i] == vc1) {
        shif = this.vmap[2][i] - vc1;
      }
      else if (this.vmap[2][i] == vc1) {
        shif = this.vmap[1][i] - vc1;
      }
    }
  }

  if (shif == 0) {
    if (typ == 7 && (vc1 == 2 || vc1 == 8)) shif = -1;
    else if ((typ == 9 && vc1 == 2) || (typ == 11 && vc1 == 8)) shif = -1;
    else if (typ == 8 && (vc1 == 1 || vc1 == 7)) shif = 1;

    del = 1;
  }
  else {
    del = (shif > 0);
  }

  i1 += shif;
  var chr = this.charAt(i1);

  if (i1 < 145) this.caching(chr, i1);
  if (!chr) chr = this.lowerCaseOf(0, i1);

  return [shif != 0, chr, del];
};

vietCharMap.prototype.lastCharsOf = function(str, len) {
  if (!len) return [str.charAt(str.length - 1), 1];

  var vchars = new Array(len);
  for (var i = 0; i < len; i++) {
    vchars[i] = [str.charAt(str.length - i - 1), 1];
  }
  return vchars;
};
/*---------- End of VietCharMap class ----------*/

/*---------- VietUnicodeMap class ----------*/
function vietUnicodeMap() {
  var vcmap = new vietCharMap();
  vcmap.vietChars = new Array("UNICODE", 97, 226, 259, 101, 234, 105, 111, 244,
    417, 117, 432, 121, 65, 194, 258, 69, 202, 73, 79, 212, 416, 85, 431, 89,
    225, 7845, 7855, 233, 7871, 237, 243, 7889, 7899, 250, 7913, 253, 193,
    7844, 7854, 201, 7870, 205, 211, 7888, 7898, 218, 7912, 221, 224, 7847,
    7857, 232, 7873, 236, 242, 7891, 7901, 249, 7915, 7923, 192, 7846, 7856,
    200, 7872, 204, 210, 7890, 7900, 217, 7914, 7922, 7841, 7853, 7863, 7865,
    7879, 7883, 7885, 7897, 7907, 7909, 7921, 7925, 7840, 7852, 7862, 7864,
    7878, 7882, 7884, 7896, 7906, 7908, 7920, 7924, 7843, 7849, 7859, 7867,
    7875, 7881, 7887, 7893, 7903, 7911, 7917, 7927, 7842, 7848, 7858, 7866,
    7874, 7880, 7886, 7892, 7902, 7910, 7916, 7926, 227, 7851, 7861, 7869,
    7877, 297, 245, 7895, 7905, 361, 7919, 7929, 195, 7850, 7860, 7868, 7876,
    296, 213, 7894, 7904, 360, 7918, 7928, 100, 273, 68, 272);
  return vcmap;
}
/*---------- End of VietUnicodeMap class ----------*/

/*---------- VietKeys classes ----------*/
function vietKeys() {
  this.getAction = function(typer) {
    var i = this.keys.indexOf(typer.ctrlChar.toLowerCase());
    if (i >= 0) typer.compose(this.actions[i]);
  };
  return this;
}
function vietKeysOff() {
  this.off = true;
  this.getAction = function(typer) {};
  return this;
}

function vietKeysTelex() {
  var k = new vietKeys();
  k.keys = "sfjrxzaeowd";
  k.actions = [1, 2, 3, 4, 5, 6, 9, 10, 11, 8, 15];
  return k;
}
function vietKeysVni() {
  var k = new vietKeys();
  k.keys = "0123456789";
  k.actions = [6, 1, 2, 4, 5, 3, 7, 8, 8, 15];
  return k;
}
function vietKeysViqr() {
  var k = new vietKeys();
  k.keys = "\xB4/'\u2019`.?~-^(*+d";
  k.actions = [1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 15];
  return k;
}
function vietKeysAll() {
  var k = new vietKeys();
  k.keys = "sfjrxzaeowd0123456789\xB4/'`.?~-^(*+d";
  k.actions = [1, 2, 3, 4, 5, 6, 9, 10, 11, 8, 15, 6, 1, 2, 4, 5, 3, 7, 8, 8,
              15, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 15];
  return k;
}
/*---------- End of VietKeys classes ----------*/

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Junior)
Vietnam Vietnam
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions