Showing posts with label CSS. Show all posts
Showing posts with label CSS. Show all posts

Thursday, February 17, 2011

Menu widget - no jQuery nor Javascript required - pure CSS

Goal:

Create a menu widget that does not require any javascript, extremely lightweight, very fast, soley based on CSS, compatible with FireFox and Chrome.
Issues:

May have some rendering issues in some versions of IE, sorry :-)
Instruments:

* css file
* html with specific menu format
* jQuery-ui library - optional if you want to use your own images/colors

Implementation Details:
HTML:

CSS:

/* =Menu
----------------------------------------------------------------------------------------- */
#header #header_Menubar
{
margin: 0;
padding: 0;
border: 0;
width: 100%;
height: 22px;
}

#header
{
background-color: #99cccc;
background-color: #aaccee;
background-color: #5BA3E0;
background-color: #006cb1;

}

/* Set menu bar background color */
#header #header_Menubar
{
background-attachment: scroll;
background-position: left center;
background-repeat: repeat-x;
}

/* Set main (horizontal) menu typology */
#header .linkList0
{
padding: 0 0 1em 0;
margin-bottom: 1em;
font-family: 'Trebuchet MS', 'Lucida Grande',
Verdana, Lucida, Geneva, Helvetica,
Arial, sans-serif;
font-weight: bold;
font-size: 1.085em;
font-size: 1em;
}

/* Set all ul properties */
#header .linkList0, #header .linkList0 ul
{
list-style: none;
margin: 0;
padding: 0;
list-style-position: outside;
}

/* Set all li properties */
#header .linkList0 > li
{
float: left;
position: relative;
font-size: 90%;
margin: 0 0 -1px;
width: 9.7em;
padding-right: 2em;
z-index: 100; /*IE7: Fix for IE7 hiding drop down list behind some other page elements */
}

/* Set all li properties */
#header .linkList01 > li
{
width: 190px;
}

#header .linkList0 .linkList01 li
{
margin-left: 0px;
}

/* Set all list background image properties */
/*#header .linkList0 li a
{
background-position: left center;
background-image: url( '../Content/Images/VerticalButtonBarGradientFade.png' );
background-repeat: repeat-x;
background-attachment: scroll;
}*/
/* Set all A ancor properties */
#header .linkList0 li a
{
display: block;
text-decoration: none;
line-height: 22px;
}

/* IE7: Fix for a bug in IE7 where the margins between list items is doubled - need to set height explicitly */
*+html #header .linkList0 ul li
{
height: auto;
margin-bottom: -.3em;
}

/* Menu: Set different borders for different nested level lists
-------------------------------------------------------------- */
#header .linkList0 > li a
{
border-left: 10px solid Transparent;
border-right: none;
}

#header .linkList0 > li a
{
border-left: 0px;
margin-left: 0px;
border-right: none;
}

#header .linkList0 .linkList01 > li a
{
border-left: 8px solid #336699;
border-right: none;
border: 1px solid Transparent;
-moz-border-radius: 5px 5px 5px 5px;
-moz-box-shadow: 3px 3px 4px #696969;
}

#header .linkList0 .linkList01 .linkList001 > li a
{
border-left: 6px solid #336699;
border-right: none;
border: 1px solid Transparent;
-moz-border-radius: 5px 5px 5px 5px;
-moz-box-shadow: 3px 3px 4px #696969;
}

#header .linkList0 .linkList01 .linkList001 .linkList0001 > li a
{
border-left: 4px solid #336699;
border-right: none;
border: 1px solid Transparent;
-moz-border-radius: 5px 5px 5px 5px;
-moz-box-shadow: 3px 3px 4px #696969;
}

/* Link and Visited pseudo-class settings for all lists (ul) */
#header .linkList0 a:link, #header .linkList0 a:visited
{
display: block;
text-decoration: none;
padding-left: 1em;
}

/* Hide all the nested/sub menu items */
#header .linkList0 ul
{
display: none;
padding: 0;
position: absolute; /*Important: must not impede on other page elements when drop down opens up */
}

/* Hide all detail popups */
#header .detailPopup
{
display: none;
}

/* Set the typology of all sub-menu list items li */
/*#header .linkList0 ul li
{
background-color: #AACCEE;
background-position: left center;
background-image: url( '../Content/Images/VerticalButtonBarGradientFade.png' );
background-repeat: repeat-x;
background-attachment: scroll;
}*/

#header .linkList0 ul li.more
{
background: Transparent url('../Content/Images/ArrowRight.gif') no-repeat right center;
}

/* Header list's margin and padding for all list items */
#header .linkList0 ul li
{
margin: 0 0 0 1em;
padding: 0;
}

#header .linkList01 ul li
{
margin: 0;
padding: 0;
width: 189px;
}

/* Set margins for the third li sibling (Plan a Call) to display to the right of the parent menu
to avoid the sub-menu overlaying the menu items below */
#header .linkList0 li.more .linkList01 li.more > ul.linkList001
{
margin: -1.7em 0 0 13.2em; /*Important, must be careful, if tbe EM since gap increases too much bewteen nested lists the gap will make the nested-list collapse prematurely */
}

/* Set right hand arrow for list items with sub-menus (class-more) */
#header li.more
{
background: Transparent url('../Content/Images/ArrowRight.gif') no-repeat right center;
padding-right: 48px;
}

/* Menu: Dynamic Behavior of menu items (hover, visted, etc)
----------------------------------------------------------- */
#header .linkList0 li a:link, #header .linkList01 li a:link
{
display: block;
}

#header .linkList0 li a:visited, #header .linkList01 li a:visited
{
display: block;
}

#header .linkList0 > li:hover
{
}

#header .linkList01 > li:hover a
,#header .linkList001 > li:hover a
{
text-decoration: underline;
}

#header .linkList0 > li abbr:hover span.detailPopup
{
display: block;
position: absolute;
top: 1em;
left: 17em;
border: double 1px #696969;
border-style: outset;
width: 120%;
height: auto;
padding: 5px;
font-weight: 100;
}

#header .linkList0 > li:hover
,#header .linkList0 .linkList01 > li:hover
{
}

#header .linkList0 .linkList01 .linkList001 > li:hover
{
}

#header .linkList0 .linkList01 .linkList001 .linkList0001 > li:hover
{
}

/* Display the hidden sub menu when hovering over the parent ul's li */
#header .linkList0 li:hover > ul
{
display: block;
}

/* Display the hidden sub menu when hovering over the parent ul's li */
#header .linkList0 .linkList01 li:hover > ul
{
display: block;
background: -moz-linear-gradient(top, #1E83CC, #619FCD);
/* Chrome, Safari:*/
background: -webkit-gradient(linear,
center top, center bottom, from(#1E83CC), to(#619FCD));

}

/* Display the hidden sub menu when hovering over the parent ul's li */
#header .linkList0 .linkList01 .linkList001 li:hover > ul
{
display: block;
}

/* Set right hand arrow for list items with sub-menus (class-more) on hover */
#header li.more:hover
{
}

Also some CSS for global settings that will affect this menu, you of course will have some other styling, but included it here so you can see how/why some css properties were set here:

/* Neutralize styling:
Elements we want to clean out entirely: */
html, body
{
margin: 0;
padding: 0;
font: 62.5%/120% Verdana, Arial, Helvetica, sans-serif;
}

/* Neutralize styling:
Elements with a vertical margin: */
h1, h2, h3, h4, h5, h6, p, pre,
blockquote, ul, ol, dl, address {
margin: 0; /* most browsers set some default value that is not shared by all browsers */
padding: 0; /* some borowsers default padding, set to 0 for all */
}

/* Apply left margin:
Only to the few elements that need it: */
li, dd, blockquote {
margin-left: 1em;
}

Tuesday, November 30, 2010

Combine and compress javascript and css files in ASP.Net MVC

Goal:

When loading js or css files, combine all the js files into one and all css files into one file respectively when rendering to improve on performance. Also compress if need be on the fly.

In this example we use many css files and even more js files to organize the ASP.Net Mvc web app into manageable pieces. The reason for the separation is mainly because it gives the team the ability to work on different part of the web app by working on the affected css or js files. It also helps to decide at a very granular level which css or js files to load and cache in the browser and which ones are very unique and/or specific and/or large so as to load only when really needed. For example I have an extremely large contract page with about 4000 lines of jQuery to handle a spreadsheet like functionality. It is not used often and used only by certain sales reps. Do not want to load this file as part of the generic/global js file since it would be wasted space for most part. This file is loaded on the fly when needed. How that is done is another story :-) Since my app is a CRM app, it has many screens that have unique css styling requirements and so in some situations the css file should only be loaded when needed. The reasons are really not that important in the context of this blog, just want to show you how to do this in ASP.Net Mvc if and when the need arises.

System Requirements:

.Net 2+

ASP.Net Mvc v1+

Solution:


In your Master file head section


<link rel="stylesheet" type="text/css" href="<%=Url.RouteUrl(new {controller = "Scripts", Action = "GetAllCss"})%>" />

<script type="text/javascript" src="<%=Url.RouteUrl(new {controller = "Scripts", Action = "GetAllScripts"})%>"></script>

In the Mvc ScriptController controller:


public static IPathMapper ServerPathMapper { get; set; }


protected static bool Enable;

protected static bool EnableHtmlCompression;

protected static bool EnableHtmlMinification;

protected static bool EnableProfiler;

protected static bool EnableScriptCompression;

protected static bool EnableScriptMinification;

protected string Css = "css";

protected string Js = "js";


[OutputCacheAttribute(CacheProfile = "Scripts")]

public FileResult GetAllScripts()

{

StringBuilder sb = LoadjQueryScripts();

sb.Append(LoadCRMScripts());


return StreamText(sb.ToString(), Js);

}

[OutputCacheAttribute(CacheProfile = "CRMScripts")]

public static StringBuilder LoadjQueryScripts()

{

string min = "";



if (ScriptOptimizerConfig.EnableMinimizedFileLoad)

min = "Min/";



var sb = new StringBuilder(CombineLikeDirectoryFiles(String.Format("~/Scripts/jquery/Base/{0}", min), FileType.js));

//var sb = new StringBuilder(CombineLikeDirectoryFiles(String.Format("~/Scripts/jquery/Plugins/{0}", min), FileType.js));

sb.Append(CombineLikeDirectoryFiles(String.Format("~/Scripts/jquery/Plugins/{0}", min), FileType.js));

sb.Append(CombineLikeDirectoryFiles(String.Format("~/Scripts/jquery/Plugins/jquery.grid/{0}", min), FileType.js));

sb.Append(CombineLikeDirectoryFiles(String.Format("~/Scripts/jquery/Plugins/jquery.datepick/{0}", min), FileType.js));

sb.Append(CombineLikeDirectoryFiles(String.Format("~/Scripts/jquery/Plugins/jquery.flot/{0}", min), FileType.js));

return sb;

}



[OutputCacheAttribute(CacheProfile = "CRMScripts")]

public static StringBuilder LoadCRMScripts()

{

string pathForCRMScripts = GetPathForCRMScripts();



if (ScriptOptimizerConfig.EnableMinimizedFileLoad)

{

string newPathForCRM = pathForCRMScripts + "Min/";



if (Directory.Exists(ServerMapPath(newPathForCRM)))

pathForCRMScripts = newPathForCRM;

}



var sb = new StringBuilder(GetFile(pathForCRMScripts, "CRMScripting.js"));

sb.Append(GetFile(pathForCRMScripts, "CRMScriptingAccount.js"));

sb.Append(GetFile(pathForCRMScripts, "CRMScriptingContact.js"));

sb.Append(GetFile(pathForCRMScripts, "CRMScriptingCallSheet.js"));



return sb;

}


public FileStreamResult StreamText(string inputText, IEquatable<string> name)

{

if (name.Equals(Js) && ScriptOptimizerConfig.Enable && ScriptOptimizerConfig.EnableScriptMinification)

inputText = new JavaScriptCompressor(inputText, false).Compress();

if (name.Equals(Css) && ScriptOptimizerConfig.Enable && ScriptOptimizerConfig.EnableCssMinification)

inputText = CssCompressor.Compress(inputText, 0, CssCompressionType.MichaelAshRegexEnhancements);

var m = new MemoryStream(Encoding.Default.GetBytes(inputText));

return File(m, name.Equals(Css) ? "text/css" : "text/javascript");

}



public static string CombineLikeDirectoryFiles(string directory, FileType typeOfFile)

{

string combinedFiles = string.Empty;

string extension = null;



switch (typeOfFile)

{

case FileType.js:

extension = "js";

if (ScriptOptimizerConfig.EnableMinimizedFileLoad)

{

string newPath = string.Format("{0}{1}", directory, directory.EndsWith("/") ? "Min" : "/Min");



if (Directory.Exists(ServerMapPath(newPath)))

directory = newPath;

}

break;

case FileType.css:

extension = "css";

break;

}



if(extension == null)

return null;



if (GetFiles(directory, extension) != null)

{

foreach (FileInfo file in GetFiles(directory, extension))

{

using (var s = new StreamReader(file.FullName))

combinedFiles += s.ReadToEnd();

}



} return combinedFiles;

}





public static string GetFile(string pathForCRMScripts, string file)

{

if (ServerPathMapper != null)

{

string fullFilePath = ServerPathMapper.MapPath(pathForCRMScripts) + file;

if (!System.IO.File.Exists(fullFilePath))

return null;

using (var s = new StreamReader(fullFilePath))

return s.ReadToEnd();

}



return null;

}



public static IList<FileInfo> GetFiles(string serverPath, string extention)

{

string path = ServerMapPath(serverPath);



if (!Directory.Exists(path))

return null;



IList<FileInfo> files = new List<FileInfo>();



string[] fileNames = Directory.GetFiles(path, "*." + extention, SearchOption.TopDirectoryOnly);

foreach (string name in fileNames)

files.Add(new FileInfo(name));



return files;

}



public static string ServerMapPath(string serverPath)

{

if (!serverPath.StartsWith("~/"))

{

if (serverPath.StartsWith("/"))

serverPath = "~" + serverPath;

else

serverPath = "~/" + serverPath;

}

if (ServerPathMapper != null)

{

string path = ServerPathMapper.MapPath(serverPath);

if (!path.EndsWith("/"))

path = path + "/";

return path;

}



return null;

}


public static string GetPathForCRMScripts()

{

return "~/Scripts/CRM/";

}


public interface IPathMapper

{

string MapPath(string relativePath);

string MapPath();

}


The IPathMapper is injected via the ScriptController's constructor via IoC with Castle Windsor's windsor.boo config file:


Component "pathMapper", IPathMapper, Vert.CRM.UI.Mechanics.ServerPathMapper


public class ServerPathMapper : IPathMapper

{

private readonly string _relativePath;



public ServerPathMapper()

{

}



public ServerPathMapper(string relativePath)

{

_relativePath = relativePath;

}



#region Implementation of IPathMapper



public string MapPath(string relativePath)

{

return HttpContext.Current.Server.MapPath(relativePath);

}



public string MapPath()

{

if (_relativePath != null)

if (HttpContext.Current != null) return HttpContext.Current.Server.MapPath(_relativePath);



return null;

}



#endregion

}


The static properties to enable profiler, compression, minification is set by using the ScriptOptimizerSection class which inherits from ConfigurationSection:


public class ScriptOptimizerSection : ConfigurationSection {



[ConfigurationProperty("enable", IsRequired = true)]

public bool Enable {

get { return (bool)this["enable"]; }

set { this["enable"] = value; }

}

[ConfigurationProperty("enableProfiler", IsRequired = true)]

public bool EnableProfiler {

get { return (bool)this["enableProfiler"]; }

set { this["enableProfiler"] = value; }

}

[ConfigurationProperty("enableScriptCompression", IsRequired = true)]

public bool EnableScriptCompression {

get { return (bool)this["enableScriptCompression"]; }

set { this["enableScriptCompression"] = value; }

}

[ConfigurationProperty("enableHtmlCompression", IsRequired = true)]

public bool EnableHtmlCompression {

get { return (bool)this["enableHtmlCompression"]; }

set { this["enableHtmlCompression"] = value; }

}

[ConfigurationProperty("enableScriptMinification", IsRequired = true)]

public bool EnableScriptMinification {

get { return (bool)this["enableScriptMinification"]; }

set { this["enableScriptMinification"] = value; }

}

[ConfigurationProperty("enableHtmlMinification", IsRequired = true)]

public bool EnableHtmlMinification {

get { return (bool)this["enableHtmlMinification"]; }

set { this["enableHtmlMinification"] = value; }

}



[ConfigurationProperty("enableCssMinification", IsRequired = true)]

public bool EnableCssMinification {

get { return (bool)this["enableCssMinification"]; }

set { this["enableCssMinification"] = value; }

}



[ConfigurationProperty("enableMinimizedFileLoad", IsRequired = true)]

public bool EnableMinimizedFileLoad

{

get { return (bool)this["enableMinimizedFileLoad"]; }

set { this["enableMinimizedFileLoad"] = value; }

}

}



public class ScriptOptimizerConfig {



protected static bool enable;

protected static bool enableProfiler;

protected static bool enableScriptCompression;

protected static bool enableHtmlCompression;

protected static bool enableScriptMinification;

protected static bool enableHtmlMinification;

protected static bool enableCssMinification;

protected static bool enableMinimizedFileLoad;



/// <summary>

///

/// </summary>

static ScriptOptimizerConfig() {

ScriptOptimizerSection section;

section = (ScriptOptimizerSection)ConfigurationManager.GetSection("OptimizeClientSide");

if (section != null)

{

enable = section.Enable;

enableProfiler = section.EnableProfiler;

enableScriptCompression = section.EnableScriptCompression;

enableHtmlCompression = section.EnableHtmlCompression;

enableScriptMinification = section.EnableScriptMinification;

enableHtmlMinification = section.EnableHtmlMinification;

enableCssMinification = section.EnableCssMinification;

enableMinimizedFileLoad = section.EnableMinimizedFileLoad;

}

}



/// <summary>

///

/// </summary>

public static bool Enable {

get { return enable; }

}

/// <summary>

///

/// </summary>

public static bool EnableProfiler {

get { return enableProfiler; }

}

/// <summary>

///

/// </summary>

public static bool EnableScriptCompression {

get { return enableScriptCompression; }

}

/// <summary>

///

/// </summary>

public static bool EnableHtmlCompression {

get { return enableHtmlCompression; }

}

/// <summary>

///

/// </summary>

public static bool EnableScriptMinification {

get { return enableScriptMinification; }

}

/// <summary>

///

/// </summary>

public static bool EnableMinimizedFileLoad

{

get { return enableMinimizedFileLoad; }

}

/// <summary>

///

/// </summary>

public static bool EnableHtmlMinification {

get { return enableHtmlMinification; }

}



public static bool EnableCssMinification

{

get { return enableCssMinification; }

}

}


The web.config file settings for optimization settings:


<configuration>



<configSections>

<sectionGroup name="elmah">

<section name="errorLog" type="Elmah.ErrorLogSectionHandler,Elmah" />



</sectionGroup>

<section name="OptimizeClientSide" type="Vert.CRM.Core.Utility.ScriptOptimizerSection, Vert.CRM.Core" />

....


<OptimizeClientSide enable="false"

enableScriptCompression="false"

enableHtmlCompression="false"

enableScriptMinification="false"

enableMinimizedFileLoad="true"

enableHtmlMinification="false"

enableCssMinification="false"

enableProfiler="false">

</OptimizeClientSide>




Wednesday, November 24, 2010

Loading CSS stylesheet for ascx ASP.Net user control

Goal:

For performance reasons you want to load a piece of CSS only for the ascx file to which it applies. You do not want to embed the style as you cannot do this in ASP.Net ascx user controls. ASP.Net will render the style tag as HTML text.

Solution:

Some folks use the Page.Header.Controls.Add to add the stylesheet link in the ascx page or in the code behind. Not needed, you may specify a "link" tag in the ascx file, not need to specify "head" tag or anything else, just the link tag itself:




<link rel="Stylesheet" media="screen" type="text/css" href="<%=ResolveUrl("~/Content/StyleSheets/Screen/Global/CssCRMUITrafficLine.css") %>" />


The benefits are significant in that it is only loaded when needed. Personally I also prefix all my css selectors with the unique ID of the ascx container (i.e. I give the outer most div of my ascx page a unique name - usually that of the ascx file name) so that when I nest the ascx file into any other file the nested dynamically loaded CSS to not affect anything else outside of its scope. Also, since the stylesheet is linked it is cached on the user's browser once it has been loaded.