2016-07-16 162 views
6

Ho una serie di div multilinea contenteditable disposti verticalmente, e voglio consentire la naturale navigazione tra di loro da tasti freccia (come se fosse un documento). Per questo, il keydown evento devo:Rendere più contenteditables si comportano come un documento

  • Conoscere riga corrente del cursore e numero di righe per determinare se è necessario salire (prima riga e premuto ↑ chiave) o giù (ultima riga e ↓ chiave)
  • sapere corrente carattere (posizione in una stringa illustrato) per determinare se è necessario salire (posizione == 0 e premere il tasto ← ed) o giù (posizione == text.length e → premuto)
  • Il processo non dovrebbe arrestarsi tra gli elementi di commutazione quando la chiave si svolge e non rilasciato (quindi keydown evento, non keyup)
  • Preferibilmente : L'evento dovrebbe smettere di propagare (ad esempio, se sono sulla prima colonna nell'ultima riga e preme la chiave ↓, non dovrebbe saltare all'ultimo carattere sulla linea e quindi scendere)
  • Preferibilmente (sarebbe davvero fantastico): dopo aver saltato al prossimo elemento, non avremmo solo l'elemento .focus(), ma emuleremo un clic nella stessa posizione verticale come quando eravamo prima, in modo che sembrasse naturale, come negli editor di testo.

Tutti gli script/librerie che ho trovato fino ad oggi non stanno facendo tutto ciò di cui ho bisogno o sono bug. Per favore includi dei demo nei tuoi suggerimenti, in modo che io possa testare senza incorporare prima il mio codice. Grazie!

Aggiornamento: visivo spiegazione - di notare che ci sono più di 2 div e 'tasto freccia giù sull'ultima riga' è solo uno dei quattro trigger

enter image description here

+0

Cosa vuoi ottenere esattamente?Qualcosa che assomiglia a Excel? Quando fai clic su un testo di seguito, dove vuoi inserire il cursore? Alla fine della fila? Dove hai cliccato? Come vengono caricati i contenuti contentedabili nel DOM? Lo carichi con JS o direttamente in HTML? Hai colonne o solo righe? –

+0

@ R.Foubert, no, non Excel, ma come editor di blocco note o WYSIWYG. Non ci sono colonne, solo paragrafi con righe/linee, e i paragrafi devono essere blocchi div separati. Questi blocchi vengono caricati da AJAX e salvati da AJAX. Quando fai clic sul testo, il punto di inserimento dovrebbe essere al pixel cliccato, proprio come qualsiasi editor. –

+0

e solo un'altra domanda. Perché hai bisogno di contenteditable? Ad esempio, potresti usare textarea, quindi perché hai scelto contenteditable? –

risposta

3

ho già scritto un po 'di codice ma non è tutto finito ... Forse puoi iniziare con quello e provare a completare quello che ho fatto se vuoi;) Continuerò a lavorarci questa settimana per fornirti una soluzione .. Ecco cosa ho fatto finora:

var ajaxResult = [ 
 
    "Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates exitialis certamina cogebatur", 
 
    "Inter has ruinarum varietates a Nisibi quam tuebatur accitus", 
 
    "Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates a Nisibi quamos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates exitialis certamina cogebatur", 
 
]; 
 
/************************************************************* 
 
* 
 
* \t LIST OF CONTENT EDITABLE DIVS MANAGEMENT 
 
* 
 
**************************************************************/ 
 
// Create the editable divs 
 
window.onload = function(){ 
 
    var contentEditables = createContentEditables(); 
 
    document.body.appendChild(contentEditables); 
 
} 
 

 
// Remember all the content editable elements in the order they appear in the dom 
 
var _currentEdit, 
 
\t _edits = []; 
 
\t 
 
function createContentEditables(){ 
 
    var div; 
 
    var result = document.createDocumentFragment(); 
 
    for (var i = 0, n = ajaxResult.length ; i < n ; i++){ 
 
    div = createContentEditable(ajaxResult[i]); 
 
    _edits.push(div); 
 
    result.appendChild(div); 
 
    } 
 

 
    return result; 
 
} 
 

 
function getPreviousEdit(edit){ 
 
    // Search for the edit index 
 
    var index = _edits.indexOf(edit); 
 

 
    if(index == 0) 
 
    return; 
 

 
    // Return the previous one 
 
    return _edits[index - 1]; 
 
} 
 

 
function getNextEdit(edit){ 
 
    // Search for the edit index 
 
    var index = _edits.indexOf(edit); 
 

 
    if(index == _edits.length - 1) 
 
    return; 
 

 
    // Return the previous one 
 
    return _edits[index + 1]; 
 
} 
 

 
/************************************************************* 
 
* 
 
* \t CONTENT EDITABLE MANAGEMENT 
 
* 
 
**************************************************************/ 
 
// We need to define the line height of the div to be able to retrieve the number of lines 
 
var LINE_HEIGHT = 16; 
 

 
// variables to keep trace of relevant information about the div 
 
var _lines, _caretPosition; 
 

 
/* 
 
* Create a div with contenteditable set to true with the text 
 
* received from the server 
 
*/ 
 
function createContentEditable(text){ 
 
    var element = document.createElement('div'); 
 
    element.className = 'contenteditable'; 
 
    element.innerHTML = text; 
 
    element.style.lineHeight = LINE_HEIGHT + 'px'; 
 
    element.setAttribute('contenteditable', true); 
 

 
    // Set listeners 
 
    element.addEventListener('mouseup', onEdit_mouseup); 
 
    element.addEventListener('keydown', onEdit_keydown); 
 
    element.addEventListener('focus', onEdit_focus); 
 

 
    return element; 
 
} 
 

 
function onEdit_keydown(domEvent){ 
 
    // Update caret position 
 
    _caretPosition = getCaretPosition(domEvent.target); 
 
    switch(domEvent.keyCode){ 
 
    case 37: // left arrow 
 
     if (_caretPosition.index == 0){ 
 
     var previousEdit = getPreviousEdit(domEvent.target); 
 
     if(previousEdit){ 
 
      console.log("go to end of previous edit"); 
 
      console.log(previousEdit); 
 
      previousEdit.focus(); 
 
     } 
 
     } 
 
     break; 
 
    case 38: // up arrow 
 
     if (_caretPosition.line == 1){ 
 
     var previousEdit = getPreviousEdit(domEvent.target); 
 
     if(previousEdit){ 
 
      console.log("go to previous edit keeping the caret offset"); 
 
      console.log(previousEdit); 
 
      previousEdit.focus(); 
 
     } 
 
     } 
 
     break; 
 
    case 39: // right arrow 
 
     if (_caretPosition.index == domEvent.target.innerHTML.length){ 
 
     var nextEdit = getNextEdit(domEvent.target); 
 
     if(nextEdit){ 
 
      console.log("go to beginning of next edit"); 
 
      console.log(nextEdit); 
 
      nextEdit.focus(); 
 
     } 
 
     } 
 
     break; 
 
    case 40: // down arrow 
 
     if (_caretPosition.line == getLines(domEvent.target)){ 
 
     var nextEdit = getNextEdit(domEvent.target); 
 
     if(nextEdit){ 
 
      console.log("go to next edit keeping the caret offset"); 
 
      console.log(nextEdit); 
 
      nextEdit.focus(); 
 
     } 
 
     } 
 
     break; 
 
    } 
 
} 
 

 
function onEdit_mouseup(domEvent){ 
 
    // Update caret position 
 
    _caretPosition = getCaretPosition(domEvent.target); 
 
} 
 

 
function onEdit_focus(domEvent){ 
 
    // Add listeners 
 
    _currentEdit = domEvent.target; 
 
    _currentEdit.addEventListener('blur', onEdit_blur); 
 
    window.addEventListener('resize', onWindow_resize); 
 
} 
 

 
function onEdit_blur(domEvent){ 
 
    // Remove listeners 
 
    domEvent.target.removeEventListener('blur', onEdit_blur); 
 
    window.removeEventListener('resize', onWindow_resize); 
 
} 
 

 
function onWindow_resize(domEvent){ 
 
    // Update caret position 
 
    _caretPosition = getCaretPosition(_currentEdit); 
 
} 
 

 
/************************************************************* 
 
* 
 
* \t HELPERS 
 
* 
 
**************************************************************/ 
 
//http://stackoverflow.com/questions/4811822/get-a-ranges-start-and-end-offsets-relative-to-its-parent-container/4812022#4812022 
 
//http://stackoverflow.com/questions/5528004/how-to-get-number-of-rows-in-contenteditable-area-and-current-caret-line-positio 
 
function getCaretPosition(element){ 
 
    var caretPosition = {index: 0, line: 0}; 
 
    var doc = element.ownerDocument || element.document; 
 
    var win = doc.defaultView || doc.parentWindow; 
 
    var elemOffsetTop = element.offsetTop; 
 
    var sel; 
 
    // Get the x position of the caret 
 
    if (typeof win.getSelection != "undefined") { 
 
    sel = win.getSelection(); 
 
    if (sel.rangeCount > 0) { 
 
     var range = win.getSelection().getRangeAt(0); 
 
     // Retrieve the current line 
 
     var rects = range.getClientRects(); 
 
     var caretOffsetTop; 
 
     if (typeof rects[1] != "undefined"){ 
 
     caretOffsetTop = rects[1].top; 
 
     } 
 
     else if (typeof rects[0] != "undefined"){ 
 
     caretOffsetTop = rects[0].top; 
 
     } 
 
     else{ 
 
     // Create dummy element to get y position of the caret 
 
     var dummy = document.createElement('CANVAS'); 
 
     dummy.id = 'findCaretHelper'; 
 
     range.insertNode(dummy); 
 
     caretOffsetTop = dummy.offsetTop; 
 
     element.removeChild(dummy); 
 
     } 
 

 
     var preCaretRange = range.cloneRange(); 
 
     preCaretRange.selectNodeContents(element); 
 
     preCaretRange.setEnd(range.endContainer, range.endOffset); 
 

 
     // Remember caret position 
 
     caretPosition.index = preCaretRange.toString().length; 
 
     caretPosition.line = Math.ceil((caretOffsetTop - elemOffsetTop)/LINE_HEIGHT) + 1; 
 
    } 
 
    } 
 
    // support ie 
 
    //else if ((sel = doc.selection) && sel.type != "Control") { 
 
    //var textRange = sel.createRange(); 
 
    //var preCaretTextRange = doc.body.createTextRange(); 
 
    //preCaretTextRange.moveToElementText(element); 
 
    //preCaretTextRange.setEndPoint("EndToEnd", textRange); 
 
    //caretPosition.x = preCaretTextRange.text.length; 
 
    //} 
 

 
    return caretPosition; 
 
} 
 

 
function getLines(element){ \t 
 
    return element.clientHeight/LINE_HEIGHT;; 
 
}
.contenteditable{ 
 
    border: solid 1px #aaa; 
 
    margin: 10px 0; 
 
}

sono riuscito ottenere informazioni sulla riga corrente, l'indice di carattere corrente nel contenuto modificabile div e alcune altre cose ... devo ancora lavorare sulla messa a fuoco di un altro contenuto div modificabili al fine di metti il ​​punto d'accesso nel posto giusto ... Spero che questo inizio di una soluzione ti possa aiutare!

+0

Questo è ciò di cui ho bisogno! Rimangono solo due piccole cose: l'evento viene sparato dopo il salto (quindi quando premi il tasto freccia giù, finisci sulla seconda riga del prossimo elemento invece del 1 °), ma penso che sia facilmente risolvibile tramite event.stopPropagation. E il secondo, come hai già detto, sarebbe davvero fantastico se avessi trovato un modo per emulare il clic sulla stessa posizione orizzontale di prima di un salto. Questa sarebbe la soluzione perfetta. Grazie! –

+0

Sì, ho ancora qualche bug ma penso che sia la strada da percorrere;) Sono stato libero di migliorare quello che ho fatto se hai un po 'di tempo ... cercherò di finire questa settimana come ti ho detto;) ahah sei il benvenuto;) PS: solo qualcosa da notare, ho dovuto usare un'altezza di linea fissa per essere in grado di gestire le linee di conteggio ecc ... Spero che tu stia bene con quello? –

+0

Posso vivere con l'altezza della linea fissa, ma penso che possiamo fare di meglio. Che dire sull'inserimento di una estensione fittizia 1x1 alla fine del contenteditable e supponiamo che se la posizione verticale del cursore> = l'offset di questa spazia Y, che deve essere l'ultima riga? Cosa ne pensi? Ciò dovrebbe ottenere risultati più affidabili dei calcoli basati sull'altezza della linea. –

0

Si potrebbe semplicemente rendere l'elemento genitore/elemento contenente contenteditable in contrapposizione a ciascun paragrafo. Ciò aggiungerà/rimuoverà automaticamente i tag p di conseguenza.

https://jsfiddle.net/ewesmwmv/2/

+0

Giusto, sarebbe una soluzione davvero semplice, ma sfortunatamente è impossibile. Deve essere più contenteditables, perché vengono caricati tramite AJAX, automaticamente, e rappresentano record separati (nodi) nel database. –

+0

Non potresti, caricare il contenuto tramite AJAX aggiungendolo al genitore modificabile, dargli una qualche forma di identificatore (in modo che possiamo rintracciare dove deve andare), quindi al salvataggio, semplicemente dividerli di nuovo e salvarli nelle posizioni corrispondenti ... Fare così sembra un approccio molto più diretto. – connorb

+0

Sì, ho semplificato la domanda, ma nella mia applicazione è in realtà un elenco gerarchico, in cui ogni nodo ha un punto a sinistra.Se riesco a renderlo tutto contenuto, gli utenti saranno in grado di eliminare i proiettili e fare altre cose sgradevoli per rompere il layout, e questo è un comportamento indesiderato. Spero che questo aiuti a chiarire le condizioni iniziali della domanda. –

Problemi correlati