Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save joebillings/7024284 to your computer and use it in GitHub Desktop.
Save joebillings/7024284 to your computer and use it in GitHub Desktop.
<action-encoding>UTF-8</action-encoding>
<library-action name="style_accessors">
<action-version version="1.2.7">
Style Accessors
Get and set styles on any element, regardless where they were initially defined.
MIT License
Copyright (c) 2013 Walter Lee Davis
</action-version>
<action-javascript>
/**
* Does this page use a root-based Resources folder?
* @return boolean
*/
if(undefined == FWPage.hasRootResourcesFolder){
FWPage.prototype.hasRootResourcesFolder = function(){
var head = fwDocument.fwTags.fwFind('head');
if(!head) fwAbort('Please call hasRootResourcesFolder from fwBeforeEndHead or later.');
var links = head.fwFindAll('link', 'href');
for(var i in links){
if(links[i].href){
var link = links[i].href.toString();
var page = this.fwFileName.toString().split('.').shift();
if(link.match('../css') && link.indexOf(page) > 0){
return true;
}
}
}
return false;
};
}
/**
* Does this page use a Resources folder?
* @return boolean
*/
if(undefined == FWPage.hasNoResourcesFolder){
FWPage.prototype.hasNoResourcesFolder = function(){
return (! fwClearGif.toString().match('Resources/'))
};
}
/**
* Find the folder where the style should be saved
* @return string
*/
if(undefined == FWPage.styleFolder){
FWPage.prototype.styleFolder = function(){
var rootFolder = fwDocument.fwPages.fwItems[0].fwFolder.fwHttpPath();
if(this.hasNoResourcesFolder()){
return '/' + this.fwFolder.fwHttpPath();
}
if(this.hasRootResourcesFolder()){
return rootFolder + '/css/';
}
return '/' + this.fwFolder.fwHttpPath() + 'css/';
};
}
/**
* Define a URL for the override sheet for this page
* @return string relative URL to override sheet
*/
if(undefined == FWPage.styleOverrideURL){
FWPage.prototype.styleOverrideURL = function(){
var folder = this.styleFolder();
return folder + this.basename() + '_override.css';
};
}
/**
* Define a path to the override sheet for this page.
* @return string root-relative path to the override sheet
*/
if(undefined == FWPage.styleOverridePath){
FWPage.prototype.styleOverridePath = function(){
var rootFolder = fwDocument.fwPages.fwItems[0].fwFolder;
if(!rootFolder) fwAbort('Could not locate the root folder. Make sure there is at least one page in it.')
var rootFolderPath = rootFolder.fwHttpPath(null, true);
return (rootFolderPath + this.styleOverrideURL()).replace(/\//g, ':');
};
}
/**
* Basename for the override sheet
* @return string page filename without extension
*/
if(undefined == FWPage.basename){
FWPage.prototype.basename = function(){
var filename = this.fwFileName.toString().split('.');
if(this.hasRootResourcesFolder()){
filename = this.fwHttpPath().toString().replace(/\//g,'_').split('.');
}
filename.pop();
return filename.join('.');
};
}
/**
* Add a link to an override stylesheet, and create the file for that sheet.
* Only alters the page once per publish cycle.
* Must be called from fwBeforeEndHead() or later.
* @return link to sheet
*/
if(undefined == FWPage.addOverride){
FWPage.prototype.addOverride = function(){
if(this.overrideLink) return this.overrideLink;
var head = fwDocument.fwTags.fwFind('head');
if(!head) fwAbort('Please call addOverride from fwBeforeEndHead or later.')
var lastLink = head.findLast('link');
var link = head.fwAddOpt('link', lastLink);
head.fwAddRawOpt('', lastLink);
var myFile = new FWFile();
myFile.fwOpenWrite(fwPage.styleOverridePath());
myFile.fwWrite(fwPage.currentRules);
myFile.fwClose();
fwParameters['styleOverride'].fwSpecify(myFile);
link.rel = fwQuote('stylesheet');
link.href = fwQuote(fwParameters['styleOverride'].toString());
fwParameters['styleOverride'].fwClear();
link.type = fwQuote('text/css');
this.overrideLink = link;
return link;
};
}
/**
* Erase or initialize the reset stylesheet.
* @return FWFile
*/
if(undefined == FWPage.resetOverride){
FWPage.prototype.resetOverride = function(){
if (this.overrideReset)
return;
var myFile = new FWFile();
myFile.fwOpenWrite(this.styleOverridePath(), true);
myFile.fwSetEncoding('UTF-8');
myFile.fwWrite('');
myFile.fwClose();
this.overrideReset = true;
return myFile;
};
}
/**
* Announce our existence so we only publish a file from the last Action on the page
* Must be called in fwBeforeStartHTML()
* @void
*/
if(undefined == FWPage.announce){
FWPage.prototype.announce = function(){
if (!fwPage.style_accessors_use)
fwPage.style_accessors_use = 1;
else
fwPage.style_accessors_use += 1;
};
}
/**
* Renounce our existence so the last Action can run addOverride
* Must be called in fwAfterEndHTML()
* @void
*/
if(undefined == FWPage.renounce){
FWPage.prototype.renounce = function(){
fwPage.style_accessors_use -= 1;
// We're the last one...
if (fwPage.style_accessors_use == 0 && fwDocument.fwExternalStylesheets && fwPage.currentRules !== undefined)
fwPage.addOverride();
};
}
/**
* Convert a string of CSS into a hash.
* @return object JSON of styles
*/
if(undefined == cssToHash){
var cssToHash = function(string){
var out = {}, string = string.toString().replace(/^"\s*(.+?)\s*"$/, '$1');
var pairs = string.split(/\s*;\s*/);
for (var i in pairs){
var pair = pairs[i].split(/\s*:\s*/);
out[pair[0]] = pair[1];
};
return out;
};
}
/**
* Convert a JSON object of style definitions into CSS.
* @return string CSS style rules
*/
if(undefined == hashToCSS){
var hashToCSS = function(hash){
var out = [];
for (var i in hash){
var pair = i + ': ' + hash[i];
out.push(pair);
}
return out.join('; ').toString();
};
}
/**
* Get the CSS selector for a single tag.
* @return string
*/
if(undefined == FWTag.getSelector){
FWTag.prototype.getSelector = function(){
if(this.id){
var id = '#' + this.id.toString().slice(1,-1);
if(id.length > 1){
return id + ', ' + id + '.f-ms';
}
}
if(this['class']){
classes = this['class'].toString().slice(1,-1).split(/\s+/);
return '.' + classes.join('.');
}
};
}
if(undefined == FWTag.findLast){
FWTag.prototype.findLast = function(type) {
var els = this.fwFindAll(type);
for(var i = els.length-1; i >= 0; i--)
{
if(els[i].fwEnclosing != "!--" && !(/ie/i.test(els[i].href)) && !lastEl)
var lastEl = els[i];
}
return lastEl;
}
}
/**
* Write CSS into the override sheet.
* @return void
*/
if(undefined == FWTag.setOverride){
FWTag.prototype.setOverride = function(content){
fwPage.currentRules = fwPage.currentRules || "";
var overwrite = false;
var selector = this.getSelector();
if(new RegExp(selector).test(fwPage.currentRules)){
var re = new RegExp(selector + ' \\{ (.+?) \\}');
fwPage.currentRules = fwPage.currentRules.replace(re, content);
overwrite = true;
}else{
fwPage.currentRules += content + "\n";
}
this.style = null;
};
}
/**
* Read the content of the entire override sheet.
* @return object JSON of styles
*/
if(undefined == FWTag.getOverride){
FWTag.prototype.getOverride = function(){
var content = fwPage.currentRules || "";
var re = new RegExp(this.getSelector() + ' \\{ (.+?) \\}');
if( re.test(content) )
return( cssToHash(content.match(re)[1]) );
return {};
};
}
/**
* Get one CSS attribute of a single tag.
* attributeName is an attribute (width, padding...) returns value (if set) or null.
* @return mixed
*/
if(undefined == FWTag.getStyle){
FWTag.prototype.getStyle = function(attributeName){
var styles = this.getAllStyles();
return styles[attributeName] || null;
};
}
/**
* Get override CSS attributes of a single tag, in format for writing to external stylesheet.
* attributeName is an attribute (width, padding...) returns value (if set) or false.
* @return string or false
*/
if(undefined == FWTag.getStyleAsRule){
FWTag.prototype.getStyleAsRule = function(){
if(this.style){
return this.getSelector() + ' { ' + this.style.toString().slice(1,-1) + ' }';
}else{
return false;
}
};
}
/**
* Get all CSS attributes of a single tag, regardless where they were set.
* @return JSON object
*/
if(undefined == FWTag.getAllStyles){
FWTag.prototype.getAllStyles = function(){
if (this==null) return null;
var styles = {}, overrides = {};
if(fwPage.fwElementStyle){
styles = cssToHash(fwPage.fwElementStyle(this));
}
if(fwDocument.fwExternalStylesheets){
overrides = this.getOverride();
for(var i in overrides){
styles[i] = overrides[i];
}
}
if(this.style){
var inline = cssToHash(this.style);
for (var i in inline) {
styles[i] = inline[i];
};
}
return styles;
};
}
/**
* Sets a CSS attribute such as "position:absolute" in a tag value
* Passing an attribute value of null resets that attribute to default
* @return void
*/
if(undefined == FWTag.setStyle){
FWTag.prototype.setStyle = function(){
if(this == null || arguments.length == 0)
return;
var stylesObj = {};
// If we have two strings, objectify them
if(arguments.length == 2 && arguments[0].constructor == String)
stylesObj[arguments[0]] = arguments[1];
else
stylesObj = arguments[0];
for(var i in stylesObj) {
var attributeName = i,
value = stylesObj[i];
if(typeof value == 'string' && value.toLowerCase() == 'null')
value = null;
var styles = {}, external = {};
var reset = { /* BACKGROUND */ 'background': 'none', 'background-attachment':
'scroll', 'background-clip': 'border-box', 'background-color': 'inherit',
'background-image': 'none', 'background-origin': 'padding-box',
'background-position': '0% 0%', 'background-repeat': 'repeat', 'background-size':
'auto', /* BORDERS */ 'border-collapse': 'separate', 'border': 'none',
'border-width': '0', 'border-style': 'none', 'border-color': '#000000',
'border-width': 'none', /* top */ 'border-top': 'none', 'border-top-color':
'#000000', 'border-top-style': 'none', 'border-top-width': '0', /* right */
'border-right': 'none', 'border-right-color': '#000000', 'border-right-style':
'none', 'border-right-width': '0', /* bottom */ 'border-bottom': 'none',
'border-bottom-color': '#000000', 'border-bottom-style': 'none',
'border-bottom-width': '0', /* left */ 'border-left': 'none', 'border-left-color':
'#000000', 'border-left-style': 'none', 'border-left-width': '0', /* border-image */
'border-image': 'none', 'border-image-outset': '0', 'border-image-repeat': 'stretch',
'border-image-slice': '100%', 'border-image-source': 'none', 'border-image-width':
'1', /* border-radius */ 'border-radius': '0', 'border-top-left-radius': '0',
'border-top-right-radius': '0', 'border-bottom-left-radius': '0',
'border-bottom-right-radius': '0', /* BOX ATTRIBUTES */ 'height': 'auto',
'max-height': 'none', 'min-height': '0', 'width': 'auto', 'max-width': 'none',
'min-width': '0', 'position': 'static', 'display': 'block', 'visibility': 'visible',
'top': 'auto', 'right': 'auto', 'bottom': 'auto', 'left': 'auto', 'float': 'none',
'clear': 'none', 'margin': '0', 'margin-top': '0', 'margin-right': '0',
'margin-bottom': '0', 'margin-left': '0', 'padding': '0', 'padding-top': '0',
'padding-right': '0', 'padding-bottom': '0', 'padding-left': '0', 'opacity': '1',
'overflow': 'visible', 'overflow-x': 'visible', 'overflow-y': 'visible', 'z-index':
'auto', 'zoom': '1', 'filter': 'none', 'cursor': 'auto', 'box-shadow': 'none',
'box-sizing': 'content-box', /* TEXT */ 'color': 'inherit', 'font': 'inherit',
'font-family': 'inherit', 'font-size': '1em', 'font-style': 'inherit',
'font-variant': 'inherit', 'font-weight': 'inherit', 'letter-spacing': 'inherit',
'line-height': 'inherit', 'list-style-image': 'none', 'list-style-position':
'outside', 'list-style-type': 'disc', 'list-style': 'disc', 'text-decoration':
'none', 'text-indent': '0', 'text-shadow': 'none', 'vertical-align': 'baseline',
'white-space': 'normal'
};
if(fwPage.fwElementStyle){
external = cssToHash(fwPage.fwElementStyle(this));
}
if(fwDocument.fwExternalStylesheets){
styles = this.getOverride();
}
if(this.style){
var inlines = cssToHash(this.style);
for(var i in inlines){
styles[i] = inlines[i];
}
}
if(value == null){
if(reset[attributeName] && external[attributeName]){
styles[attributeName] = reset[attributeName];
}else{
delete styles[attributeName];
}
}else{
styles[attributeName] = value;
}
this.style = fwQuote(hashToCSS(styles));
if(fwDocument.fwExternalStylesheets){
// write styles to override
this.setOverride(this.getStyleAsRule());
this.style = null;
}
}
};
}
</action-javascript>
</library-action>
@joebillings
Copy link
Author

This version fixes the republish problem. There are added requirements in this version:

  • fwPage.announce() must be called in the fwBeforeStartHTML() callback.
  • fwPage.renounce() must be called in the fwAfterEndHTML() callback.

These will ensure that the page does not get republished when not dirty.

And the existing requirements:

  • <action-include name="style_accessors" /> and <action-file name="styleOverride" var /> must be included at the root of the Action.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment