User:Subh83/JavaScriptTools/utilsDOMdynamics.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/****************************************************
* Created by Subhrajit Bhattacharya [[User:Subh83]] *
* Licensed under GNU-GPL v3.0                       *
*****************************************************/
var UserSubh83_utilsDOMdynamics = true;

// ================================================================
/*** DOM traversal/search utils ***/

function FindNodeUpLeftTree(elemin, tagnames) {
    // Given an element 'elemin', the function finds the first node with 
    //    tag name in array 'tagnames' that is either a previous sibling, 
    //    or a previous sibling of of any ancestor.
    var elem = elemin;
    for (var j=0; j<tagnames.length; j++) tagnames[j] = tagnames[j].toLowerCase();
 
    var prevelem;
    for (var i=0;i<=10000;i++) {
        if (elem.previousSibling)
            prevelem = elem.previousSibling;
        else if (elem.parentNode)
            prevelem = elem.parentNode;
 
        if (!prevelem.tagName) {
            elem = prevelem;
            continue;
        }
 
        if (prevelem) {
            var tn = prevelem.tagName.toLowerCase();
            for (var j=0; j<tagnames.length; j++)
                if (tn==tagnames[j])
                    return prevelem;
            elem = prevelem;
        } else
            return false;
    }
}

function FindNodeUpTree(elemin, tagnames) {
    // Given an element 'elemin', the function finds an ancestor with tag from 'tagnames'
    var elem = elemin;
    for (var j=0; j<tagnames.length; j++) tagnames[j] = tagnames[j].toLowerCase();
 
    var prevelem;
    for (var i=0;i<=10000;i++) {
        if (elem.parentNode)
            prevelem = elem.parentNode;
 
        if (!prevelem.tagName) {
            elem = prevelem;
            continue;
        }
 
        if (prevelem) {
            var tn = prevelem.tagName.toLowerCase();
            for (var j=0; j<tagnames.length; j++)
                if (tn==tagnames[j])
                    return prevelem;
            elem = prevelem;
        } else
            return false;
    }
}

// Wikipedia specific: For finding the edit section corresponding to a paragraph
function GetPrevEditsectionLinkElement(elem) {
    // Traverse the DOM tree upwards until a 'h' element is encountered
    var hElem = FindNodeUpLeftTree(elem, ['h1','h2','h3','h4','h5','h6']);
    // Return the 'a' in it
    if (hElem) {
        var hElemAs = hElem.getElementsByTagName("a");
        if (hElemAs && hElemAs.length>0)
            return hElemAs[0];
    }
    return false;
}


// ================================================================
/*** Poistioning/display utils ***/

function setMenuPosition (e, menudiv) {
    // Position 'menudiv' at mouse pointer position.
    var posx = e.clientX; 
    var posy = e.clientY;

    var w, h;
    if (document.body && document.body.offsetWidth) {
        w = document.body.offsetWidth;
        h = document.body.offsetHeight;
    }
    if (document.compatMode=='CSS1Compat' &&
               document.documentElement &&
                  document.documentElement.offsetWidth ) {
        w = document.documentElement.offsetWidth;
        h = document.documentElement.offsetHeight;
    }
    if (window.innerWidth && window.innerHeight) {
        w = window.innerWidth;
        h = window.innerHeight;
    }

    menudiv.style.position = "fixed";
    if (posx < w/2) {
        menudiv.style.left = posx+"px";
        menudiv.style.pixelLeft = posx+"px";
        menudiv.style.right = "auto";
        menudiv.style.pixelRight = "auto";
    } else {
        menudiv.style.right = (w-posx)+"px";
        menudiv.style.pixelRight = (w-posx)+"px";
        menudiv.style.left = "auto";
        menudiv.style.pixelLeft = "auto";
    }

    if (posy < h/2) {
        menudiv.style.top = posy+"px";
        menudiv.style.pixelTop = posy+"px";
        menudiv.style.bottom = "auto";
        menudiv.style.pixelBottom = "auto";
    } else {
        menudiv.style.bottom = (h-posy)+"px";
        menudiv.style.pixelBottom = (h-posy)+"px";
        menudiv.style.top = "auto";
        menudiv.style.pixelTop = "auto";
    }
}

function ToggleElementDisplay(divID) {
    var theElem = document.getElementById(divID);
    if (theElem) {
        if (theElem.style.display=='none') { theElem.style.display='block'; return 1; }
        else { theElem.style.display='none'; return 0; }
    }
}

// ================================================================
/*** Textarea, textbox selection & editing utils ***/

function selectRangeInTextarea(ctrl, start, end, autoscroll) {
    if(ctrl.setSelectionRange) {
        ctrl.focus();
        ctrl.setSelectionRange(start, end);
    }
    else if (ctrl.createTextRange) {
        var range = ctrl.createTextRange();
        range.collapse(true);
        range.moveStart('character', start);
        range.moveEnd('character', end);
        range.select();
    }
    if (typeof(autoscroll)=='undefined') autoscroll = true;
    if(autoscroll)
        ctrl.scrollTop = findScrollTopFromPos(ctrl, start, 2);
    ctrl.focus();
}

function findScrollTopFromPos(ctrl, pos, topOffset) {
    if(typeof(topOffset)=='undefined') topOffset = 0;
    var reducingtext = ctrl.value;
    reducingtext.replace(/\t/g, "    ").replace(/\r\n|\n\r/g, "\n").replace(/\r/g, "\n");
    var ColsNo = getTextareaNumberOfColumns(ctrl);
    var LineRegexS = "^.{0,"+ColsNo+"}?\\n|^.{0,"+ColsNo+"}\\s|.{0,"+ColsNo+"}";
    var LineRegex = new RegExp(LineRegexS);
    var LineCount=0, CharCount=0, LinePos=0;
    while (CharCount<(ctrl.value.length-ColsNo-1)) {
        line = reducingtext.match(LineRegex);
        CharCount += line[0].length;
        if (CharCount<=pos) LinePos = LineCount;
        LineCount++;
        reducingtext = reducingtext.substring(line[0].length);
    }
    return parseInt( ctrl.scrollHeight * Math.min(1,Math.max(0, ((LinePos-topOffset)/LineCount) )) );
}

function getTextareaNumberOfColumns(ctrl) {
    var fullWidth = ctrl.clientWidth;
    ctrl.style.width = 'auto';
    while(ctrl.clientWidth < fullWidth) {
        ctrl.cols = ctrl.cols + 1;
    }
    while (ctrl.clientWidth > fullWidth) {
        ctrl.cols = ctrl.cols - 1;
    }
    return(ctrl.cols);
}

function getSelectionInTextarea(ctrl) {
    var sel = new Object();
    if(ctrl.selectionStart) {
        sel.start = ctrl.selectionStart;
        sel.end = ctrl.selectionEnd;
    }
    else if (document.selection) {
        var textRange = document.selection.createRange();
        sel.start = textRange.startOffset;
        sel.end = textRange.endOffset;
    }
    if (typeof(sel.start)!='undefined') {
        sel.text = ctrl.value.substring(sel.start, sel.end);
        return sel;
    }
    else {
        sel.start = 0;
        sel.end = 0;
        sel.text = 0;
    }
    return sel;
}

function updateSelectionInTextarea(ctrl, newtext, sel, newSelectionStartOffset, newSelectionLength) {
    if (typeof(sel)=='undefined') sel = getSelectionInTextarea(ctrl);
    var newcontent = ctrl.value.substring(0, sel.start) + newtext + ctrl.value.substring(sel.end);
    ctrl.value = newcontent;

    var retsel = new Object();
    if(typeof(newSelectionStartOffset)=='undefined') newSelectionStartOffset = 0;
    if(typeof(newSelectionLength)=='undefined') newSelectionLength = newtext.length;
    retsel.start = sel.start + newSelectionStartOffset;
    retsel.end = sel.start + newSelectionStartOffset + newSelectionLength;
    retsel.text = newtext.substr(newSelectionStartOffset, newSelectionLength);
    return retsel;
}

// ================================================================
/*** Event handling utils ***/

function AppendHandlerToEvent(theevent, newhandler, position, condition, finalEvalS) {
    // 'theevent' is a string. 'newhandler' is a function name or a string.
    // position is 'post' (default) or 'pre'
    // If 'condition' is provided, the function that is evaluated first must 
    //       return the value of the 'condition' for the next function to be evaluated.
    // 'finalEvalS' is a string to be evaluated at the end of the combined handler execution.
    //       This is required for passing signals to browser's native handlers. It can contain 
    //       variables 'retOld' and 'retNew'.

    if (typeof(position)=='undefined') position='post';
    if (typeof(finalEvalS)=='undefined') finalEvalS="";
    eval("var oldhandler = " + theevent);

    var evalStartS = theevent + " = function(e) {                                                       ";
    var oldEvalS =              "       if (oldhandler) {                                               " +
                                "           if (typeof(oldhandler)=='string') eval('r='+oldhandler);    " +
                                "           else r = oldhandler(e);                                     " +
                                "           retOld = r;                                                 " +
                                "       }                                                               ";

    var condStartS = ""; condEndS = "";
    if (typeof(condition)!='undefined') { condStartS = " if (r===condition) {"; condEndS = "}"; }

    var newEvalS =              "       if (typeof(newhandler)=='string') eval('r='+newhandler);        " +
                                "       else r = newhandler(e);                                         " +
                                "       retNew = r;                                                     ";
    var evalEndS = finalEvalS + "   }                                                                   ";

    if (position == 'post') var evalS = evalStartS + oldEvalS + condStartS + newEvalS + condEndS + evalEndS;
    else if (position == 'pre') var evalS = evalStartS + newEvalS + condStartS + oldEvalS + condEndS + evalEndS;

    eval(evalS);
}