ugrás a tartalomhoz

Dinamikus textarea

tóthika · 2014. Feb. 6. (Cs), 16.59
Sziasztok!

Adott egy textarea elem, melynek 1 sora van.
Ha a felhasználó beír egy karaktert az elembe, akkor meghívódik egy függvény.
Ha a karakter ENTER, akkor a feltétel teljesül.
1) kérdés: Hogyan tudnám azt megoldani, hogy az ENTER leütésével egy sorral nagyobb legyen a <textarea> magassága?
2) kérdés: Hogyan tudnám lekérdezni, hogy a fókusz még mindig rajta van-e az elemen?

Eddigi:
<textarea id="uzenet" rows="3" cols="20" onKeyPress="sor(event, 'uzenet');">
function sor(e, id)
{
	if(e.which == 26)
	{
		document.getElementById(id).rows = document.getElementById(id).rows + 1;
	}
}
 
1

Dokumentáció

Hidvégi Gábor · 2014. Feb. 6. (Cs), 17.11
1, állítsd be a magasságát
2, nézd meg, hogy a textarea elemnek milyen eseményei vannak, abból ki tudod találni

Az 1-es elképzelésed egyébként nem teljes, mert nem csak enter leütésével lehet sortörést bevinni.
2

Fél siker

tóthika · 2014. Feb. 6. (Cs), 17.44
A fókusz ellenőrzése már megvan, de az a fránya rows tulajdonság nem akarja az igazságot!
function sor(e, id)
{
	if(e.which == 13 && document.activeElement.id == id)
	{
		document.getElementById(id).rows = document.getElementById(id).rows + 1;
	}
}
A nem ENTER-rel való sortörés pedig azért nem probléma, mert a textarea csak egy pár sorig növekedne.
5

setAttribute függvény lesz a barátod

gabesz666 · 2014. Feb. 6. (Cs), 21.43
A textarea elemnek van egy setAttribute függvénye, amivel be tudod állítani a magasságot:

function sor(e, id)  
{
    var elem = document.getElementById(id), rows = +elem.getAttribute('rows');

    if(e.which == 13 && document.activeElement.id == id)  
    {  
        elem.setAttribute('rows', rows + 1);
    }  
} 
A megközelítés egyébként amiatt problémás, mert mi van a felhasználó Ctrl + V kombót használ a tartalom beszúrására? Illetve mi van sor törlés esetén? Vagy ha kijelöl mondjuk két sort és egyben törli? Itt inkább a tartalom magasságát kéne kiszámítanod valahogy.
7

Ha már mindenképpen ezzel az

bamegakapa · 2014. Feb. 7. (P), 02.06
Ha már mindenképpen ezzel az inline eseménykezelős modellel akar szenvedni az ember (ne tegye, nem éri meg), akkor is pár kör megspórolható. Az id helyett át lehet adni rögtön egy referenciát az elemre:
<textarea id="uzenet" rows="3" cols="20" onKeyPress="sor(event, this);">
Utána pedig így nézne ki a dolog:
function sor(e, elem) {  
    //a + trükk kevesebb karakter és laza, de a parseInt olvashatóbbá teszi
    var rows = parseInt(elem.getAttribute('rows'), 10);
 
    if(e.which === 13 && document.activeElement === elem) {    
        elem.setAttribute('rows', rows + 1);  
    }    
}
Egyébként igazad van, a getAttribute/setAttribute sokkal jobb megoldás, olvashatóbb, mint a sima property.
3

Én azt tanácsolnám, felejtsd

bamegakapa · 2014. Feb. 6. (Cs), 18.20
Én azt tanácsolnám, felejtsd el ezeket a HTML kódba suvasztott inline eseménykezelőket minél előbb (onkeypress és társai), nehezen átlátható és fejleszthető, valamint logikátlan kódot eredményeznek. Ráadásul a HTML attribútumokban semmi helye Javascript kódnak, csak bonyolítja az életet.

Manapság csak azért találkozni még mindig velük, mert sok ősrégi tutorial még ezeket tanítja.

Javaslom az addEventListener tanulmányozását és a mielőbbi átállást.

A rowsnak meg működnie kéne, gyorsan összedobtam egy kis demót.
4

A válasz

tóthika · 2014. Feb. 6. (Cs), 18.51
Csak úgy érdekességképpen megírtam külön egy html fájlba a textarea elemet és a javascript-et. Úgy látszik, hogy egyébként működik, de valami miatt az oldalba beépítve már nem.
A válaszolóknak pedig köszönöm, a tanácsaikat pedig megjegyzem! :)
6

<textarea rows="1"

Karvaly84 · 2014. Feb. 6. (Cs), 22.34
<textarea rows="1" onkeyup="if (event.keyCode == 13) ++this.rows" onblur="alert(this.value)"></textarea>
De egyébként, ahogy előttem is mondták, érdemes a DOM Event magasabb szintjeire lépni.
8

Anno valamelyik népszerű

Ajnasz · 2014. Feb. 7. (P), 12.11
Anno valamelyik népszerű közösségi oldal megoldását másolva, úgy készítettem erre megoldást, hogy csináltam egy divet, ami ugyanolyan stílussal rendelkezett a textarea: padding, width, height, font-family, font-size, line-height, font-weight stb.
Ez arra szolgált, hogy lemásolja a textarea-t, viszont itt nincs overflow és automatikusan növekszik a magassága, ahogyan új sorok kerülnek bele.
A divet a viewportból kimozgattam, hogy ne látszódjon.

Tetszőlegesen definiált event (keyup, change) esetén a textarea tartalmát a div-be kell másolni, majd lemérni a div magasságát, amit utána beállítani a textarea-ra.
9

Ha esetleg valakinek jól

tóthika · 2014. Feb. 13. (Cs), 23.02
Ha esetleg valakinek jól jönne ez a (mostmár egyszerű) probléma megoldása, akkor tessék:

function meretezes(obj)
{
   document.getElementById(obj).style.height = document.getElementById(obj).scrollHeight + "px";
}
Ez a függvény csak annyit tesz, hogy a függvény változójaként megadunk egy id-t, a textareánk id-jét, amellyel lekérdezzük a szövegdobozban lévő szöveg "magasságát", és ezt beállítja az elem magasságának. A függvény meghívása példában:

<textarea id="textbox" onKeyDown="meretezes('textbox')"></textarea>
Remélem, segítettem néhány embernek! :)

Ui.: Csak az egyszerűség kedvéért írtam ilyen szájbarágósan
10

Az inline onkeydown-ért

bamegakapa · 2014. Feb. 14. (P), 01.49
Az inline onkeydown-ért beírtam egy fekete pontot :).
13

Nem nagy fejtörő, de direkt

tóthika · 2014. Feb. 16. (V), 00.22
Nem nagy fejtörő, de direkt írtam inline eseménykezelővel:

document.getElementById("textbox").addEventListener("keydown", function(){ meretezes("textbox"); });
14

Akkor

Karvaly84 · 2014. Feb. 16. (V), 00.35
Akkor már:

document.getElementById("textbox").addEventListener("keydown", function() {
    this.style.height = this.scrollHeight + "px";
}, false);
De ha ragaszkodsz az inline módhoz, akkor egyből átt is adhatod a függvénynek az elemet:

<textarea id="textbox" onKeyDown="meretezes(this)"></textarea>
Ekkor a meretezes az alábbiak szerint módosul:

function meretezes(obj) {
    obj.style.height = obj.scrollHeight + "px";
}  
15

Nem értem. Ez miért indokolja

bamegakapa · 2014. Feb. 16. (V), 02.15
Nem értem. Ez miért indokolja az inline eseménykezelő alkalmazását?
11

Méret visszacsökkentése

pkadam · 2014. Feb. 14. (P), 19.55
A fenti kód a méretet már nem csökkenti, pedig hasznos lenne – a keydown eseményen kívül pedig sok egyebet is kezel az input event, valamint érdemes a kézi méretezés lehetőségét letiltani. Az alábbi kódminta alapján dinamikusan méreteződő textarea-k gyárthatóak – a használt eseménykezeléshez jQuery plugin szükséges.

<textarea class="autosize"></textarea>

<script>
$('.autosize').css({resize:'none'}).on('input', function() {
	this.style.height = 0;
	this.style.overflow = 'hidden';
	$(this).height(this.scrollHeight);
	this.style.overflow = 'auto';
});
</script>
  • A 7. sorban a jQuery height() függvényét használjuk, mert az számításba veszi a padding-et és a border-t is. (Ennek hiányában előfordulhatna, hogy nem lesz elég magas az elem, és megjelenik a gördítősáv.) Ez megkerülhető a box-sizing, border és padding CSS-tulajdonságok figyelembevételével.
  • Az overflow ideiglenes átállítására azért van szükség, mert 0 magasság esetén megjelenne a gördítősáv, emiatt már akkor magasságnövelést érezne szükségesnek a kód, amikor még van néhány karakternyi hely a sor végén.
  • Kikapcsolt JS esetére UX-szempontból a resize CSS-tulajdonság JS-ben került beállításra.
12

Minimális és maximális méret

pkadam · 2014. Feb. 15. (Szo), 20.18
A kódot érdemes lehet kiegészíteni azzal, hogy a szövegdoboznak legyen egy minimális és maximális magassága. Ez új hozzászólást kíván, mert sokkal összetettebb problémát jelent. A CSS-beli megadásuk még semmi különös:

.autosize {
	min-height: 80px;
	max-height: 400px;
}
A probléma akkor jelentkezik, amikor elértük a maximális magasságot: a tulajdonságok módosítása visszaállítja a gördítősávot felülre, emiatt a kurzor minden beíráskor kicsúszik a látómezőből (ha nem oda gépeltünk épp).

Elmenthetnénk az elem scrollTop tulajdonságát – azonban ez az input esemény után történik, így az azonnal lefutó eseménykezelő még a régi értéket fogja megkapni. Tehát szükséges lenne egy nagyon rövid setTimeout(), így:

function resizer(elem) {
    var scrollTop = elem.scrollTop;
    elem.style.height = 0;
    elem.style.overflow = 'hidden';
    $(elem).height(elem.scrollHeight);
    elem.style.overflow = 'auto';
    elem.scrollTop = scrollTop;
}

$('.autosize').css({resize:'none'}).on('input', function() {
	setTimeout(resizer, 1, this);
});  
Ez működőképes, azonban a setTimeout() miatt egy nagyon rövid időre fel fog villanni a gördítősáv. Ez a használaton nem ront, mégis zavaró lehet. Ha azonban még a setTimeout() meghívása előtt overflow: hidden-t állítunk be, a scrollTop tulajdonságot nem kapjuk meg.

A fentiek afelé mutatnak, hogy a növelési fázis és a max-height elérése utáni állapot eltérő megközelítést kíván. A növelési fázisban nem kell setTimeout(), hiszen még nincs gördítősávunk, tehát a helyzetét sem kell lekérnünk – így annak felvillanása sem zavar az összképbe. A max-height elérése után pedig a setTimeout() már nem okoz problémát. Tehát az eseménykezelő első lépésében egy feltételvizsgálatra lesz szükség, amiben összehasonlítjuk az elem aktuális és maximális magasságát, figyelembe véve azt az esetet, amikor utóbbi nincs megadva (tehát a parseInt() függvény NaN értékkel tér vissza). Továbbá érdemes a túl sűrűn meghívott setTimeout() esetére clearTimeout()-ot alkalmazni a meghívása előtt, különben előfordulhat, hogy hibás scrollTop értéket kapunk (ha például lenyomva tartjuk az Enter billentyűt).

function resizer(elem) {
    var scrollTop = elem.scrollTop;
    elem.style.height = 0;
    elem.style.overflow = 'hidden';
    $(elem).height(elem.scrollHeight);
    elem.style.overflow = 'auto';
    elem.scrollTop = scrollTop;
}

$('.autosize').css({resize:'none'}).on('input', function() {
	var height = parseInt(this.style.height);
	var maxHeight = parseInt($(this).css('max-height'));
	if (isNaN(maxHeight) || height < maxHeight) {
		resizer(this);
	}
	else {
		if (typeof resizerID !== 'undefined') {
			clearTimeout(resizerID);
		}
		resizerID = setTimeout(resizer, 1, this);
	}
});  
Ez gyakorlatilag megoldja az összes problémát, azonban egy Firefox-bug miatt a visszavonás funkciót elérhetetlenné teszi. Ez már a 11-es hozzászólás kódját is érinti, ugyanis az overflow tulajdonság módosítása felelős ezért.

Emiatt nem tudjuk kiküszöbölni a TEXTAREA klónozását, vagyis a szükséges műveleteket a másolt ideiglenes elemen kell végrehajtanunk. Ez azzal az előnnyel is jár, hogy már nincs szükségünk a setTimeout()-ra, így a kód egyszerűsíthető, mivel az átméretezés függvénybe való kiszervezésére sincs szükség. Ezen felül a gördítősáv pozíciójával sem kell foglalkoznunk, mivel az elemünk overflow tulajdonsága érintetlen marad.

Természetesen érdemes azzal az esettel is foglalkozni, ha nem adtunk meg min-height-et (illetve eltér az eredeti magasságtól). Ekkor ugyanis az első leütött karakternél a dobozunk mérete minimálisra csökken, ami a felhasználói élmény szempontjából zavaró lehet.

A végleges kód tehát:

$('.autosize')
	.css({resize: 'none'})
	.on('input', function() {
		var clone = $(this)
			.clone()
			.val($(this).val())
			.insertAfter($(this))
			.css({height: 0, overflow: 'hidden'})
			.get(0);
		$(this).height(clone.scrollHeight);
		$(clone).remove();
	})
	.each(function() {
		if ($(this).css('min-height') !== $(this).css('height')) {
			$(this).css({minHeight: $(this).height()});
		}
	});
16

A hozzászólásod számomra

tóthika · 2014. Feb. 16. (V), 20.00
A hozzászólásod számomra hasznos volt, szóval köszönöm a tippeket, és használni is fogom. :)