Keresztplatformos turbó textarea a Jézuskától
Szeretnék a karácsonykor is minket olvasóknak egy kis meglepetéssel szolgálni, aminek némileg érdekes története van. Egy levelező listás szállal indult, melynek keretén belül szerettem volna egy minél inkább böngésző független módszert találni arra, hogy egy textarea belső területén le tudjam kérdezni illetve be tudjam állítani a kurzor pozícióját, valamint bővíteni tudjam annak szerkesztő funkcióit. Sajnos ezekben a napokban idő hiányában esélyem sem volt, hogy utána járjak a témának. De tegnap jött a váratlan fordulat.
Úgy alakult, hogy Bukarestben karácsonyozom, és épp még egy kis elvarratlan munkának álltam volna neki, de sajnos az itteni kábelnet szolgáltató ezt nem így akarta, másfél napig nem volt internet kapcsolatom. Első bosszúság utáni elővettem ezt a témát, és szerencsére offline anyagokból, illetve a levelezőlistás válaszok alapján sikerült egy működő megoldást találni.
Célom részben az volt, hogy két oldal betöltődés között egy szövegmezőben a kurzor megőrizze a pozícióját, másrészt hogy egy olyan keretet alakítsak ki, amely megfelelő alapot nyújt egy szövegmezőben való szerkesztési funkciók kellő kibővítéséhez. Most ebből két apró, de hasznos funkciót mutatnék be, melyek nagyban segítik programkódok, sablonok szövegmezőben történő szerkesztését: tab leütésekor egy előre beállított behúzást helyez el a szövegben, illetve enter leütésekor automatikusan beszúrja az előző sorból örökölt behúzást. Persze könnyen belátható, hogy innetől kezdve a lehetőségek tárháza végtelen. Több-kevesebb munkával kedvenc editorunk alap funkcióit kedvünk szerint megvalósíthatjuk.
Tekintettel a hátralévő ajándék csomagolási feladataimra, a részletek magyarázása nélkül lentebb megtalálható a forráskód, illetve kipróbálható működés közben is. Így is sok hasznos dolgot kinézhetünk belőle az eseménykezelők diszkrét csatolásától az esemény objektum elérésén át a karakter kódok (módosítók) megszerzéséig és a cél elem megállapításáig.
Alapvetően nem tűnt bonyolultnak a dolog, csak sajnos az a böngésző, mely folyamatosan karban tartja kreativitásunkat, és nem hagy minket ellustulni, ezúttal is feladatokkal látott el. A trükk a
A lenti megoldást Internet Explorer 6.0 és Firefox 1.5 alatt teszteltem, de elvileg minden
Internet Explorer alatt van még egy kis gondom, a tab lenyomásakor úgy tűnik, hogy nem hívódik meg az eseménykezelő függvény, ez még megoldásra vár.
Update: ez már kiküszöbölve, az alábbi módon.
Első körben megpróbáltam
Második körben arra gondoltam, hogy akkor Internet Explorer esetén az
Persze a törpök élete nem csak játék és mese. Ugyanis amikor megnyomunk egy billentyűt, akkor két dologra is kíváncsiak lehetünk: milyen karakter érkezik ennek hatására, illetve melyik billentyűt ütötték le. Ezek megkülönböztetésére a szabvány szerint két külön tulajdonság szolgál: előbbit az
Hogy egy kicsit tisztázódjanak számomra is a dolgok, tettem egy kis hibaellenőrző ablakot a kódba, és ezzel frissítettem a példa oldalt is. Ebből láthatjuk, hogy pontosan milyen eseménykezelő hívódik meg, milyen sorrendben, milyen módosító van lenyomva. Ebből például egyből kiderült egy érdekesség: Internet Explorer alattÉs végül szerintem milyen a jó Karácsony? Rég nem látott rokonokkal találkozós, sírós nevetős, töltött káposztából jól beevős, boldogságos örülős!
Szerintem ilyet kívánok Nektek! :)
[i]Update: ez a thread kapcsolódik a témához.
■ Úgy alakult, hogy Bukarestben karácsonyozom, és épp még egy kis elvarratlan munkának álltam volna neki, de sajnos az itteni kábelnet szolgáltató ezt nem így akarta, másfél napig nem volt internet kapcsolatom. Első bosszúság utáni elővettem ezt a témát, és szerencsére offline anyagokból, illetve a levelezőlistás válaszok alapján sikerült egy működő megoldást találni.
Célom részben az volt, hogy két oldal betöltődés között egy szövegmezőben a kurzor megőrizze a pozícióját, másrészt hogy egy olyan keretet alakítsak ki, amely megfelelő alapot nyújt egy szövegmezőben való szerkesztési funkciók kellő kibővítéséhez. Most ebből két apró, de hasznos funkciót mutatnék be, melyek nagyban segítik programkódok, sablonok szövegmezőben történő szerkesztését: tab leütésekor egy előre beállított behúzást helyez el a szövegben, illetve enter leütésekor automatikusan beszúrja az előző sorból örökölt behúzást. Persze könnyen belátható, hogy innetől kezdve a lehetőségek tárháza végtelen. Több-kevesebb munkával kedvenc editorunk alap funkcióit kedvünk szerint megvalósíthatjuk.
Tekintettel a hátralévő ajándék csomagolási feladataimra, a részletek magyarázása nélkül lentebb megtalálható a forráskód, illetve kipróbálható működés közben is. Így is sok hasznos dolgot kinézhetünk belőle az eseménykezelők diszkrét csatolásától az esemény objektum elérésén át a karakter kódok (módosítók) megszerzéséig és a cél elem megállapításáig.
Alapvetően nem tűnt bonyolultnak a dolog, csak sajnos az a böngésző, mely folyamatosan karban tartja kreativitásunkat, és nem hagy minket ellustulni, ezúttal is feladatokkal látott el. A trükk a
getCursorPosition
függvényben található. Úgy tűnik van egy kis probléma a karakter fogalmával a textarea-ban tárolt szöveg és a belőle létrehozott textRange
objektum szempontjából. Az előbbi az új sort két karakternek veszi, míg ez utóbbi csak egynek, és ebből adódnak a problémák.A lenti megoldást Internet Explorer 6.0 és Firefox 1.5 alatt teszteltem, de elvileg minden
Range
objektumot illetve textarea.selectionStart
tulajdonságot támogató böngészőben működhet. Internet Explorer alatt van még egy kis gondom, a tab lenyomásakor úgy tűnik, hogy nem hívódik meg az eseménykezelő függvény, ez még megoldásra vár.
Update: ez már kiküszöbölve, az alábbi módon.
Első körben megpróbáltam
onkeydown
eseményre tenni az eseménykezelőt, de Firefox esetén ilyenkor nem működött az események alapértelmezett kezelésének kikpacsolása. Erre vonatkozóan nem találtam a fejlesztői dokumentációban információt, tehát ez megítélésem szerint hibának tekinthető.Második körben arra gondoltam, hogy akkor Internet Explorer esetén az
onkeydown
eseményhez is hozzárendelem ugyanezt a kezelőt, az event.type
tulajdonságból úgyis megtudható, hogy mi okozta a függvény meghívását. Persze a törpök élete nem csak játék és mese. Ugyanis amikor megnyomunk egy billentyűt, akkor két dologra is kíváncsiak lehetünk: milyen karakter érkezik ennek hatására, illetve melyik billentyűt ütötték le. Ezek megkülönböztetésére a szabvány szerint két külön tulajdonság szolgál: előbbit az
event
objektum charCode
, utóbbit a charKey
tulajdonságából olvashatjuk ki. Karitatív böngészőnkben viszont csak charKey
van, és az a logikája, hogy onkeydown
, onkeyup
esetén a billentyű, onkeypress
esetében pedig a karakter kódját tartalmazza.Hogy egy kicsit tisztázódjanak számomra is a dolgok, tettem egy kis hibaellenőrző ablakot a kódba, és ezzel frissítettem a példa oldalt is. Ebből láthatjuk, hogy pontosan milyen eseménykezelő hívódik meg, milyen sorrendben, milyen módosító van lenyomva. Ebből például egyből kiderült egy érdekesség: Internet Explorer alatt
onkeypress
esemény esetén is a billentyű kódját kapjuk vissza, ha például shift
is le van nyomva. Tettem egy kis példát a kódba Ctrl+S
kezelésére is.<html>
<head>
<script type="text/javascript">
// Constants
var CONST_IDENT = ' ';
var CONST_NL = "\n";
var CONST_CHARCODE_S = 115;
var CONST_KEYCODE_S = 83;
var CONST_KEYCODE_TAB = 9;
var CONST_KEYCODE_ENTER = 13;
var timer = null;
// Add a handler to an event
// TODO: http://simon.incutio.com/archive/2004/05/26/addLoadEvent
// TODO: http://dean.edwards.name/weblog/2005/09/busted/
function addEventHandler(obj, eventType, handler) {
if (obj.addEventListener) {
obj.addEventListener(eventType, handler, true);
return true;
} else if (obj.attachEvent) {
var r = obj.attachEvent("on"+eventType, handler);
return r;
} else {
return false;
}
}
// Setting up textareas on the page
addEventHandler(window, 'load', setupControls);
// Assign the key <-> action mapping handler function to the textareas
function setupControls() {
var elems = document.getElementsByTagName('TEXTAREA');
for(i = 0; i < elems.length; i++) {
addEventHandler(elems, 'keypress', function(event) {
return controlsKeyHandler(this, event);
});
// For Internet Explorer
// In this browser the onkeypress event doesn't fire on special keys
// so we need to find an alternate way to handle them.
if (document.selection) {
addEventHandler(elems[i], 'keydown', function(event) {
return controlsKeyHandler(this, event);
});
}
}
}
// A helper function for debugging purpose.
function showObj( obj, colNum ) {
var alertStr = '';
var j = 1;
for( var i in obj ) {
switch( i ) {
case 'outerHTML' : continue;
case 'innerHTML' : continue;
}
alertStr += ( '' + i + '->' + obj[i] + "\t" );
if ( j++ % colNum == 0 ) {
alertStr += "\n";
}
}
alert( alertStr );
}
// Key <-> action mapping handler function
// onkeydown parameter shows if the event handler is called by onkeydown
function controlsKeyHandler(textField, event) {
var event = event ? event : (window.event ? window.event : null);
if (event) {
var textField = event.target ? event.target : (event.srcElement ? event.srcElement : null);
if (textField) {
var keyCode = event.charCode ? event.charCode : (event.keyCode ? event.keyCode : event.which);
if (event.modifiers) {
var alt = event.modifiers & Event.ALT_MASK;
var ctrl = event.modifiers & Event.CONTROL_MASK;
var shift = event.modifiers & Event.SCHIFT_MASK;
var meta = event.modifiers & Event.META_MASK;
} else {
var alt = event.altKey;
var ctrl = event.ctrlKey;
var shift = event.shiftKey;
var meta = false;
}
// Some helper variables
var modifiers = alt || ctrl || shift || meta;
var onlyCtrl = ctrl && !(alt || shift || meta);
var onlyAlt = alt && !(ctrl || shift || meta);
var onlyShift = shift && !(alt || ctrl || meta);
var onlyMeta = meta && !(alt || shift || ctrl);
var onkeydown = event.type.toLowerCase() == "keydown";
var onkeypress = event.type.toLowerCase() == "keypress";
debug("|"+event.type+":"+keyCode+"-m:"+modifiers+"-c:"+onlyCtrl+"-a:"+onlyAlt+"-s:"+onlyShift);
// This flag controls if default action should be canceled
var stopPropagation = true;
// Key <-> action mappings
if (onlyCtrl && ((onkeypress && keyCode == CONST_CHARCODE_S) || (onkeydown && keyCode == CONST_KEYCODE_S))) {
controlsSaveTemplate();
} else if (keyCode == CONST_KEYCODE_TAB) {
controlsAddIdent(textField);
} else if (keyCode == CONST_KEYCODE_ENTER) {
controlsKeepIdent(textField);
} else {
stopPropagation = false;
}
if (stopPropagation) {
if (event.returnValue) {
event.returnValue = false;
event.cancelBubble = true;
} else if (event.preventDefault) {
event.preventDefault();
event.stopPropagation()
} else {
return false;
}
}
}
}
}
function debug(msg)
{
var debug = document.getElementById('debug');
if (debug) {
debug.value = msg+"\n"+debug.value;
if (timer) {
clearInterval(timer);
}
timer = setInterval(function() {debug.value = "\n"+debug.value; clearInterval(timer);}, 1000);
}
}
// Call the save button's click() function
function controlsSaveTemplate(textField)
{
alert("save");
}
// Add ident string on pressing TAB
function controlsAddIdent(textField)
{
insertAtCursor(textField, CONST_IDENT);
}
// Keeps the previous line's ident on pressing enter
function controlsKeepIdent(textField)
{
var ident = '';
var position = getCursorPosition(textField, true);
var i = position-1;
while(i > -1 && textField.value.charAt(i) != CONST_NL) {
i--;
}
var currentLine = textField.value.substring(i+1, position);
if (currentLine) {
var match = currentLine.match(/^([\s]+)[\S]+/);
if (match) {
ident = match[1];
}
}
insertAtCursor(textField, CONST_NL+ident);
}
// Insert text before and after the current cursor position
function insertAtCursor(textField, before, after) {
if (after == null) {
after = '';
}
if (textField.createTextRange) {
textField.focus();
var position = getCursorPosition(textField);
// Adding the before/after text to the textRange
var range = document.selection.createRange();
range.text = before+after;
// Moving the cursor to the right position
if (before.length) {
position += before.length;
}
setCursorPosition(textField, position);
} else if (textField.selectionStart ||textField.selectionStart == 0) {
var scrollTop = textField.scrollTop;
var startPos = textField.selectionStart;
var endPos = textField.selectionEnd;
textField.value = textField.value.substring(0, startPos)
+ (before + after)
+ textField.value.substring(endPos, textField.value.length);
setCursorPosition(textField, startPos+before.length, scrollTop);
} else {
var position = textField.value.length+before.length;
textField.value += (before + after);
setCursorPosition(textField, position);
}
}
// Get the current cursor position of a text range
function getCursorPosition(textField, MSIEReturnNotLogicalPosition)
{
// Handling this issue in case of Internet Explorer need a little hack.
// The problem is, that IE handle the concept of new line different
// from text field value and textRange "character" unit point of view.
// In the value the \r,\n charcters exist separately, but when use
// the textRange.move("charcter", 1) new line is only one step.
// That's why we need a little correction.
if (textField.createTextRange) {
var newLineNumAfterCursor = 0;
match = textField.value.match(/\n/g);
if (match) {
newLineNumAfterCursor = match.length;
}
textField.focus();
var position = textField.value.length;
var cursor = document.selection.createRange().duplicate();
while (cursor.parentElement() == textField && cursor.move('character', 1)) {
if (textField.value.charAt(position - 1) == CONST_NL) {
position -= 1;
newLineNumAfterCursor--;
}
position--;
}
if (MSIEReturnNotLogicalPosition) {
return position + 1;
} else {
return position + 1 - (newLineNumAfterCursor);
}
} else if (textField.selectionStart || textField.selectionStart == 0) {
textField.focus();
return textField.selectionStart;
}
}
// Set the current cursor position of a text range
function setCursorPosition(textField, position, scrollTop)
{
if (textField.createTextRange) {
var textRange = textField.createTextRange();
textRange.moveStart("character", position);
textRange.collapse();
textRange.select();
} else if (textField.selectionStart) {
textField.focus();
textField.selectionStart = textField.selectionEnd = position;
}
if (typeof scrollTop != "undefined" && (textField.scrollTop || textField.scrollTop == 0)) {
textField.scrollTop = scrollTop;
}
}
</script>
</head>
<body onload="setCursorPosition(document.getElementById('foo'), 522);">
<a href="#" onmouseover="alert(getCursorPosition(document.getElementById('foo'))); return false;">foo</a>
<a href="#" onmouseover="alert(document.getElementById('foo').scrollTop); return false;">bar</a>
<form>
<textarea name="foo" id="foo" rows="12" cols="50">
1111111111 2222222222 3333333333 4444444444 5555555555
</textarea>
<textarea id="debug" rows="40" cols="70"></textarea>
<input type="submit">
</form>
</body>
</html>
Szerintem ilyet kívánok Nektek! :)
[i]Update: ez a thread kapcsolódik a témához.
Nálunk is töltött káposztva van ! :)
Mr.Tiny
<Nincs cím>
Így lehetőséged lesz burjánzó kreativitásod termékét megosztani velünk!
új sor
\r\n
vs\n
?inkább "fizikai" vs "logikai"
textRange
is. Inkább azt tudom elkézpzelni, hogy amove
metódusa használatakor"character"
mértékegység esetén egynek veszi ezt a kettőt, mint ahogy amikor lépkedsz a szövegben, ott is csak egyszer kell a nyilat megnyomnod, amikor a sorvégére érsz.Felhő
update
Felhő
kis bugfix
setCursorPosition
függvénynek most már ezt paraméterként is meg lehet adni, így például kedvünkre beállíthatjuk betöltődés után is.Felhő
include() probléma
De egy nagy problémám akadt:
Van egy oldal, amiben benne van a textarea, a megfelelő JS kódokkal. Önállóan tökéletesen működik, viszont ha egy másik fájl ágyazza be include()-parancscsal, hibát jelez. (text is not definied)(a text a textarea neve) vagypedig (ha a textet definiálom az elején) textField has no properties.
Nagyon kezdő vagyok JS-ben, lehet, h hatalmas elvi hibát vétettem, de önerőből nem tudom kiküszöbölni.
Segítségeteket előre is köszönöm!
include?
Mit szeretnél pontosan az include paranccsal elérni? Ez arra való, hogy egy PHP fájlba behúzz egy másik PHP fájlt, nem pedig, hogy JS kódot illessz az oldalba.
Meg amúgy is ilyen esetekben szerencsés, ha példakódot is mutatsz, anélkül elég nehéz látatatlanban bármit is mondani.
Felhő
JS a PHP-ben
kód nélkül esélytelen
Felhő
Újra
És még1x köszönet érte, mert sok jó ötletet egyesít újrafelhasználhatóan!