├── README.md ├── LICENSE ├── jquery.toTextarea.min.js └── jquery.toTextarea.js /README.md: -------------------------------------------------------------------------------- 1 | jquery.toTextarea.js 2 | ==================== 3 | 4 | Makes a div act like a textarea to allow auto resizing and formatting options. 5 | 6 | Also allows drag and drop images from desktop. 7 | 8 | Demo 9 | ==== 10 | 11 | http://jsfiddle.net/UziTech/4msdgjox/ 12 | 13 | Usage 14 | ===== 15 | 16 | ```html 17 |
18 | ``` 19 | ```javascript 20 | $(".textarea").toTextarea({ 21 | allowHTML: false,//allow HTML formatting with CTRL+b, CTRL+i, etc. 22 | allowImg: false,//allow drag and drop images 23 | singleLine: false,//make a single line so it will only expand horizontally 24 | pastePlainText: true,//paste text without styling as source 25 | placeholder: false//a placeholder when no text is entered. This can also be set by a placeholder="..." or data-placeholder="..." attribute 26 | }); 27 | ``` 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Tony Brix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /jquery.toTextarea.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t,a,i){var n=function(e,i){var n=null;if(void 0!==a.body.createTextRange)n=a.body.createTextRange(),n.moveToPoint(e,i),n.select(),n=t.getSelection().getRangeAt(0);else if(void 0!==a.createRange)if(a.caretPositionFromPoint){var r=a.caretPositionFromPoint(e,i);n=a.createRange(),n.setStart(r.offsetNode,r.offset),n.collapse(!0)}else a.caretRangeFromPoint&&(n=a.caretRangeFromPoint(e,i));return n},r=function(e){for(var t=e.length-1;t>=0;t--)if(e[t].childNodes.length>0){var a=r(e[t].childNodes);if(null!==a)return a}else if(""!==e[t].data)return e[t];return null},l=function(i,l,o){if("string"==typeof i){var s=t.getSelection(),d=i.substring(i.length-1),c=e(this).text(),f=a.createTextNode("\n");""!==c&&(lastChar=c.substring(c.length-1),lastNode=r(this.childNodes));var h="\n"===d&&"\n"!==lastChar&&(null===lastChar||s.anchorNode===lastNode&&s.anchorOffset===lastNode.length||s.focusNode===lastNode&&s.focusOffset===lastNode.length),g=a.createTextNode(i),u=null;u=void 0!==l&&void 0!==o?n(l,o):s.getRangeAt(0),u.deleteContents(),h&&u.insertNode(f),u.insertNode(g),u=a.createRange(),u.setStartAfter(g),u.collapse(!0),s.removeAllRanges(),s.addRange(u),this.normalize()}},o=function(a,i,r){var l=e(this),o=new FileReader;o.onload=function(e){var a=new Image;a.onload=function(){var e=t.getSelection(),a=n(i,r);null!==a?(a.insertNode(this),a.collapse(!1),e.removeAllRanges(),e.addRange(a)):l.is(":focus")?(a=e.getRangeAt(0),a.collapse(!1),a.insertNode(this),a.collapse(!1),e.removeAllRanges(),e.addRange(a)):l.append(this)},a.onerror=function(){alert("Not an image")},a.src=e.target.result},o.readAsDataURL(a)},s=function(){if(a.body.createTextRange){var e=a.body.createTextRange();e.moveToElementText(this),e.select()}else if(t.getSelection){var i=t.getSelection(),e=a.createRange();e.selectNodeContents(this),i.removeAllRanges(),i.addRange(e)}};e.fn.toTextarea=function(a){if("destroy"===a||!0===a)return this.each(function(){var t=e(this);t.data().isTextarea&&(this.id&&e("label[for='"+this.id+"']").off(".toTextarea"),t.prop({contentEditable:!1}).off(".toTextarea").data({isTextarea:!1}).removeClass("toTextarea-disabled toTextarea-placeholder toTextarea"))});if("disable"===a)return this.each(function(){var t=e(this);t.data().isTextarea&&!t.data().disabled&&(this.id&&e("label[for='"+this.id+"']").off(".toTextarea"),t.prop({contentEditable:!1}).data({disabled:!0}).addClass("toTextarea-disabled"))});if("enable"===a)return this.each(function(){var t=e(this);t.data().isTextarea&&t.data().disabled&&(this.id&&e("label[for='"+this.id+"']").on("click.toTextarea",function(){t.focus()}),t.prop({contentEditable:!0}).data({disabled:!1}).removeClass("toTextarea-disabled"))});var i={allowHTML:!1,allowImg:!1,singleLine:!1,pastePlainText:!0,placeholder:!1};return e.isPlainObject(a)&&e.extend(i,a),this.each(function(){var a=e(this);if(!a.data().isTextarea&&1){this.id&&e("label[for='"+this.id+"']").on("click.toTextarea",function(){a.focus()});var n=i.allowHTML;"function"==typeof i.allowHTML&&(n=i.allowHTML.call(this));var r=i.allowImg;"function"==typeof i.allowImg&&(r=i.allowImg.call(this));var d=i.singleLine;"function"==typeof i.singleLine&&(d=i.singleLine.call(this));var c=i.pastePlainText;"function"==typeof i.pastePlainText&&(c=i.pastePlainText.call(this));var f=i.placeholder;"function"==typeof i.placeholder&&(f=i.placeholder.call(this)),f||(f=a.attr("placeholder")||a.data().placeholder),a.addClass("toTextarea").prop({contentEditable:!0}).data({isTextarea:!0,disabled:!1}).on("select.toTextarea",function(){e(this).data().disabled||s.call(this)}).on("keypress.toTextarea keyup.toTextarea",function(){e(this).trigger("input")}),f&&a.attr({"data-placeholder":f}).addClass("toTextarea-placeholder"),d?(a.addClass("toTextarea-singleLine").on("keypress.toTextarea",function(t){if(!e(this).data().disabled&&13===t.which)return t.preventDefault(),!1}),a.on("paste.toTextarea",function(a){if(!e(this).data().disabled){var i=null;if(t.clipboardData)i=t.clipboardData.getData("Text");else{if(!a.originalEvent.clipboardData)return!0;i=a.originalEvent.clipboardData.getData("text/plain")}return i=i.replace(/[\n]/g," "),l.call(this,i),a.preventDefault(),e(this).trigger("input"),!1}}).on("drop.toTextarea",function(t){if(!e(this).data().disabled){var a=null;return a=t.originalEvent.dataTransfer.getData("text"),a=a.replace(/[\n]/g," "),l.call(this,a,t.originalEvent.clientX,t.originalEvent.clientY),t.preventDefault(),e(this).trigger("input"),!1}})):(a.on("keypress.toTextarea",function(t){if(!e(this).data().disabled&&13===t.which)return l.call(this,"\n"),t.preventDefault(),!1}),n&&!c||a.on("paste.toTextarea",function(a){if(!e(this).data().disabled){var i=null;if(t.clipboardData)i=t.clipboardData.getData("Text");else{if(!a.originalEvent.clipboardData)return!0;i=a.originalEvent.clipboardData.getData("text/plain")}return l.call(this,i),a.preventDefault(),e(this).trigger("input"),!1}}).on("drop.toTextarea",function(t){if(!e(this).data().disabled){var a=null;return a=t.originalEvent.dataTransfer.getData("text"),l.call(this,a,t.originalEvent.clientX,t.originalEvent.clientY),t.preventDefault(),e(this).trigger("input"),!1}})),r&&a.on("drop.toTextarea",function(t){if(!e(this).data().disabled&&t.originalEvent.dataTransfer.files.length>0){for(var a=0,i=t.originalEvent.dataTransfer.files.length;a0&&"Files"===t.originalEvent.dataTransfer.types[0])return t.preventDefault(),!1}),n||a.on("keydown.toTextarea",function(t){if(!e(this).data().disabled&&t.ctrlKey&&(66===t.which||73===t.which||75===t.which||85===t.which))return t.preventDefault(),!1})}})},e(function(){var t=e(""),a=e("head link[rel='stylesheet'], head style");a.length>0?a.eq(0).before(t):e("head").append(t)})}(jQuery,window,document); -------------------------------------------------------------------------------- /jquery.toTextarea.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Tony Brix, http://tonybrix.info 3 | * License: MIT 4 | * Version: 0.4.0 5 | */ 6 | 7 | ; 8 | (function ($, window, document, undefined) { 9 | // modified from http://stackoverflow.com/a/12924488/806777 10 | var getRangeFromPoint = function (x, y) { 11 | var range = null; 12 | 13 | // First try ie way 14 | if (document.body.createTextRange !== undefined) { 15 | range = document.body.createTextRange(); 16 | range.moveToPoint(x, y); 17 | range.select(); 18 | range = window.getSelection().getRangeAt(0); 19 | } else if (document.createRange !== undefined) { 20 | // Try the standards-based way next 21 | if (document.caretPositionFromPoint) { 22 | var pos = document.caretPositionFromPoint(x, y); 23 | range = document.createRange(); 24 | range.setStart(pos.offsetNode, pos.offset); 25 | range.collapse(true); 26 | } 27 | 28 | // Next, the WebKit way 29 | else if (document.caretRangeFromPoint) { 30 | range = document.caretRangeFromPoint(x, y); 31 | } 32 | } 33 | 34 | return range; 35 | }; 36 | 37 | var getLastNode = function (childNodes) { 38 | for (var i = childNodes.length - 1; i >= 0; i--) { 39 | if (childNodes[i].childNodes.length > 0) { 40 | var lastNode = getLastNode(childNodes[i].childNodes); 41 | if (lastNode !== null) { 42 | return lastNode; 43 | } else { 44 | continue; 45 | } 46 | } else if (childNodes[i].data !== "") { 47 | return childNodes[i]; 48 | } 49 | } 50 | return null; 51 | }; 52 | 53 | // modified from http://stackoverflow.com/a/20398132/806777 54 | var insertTextAtCursor = function (text, x, y) { 55 | if (typeof text !== "string") { 56 | return; 57 | } 58 | 59 | var sel = window.getSelection(); 60 | 61 | // fix a bug that won't create a new line if there isn't a new line at the end of the text 62 | var textLastChar = text.substring(text.length - 1); 63 | var fulltext = $(this).text(); 64 | var newLineNode = document.createTextNode("\n"); 65 | if (fulltext !== "") { 66 | lastChar = fulltext.substring(fulltext.length - 1); 67 | lastNode = getLastNode(this.childNodes); 68 | } 69 | var needsExtra = (textLastChar === "\n" && lastChar !== "\n" && (lastChar === null || (sel.anchorNode === lastNode && sel.anchorOffset === lastNode.length) || (sel.focusNode === lastNode && sel.focusOffset === lastNode.length))); 70 | 71 | // make the text replace selection 72 | var textNode = document.createTextNode(text); 73 | var range = null; 74 | if (x !== undefined && y !== undefined) { 75 | range = getRangeFromPoint(x, y); 76 | } else { 77 | range = sel.getRangeAt(0); 78 | } 79 | range.deleteContents(); 80 | // check if it needs an extra new line 81 | if (needsExtra) { 82 | range.insertNode(newLineNode); 83 | } 84 | range.insertNode(textNode); 85 | 86 | // create a new range 87 | range = document.createRange(); 88 | range.setStartAfter(textNode); 89 | range.collapse(true); 90 | 91 | // make the cursor there 92 | sel.removeAllRanges(); 93 | sel.addRange(range); 94 | 95 | // combine text nodes 96 | this.normalize(); 97 | }; 98 | 99 | var insertHTMLAtCursor = function (html, x, y) { 100 | var sel = window.getSelection(); 101 | 102 | // make the text replace selection 103 | var temp = document.createElement("div"); 104 | temp.innerHTML = html; 105 | var htmlNodes = temp.childNodes; 106 | var range = null; 107 | if (x !== undefined && y !== undefined) { 108 | range = getRangeFromPoint(x, y); 109 | } else { 110 | range = sel.getRangeAt(0); 111 | } 112 | range.deleteContents(); 113 | // insert all child nodes 114 | var lastNode = null; 115 | for (var i = 0; i < htmlNodes.length; i++) { 116 | lastNode = htmlNodes[i]; 117 | range.insertNode(lastNode); 118 | } 119 | 120 | // create a new range 121 | range = document.createRange(); 122 | range.setStartAfter(lastNode); 123 | range.collapse(true); 124 | 125 | // make the cursor there 126 | sel.removeAllRanges(); 127 | sel.addRange(range); 128 | 129 | // combine text nodes 130 | this 131 | .normalize(); 132 | }; 133 | 134 | var addImgOnDrop = function (file, caretX, caretY) { 135 | // PENDING: make image resizable? 136 | // PENDING: set default image dimensions? 137 | // PENDING: resize large images to default dimensions using canvas? 138 | // PENDING: set cursor: move; on img? 139 | var $this = $(this); 140 | var reader = new FileReader(); 141 | 142 | reader.onload = function (event) { 143 | var image = new Image(); 144 | image.onload = function () { 145 | // copy img to mouse position 146 | var sel = window.getSelection(); 147 | var range = getRangeFromPoint(caretX, caretY); 148 | if (range !== null) { 149 | range.insertNode(this); 150 | 151 | // set cursor after