2010-05-09 22 views
10

Tôi đang làm việc trên một plugin jQuery cho phép bạn thực hiện các thẻ kiểu @username như Facebook trong hộp nhập cập nhật trạng thái của chúng.Tự động hoàn thành giống như thẻ và di chuyển con trỏ/con trỏ trong các phần tử có thể chỉnh sửa

Vấn đề của tôi là, thậm chí sau nhiều giờ nghiên cứu và thử nghiệm, có vẻ như thực sự khó để đơn giản di chuyển dấu mũ. Tôi đã quản lý để tiêm các thẻ <a> với tên của một ai đó, nhưng đặt dấu mũ sau khi nó có vẻ như khoa học tên lửa, đặc biệt nếu nó được cho là làm việc trong tất cả các trình duyệt.

Và tôi đã thậm chí không nhìn vào thay thế gõ @username văn bản với thẻ nào, thay vì chỉ tiêm nó như tôi đang làm ngay bây giờ ... lol

Có một tấn câu hỏi về làm việc với contenteditable ở đây trên Stack Overflow, và tôi nghĩ rằng tôi đã đọc tất cả trong số họ, nhưng họ không thực sự bao gồm đúng những gì tôi cần. Vì vậy, bất kỳ thông tin nào mà bất kỳ ai cũng có thể cung cấp đều sẽ tuyệt vời :)

+0

Bạn đã bao giờ tìm thấy một giải thích thêm? Tôi đã đăng một câu hỏi tương tự trên http://stackoverflow.com/questions/3764273/jquery-facebook-like-autosuggest-triggered-by và http://stackoverflow.com/questions/3972014/get-caret-position-in- contenteditable-div nhưng không may mắn ... – Bertvan

+0

Tôi hoàn toàn có thể thông cảm với sự thiếu trợ giúp về nội dung có thể chỉnh sửa! Gần đây tôi phải tự mình làm rất nhiều việc này. –

Trả lời

1

Như bạn nói bạn đã có thể chèn thẻ vào dấu mũ, tôi sẽ bắt đầu từ đó. Điều đầu tiên cần làm là cung cấp cho thẻ của bạn một id khi bạn chèn nó. Sau đó bạn sẽ có một cái gì đó như thế này:

<div contenteditable='true' id='status'>I went shopping with <a href='#' id='atagid'>Jane</a></div>

Đây là một chức năng mà nên đặt con trỏ ngay sau thẻ.

function setCursorAfterA() 
{ 
    var atag = document.getElementById("atagid"); 
    var parentdiv = document.getElementById("status"); 
    var range,selection; 
    if(window.getSelection) //FF,Chrome,Opera,Safari,IE9+ 
    { 
     parentdiv.appendChild(document.createTextNode(""));//FF wont allow cursor to be placed directly between <a> tag and the end of the div, so a space is added at the end (this can be trimmed later) 
     range = document.createRange();//create range object (like an invisible selection) 
     range.setEndAfter(atag);//set end of range selection to just after the <a> tag 
     range.setStartAfter(atag);//set start of range selection to just after the <a> tag 
     selection = window.getSelection();//get selection object (list of current selections/ranges) 
     selection.removeAllRanges();//remove any current selections (FF can have more than one) 
     parentdiv.focus();//Focuses contenteditable div (necessary for opera) 
     selection.addRange(range);//add our range object to the selection list (make our range visible) 
    } 
    else if(document.selection)//IE 8 and lower 
    { 
     range = document.body.createRange();//create a "Text Range" object (like an invisible selection) 
     range.moveToElementText(atag);//select the contents of the a tag (i.e. "Jane") 
     range.collapse(false);//collapse selection to end of range (between "e" and "</a>"). 
     while(range.parentElement() == atag)//while ranges cursor is still inside <a> tag 
     { 
      range.move("character",1);//move cursor 1 character to the right 
     } 
     range.move("character",-1);//move cursor 1 character to the left 
     range.select()//move the actual cursor to the position of the ranges cursor 
    } 
    /*OPTIONAL: 
    atag.id = ""; //remove id from a tag 
    */ 
} 

EDIT: Tested và kịch bản cố định. Nó chắc chắn hoạt động trong IE6, chrome 8, firefox 4, và opera 11. Không có các trình duyệt khác để kiểm tra, nhưng nó không sử dụng bất kỳ chức năng nào đã thay đổi gần đây nên nó hoạt động trong bất cứ thứ gì hỗ trợ contenteditable.

Nút này rất thuận tiện để thử nghiệm: <input type='button' onclick='setCursorAfterA()' value='Place Cursor After &lt;a/&gt; tag' >

Nico

5

Bạn có thể sử dụng my Rangy library, mà cố gắng với một số thành công để bình thường hóa việc triển khai nhiều trình duyệt và lựa chọn. Nếu bạn đã quản lý để chèn <a> như bạn nói và bạn đã có nó trong một biến gọi là aElement, bạn có thể làm như sau:

var range = rangy.createRange(); 
range.setStartAfter(aElement); 
range.collapse(true); 
var sel = rangy.getSelection(); 
sel.removeAllRanges(); 
sel.addRange(range); 
+1

+1 cho thư viện, đây sẽ là một trợ giúp lớn – Bertvan

+0

Câu hỏi của bạn đã khiến tôi quan tâm và tôi đã viết một số mã chung để thực hiện loại điều này. Tôi sẽ đăng lại sớm. –

3

tôi đã quan tâm đến việc này, vì vậy tôi đã viết những khởi đầu cho một giải pháp đầy đủ. Sau đây sử dụng Rangy library của tôi với selection save/restore module để lưu và khôi phục lựa chọn và bình thường hóa các vấn đề trình duyệt chéo. Nó bao quanh tất cả các văn bản phù hợp (@ bất cứ điều gì trong trường hợp này) với một yếu tố liên kết và vị trí lựa chọn mà nó đã được trước đó. Điều này được kích hoạt sau khi không có hoạt động bàn phím trong một giây. Nó nên được khá tái sử dụng.

function createLink(matchedTextNode) { 
    var el = document.createElement("a"); 
    el.style.backgroundColor = "yellow"; 
    el.style.padding = "2px"; 
    el.contentEditable = false; 
    var matchedName = matchedTextNode.data.slice(1); // Remove the leading @ 
    el.href = "http://www.example.com/?name=" + matchedName; 
    matchedTextNode.data = matchedName; 
    el.appendChild(matchedTextNode); 
    return el; 
} 

function shouldLinkifyContents(el) { 
    return el.tagName != "A"; 
} 

function surroundInElement(el, regex, surrounderCreateFunc, shouldSurroundFunc) { 
    var child = el.lastChild; 
    while (child) { 
     if (child.nodeType == 1 && shouldSurroundFunc(el)) { 
      surroundInElement(child, regex, surrounderCreateFunc, shouldSurroundFunc); 
     } else if (child.nodeType == 3) { 
      surroundMatchingText(child, regex, surrounderCreateFunc); 
     } 
     child = child.previousSibling; 
    } 
} 

function surroundMatchingText(textNode, regex, surrounderCreateFunc) { 
    var parent = textNode.parentNode; 
    var result, surroundingNode, matchedTextNode, matchLength, matchedText; 
    while (textNode && (result = regex.exec(textNode.data))) { 
     matchedTextNode = textNode.splitText(result.index); 
     matchedText = result[0]; 
     matchLength = matchedText.length; 
     textNode = (matchedTextNode.length > matchLength) ? 
      matchedTextNode.splitText(matchLength) : null; 
     surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true)); 
     parent.insertBefore(surroundingNode, matchedTextNode); 
     parent.removeChild(matchedTextNode); 
    } 
} 

function updateLinks() { 
    var el = document.getElementById("editable"); 
    var savedSelection = rangy.saveSelection(); 
    surroundInElement(el, /@\w+/, createLink, shouldLinkifyContents); 
    rangy.restoreSelection(savedSelection); 
} 

var keyTimer = null, keyDelay = 1000; 

function keyUpLinkifyHandler() { 
    if (keyTimer) { 
     window.clearTimeout(keyTimer); 
    } 
    keyTimer = window.setTimeout(function() { 
     updateLinks(); 
     keyTimer = null; 
    }, keyDelay); 
} 

HTML:

<p contenteditable="true" id="editable" onkeyup="keyUpLinkifyHandler()"> 
    Some editable content for @someone or other 
</p> 
+0

tuyệt vời. Tôi đang cố gắng mở rộng điều này bằng cách sử dụng rangy để đánh dấu URL khi bạn nhập. Tuy nhiên dường như có một số vấn đề - một khi liên kết được phát hiện, nó không thể được chỉnh sửa, và thiết lập contentEditable thành true bên trong thẻ A phá vỡ cũng phá vỡ mọi thứ. Có cách nào tốt hơn cho việc này không? –

+0

@MattRoberts: Tôi không nghĩ rằng có một cách tiếp cận cơ bản tốt hơn, tốt hơn, thực hiện cẩn thận hơn. Điều này được dự định là một điểm khởi đầu thực sự. –

+0

Cảm ơn Tim. Tôi đã tự hỏi liệu mô-đun đánh dấu mới của bạn có phù hợp với loại nhiệm vụ này không? –

Các vấn đề liên quan