milo

children

function
 children() 

Option name Type Description
el Element

element to return the children of (only DOM elements)

return Array.<Element>

Returns the list of element children of DOM element

function children(el) {
   return filterNodeListByType(el.childNodes, Node.ELEMENT_NODE);
}

filterNodeListByType

function
 filterNodeListByType() 

Option name Type Description
nodeList NodeList

the list of nodes, for example childNodes property of DOM element

nodeType Integer

an integer constant defined by DOM API, e.g. Node.ELEMENT_NODE or Node.TEXT_NODE

return Array.<Node>

Filters the list of nodes by type

function filterNodeListByType(nodeList, nodeType) {
    return _.filter(nodeList, function (node) {
        return node.nodeType == nodeType;
    });
}

containingElement

function
 containingElement() 

Option name Type Description
node Node
return Element

Find nearest parent element for node.
If node is an element, it will be returned.

function containingElement(node) {
    while (node) {
        if (node.nodeType == Node.ELEMENT_NODE)
            return node;
        node = node.parentNode;
    }
    return null;
}

selectElementContents

function
 selectElementContents() 

Option name Type Description
el Element

DOM element

Selects inner contents of DOM element

function selectElementContents(el) {
    var doc = el.ownerDocument;
    if (! doc) return logger.error('selectElementContents: element has no document');
    var range = doc.createRange();
    range.selectNodeContents(el);
    var win = getNodeWindow(el)
        , sel = win.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
}

selectElementText

function
 selectElementText() 

Option name Type Description
el Element

Selects text inside element

function selectElementText(el) {
    var fromNode = firstTextNode(el)
        , toNode = lastTextNode(el);

    if (fromNode && toNode)
        setSelection(fromNode, 0, toNode, toNode.textContent.length);
}

setCaretPosition

function
 setCaretPosition() 

Option name Type Description
node Node

DOM node

pos Number

caret position

Sets the caret position to the position in the node

function setCaretPosition(node, pos) {
    var doc = node.ownerDocument;
    if (! doc) return logger.error('setCaretPosition: element has no document');
    var range = doc.createRange();
    range.setStart(node, pos);
    var win = getNodeWindow(node)
        , sel = win.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
}

getSelectionDirection

function
 getSelectionDirection() 

Option name Type Description
a sel

selection object

return Integer

can be -1, 0, 1 or undefined

get the direction of a selection

1 forward, -1 backward, 0 no direction, undefined one of the node is detached or in a different frame

function getSelectionDirection(sel){
    return _getDirection(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
}

function _getDirection(fromNode, startOffset, toNode, endOffset){
    var docPosition = fromNode.compareDocumentPosition(toNode);
    if (docPosition & Node.DOCUMENT_POSITION_FOLLOWING){
        return 1;
    }
    else if (docPosition & Node.DOCUMENT_POSITION_PRECEDING){
        return -1;
    }
    else if (fromNode == toNode){
        if (startOffset < endOffset){
            return 1;
        }
        else if (startOffset > endOffset){
            return -1;
        }
        else {
            return 0;
        }
    }
}

setSelection

function
 setSelection() 

Option name Type Description
fromNode Node

DOM node to start selection in

startOffset Number
toNode Node

DOM node to end selection in

endOffset Number

Selects a range in a document

function setSelection(fromNode, startOffset, toNode, endOffset) {
    var doc = fromNode.ownerDocument;
    if (! doc) return logger('setCaretPosition: element has no document');
    var backward = _getDirection(fromNode, startOffset, toNode, endOffset) == -1;
    var range = doc.createRange();
    var container, originalContentEditable;
    // does not work in non contentEditable items

    var win = getNodeWindow(fromNode)
        , sel = win.getSelection();


    if (backward){
        range.setStart(toNode, endOffset);
        range.setEnd(fromNode, startOffset);
        range.collapse(false);
    }
    else {
        range.setStart(fromNode, startOffset);
        range.setEnd(toNode, endOffset);
    }

    container = range.commonAncestorContainer == Node.ELEMENT_NODE ?
        range.commonAncestorContainer :
        range.commonAncestorContainer.parentElement;

    if (!container.isContentEditable){
        originalContentEditable = container.contentEditable; // false or inherit
        container.contentEditable = "true";
    }

    sel.removeAllRanges();
    sel.addRange(range);

    if (backward){
        sel.extend(toNode, endOffset);
    }

    if (originalContentEditable){
        // restoring contentEditable
        container.contentEditable = originalContentEditable;
    }
}

clearSelection

function
 clearSelection() 

Option name Type Description
win Window

Clears selection in a given window

function clearSelection(win) {
    win = win || window;
    var sel = win.getSelection();
    sel.removeAllRanges();
}

getElementOffset

function
 getElementOffset() 

Option name Type Description
el Element

the element for which position needs to be returned

if includeBorder

is to include the border width

return Object

vector object with properties topOffset and leftOffset

Calculates an element's total top and left offset from the document edge.

function getElementOffset(el, includeBorder) {
    var yPos, xPos;

    yPos = el.offsetTop;
    xPos = el.offsetLeft;
    el = el.offsetParent;

    while (el) {
        yPos += el.offsetTop + getBorder(el, 'Height', includeBorder);
        xPos += el.offsetLeft + getBorder(el, 'Width', includeBorder);
        el = el.offsetParent;
    }

    return { topOffset: yPos, leftOffset: xPos };
}


function getBorder(el, type, includeBorder) {
    if (includeBorder) {
        var side = (type == 'Height') ? 'top' : 'left',
            styles = window.getComputedStyle(el),
            sideValue = parseInt(styles.getPropertyValue('border-' + side + '-width'), 10);

        if (sideValue) return sideValue;
    }
    return 0;
}

removeElement

function
 removeElement() 

Option name Type Description
el Element

the element to be removed

Removes element from the document

function removeElement(el) {
    var parent = el.parentNode;
    if (parent){
        parent.removeChild(el);
        parent.normalize();
    }
}

firstTextNode

function
 firstTextNode() 

Option name Type Description
node Element, Node

the node to be searched, if the node is text node we return the node.

return TextNode

Returns the first child text node of an element

function firstTextNode(node) {
    if (node.nodeType == Node.TEXT_NODE) return node;
    var treeWalker = createTreeWalker(node, NodeFilter.SHOW_TEXT);
    return treeWalker.firstChild();
}

lastTextNode

function
 lastTextNode() 

Option name Type Description
node Element, Node

the node to be searched, if the node is text node we return the node.

return TextNode

Returns the last child text node of an element

function lastTextNode(node) {
    if (node.nodeType == Node.TEXT_NODE) return node;
    var treeWalker = createTreeWalker(node, NodeFilter.SHOW_TEXT);
    return treeWalker.lastChild();
}

unwrapElement

function
 unwrapElement() 

Option name Type Description
el Element

the element to be "unwrapped"

Removes element from the document putting its children in its place

function unwrapElement(el) {
    var parent = el.parentNode;

    if (parent) {
        var frag = document.createDocumentFragment();
        // must be copied to avoid iterating a mutating list of childNodes
        var children = _.slice(el.childNodes);
        children.forEach(frag.appendChild, frag);
        parent.replaceChild(frag, el);
        parent.normalize();
    }
}

wrapInElement

function
 wrapInElement() 

Option name Type Description
wrapIntoEl Element
el Element

Wraps an element in another element

function wrapInElement(wrapIntoEl, el) {
    var parent = el.parentNode;

    if (parent) {
        parent.insertBefore(wrapIntoEl, el);
        wrapIntoEl.appendChild(el);
    }
}

trimNodeRight

function
 trimNodeRight() 

Option name Type Description
node TextNode
return Boolean

Trims a text node of trailing spaces, and returns true if a trim was performed.

function trimNodeRight(node) {
    return _trimNode(node, 'trimRight');
}

trimNodeLeft

function
 trimNodeLeft() 

Option name Type Description
node TextNode
return Boolean

Trims a text node of leading spaces, and returns true if a trim was performed.

function trimNodeLeft(node) {
    return _trimNode(node, 'trimLeft');
}


function _trimNode(node, methodName) {
    var len = node.length;
    node.textContent = node.textContent[methodName]();
    return len !== node.length;
}

detachComponent

function
 detachComponent() 

Option name Type Description
el Element

Removes the reference to component from element

function detachComponent(el) {
    delete el[config.componentRef];
}

stripHtml

function
 stripHtml() 

Option name Type Description
str String

Any string

return String

returns the string cleaned of any html content.

Retrieves the content of a html string

function stripHtml(str) {
    var div = document.createElement('DIV');
    div.innerHTML = str;
    return div.textContent || '';
}

walkTree

function
 walkTree() 

Option name Type Description
root HTMLElement

The containing root element to be walked. Will not be iterated.

filter NodeFiler

A NodeFilter constant, see https://developer.mozilla.org/en/docs/Web/API/TreeWalker

iterator Function

A function to be called on each node. Returning 'false' will break.

context Object

An optional context to passed, defaults to root.

Convenience wrapper for native TreeWalker that automatically walks the tree and calls an iterator function.
This will not iterate the root element.

function walkTree(root, filter, iterator, context) {
    var tw = document.createTreeWalker(root, filter);
    while(tw.nextNode()) {
        var result = iterator.call(context || root, tw.currentNode);
        if (result === false) break;
    }
}

treePathOf

function
 treePathOf() 

Option name Type Description
rootEl Element

element to search

el Element

element to find the index of

return Array.<Number>

Returns array of child indexes of element path inside root element in DOM tree using breadth first tree traversal.
Returns undefined if the element is not inside root element, 0 if the root element itself is passed.

function treePathOf(rootEl, el) {
    if (! (rootEl && rootEl.contains(el))) return;

    var treePath = []
        , node = rootEl;

    while (node != el) {
        var nodeIndex = _.findIndex(node.childNodes, containsEl);
        treePath.push(nodeIndex);
        node = node.childNodes[nodeIndex];
    }

    return treePath;

    function containsEl(child) {
        return child.contains(el);
    }
}

getNodeAtTreePath

function
 getNodeAtTreePath() 

Option name Type Description
rootEl Element
treePath Array.<Number>
nearest Boolean

return nearest possible node if exact node does not exist

return Node

Returns element at given tree path

function getNodeAtTreePath(rootEl, treePath, nearest) {
    if (!treePath) return;

    var len = treePath.length;
    if (len === 0) return rootEl;

    var node = rootEl;

    for (var i = 0; i < len; i++) {
        var children = node.childNodes;
        if (! children) {
            if (! nearest) node = undefined;
            break;
        }
        var childIndex = treePath[i]
            , child = children[childIndex];
        if (! child) {
            node = nearest
                    ? children[children.length - 1]
                    : undefined;
            break;
        }
        node = child;
    }

    return node;
}

insertAtTreePath

function
 insertAtTreePath() 

Option name Type Description
rootEl Element

element into which to insert

treeIndex Number

index in DOM tree inside root element (see treePathOf)

el Element

element to be inserted

return Boolean

true if was successfully inserted

Inserts an element inside root at a given path in tree (that has the same meaning as the index returned by treePathOf function). If element is already in the root's tree, it will be removed first and then moved to the passed treeIndex
Insertion at index 0 is not possible and will return undefined as it would mean replacing the root element.

function insertAtTreePath(rootEl, treePath, el, nearest) {
    var toNormalize = el.nodeType == Node.TEXT_NODE;
    if (rootEl.contains(el))
        removeElement(el); // can't use removeChild as rootEl here is not an immediate parent

    if (treePath.length === 0) return;

    var parent = getNodeAtTreePath(rootEl, treePath.slice(0, -1), nearest)
        , children = parent.childNodes;

    if (! children) {
        if (nearest) {
            parent = parent.parentNode;
            children = parent.childNodes;
        } else return;
    }

    var childIndex = treePath[treePath.length - 1]
        , child = children[childIndex];

    if (child) {
        parent.insertBefore(el, child);
        if (toNormalize) parent.normalize();
        return true;
    } else if (children.length === 0 && (childIndex === 0 || nearest)) {
        parent.appendChild(el);
        if (toNormalize) parent.normalize();
        return true;
    } else {
        child = children[childIndex - 1];
        if (child || nearest) {
            parent.appendChild(el);
            if (toNormalize) parent.normalize();
            return true;
        }
    }
}

isTreePathBefore

function
 isTreePathBefore() 

Option name Type Description
path1 Array

A treepath array

path2 Array

A treepath array

return Boolean

Returns true if the first tree path points to a node which is before the other in the document order.

function isTreePathBefore(path1, path2) {
    var i = 0
        , isBefore;
    if (!Array.isArray(path1) && Array.isArray(path2))
        return logger.error('isTreePathBefore: One or both paths are not valid treepath arrays.');

    for (i; i < path1.length; i++) {
        if (path1[i] < path2[i]) {
            isBefore = true;
            break;
        } else if (path1[i] > path2[i]) {
            isBefore = false;
            break;
        }
    }

    if (typeof isBefore == 'undefined')
        if (path1.length < path2.length)
            logger.warn('isTreePathBefore: One node is inside another');

    return isBefore || false;
}

htmlEntities

function
 htmlEntities() 

Option name Type Description
str String

the string to convert

return String

the string with html entities

Converts non latin characters to HTML entity codes.

function htmlEntities(str) {
    return str.replace(/[\u00A0-\u99999<>\&]/gim, function(i) {
        return '&#'+i.charCodeAt(0)+';';
    });
}


function createTreeWalker(el, whatToShow) {
    whatToShow = whatToShow || (NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
    return document.createTreeWalker(el, whatToShow);
}

getNodeWindow

function
 getNodeWindow() 

Option name Type Description
node Node
return Window

Returns the reference to the window the node is in

function getNodeWindow(node) {
    var doc = node.ownerDocument;
    return doc && (doc.defaultView || doc.parentWindow);
}

forEachNodesInRange

function
 forEachNodesInRange() 

Option name Type Description
a range

range

a cb

function taking a node as argument

do something for each nodes contained in a range

function forEachNodesInRange(range, cb){
    var rangeContainer = range.commonAncestorContainer
        , doc = rangeContainer.ownerDocument;

    function isNodeInsideRange(node){
        var nodeRange = document.createRange();
        var isInside = false;
        nodeRange.selectNode(node);

        if (nodeRange.compareBoundaryPoints(window.Range.START_TO_START, range) != -1
            && nodeRange.compareBoundaryPoints(window.Range.END_TO_END, range) != 1){
            isInside = true;
        }
        return isInside;
    }

    var treeWalker = doc.createTreeWalker(rangeContainer,
            NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);

    var currentNode;
    while (currentNode = treeWalker.nextNode()){ // should be assignment
        if (isNodeInsideRange(currentNode)){
            cb(currentNode);
        }
    }
}

getComponentsFromRange

function
 getComponentsFromRange() 

Option name Type Description
a range

DOM range.

get all components contained in a range

function getComponentsFromRange(range) {
    var win = getNodeWindow(range.startContainer)
        , Component = win.milo.Component;

    var components = [];
    forEachNodesInRange(range, function (node){
        if (node.nodeType != Node.TEXT_NODE) {
            var comp = Component.getComponent(node);
            if (comp)
                components.push(comp);
        }
    });

    return components;
}

deleteRangeWithComponents

function
 deleteRangeWithComponents() 

Option name Type Description
delete range

a DOM range and all the components inside

delete a range

function deleteRangeWithComponents(range) {
    var components = getComponentsFromRange(range);

    components.forEach(function(comp) {
        comp.destroy(true);
    });

    range.deleteContents();
}

areRangesEqual

function
 areRangesEqual() 

Option name Type Description
range1 range
range2 range
return Boolean

are the two ranges equivalent

check if two ranges are equivalent

function areRangesEqual(range1, range2){
    return range1.compareBoundaryPoints(window.Range.START_TO_START, range2) === 0 && range1.compareBoundaryPoints(window.Range.END_TO_END, range2) === 0;
}

xpathSelector

function
 xpathSelector() 

Option name Type Description
xpath String

xpath expression, e.g. '//a[contains(text(), "Click here")]' or '/html/body//h1'

context Node

optional context node to search inside, document by default

return Node

Return the first node that matches xpath expression

function xpathSelector(xpath, context) {
    if (!document.evaluate) return logger.error('document.evaluate is not supported');
    context = context || document;
    var result = document.evaluate(xpath, context, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
    return result && result.singleNodeValue;
}

xpathSelectorAll

function
 xpathSelectorAll() 

Option name Type Description
xpath String

xpath expression, e.g. '//a[contains(text(), "Click here")]' or '/html/body//h1'

context Node

optional context node to search inside, document by default

return Array.<Node>

Return array of nodes that match xpath expression

function xpathSelectorAll(xpath, context) {
    if (!document.evaluate) return logger.error('document.evaluate is not supported');
    context = context || document;
    var result = document.evaluate(xpath, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    var nodes = [], i = 0, node;
    while (node = result.snapshotItem(i)) nodes[i++] = node;
    return nodes;
}

addDebugPoint

function
 addDebugPoint() 

Option name Type Description
x Number
y Number

Adds a single pixel div to the body at a given x and y position. Useful for debugging position specific code.

function addDebugPoint(x, y) {
    var dbEl = document.createElement('div');
    dbEl.setAttribute('style', 'width: 1px; height: 1px; position:fixed; left:'+x+'px; top:'+y+'px; background-color:red; z-index: 100');
    setTimeout(function() {document.body.appendChild(dbEl);}, 200);
}