This little blog has been getting a lot of coverage lately thanks to a write up by Ajaxian. Developing with Symfony is great and always gives you a lot to think and write about.
For my new product I was having a CSS conflict. This tends to happen when you include your own html and css into someone else’s website. For instance if you have a widget as such:
html
<div id="mywidget">
<h1>My hello world widget</h1>
</div>
css
H1 {
color:green;
font-size:20px;
}
The solution to this problem is quite straightforward, you simply specify your css selector as div#mywidget H1. However, what if you want to allow people to customize the looks of your widget. Now you could off course ask them to include the div#mywidget part, but chances are this will give problems.
Since I was already using the great sfCombineFilterPlugin an easy solution was available. (If you didn’t use the sfCombineFilterPlugin yet, go check it out immediately. Also have a look at Yahoo’s Yslow)
The sfCombineFilterPlugin uses php to gzip, minify and cache your css and javascript. Here is how to extend that behavior to include the #mywidget specification. (Assuming you have sfCombineFilter installed)
Step 1: open your .htaccess
Just below the RewriteBase instruction add:
# if we are retrieving javascript or css
RewriteRule ^css/packed/prepend/(.*\.css) /sfCombineFilterPlugin/combine.php?type=css&prepend=1&files=$1
RewriteRule ^css/packed/(.*\.css) /sfCombineFilterPlugin/combine.php?type=css&files=$1
RewriteRule ^js/packed/(.*\.js) /sfCombineFilterPlugin/combine.php?type=javascript&files=$1
Step 2: add this class to the top of web/sfCombineFilter/combine.php
Partly based on CSS parser class.
class prependCss
{
public static function prependCssString($str) {
// Remove comments
$str = preg_replace("//*(.*)?*//Usi", "", $str);
$parts = explode("}",$str);
if(count($parts) > 0) {
foreach($parts as $part) {
list($keystr,$codestr) = explode("{",$part);
$keys = explode(",",trim($keystr));
$newkeys = array();
if(count($keys) > 0) {
foreach($keys as $key) {
if(strlen($key) > 0) {
$key = (!strstr($key, '#mywidget')) ? '#mywidget'.$key : $key;
$newkeys[] = $key;
}
}
$keystr = implode(', ',$newkeys);
}
if(!empty($keystr)) //needed for spaces behind last }
$rules[] = $keystr . " {" . $codestr . "}";
}
$prependedCss = implode("n", $rules);
}
//
return $prependedCss;
}
public static function prependCssFile($filename) {
if(file_exists($filename)) {
return self::prependCssString(file_get_contents($filename));
} else {
return false;
}
}
}
Step 3: hack around in combine.php
below $minify_js add:
if($_GET['prepend']==1)
$prepend = true;
change the stuff below this comment to:
// Get contents of the files
$contents = '';
reset($elements);
foreach ($files as $path) {
if($prepend && $_GET['type'] == 'css') {
$contents .= "\n\n" . prependCss::prependCssFile($path);
} else {
$contents .= "\n\n" . file_get_contents($path);
}
}
And finally just change your urls to css/packed/prepend/yourcss.css (if you are using relative paths in your css you might need to add an ../)
Conclusion
Using this technique your css will load without any problems in third party sites. This comes in very useful when creating widgets or greasemonkey scripts.