cyberbit/modation

View on GitHub
src/js/jquery.caretposition.custom.js

Summary

Maintainability
A
2 hrs
Test Coverage
/**
 * jQuery plugin for getting position of cursor in textarea

 * @license under GNU license
 * @author Bevis Zhao (i@bevis.me, http://bevis.me)
 */
$(function() {
    //Mods to eliminate $.browser errors
    $.browser = {};
    $.browser.msie = false;
    $.browser.mozilla = false;
    
    var calculator = {
        // key styles
        primaryStyles: ['fontFamily', 'fontSize', 'fontWeight', 'fontVariant', 'fontStyle',
            'paddingLeft', 'paddingTop', 'paddingBottom', 'paddingRight',
            'marginLeft', 'marginTop', 'marginBottom', 'marginRight',
            'borderLeftColor', 'borderTopColor', 'borderBottomColor', 'borderRightColor',
            'borderLeftStyle', 'borderTopStyle', 'borderBottomStyle', 'borderRightStyle',
            'borderLeftWidth', 'borderTopWidth', 'borderBottomWidth', 'borderRightWidth',
            'line-height', 'outline', 'text-align'],

        specificStyle: {
            'word-wrap': 'break-word',
            'overflow-x': 'hidden',
            'overflow-y': 'auto'
        },

        simulator : $('<div id="textarea_simulator"/>').css({
                position: 'absolute',
                top: 0,
                left: 0,
                visibility: 'hidden'
            }).appendTo(document.body),

        toHtml : function(text) {
            return text.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g, '<br>')
                .split(' ').join('<span style="white-space:prev-wrap">&nbsp;</span>');
        },
        // calculate position
        getCaretPosition: function() {
            var cal = calculator, self = this, element = self[0], elementOffset = self.offset();

            // IE has easy way to get caret offset position
            if ($.browser.msie && $.browser.version <= 9) {
                // must get focus first
                element.focus();
                var range = document.selection.createRange();
                $('#hskeywords').val(element.scrollTop);
                return {
                    left: range.boundingLeft - elementOffset.left,
                    top: parseInt(range.boundingTop) - elementOffset.top + element.scrollTop
                        + document.documentElement.scrollTop + parseInt(self.getComputedStyle("fontSize"))
                };
            }

            cal.simulator.empty();
            // clone primary styles to imitate textarea
            $.each(cal.primaryStyles, function(index, styleName) {
                self.cloneStyle(cal.simulator, styleName);
            });

            // caculate width and height
            cal.simulator.css($.extend({
                'width': self.width(),
                'height': self.height()
            }, cal.specificStyle));

            var value = (self.val() || self.text()), cursorPosition = self.getCursorPosition();
            var beforeText = value.substring(0, cursorPosition),
                afterText = value.substring(cursorPosition);

            var before = $('<span class="before"/>').html(cal.toHtml(beforeText)),
                focus = $('<span class="focus"/>'),
                after = $('<span class="after"/>').html(cal.toHtml(afterText));

            cal.simulator.append(before).append(focus).append(after);
            var focusOffset = focus.offset(), simulatorOffset = cal.simulator.offset();
            // alert(focusOffset.left  + ',' +  simulatorOffset.left + ',' + element.scrollLeft);
            return {
                top: focusOffset.top - simulatorOffset.top - element.scrollTop
                    // calculate and add the font height except Firefox
                    + ($.browser.mozilla ? 0 : parseInt(self.getComputedStyle("fontSize"))),
                left: focus[0].offsetLeft -  cal.simulator[0].offsetLeft - element.scrollLeft
            };
        }
    };

    $.fn.extend({
        setCursorPosition : function(position){
        if(this.length == 0) return this;
        return $(this).setSelection(position, position);
        },
        setSelection: function(selectionStart, selectionEnd) {
        if(this.length == 0) return this;
        input = this[0];

        if (input.createTextRange) {
            var range = input.createTextRange();
            range.collapse(true);
            range.moveEnd('character', selectionEnd);
            range.moveStart('character', selectionStart);
            range.select();
        } else if (input.setSelectionRange) {
            input.focus();
            input.setSelectionRange(selectionStart, selectionEnd);
        } else {
            var el = this.get(0);

                var range = document.createRange();
                range.collapse(true);
                range.setStart(el.childNodes[0], selectionStart);
                range.setEnd(el.childNodes[0], selectionEnd);

                var sel = window.getSelection();
                sel.removeAllRanges();
                sel.addRange(range);
        }

        return this;
        },
        getComputedStyle: function(styleName) {
            if (this.length == 0) return;
            var thiz = this[0];
            var result = this.css(styleName);
            result = result || ($.browser.msie ?
                thiz.currentStyle[styleName]:
                document.defaultView.getComputedStyle(thiz, null)[styleName]);
            return result;
        },
        // easy clone method
        cloneStyle: function(target, styleName) {
            var styleVal = this.getComputedStyle(styleName);
            if (!!styleVal) {
                $(target).css(styleName, styleVal);
            }
        },
        cloneAllStyle: function(target, style) {
            var thiz = this[0];
            for (var styleName in thiz.style) {
                var val = thiz.style[styleName];
                typeof val == 'string' || typeof val == 'number'
                    ? this.cloneStyle(target, styleName)
                    : NaN;
            }
        },
        getCursorPosition : function() {
            var element = input = this[0];
            var value = (input.value || input.innerText)

            if(!this.data("lastCursorPosition")){
                this.data("lastCursorPosition",0);
            }

            var lastCursorPosition = this.data("lastCursorPosition");

          if (document.selection) {
             input.focus();
              var sel = document.selection.createRange();
              var selLen = document.selection.createRange().text.length;
              sel.moveStart('character', -value.length);
              lastCursorPosition = sel.text.length - selLen;
          } else if (input.selectionStart || input.selectionStart == '0') {
              return input.selectionStart;
          } else if (typeof window.getSelection != "undefined" && window.getSelection().rangeCount>0) {
                try{
                var selection = window.getSelection();
              var range = selection.getRangeAt(0);
              var preCaretRange = range.cloneRange();
              preCaretRange.selectNodeContents(element);
              preCaretRange.setEnd(range.endContainer, range.endOffset);
              lastCursorPosition =  preCaretRange.toString().length;
              }catch(e){
                  lastCursorPosition = this.data("lastCursorPosition");    
              }
          } else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
              var textRange = document.selection.createRange();
              var preCaretTextRange = document.body.createTextRange();
              preCaretTextRange.moveToElementText(element);
              preCaretTextRange.setEndPoint("EndToEnd", textRange);
              lastCursorPosition =  preCaretTextRange.text.length;
          }

            this.data("lastCursorPosition",lastCursorPosition);
          return lastCursorPosition;
      },
        getCaretPosition: calculator.getCaretPosition
    });
});