/* TextareaDecorator.js * written by Colin Kuebler 2012 * Part of LDT, dual licensed under GPLv3 and MIT * Builds and maintains a styled output layer under a textarea input layer */ function TextareaDecorator( textarea, parser ){ /* INIT */ var api = this; // construct editor DOM var parent = document.createElement("div"); var output = document.createElement("pre"); parent.appendChild(output); var label = document.createElement("label"); parent.appendChild(label); // replace the textarea with RTA DOM and reattach on label textarea.parentNode.replaceChild( parent, textarea ); label.appendChild(textarea); // transfer the CSS styles to our editor parent.className = 'ldt ' + textarea.className; textarea.className = ''; // turn off built-in spellchecking in firefox textarea.spellcheck = false; // turn off word wrap textarea.wrap = "off"; // coloring algorithm var color = function( input, output, parser ){ var oldTokens = output.childNodes; var newTokens = parser.tokenize(input); var firstDiff, lastDiffNew, lastDiffOld; // find the first difference for( firstDiff = 0; firstDiff < newTokens.length && firstDiff < oldTokens.length; firstDiff++ ) if( newTokens[firstDiff] !== oldTokens[firstDiff].textContent ) break; // trim the length of output nodes to the size of the input while( newTokens.length < oldTokens.length ) output.removeChild(oldTokens[firstDiff]); // find the last difference for( lastDiffNew = newTokens.length-1, lastDiffOld = oldTokens.length-1; firstDiff < lastDiffOld; lastDiffNew--, lastDiffOld-- ) if( newTokens[lastDiffNew] !== oldTokens[lastDiffOld].textContent ) break; // update modified spans for( ; firstDiff <= lastDiffOld; firstDiff++ ){ oldTokens[firstDiff].className = parser.identify(newTokens[firstDiff]); oldTokens[firstDiff].textContent = oldTokens[firstDiff].innerText = newTokens[firstDiff]; } // add in modified spans for( var insertionPt = oldTokens[firstDiff] || null; firstDiff <= lastDiffNew; firstDiff++ ){ var span = document.createElement("span"); span.className = parser.identify(newTokens[firstDiff]); span.textContent = span.innerText = newTokens[firstDiff]; output.insertBefore( span, insertionPt ); } }; api.input = textarea; api.output = output; api.update = function(){ var input = textarea.value; if( input ){ color( input, output, parser ); // determine the best size for the textarea var lines = input.split('\n'); // find the number of columns var maxlen = 0, curlen; for( var i = 0; i < lines.length; i++ ){ // calculate the width of each tab var tabLength = 0, offset = -1; while( (offset = lines[i].indexOf( '\t', offset+1 )) > -1 ){ tabLength += 7 - (tabLength + offset) % 8; } var curlen = lines[i].length + tabLength; // store the greatest line length thus far maxlen = maxlen > curlen ? maxlen : curlen; } textarea.cols = maxlen + 1; textarea.rows = lines.length + 1; } else { // clear the display output.innerHTML = ''; // reset textarea rows/cols textarea.cols = textarea.rows = 1; } }; // detect all changes to the textarea, // including keyboard input, cut/copy/paste, drag & drop, etc if( textarea.addEventListener ){ // standards browsers: oninput event textarea.addEventListener( "input", api.update, false ); } else { // MSIE: detect changes to the 'value' property textarea.attachEvent( "onpropertychange", function(e){ if( e.propertyName.toLowerCase() === 'value' ){ api.update(); } } ); } // initial highlighting api.update(); return api; };