/* Copyright (c) 2004-2006, The Dojo Foundation All Rights Reserved. Licensed under the Academic Free License version 2.1 or above OR the modified BSD license. For more information on Dojo licensing, see: http://dojotoolkit.org/community/licensing.shtml */ dojo.provide("dojo.html.style"); dojo.require("dojo.html.common"); dojo.require("dojo.uri.Uri"); dojo.html.getClass = function(/* HTMLElement */node){ // summary // Returns the string value of the list of CSS classes currently assigned directly // to the node in question. Returns an empty string if no class attribute is found; node = dojo.byId(node); if(!node){ return ""; } var cs = ""; if(node.className){ cs = node.className; }else if(dojo.html.hasAttribute(node, "class")){ cs = dojo.html.getAttribute(node, "class"); } return cs.replace(/^\s+|\s+$/g, ""); // string } dojo.html.getClasses = function(/* HTMLElement */node) { // summary // Returns an array of CSS classes currently assigned directly to the node in question. // Returns an empty array if no classes are found; var c = dojo.html.getClass(node); return (c == "") ? [] : c.split(/\s+/g); // array } dojo.html.hasClass = function(/* HTMLElement */node, /* string */classname){ // summary // Returns whether or not the specified classname is a portion of the // class list currently applied to the node. Does not cover cascaded // styles, only classes directly applied to the node. return (new RegExp('(^|\\s+)'+classname+'(\\s+|$)')).test(dojo.html.getClass(node)) // boolean } dojo.html.prependClass = function(/* HTMLElement */node, /* string */classStr){ // summary // Adds the specified class to the beginning of the class list on the // passed node. This gives the specified class the highest precidence // when style cascading is calculated for the node. Returns true or // false; indicating success or failure of the operation, respectively. classStr += " " + dojo.html.getClass(node); return dojo.html.setClass(node, classStr); // boolean } dojo.html.addClass = function(/* HTMLElement */node, /* string */classStr){ // summary // Adds the specified class to the end of the class list on the // passed &node;. Returns &true; or &false; indicating success or failure. if (dojo.html.hasClass(node, classStr)) { return false; } classStr = (dojo.html.getClass(node) + " " + classStr).replace(/^\s+|\s+$/g,""); return dojo.html.setClass(node, classStr); // boolean } dojo.html.setClass = function(/* HTMLElement */node, /* string */classStr){ // summary // Clobbers the existing list of classes for the node, replacing it with // the list given in the 2nd argument. Returns true or false // indicating success or failure. node = dojo.byId(node); var cs = new String(classStr); try{ if(typeof node.className == "string"){ node.className = cs; }else if(node.setAttribute){ node.setAttribute("class", classStr); node.className = cs; }else{ return false; } }catch(e){ dojo.debug("dojo.html.setClass() failed", e); } return true; } dojo.html.removeClass = function(/* HTMLElement */node, /* string */classStr, /* boolean? */allowPartialMatches){ // summary // Removes the className from the node;. Returns true or false indicating success or failure. try{ if (!allowPartialMatches) { var newcs = dojo.html.getClass(node).replace(new RegExp('(^|\\s+)'+classStr+'(\\s+|$)'), "$1$2"); } else { var newcs = dojo.html.getClass(node).replace(classStr,''); } dojo.html.setClass(node, newcs); }catch(e){ dojo.debug("dojo.html.removeClass() failed", e); } return true; // boolean } dojo.html.replaceClass = function(/* HTMLElement */node, /* string */newClass, /* string */oldClass) { // summary // Replaces 'oldClass' and adds 'newClass' to node dojo.html.removeClass(node, oldClass); dojo.html.addClass(node, newClass); } // Enum type for getElementsByClass classMatchType arg: dojo.html.classMatchType = { ContainsAll : 0, // all of the classes are part of the node's class (default) ContainsAny : 1, // any of the classes are part of the node's class IsOnly : 2 // only all of the classes are part of the node's class } dojo.html.getElementsByClass = function( /* string */classStr, /* HTMLElement? */parent, /* string? */nodeType, /* integer? */classMatchType, /* boolean? */useNonXpath ){ // summary // Returns an array of nodes for the given classStr, children of a // parent, and optionally of a certain nodeType // FIXME: temporarily set to false because of several dojo tickets related // to the xpath version not working consistently in firefox. useNonXpath = false; var _document = dojo.doc(); parent = dojo.byId(parent) || _document; var classes = classStr.split(/\s+/g); var nodes = []; if( classMatchType != 1 && classMatchType != 2 ) classMatchType = 0; // make it enum var reClass = new RegExp("(\\s|^)((" + classes.join(")|(") + "))(\\s|$)"); var srtLength = classes.join(" ").length; var candidateNodes = []; if(!useNonXpath && _document.evaluate) { // supports dom 3 xpath var xpath = ".//" + (nodeType || "*") + "[contains("; if(classMatchType != dojo.html.classMatchType.ContainsAny){ xpath += "concat(' ',@class,' '), ' " + classes.join(" ') and contains(concat(' ',@class,' '), ' ") + " ')"; if (classMatchType == 2) { xpath += " and string-length(@class)="+srtLength+"]"; }else{ xpath += "]"; } }else{ xpath += "concat(' ',@class,' '), ' " + classes.join(" ') or contains(concat(' ',@class,' '), ' ") + " ')]"; } var xpathResult = _document.evaluate(xpath, parent, null, XPathResult.ANY_TYPE, null); var result = xpathResult.iterateNext(); while(result){ try{ candidateNodes.push(result); result = xpathResult.iterateNext(); }catch(e){ break; } } return candidateNodes; // NodeList }else{ if(!nodeType){ nodeType = "*"; } candidateNodes = parent.getElementsByTagName(nodeType); var node, i = 0; outer: while(node = candidateNodes[i++]){ var nodeClasses = dojo.html.getClasses(node); if(nodeClasses.length == 0){ continue outer; } var matches = 0; for(var j = 0; j < nodeClasses.length; j++){ if(reClass.test(nodeClasses[j])){ if(classMatchType == dojo.html.classMatchType.ContainsAny){ nodes.push(node); continue outer; }else{ matches++; } }else{ if(classMatchType == dojo.html.classMatchType.IsOnly){ continue outer; } } } if(matches == classes.length){ if( (classMatchType == dojo.html.classMatchType.IsOnly)&& (matches == nodeClasses.length)){ nodes.push(node); }else if(classMatchType == dojo.html.classMatchType.ContainsAll){ nodes.push(node); } } } return nodes; // NodeList } } dojo.html.getElementsByClassName = dojo.html.getElementsByClass; dojo.html.toCamelCase = function(/* string */selector){ // summary // Translates a CSS selector string to a camel-cased one. var arr = selector.split('-'), cc = arr[0]; for(var i = 1; i < arr.length; i++) { cc += arr[i].charAt(0).toUpperCase() + arr[i].substring(1); } return cc; // string } dojo.html.toSelectorCase = function(/* string */selector){ // summary // Translates a camel cased string to a selector cased one. return selector.replace(/([A-Z])/g, "-$1" ).toLowerCase(); // string } dojo.html.getComputedStyle = function(/* HTMLElement */node, /* string */cssSelector, /* integer? */inValue){ // summary // Returns the computed style of cssSelector on node. node = dojo.byId(node); // cssSelector may actually be in camel case, so force selector version var cssSelector = dojo.html.toSelectorCase(cssSelector); var property = dojo.html.toCamelCase(cssSelector); if(!node || !node.style){ return inValue; } else if (document.defaultView && dojo.html.isDescendantOf(node, node.ownerDocument)){ // W3, gecko, KHTML try{ // mozilla segfaults when margin-* and node is removed from doc // FIXME: need to figure out a if there is quicker workaround var cs = document.defaultView.getComputedStyle(node, ""); if(cs){ return cs.getPropertyValue(cssSelector); // integer } }catch(e){ // reports are that Safari can throw an exception above if(node.style.getPropertyValue){ // W3 return node.style.getPropertyValue(cssSelector); // integer } else { return inValue; // integer } } } else if(node.currentStyle){ // IE return node.currentStyle[property]; // integer } if(node.style.getPropertyValue){ // W3 return node.style.getPropertyValue(cssSelector); // integer }else{ return inValue; // integer } } dojo.html.getStyleProperty = function(/* HTMLElement */node, /* string */cssSelector){ // summary // Returns the value of the passed style node = dojo.byId(node); return (node && node.style ? node.style[dojo.html.toCamelCase(cssSelector)] : undefined); // string } dojo.html.getStyle = function(/* HTMLElement */node, /* string */cssSelector){ // summary // Returns the computed value of the passed style var value = dojo.html.getStyleProperty(node, cssSelector); return (value ? value : dojo.html.getComputedStyle(node, cssSelector)); // string || integer } dojo.html.setStyle = function(/* HTMLElement */node, /* string */cssSelector, /* string */value){ // summary // Set the value of passed style on node node = dojo.byId(node); if(node && node.style){ var camelCased = dojo.html.toCamelCase(cssSelector); node.style[camelCased] = value; } } dojo.html.setStyleText = function (/* HTMLElement */target, /* string */text) { // summary // Try to set the entire cssText property of the passed target; equiv of setting style attribute. try { target.style.cssText = text; } catch (e) { target.setAttribute("style", text); } } dojo.html.copyStyle = function(/* HTMLElement */target, /* HTMLElement */source){ // summary // work around for opera which doesn't have cssText, and for IE which fails on setAttribute if(!source.style.cssText){ target.setAttribute("style", source.getAttribute("style")); }else{ target.style.cssText = source.style.cssText; } dojo.html.addClass(target, dojo.html.getClass(source)); } dojo.html.getUnitValue = function(/* HTMLElement */node, /* string */cssSelector, /* boolean? */autoIsZero){ // summary // Get the value of passed selector, with the specific units used var s = dojo.html.getComputedStyle(node, cssSelector); if((!s)||((s == 'auto')&&(autoIsZero))){ return { value: 0, units: 'px' }; // object } // FIXME: is regex inefficient vs. parseInt or some manual test? var match = s.match(/(\-?[\d.]+)([a-z%]*)/i); if (!match){return dojo.html.getUnitValue.bad;} return { value: Number(match[1]), units: match[2].toLowerCase() }; // object } dojo.html.getUnitValue.bad = { value: NaN, units: '' }; dojo.html.getPixelValue = function(/* HTMLElement */node, /* string */cssSelector, /* boolean? */autoIsZero){ // summary // Get the value of passed selector in pixels. var result = dojo.html.getUnitValue(node, cssSelector, autoIsZero); // FIXME: there is serious debate as to whether or not this is the right solution if(isNaN(result.value)){ return 0; // integer } // FIXME: code exists for converting other units to px (see Dean Edward's IE7) // but there are cross-browser complexities if((result.value)&&(result.units != 'px')){ return NaN; // integer } return result.value; // integer } dojo.html.setPositivePixelValue = function(/* HTMLElement */node, /* string */selector, /* integer */value){ // summary // Attempt to set the value of selector on node as a positive pixel value. if(isNaN(value)){return false;} node.style[selector] = Math.max(0, value) + 'px'; return true; // boolean } dojo.html.styleSheet = null; // FIXME: this is a really basic stub for adding and removing cssRules, but // it assumes that you know the index of the cssRule that you want to add // or remove, making it less than useful. So we need something that can // search for the selector that you you want to remove. dojo.html.insertCssRule = function(/* string */selector, /* string */declaration, /* integer? */index) { // summary // Attempt to insert declaration as selector on the internal stylesheet; if index try to set it there. if (!dojo.html.styleSheet) { if (document.createStyleSheet) { // IE dojo.html.styleSheet = document.createStyleSheet(); } else if (document.styleSheets[0]) { // rest // FIXME: should create a new style sheet here // fall back on an exsiting style sheet dojo.html.styleSheet = document.styleSheets[0]; } else { return null; // integer } // fail } if (arguments.length < 3) { // index may == 0 if (dojo.html.styleSheet.cssRules) { // W3 index = dojo.html.styleSheet.cssRules.length; } else if (dojo.html.styleSheet.rules) { // IE index = dojo.html.styleSheet.rules.length; } else { return null; // integer } // fail } if (dojo.html.styleSheet.insertRule) { // W3 var rule = selector + " { " + declaration + " }"; return dojo.html.styleSheet.insertRule(rule, index); // integer } else if (dojo.html.styleSheet.addRule) { // IE return dojo.html.styleSheet.addRule(selector, declaration, index); // integer } else { return null; // integer } // fail } dojo.html.removeCssRule = function(/* integer? */index){ // summary // Attempt to remove the rule at index. if(!dojo.html.styleSheet){ dojo.debug("no stylesheet defined for removing rules"); return false; } if(dojo.render.html.ie){ if(!index){ index = dojo.html.styleSheet.rules.length; dojo.html.styleSheet.removeRule(index); } }else if(document.styleSheets[0]){ if(!index){ index = dojo.html.styleSheet.cssRules.length; } dojo.html.styleSheet.deleteRule(index); } return true; // boolean } dojo.html._insertedCssFiles = []; // cache container needed because IE reformats cssText when added to DOM dojo.html.insertCssFile = function(/* string */URI, /* HTMLDocument? */doc, /* boolean? */checkDuplicates, /* boolean */fail_ok){ // summary // calls css by XmlHTTP and inserts it into DOM as if(!URI){ return; } if(!doc){ doc = document; } var cssStr = dojo.hostenv.getText(URI, false, fail_ok); if(cssStr===null){ return; } cssStr = dojo.html.fixPathsInCssText(cssStr, URI); if(checkDuplicates){ var idx = -1, node, ent = dojo.html._insertedCssFiles; for(var i = 0; i < ent.length; i++){ if((ent[i].doc == doc) && (ent[i].cssText == cssStr)){ idx = i; node = ent[i].nodeRef; break; } } // make sure we havent deleted our node if(node){ var styles = doc.getElementsByTagName("style"); for(var i = 0; i < styles.length; i++){ if(styles[i] == node){ return; } } // delete this entry dojo.html._insertedCssFiles.shift(idx, 1); } } var style = dojo.html.insertCssText(cssStr, doc); dojo.html._insertedCssFiles.push({'doc': doc, 'cssText': cssStr, 'nodeRef': style}); // insert custom attribute ex dbgHref="../foo.css" usefull when debugging in DOM inspectors, no? if(style && djConfig.isDebug){ style.setAttribute("dbgHref", URI); } return style; // HTMLStyleElement } dojo.html.insertCssText = function(/* string */cssStr, /* HTMLDocument? */doc, /* string? */URI){ // summary // Attempt to insert CSS rules into the document through inserting a style element // DomNode Style = insertCssText(String ".dojoMenu {color: green;}"[, DomDoc document, dojo.uri.Uri Url ]) if(!cssStr){ return; // HTMLStyleElement } if(!doc){ doc = document; } if(URI){// fix paths in cssStr cssStr = dojo.html.fixPathsInCssText(cssStr, URI); } var style = doc.createElement("style"); style.setAttribute("type", "text/css"); // IE is b0rken enough to require that we add the element to the doc // before changing it's properties var head = doc.getElementsByTagName("head")[0]; if(!head){ // must have a head tag dojo.debug("No head tag in document, aborting styles"); return; // HTMLStyleElement }else{ head.appendChild(style); } if(style.styleSheet){// IE var setFunc = function(){ try{ style.styleSheet.cssText = cssStr; }catch(e){ dojo.debug(e); } }; if(style.styleSheet.disabled){ setTimeout(setFunc, 10); }else{ setFunc(); } }else{ // w3c var cssText = doc.createTextNode(cssStr); style.appendChild(cssText); } return style; // HTMLStyleElement } dojo.html.fixPathsInCssText = function(/* string */cssStr, /* string */URI){ // summary // usage: cssText comes from dojoroot/src/widget/templates/Foobar.css // it has .dojoFoo { background-image: url(images/bar.png);} then uri should point to dojoroot/src/widget/templates/ if(!cssStr || !URI){ return; } var match, str = "", url = "", urlChrs = "[\\t\\s\\w\\(\\)\\/\\.\\\\'\"-:#=&?~]+"; var regex = new RegExp('url\\(\\s*('+urlChrs+')\\s*\\)'); var regexProtocol = /(file|https?|ftps?):\/\//; regexTrim = new RegExp("^[\\s]*(['\"]?)("+urlChrs+")\\1[\\s]*?$"); if(dojo.render.html.ie55 || dojo.render.html.ie60){ var regexIe = new RegExp("AlphaImageLoader\\((.*)src\=['\"]("+urlChrs+")['\"]"); // TODO: need to decide how to handle relative paths and AlphaImageLoader see #1441 // current implementation breaks on build with intern_strings while(match = regexIe.exec(cssStr)){ url = match[2].replace(regexTrim, "$2"); if(!regexProtocol.exec(url)){ url = (new dojo.uri.Uri(URI, url).toString()); } str += cssStr.substring(0, match.index) + "AlphaImageLoader(" + match[1] + "src='" + url + "'"; cssStr = cssStr.substr(match.index + match[0].length); } cssStr = str + cssStr; str = ""; } while(match = regex.exec(cssStr)){ url = match[1].replace(regexTrim, "$2"); if(!regexProtocol.exec(url)){ url = (new dojo.uri.Uri(URI, url).toString()); } str += cssStr.substring(0, match.index) + "url(" + url + ")"; cssStr = cssStr.substr(match.index + match[0].length); } return str + cssStr; // string } dojo.html.setActiveStyleSheet = function(/* string */title){ // summary // Activate style sheet with specified title. var i = 0, a, els = dojo.doc().getElementsByTagName("link"); while (a = els[i++]) { if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")){ a.disabled = true; if (a.getAttribute("title") == title) { a.disabled = false; } } } } dojo.html.getActiveStyleSheet = function(){ // summary // return the title of the currently active stylesheet var i = 0, a, els = dojo.doc().getElementsByTagName("link"); while (a = els[i++]) { if (a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled ){ return a.getAttribute("title"); // string } } return null; // string } dojo.html.getPreferredStyleSheet = function(){ // summary // Return the preferred stylesheet title (i.e. link without alt attribute) var i = 0, a, els = dojo.doc().getElementsByTagName("link"); while (a = els[i++]) { if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("rel").indexOf("alt") == -1 && a.getAttribute("title") ){ return a.getAttribute("title"); // string } } return null; // string } dojo.html.applyBrowserClass = function(/* HTMLElement */node){ // summary // Applies pre-set class names based on browser & version to the passed node. // Modified version of Morris' CSS hack. var drh=dojo.render.html; var classes = { dj_ie: drh.ie, dj_ie55: drh.ie55, dj_ie6: drh.ie60, dj_ie7: drh.ie70, dj_iequirks: drh.ie && drh.quirks, dj_opera: drh.opera, dj_opera8: drh.opera && (Math.floor(dojo.render.version)==8), dj_opera9: drh.opera && (Math.floor(dojo.render.version)==9), dj_khtml: drh.khtml, dj_safari: drh.safari, dj_gecko: drh.mozilla }; // no dojo unsupported browsers for(var p in classes){ if(classes[p]){ dojo.html.addClass(node, p); } } };