/*
# Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
(function(window) {
"use strict";
/**
* This module contains various CJT extension methods supporting
* dom object minipulation and measurement.
* @module Cpanel.dom
*/
// ----------------------
// Shortcuts
// ----------------------
var YAHOO = window.YAHOO;
var DOM = window.DOM;
var EVENT = window.EVENT;
var document = window.document;
var CPANEL = window.CPANEL;
// ----------------------
// Setup the namespaces
// ----------------------
CPANEL.namespace("dom");
var _ARROW_KEY_CODES = {
37: 1,
38: 1,
39: 1,
40: 1
};
/**
* Used internally for normalizing <select> keyboard behavior.
*
* @method _do_blur_and_focus
* @private
*/
var _do_blur_then_focus = function() {
this.blur();
this.focus();
};
// Used for creating arbitrary markup.
var dummy_div;
YAHOO.lang.augmentObject(CPANEL.dom, {
/**
* Detects if the oninput event is working for the current browser.
* NOTE: IE9's oninput event is horribly broken. Best just to avoid it.
* http://msdn.microsoft.com/en-us/library/ie/gg592978%28v=vs.85%29.aspx
*
* @property has_oninput
* @static
* @type {Boolean}
*/
has_oninput: (parseInt(YAHOO.env.ua.ie, 10) !== 9) && ("oninput" in document.createElement("input")),
/**
* Gets the region for the content box.
* @method get_content_region
* @static
* @param {String|HTMLElement} el Element to calculate the region.
* @return {YAHOO.util.Region} Region consumed by the element. Accounts for
* padding and border. Also has custom properties:
* @param {[2]} outer_xy XY of the outer bounds?
* @param {RegionLike} padding Padding size for element
* @param {RegionLike} border Border top and left sizes.
*/
get_content_region: function(el) {
el = DOM.get(el);
var padding_top = parseFloat(DOM.getStyle(el, "paddingTop")) || 0;
var padding_bottom = parseFloat(DOM.getStyle(el, "paddingBottom")) || 0;
var padding_left = parseFloat(DOM.getStyle(el, "paddingLeft")) || 0;
var padding_right = parseFloat(DOM.getStyle(el, "paddingRight")) || 0;
var border_left = parseFloat(DOM.getStyle(el, "borderLeftWidth")) || 0;
var border_top = parseFloat(DOM.getStyle(el, "borderTopWidth")) || 0;
var xy = DOM.getXY(el);
var top = xy[1] + border_top + padding_top;
var left = xy[0] + border_left + padding_left;
var bottom = top + el.clientHeight - padding_top - padding_bottom;
var right = left + el.clientWidth - padding_left - padding_right;
var region = new YAHOO.util.Region(top, right, bottom, left);
region.outer_xy = xy;
region.padding = {
"top": padding_top,
right: padding_right,
bottom: padding_bottom,
left: padding_left
};
region.border = {
"top": border_top,
// no bottom or right since these are unneeded here
left: border_left
};
return region;
},
/**
* Gets the height of the element accounting for border and
* padding offsets.
* @method get_content_height
* @static
* @param {HTMLElement|String} el Element to measure.
* @return {Number} Height of the element.
*/
get_content_height: function(el) {
el = DOM.get(el);
// most browsers return something useful from this
var dom = parseFloat(DOM.getStyle(el, "height"));
if (!isNaN(dom)) {
return dom;
}
// IE makes us get it this way
var padding_top = parseFloat(DOM.getStyle(el, "paddingTop")) || 0;
var padding_bottom = parseFloat(DOM.getStyle(el, "paddingBottom")) || 0;
var client_height = el.clientHeight;
if (client_height) {
return client_height - padding_top - padding_bottom;
}
var border_top = parseFloat(DOM.getStyle(el, "borderTopWidth")) || 0;
var border_bottom = parseFloat(DOM.getStyle(el, "borderBottomWidth")) || 0;
return el.offsetHeight - padding_top - padding_bottom - border_top - border_bottom;
},
/**
* Gets the width of the element accounting for border and
* padding offsets.
* @method get_content_width
* @static
* @param {HTMLElement|String} el Element to measure.
* @return {Number} Width of the element.
*/
get_content_width: function(el) {
el = DOM.get(el);
// most browsers return something useful from this
var dom = parseFloat(DOM.getStyle(el, "width"));
if (!isNaN(dom)) {
return dom;
}
// IE makes us get it this way
var padding_left = parseFloat(DOM.getStyle(el, "paddingLeft")) || 0;
var padding_right = parseFloat(DOM.getStyle(el, "paddingRight")) || 0;
var client_width = el.clientWidth;
if (client_width) {
return client_width - padding_left - padding_right;
}
var border_left = parseFloat(DOM.getStyle(el, "borderLeftWidth")) || 0;
var border_right = parseFloat(DOM.getStyle(el, "borderRightWidth")) || 0;
return el.offsetWidth - padding_left - padding_right - border_left - border_right;
},
/**
* Gets the region of the current viewport
* @method get_viewport_region.
* @return {YAHOO.util.Region} region for the viewport
*/
get_viewport_region: function() {
var vp_width = DOM.getViewportWidth();
var vp_height = DOM.getViewportHeight();
var scroll_x = DOM.getDocumentScrollLeft();
var scroll_y = DOM.getDocumentScrollTop();
return new YAHOO.util.Region(
scroll_y,
scroll_x + vp_width,
scroll_y + vp_height,
scroll_x
);
},
/**
* Adds the class if it does not exist, removes it if it does
* exist on the element.
* @method toggle_class
* @static
* @param {HTMLElement|String} el The element to toggle the
* CSS class name.
* @param {String} the_class A CSS class name to add or remove.
*/
toggle_class: function(el, the_class) {
el = DOM.get(el);
// TODO: May want to consider caching since these are expensive to
// regenerate on each call.
var pattern = new RegExp("\\b" + the_class.regexp_encode() + "\\b");
if (el.className.search(pattern) === -1) {
DOM.addClass(el, the_class);
return the_class;
} else {
DOM.removeClass(el, the_class);
}
},
/**
* Create one or more DOM nodes from markup.
* These nodes are NOT injected into the page.
*
* @method create_from_markup
* @param markup {String} HTML to use for creating DOM nodes
* @return {Array} The DOM element nodes from the markup.
*/
create_from_markup: function(markup) {
if (!dummy_div) {
dummy_div = document.createElement("div");
}
dummy_div.innerHTML = markup;
return CPANEL.Y(dummy_div).all("> *");
},
/**
* Ensure that keyboard manipulation of the <select> box will change
* the actual value right away. On some platforms (e.g., MacOS),
* "onchange" doesn't fire on up/down arrows until you blur() the element.
*
* This is primarily useful for validation; we might not want to use this
* if "onchange" fires off anything "big" in the UI since it breaks users'
* expectations of how drop-downs behave on their platform.
*
* On a related note, bear in mind that document.activeElement will be
* different when "onchange" fires from a blur(): if it fires natively from
* an arrow keydown, then activeElement is the <select>;
* after a blur(), document.activeElement is probably document.body.
*
* @method normalize_select_arrows
* @static
*/
normalize_select_arrows: function(el) {
EVENT.on(el, "keydown", function(e) {
if (e.keyCode in _ARROW_KEY_CODES) {
window.setTimeout(_do_blur_then_focus.bind(this), 1);
}
});
},
/**
* Sets the value of the element to the passed in value. If form is
* provided, will be in the specified form.
* @method set_form_el_value
* @static
* @param {HTMLElement|String} form Optional, either a DOM element or
* an ID of the form.
* @param {HTMLCollection, HTMLSelect, HTMLInput, HTMLTextarea, String} el can be an
* HTML collection, a <select> element, an <input>, a <textarea>,
* a name in the form, or an ID of one of these.
* @param {Any} val Value to set the element to.
* @return {Boolean} true if successful, false otherwise.
*/
set_form_el_value: function(form, el, val) {
if (arguments.length === 2) {
val = el;
el = form;
form = null;
}
// TODO: Need to check if form is found before calling form[el],
// will throw an uncaught exception.
if (typeof el === "string") {
var element = null;
if (form) {
form = DOM.get(form);
if (form) {
// Assumes the el is a name before
// checking if its an id further down.
element = form[el];
}
}
if (!element) {
// Form was not provided,
// el is an id and not a named form item,
// or el is already a DOM node.
element = DOM.get(el);
}
el = element;
}
var opts = el.options;
if (opts) {
for (var o = opts.length - 1; o >= 0; o--) {
if (opts[o].value === val) {
el.selectedIndex = o; // If a multi-<select>, clobber.
return true;
}
}
} else if ("length" in el) {
for (var e = el.length - 1; e >= 0; e--) {
if (el[e].value === val) {
el[e].checked = true;
return true;
}
}
} else if ("value" in el) {
el.value = val;
return true;
}
return false;
},
/**
* Shows the current element.
* @method show
* @static
* @param {String|HTMLElement} el element to show
* @param {String} display_type optional, alternative display type if the default is not desired */
show: function(el, display_type) {
display_type = display_type || "";
DOM.setStyle(el, "display", display_type);
},
/**
* Hides the current element.
* @method hide
* @static
* @param {String|HTMLElement} el element to hide */
hide: function(el) {
DOM.setStyle(el, "display", "none");
},
/**
* Checks if the current element is visible.
* @method isVisible
* @static
* @param {String|HTMLElement} el element to check
* @return {Boolean} true if visible, false if not. */
isVisible: function(el) {
return DOM.getStyle(el, "display") !== "none";
},
/**
* Checks if the current element is hidden.
* @method isHidden
* isHidden
* @param {String|HTMLElement} el element to check
* @return {Boolean} true if not visible, false otherwise. */
isHidden: function(el) {
return DOM.getStyle(el, "display") === "none";
},
/**
* Determins if the passed in element or
* the documentElement if no element is passed in,
* is in RTL mode.
* @method isRtl
* @param {String|HtmlElement} el Optional element, if provided,
* this function will look for the dir attribute on the element, otherwise
* it will look at the document.documentElement dir attribute.
* @return {Boolean} The document or element is in rtl if true and in ltr
* if false.
*/
isRtl: function(el) {
if (!el) {
if (document) {
return (document.documentElement.dir === "rtl");
}
} else {
el = DOM.get(el);
if (el) {
return el.dir === "rtl";
}
}
// We are not operating in a browser
// so we don't know, so just say no.
return false;
}
});
// QUESTION: Why do we need the same function with different names?
CPANEL.dom.get_inner_region = CPANEL.dom.get_content_region;
})(window);