1/*
2 * RapidContext <https://www.rapidcontext.com/>
3 * Copyright (c) 2007-2024 Per Cederberg. All rights reserved.
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the BSD license.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * See the RapidContext LICENSE for more details.
13 */
14
15// Create default RapidContext object
16if (typeof(RapidContext) == "undefined") {
17 RapidContext = {};
18}
19
20/**
21 * Provides utility functions for basic objects, arrays, DOM nodes and CSS.
22 * These functions are complementary to what is available in MochiKit and/or
23 * jQuery.
24 * @namespace RapidContext.Util
25 */
26if (typeof(RapidContext.Util) == "undefined") {
27 RapidContext.Util = {};
28}
29
30
31// General utility functions
32
33/**
34 * Converts a string to a title-cased string. All word boundaries are replaced
35 * with a single space and the subsequent character is capitalized.
36 *
37 * All underscore ("_"), hyphen ("-") and lower-upper character pairs are
38 * recognized as word boundaries. Note that this function does not change the
39 * capitalization of other characters in the string.
40 *
41 * @param {string} str the string to convert
42 *
43 * @return {string} the converted string
44 *
45 * @example
46 * RapidContext.Util.toTitleCase("a short heading")
47 * ==> "A Short Heading"
48 *
49 * @example
50 * RapidContext.Util.toTitleCase("camelCase")
51 * ==> "Camel Case"
52 *
53 * @example
54 * RapidContext.Util.toTitleCase("bounding-box")
55 * ==> "Bounding Box"
56 *
57 * @example
58 * RapidContext.Util.toTitleCase("UPPER_CASE_VALUE")
59 * ==> "UPPER CASE VALUE"
60 */
61RapidContext.Util.toTitleCase = function (str) {
62 str = str.replace(/[._-]+/g, " ").trim();
63 str = str.replace(/[a-z][A-Z]/g, function (match) {
64 return match.charAt(0) + " " + match.charAt(1);
65 });
66 str = str.replace(/(^|\s)[a-z]/g, function (match) {
67 return match.toUpperCase();
68 });
69 return str;
70};
71
72
73// DOM utility functions
74
75/**
76 * Blurs (unfocuses) a specified DOM node and all relevant child nodes. This
77 * function will recursively blur all `<a>`, `<button>`, `<input>`,
78 * `<textarea>` and `<select>` child nodes found.
79 *
80 * @param {Object} node the HTML DOM node
81 */
82RapidContext.Util.blurAll = function (node) {
83 node.blur();
84 var tags = ["A", "BUTTON", "INPUT", "TEXTAREA", "SELECT"];
85 for (var i = 0; i < tags.length; i++) {
86 var nodes = node.getElementsByTagName(tags[i]);
87 for (var j = 0; j < nodes.length; j++) {
88 nodes[j].blur();
89 }
90 }
91};
92
93/**
94 * Registers size constraints for the element width and/or height. The
95 * constraints may either be fixed numeric values or simple arithmetic (in a
96 * string). The formulas will be converted to CSS calc() expressions.
97 *
98 * Legacy constraint functions are still supported and must take two arguments
99 * (parent width and height) and should return a number. The returned number is
100 * set as the new element width or height (in pixels). Any returned value will
101 * also be bounded by the parent element size to avoid calculation errors.
102 *
103 * @param {Object} node the HTML DOM node
104 * @param {number|string|function} [width] the width constraint
105 * @param {number|string|function} [height] the height constraint
106 *
107 * @deprecated Use CSS width and height with calc() instead.
108 *
109 * @example
110 * RapidContext.Util.registerSizeConstraints(node, "50%-20", "100%");
111 * ==> Sets width to 50%-20 px and height to 100% of parent dimension
112 *
113 * @example
114 * RapidContext.Util.resizeElements(node, otherNode);
115 * ==> Evaluates the size constraints for both nodes
116 */
117RapidContext.Util.registerSizeConstraints = function (node, width, height) {
118 function toCSS(val) {
119 if (/[+-]/.test(val)) {
120 val = "calc( " + val.replace(/[+-]/g, " $& ") + " )";
121 }
122 val = val.replace(/(\d)( |$)/g, "$1px$2");
123 return val;
124 }
125 console.warn("deprecated: call to RapidContext.Util.registerSizeConstraints(), use CSS calc() instead");
126 node = MochiKit.DOM.getElement(node);
127 if (typeof(width) == "number" || typeof(width) == "string") {
128 node.style.width = toCSS(String(width));
129 } else if (typeof(width) == "function") {
130 console.info("registerSizeConstraints: width function support will be removed", node);
131 node.sizeConstraints = node.sizeConstraints || { w: null, h: null };
132 node.sizeConstraints.w = width;
133 }
134 if (typeof(height) == "number" || typeof(height) == "string") {
135 node.style.height = toCSS(String(height));
136 } else if (typeof(height) == "function") {
137 console.info("registerSizeConstraints: height function support will be removed", node);
138 node.sizeConstraints = node.sizeConstraints || { w: null, h: null };
139 node.sizeConstraints.h = height;
140 }
141};
142
143/**
144 * Resizes one or more DOM nodes using their registered size constraints and
145 * their parent element sizes. The resize operation will only modify those
146 * elements that have constraints, but will perform a depth-first recursion
147 * over all element child nodes as well.
148 *
149 * Partial constraints are accepted, in which case only the width or the height
150 * is modified. Aspect ratio constraints are applied after the width and height
151 * constraints. The result will always be bounded by the parent element width
152 * or height.
153 *
154 * The recursive descent of this function can be limited by adding a
155 * `resizeContent` function to a DOM node. Such a function will be called to
156 * handle all subnode resizing, making it possible to limit or omitting the
157 * DOM tree traversal.
158 *
159 * @param {...Node} node the HTML DOM nodes to resize
160 *
161 * @deprecated Use CSS width and height with calc() instead.
162 *
163 * @example
164 * RapidContext.Util.resizeElements(node);
165 * ==> Evaluates the size constraints for a node and all child nodes
166 *
167 * @example
168 * elem.resizeContent = () => {};
169 * ==> Assigns a no-op child resize handler to elem
170 */
171RapidContext.Util.resizeElements = function (/* ... */) {
172 console.warn("deprecated: call to RapidContext.Util.resizeElements(), use CSS calc() instead");
173 Array.from(arguments).forEach(function (arg) {
174 var node = MochiKit.DOM.getElement(arg);
175 if (node && node.nodeType === 1 && node.parentNode && node.sizeConstraints) {
176 var ref = { w: node.parentNode.w, h: node.parentNode.h };
177 if (ref.w == null && ref.h == null) {
178 ref = MochiKit.Style.getElementDimensions(node.parentNode, true);
179 }
180 var dim = RapidContext.Util._evalConstraints(node.sizeConstraints, ref);
181 MochiKit.Style.setElementDimensions(node, dim);
182 node.w = dim.w;
183 node.h = dim.h;
184 }
185 if (node && typeof(node.resizeContent) == "function") {
186 try {
187 node.resizeContent();
188 } catch (e) {
189 console.error("Error in resizeContent()", node, e);
190 }
191 } else if (node && node.childNodes) {
192 Array.from(node.childNodes).forEach(function (child) {
193 if (child.nodeType === 1) {
194 RapidContext.Util.resizeElements(child);
195 }
196 });
197 }
198 });
199};
200
201/**
202 * Evaluates the size constraint functions with a refeence dimension
203 * object. This is an internal function used to encapsulate the
204 * function calls and provide logging on errors.
205 *
206 * @param {Object} sc the size constraints object
207 * @param {Object} ref the MochiKit.Style.Dimensions maximum
208 * reference values
209 *
210 * @return {Object} the MochiKit.Style.Dimensions with evaluated size
211 * constraint values (some may be null)
212 */
213RapidContext.Util._evalConstraints = function (sc, ref) {
214 var w, h;
215 if (typeof(sc.w) == "function") {
216 try {
217 w = Math.max(0, Math.min(ref.w, sc.w(ref.w, ref.h)));
218 } catch (e) {
219 console.error("Error evaluating width size constraint; " +
220 "w: " + ref.w + ", h: " + ref.h, e);
221 }
222 }
223 if (typeof(sc.h) == "function") {
224 try {
225 h = Math.max(0, Math.min(ref.h, sc.h(ref.w, ref.h)));
226 } catch (e) {
227 console.error("Error evaluating height size constraint; " +
228 "w: " + ref.w + ", h: " + ref.h, e);
229 }
230 }
231 if (w != null) {
232 w = Math.floor(w);
233 }
234 if (h != null) {
235 h = Math.floor(h);
236 }
237 return new MochiKit.Style.Dimensions(w, h);
238};
239
RapidContext
Access · Discovery · Insight
www.rapidcontext.com