Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Fastest & Smallest Vietnamese JavaScript Input Editor

, 22 Sep 2010 CPOL
JavaScript VietUni version 1.7 Final
vietuni_js_1.7_bin.zip
vietuni_js_1.7_src.zip
/**
 * 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)

Share

About the Author

nthachus
Software Developer (Junior)
Vietnam Vietnam
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141216.1 | Last Updated 22 Sep 2010
Article Copyright 2008 by nthachus
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid