1 /* 2 * Dual-licensed under the MIT License & the Academic Free License v. 2.1. 3 * See the file LICENSE for more information. 4 * 5 * (c) 2007-2008 by Per Cederberg & Dynabyte AB. All rights reserved. 6 */ 7 8 // Check for loaded MochiKit 9 if (typeof(MochiKit) == "undefined") { 10 throw new ReferenceError("MochiKit must be loaded before loading this script"); 11 } 12 13 /** 14 * @name MochiKit.Style 15 * @namespace Provides a painless CSS manipulation API. 16 */ 17 // Check for loaded MochiKit.Style 18 if (typeof(MochiKit.Style) == "undefined") { 19 throw new ReferenceError("MochiKit.Style must be loaded before loading this script"); 20 } 21 22 /** 23 * Returns the border widths for an HTML DOM node. The widths for 24 * all four sides will be returned. 25 * 26 * @param {Object} node the HTML DOM node 27 * 28 * @return {Object} an object with "t", "b", "l" and "r" properties, 29 * each containing either an integer value or null 30 */ 31 MochiKit.Style.getBorderBox = function(node) { 32 var getStyle = MochiKit.Style.getStyle; 33 var px = MochiKit.Style._toPixels; 34 return { t: px(getStyle(node, "border-width-top")), 35 b: px(getStyle(node, "border-width-bottom")), 36 l: px(getStyle(node, "border-width-left")), 37 r: px(getStyle(node, "border-width-right")) }; 38 } 39 40 /** 41 * Returns the padding sizes for an HTML DOM node. The sizes for all 42 * four sides will be returned. 43 * 44 * @param {Object} node the HTML DOM node 45 * 46 * @return {Object} an object with "t", "b", "l" and "r" properties, 47 * each containing either an integer value or null 48 */ 49 MochiKit.Style.getPaddingBox = function(node) { 50 var getStyle = MochiKit.Style.getStyle; 51 var px = MochiKit.Style._toPixels; 52 return { t: px(getStyle(node, "padding-top")), 53 b: px(getStyle(node, "padding-bottom")), 54 l: px(getStyle(node, "padding-left")), 55 r: px(getStyle(node, "padding-right")) }; 56 } 57 58 /** 59 * Converts a style pixel value to the corresponding integer. If the 60 * string ends with "px", those characters will be silently removed. 61 * 62 * @param {String} value the style string value to convert 63 * 64 * @return {Number} the numeric value, or 65 * null if the conversion failed 66 */ 67 MochiKit.Style._toPixels = function(value) { 68 if (value != null) { 69 try { 70 value = MochiKit.Format.rstrip(value, "px"); 71 value = Math.round(parseFloat(value)); 72 } catch (ignore) { 73 value = null; 74 } 75 } 76 return (value == null || isNaN(value)) ? null : value; 77 } 78 79 /** 80 * Returns the scroll offset for an HTML DOM node. 81 * 82 * @param {Object} node the HTML DOM node 83 * 84 * @return {Object} a MochiKit.Style.Coordinates object with "x" and 85 * "y" properties containing the element scroll offset 86 */ 87 MochiKit.Style.getScrollOffset = function(node) { 88 node = MochiKit.DOM.getElement(node); 89 var x = node.scrollLeft || 0; 90 var y = node.scrollTop || 0; 91 return new MochiKit.Style.Coordinates(x, y); 92 } 93 94 /** 95 * Sets the scroll offset for an HTML DOM node. 96 * 97 * @param {Object} node the HTML DOM node 98 * @param {Object} offset the MochiKit.Style.Coordinates containing 99 * the new scroll offset "x" and "y" values 100 */ 101 MochiKit.Style.setScrollOffset = function(node, offset) { 102 node = MochiKit.DOM.getElement(node); 103 node.scrollLeft = offset.x; 104 node.scrollTop = offset.y; 105 } 106 107 /** 108 * Resets the scroll offsets to zero for for an HTML DOM node. 109 * Optionally all child node offsets can also be reset. 110 * 111 * @param {Object} node the HTML DOM node 112 * @param {Boolean} [recursive] the recursive flag, defaults to 113 * false 114 */ 115 MochiKit.Style.resetScrollOffset = function(node, recursive) { 116 node = MochiKit.DOM.getElement(node); 117 node.scrollLeft = 0; 118 node.scrollTop = 0; 119 if (recursive) { 120 node = node.firstChild; 121 while (node != null) { 122 if (node.nodeType === 1) { // Node.ELEMENT_NODE 123 MochiKit.Style.resetScrollOffset(node, true); 124 } 125 node = node.nextSibling; 126 } 127 } 128 } 129 130 /** 131 * Adjusts the scroll offsets for an HTML DOM node to ensure optimal 132 * visibility for the specified coordinates box. This function will 133 * scroll the node both vertially and horizontally to ensure that 134 * the top left corner of the box is always visible and that as much 135 * of the box extent as possible is visible. 136 * 137 * @param {Object} node the HTML DOM node 138 * @param {Object} box the coordinates box with optional properties 139 * {l, t, r, b} or {x, y, w, h} 140 */ 141 MochiKit.Style.adjustScrollOffset = function(node, box) { 142 node = MochiKit.DOM.getElement(node); 143 var dim = MochiKit.Style.getElementDimensions(node); 144 var xMin = MochiKit.Base.defaultValue(box.l, box.x, NaN); 145 var xMax = MochiKit.Base.defaultValue(box.r, xMin + box.w, NaN); 146 var yMin = MochiKit.Base.defaultValue(box.t, box.y, NaN); 147 var yMax = MochiKit.Base.defaultValue(box.b, yMin + box.h, NaN); 148 if (!isNaN(xMax) && node.scrollLeft + dim.w < xMax) { 149 node.scrollLeft = xMax - dim.h; 150 } 151 if (!isNaN(xMin) && node.scrollLeft > xMin) { 152 node.scrollLeft = xMin; 153 } 154 if (!isNaN(yMax) && node.scrollTop + dim.h < yMax) { 155 node.scrollTop = yMax - dim.h; 156 } 157 if (!isNaN(yMin) && node.scrollTop > yMin) { 158 node.scrollTop = yMin; 159 } 160 } 161 162 /** 163 * Registers algebraic constraints for an element width, height and 164 * aspect ratio. The constraints may either be fixed numeric values, 165 * functions or string formulas. The string formulas will be 166 * converted to JavaScript functions, replacing any "%" character 167 * with the parent reference value (i.e. the parent element width, 168 * height or aspect ratio). It is also possible to directly reference 169 * the parent values with the "w" and "h" variables. Any functions 170 * specified must take both the parent element width and height as 171 * arguments (possibly ignoring one or the other) and return a 172 * number. Any value returned when evaluating the functions or 173 * formulas will be bounded to the parent element size. 174 * 175 * @example // To set width to 50% - 20 px & height to 100% 176 * registerSizeConstraints(node, "50% - 20", "100%"); 177 * // To set a square aspect ratio 178 * registerSizeConstraints(node, null, null, 1.0); 179 * 180 * @param {Object} node the HTML DOM node 181 * @param {Number/Function/String} [width] the width constraint 182 * @param {Number/Function/String} [height] the height constraint 183 * @param {Number/Function/String} [aspect] the aspect ratio constraint 184 */ 185 MochiKit.Style.registerSizeConstraints = function(node, width, height, aspect) { 186 node = MochiKit.DOM.getElement(node); 187 var sc = node.sizeConstraints = { w: null, h: null, a: null }; 188 if (typeof(width) == "number") { 189 sc.w = function(w, h) { return width; } 190 } else if (typeof(width) == "function") { 191 sc.w = width; 192 } else if (typeof(width) == "string") { 193 var code = "return " + width.replace(/%/g, "*0.01*w") + ";"; 194 sc.w = new Function("w", "h", code); 195 } 196 if (typeof(height) == "number") { 197 sc.h = function(w, h) { return height; } 198 } else if (typeof(height) == "function") { 199 sc.h = height; 200 } else if (typeof(height) == "string") { 201 var code = "return " + height.replace(/%/g, "*0.01*h") + ";"; 202 sc.h = new Function("w", "h", code); 203 } 204 if (typeof(aspect) == "number") { 205 sc.a = function(w, h) { return aspect; } 206 } else if (typeof(aspect) == "function") { 207 sc.a = aspect; 208 } else if (typeof(aspect) == "string") { 209 var code = "return " + aspect.replace(/%/g, "*0.01*w/h") + ";"; 210 sc.a = new Function("w", "h", code); 211 } 212 } 213 214 /** 215 * Resizes a list of DOM nodes using their parent element sizes and 216 * any registered size constraints. The resize operation is recursive 217 * and will also be applied to all child nodes. If an element lacks a 218 * size constraint for either width or height, that size aspect will 219 * not be modified. 220 * 221 * @param {Object} [...] the HTML DOM nodes to resize 222 * 223 * @see registerSizeConstraints 224 */ 225 MochiKit.Style.resizeElements = function(/* ... */) { 226 var args = MochiKit.Base.flattenArray(arguments); 227 for (var i = 0; i < args.length; i++) { 228 var node = MochiKit.DOM.getElement(args[i]); 229 if (node != null && node.nodeType === 1 && // Node.ELEMENT_NODE 230 node.parentNode != null && node.sizeConstraints != null) { 231 232 var ref = { w: node.parentNode.w, h: node.parentNode.h }; 233 if (ref.w == null && ref.h == null) { 234 ref = MochiKit.Style.getElementDimensions(node.parentNode, true); 235 } 236 var dim = MochiKit.Style._evalConstraints(node.sizeConstraints, ref); 237 MochiKit.Style.setElementDimensions(node, dim); 238 node.w = dim.w; 239 node.h = dim.h; 240 } 241 if (node != null && typeof(node.resizeContent) == "function") { 242 node.resizeContent(); 243 } else { 244 node = node.firstChild; 245 while (node != null) { 246 if (node.nodeType === 1) { // Node.ELEMENT_NODE 247 MochiKit.Style.resizeElements(node); 248 } 249 node = node.nextSibling; 250 } 251 } 252 } 253 } 254 255 /** 256 * Evaluates the size constraint functions with a refeence dimension 257 * object. This is an internal function used to encapsulate the 258 * function calls and provide logging on errors. 259 * 260 * @param {Object} sc the size constraints object 261 * @param {Object} ref the MochiKit.Style.Dimensions maximum 262 * reference values 263 * 264 * @return {Object} the MochiKit.Style.Dimensions with evaluated size 265 * constraint values (some may be null) 266 */ 267 MochiKit.Style._evalConstraints = function(sc, ref) { 268 var log = MochiKit.Logging.logError; 269 if (typeof(sc.w) == "function") { 270 try { 271 var w = Math.max(0, Math.min(ref.w, sc.w(ref.w, ref.h))); 272 } catch (e) { 273 log("Error evaluating width size constraint; " + 274 "w: " + ref.w + ", h: " + ref.h, e); 275 } 276 } 277 if (typeof(sc.h) == "function") { 278 try { 279 var h = Math.max(0, Math.min(ref.h, sc.h(ref.w, ref.h))); 280 } catch (e) { 281 log("Error evaluating height size constraint; " + 282 "w: " + ref.w + ", h: " + ref.h, e); 283 } 284 } 285 if (typeof(sc.a) == "function") { 286 try { 287 var a = sc.a(ref.w, ref.h); 288 w = w || ref.w; 289 h = h || ref.h; 290 if (h * a > ref.w) { 291 h = ref.w / a; 292 } 293 if (w / a > ref.h) { 294 w = ref.h * a; 295 } 296 if (w > h * a) { 297 w = h * a; 298 } else { 299 h = w / a; 300 } 301 } catch (e) { 302 log("Error evaluating aspect size constraint; " + 303 "w: " + ref.w + ", h: " + ref.h, e); 304 } 305 } 306 if (w != null) { 307 w = Math.floor(w); 308 } 309 if (h != null) { 310 h = Math.floor(h); 311 } 312 return new MochiKit.Style.Dimensions(w, h); 313 } 314