This is caused by the fact that the 'title' div spans the full width of the control and so the links end up behind this div.
To fix this problem, add the following line to your stylesheet:
.ajax__calendar_title {width:150px; margin:auto; }
Useful tidbits related to software development, that I think might be of use or interest to everyone else (or to me when I forget what I did!)
.ajax__calendar_title {width:150px; margin:auto; }
<asp:Calendar ID="calCollectionDate" runat="server" SelectionMode="Day" WeekendDayStyle-CssClass="disabledDay"></asp:Calendar>Use Javascript to find the 'disabledDay' items and remove the links:
<script type="text/javascript" language="javascript"> <!-- var disabledDays=getElementsByClassName('disabledDay', '.*', document.getElementById('<%=calCollectionDate.ClientID %>')); for(var i=0;i<disabledDays.length;i++) { disabledDays[i].innerHTML="<span>"+disabledDays[i].innerHTML.replace(/\<a.*?\>(.*?)\<\/a\>/ig,'$1')+"</span>"; disabledDays[i].style.color='#8e8e8e'; } --> </script>This script relies on a third party script for 'getElementByClassName' which can be downloaded from www.robertnyman.com
If your site runs on an ASP.NET server and does not support the classic SSI method of including files for output.
i.e.
<!--#include virtual="/myinclude.asp" -->
Then you may wish to implement your own version of SSI.
In my particular example, I am reading from a HTML file on the server and processing the content before rendering it to the client. I don't see any reason however, why this technique could not be applied in the 'Render' phase of any ASP.NET page.
Essentially, the following function will look for any server side include code in the HTML it is about the render. It will then attempt to process the files that have been included and then swap out the content in the original HTML.
Public Function RunSSI(ByVal htmlContent As String) As String 'look for SSI While htmlContent.Contains("<!--#include virtual=") Dim startOfSSI As Integer = htmlContent.IndexOf("<!--#include virtual=") Dim LengthOfSSI As Integer = htmlContent.IndexOf("-->", startOfSSI) + "-->".Length - startOfSSI Dim ssiText As String = htmlContent.Substring(startOfSSI, LengthOfSSI) Dim ssiFile As String = ssiText.Substring(ssiText.IndexOf("virtual=""") + "virtual=""".Length, ssiText.IndexOf("""", ssiText.IndexOf("virtual=""") + "virtual=""".Length) - (ssiText.IndexOf("virtual=""") + "virtual=""".Length)) 'execute the file Dim contentResponse As String Try Dim wc As New Net.WebClient() Dim URL As String = "" If ssiFile.startsWith("/") Then URL = Request.Url.AbsoluteUri.Replace(Request.Url.AbsolutePath, ssiFile) Else URL = ssiFile End If contentResponse = wc.DownloadString(URL) Catch ex As Exception contentResponse = "<!--SSI Processing Error: " & ex.Message & " " & """" & URL & """-->" End Try 'now replace the include with the response htmlContent = htmlContent.Remove(startOfSSI, LengthOfSSI) htmlContent = htmlContent.Insert(startOfSSI, contentResponse) End While Return htmlContent End Function
You will notice, that the SSI markup should be the 'virtual' version and that the code will only work with an absolute path (either http://... or /...).
For the avoidance of a permaloop and to help debugging any SSI which is not successfully processed is replaced with a comment stating the error and URL which it attempted to process.
Namespace UserControls Partial Public Class ExampleUserControl Inherits System.Web.UI.UserControl Public Event ButtonClick(ByVal sender As Object, ByVal e As System.EventArgs) Private Sub btnCompare_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnClickMe.Click RaiseEvent ButtonClick(Me, EventArgs.Empty) End Sub End Class End NamespacePage codeinfront:
<%@ Register src="/UserControls/ExampleUserControl.ascx" tagname="ExampleUserControl" tagprefix="MyCtrls" %> <asp:Repeater ID="rptExample" runat="server"> <ItemTemplate> <MyCtrls:ExampleUserControl ID="ExampleUserControl1" runat="server" OnButtonClick="ButtonClickHandler"></MyCtrls:ExampleUserControl> </ItemTemplate> </asp:Repeater>Page codebehind:
Protected Sub ButtonClickHandler(ByVal sender As Object, ByVal e As EventArgs) 'handle the user control event End SubOf course, you need to have 'AutoEventWireup=True' in the web.config or @Page directive for this to work.
Dim SIZEOF_HEADER As Integer = &H16 Dim SKIP_BYTES_AFTER_ASMNAME As Integer = 5 Dim oldNamespace As String = "CwCms.DataTypes" Dim newNamespace As String = "CwCms.ControlDataTypes" Dim sizeof_ASMname As Integer = rawCmsRecord.Value(SIZEOF_HEADER) Dim start_of_classname_struct As Integer = SIZEOF_HEADER + 1 + sizeof_ASMname + SKIP_BYTES_AFTER_ASMNAME Dim newRecord((rawCmsRecord.Value.Length - 1) + newNamespace.Length - oldNamespace.Length) As Byte Dim offset As Integer = 0 For i As Integer = 0 To rawCmsRecord.Value.Length - 1 'copy the bytes to the new record If i <> start_of_classname_struct Then newRecord(i + offset) = rawCmsRecord.Value(i) Else 'weve reached the start of the classname struct. inject the custom stuff Dim sizeof_classname As Integer = rawCmsRecord.Value(start_of_classname_struct) Dim oldclassname As String = "" For i2 = i + 1 To i + sizeof_classname oldclassname &= ChrW(rawCmsRecord.Value(i2)) Next oldclassname = Right(oldclassname, oldclassname.Length - oldNamespace.Length) Dim newFQCN As String = newNamespace & oldclassname offset = newFQCN.Length - sizeof_classname 'overwrite the new length newRecord(i) = CByte(newFQCN.Length) 'put the new classname on the end Dim ctr As Integer = 0 For ctr = 1 To newFQCN.Length newRecord(i + ctr) = CByte(AscW(newFQCN(ctr - 1))) Next i += sizeof_classname End If Next rawCmsRecord.Value = newRecord rawCmsRecord.Save()As the format is not documented by Microsoft the format may change without notice and as I have mentioned this is only a quick interpretation of what I saw, to get the job done. Before implementing this code for yourself, check it will not break your data, I am not responsible!
Using the built in .NET validation, you can assign a 'validation group' to a set of validation controls, submit buttons and validation summary.
This creates a separation between different sets of controls on the page, so that only the relevant controls are validated for a specific submit button.
When you design your application as separate user control components, you may want to set a different validation group on the validators of the user control, depending on the context.
To allow this, I have exposed a 'ValidationGroup' property on the user control. This subsequently makes a recursive to all its child controls to set the validation group of all the validators.
This way, you can set the validation group of the user control instance as you would any other control and it will assign all of its contained validators to that group.
Code:
Public Property ValidationGroup() As String Get Return CStr(ViewState("ValidationGroup")) End Get Set(ByVal value As String) SetValidationGroupOnChildren(Me, value) ViewState("ValidationGroup") = value End Set End Property Private Sub SetValidationGroupOnChildren(ByVal parent As Control, ByVal validationGroup As String) For Each ctrl As Control In parent.Controls If TypeOf ctrl Is BaseValidator Then CType(ctrl, BaseValidator).ValidationGroup = validationGroup ElseIf TypeOf ctrl Is IButtonControl Then CType(ctrl, IButtonControl).ValidationGroup = validationGroup ElseIf ctrl.HasControls() And ctrl.Visible = True Then SetValidationGroupOnChildren(ctrl, validationGroup) End If Next End Sub
using Microsoft.VisualStudio.TestTools.UnitTesting; namespace YourSolutionNamespace.Test { [TestClass] public static class InitTestEnv { [AssemblyInitialize] public static void Initialize(TestContext context) { //configure the BLL BLL.Configuration.EnableCaching = false; //connect it to a datasource if (!BLL.DataProviders.HasValidDatasource()) BLL.DataProviders.SetDatasource(BLL.DataProviders.ProviderList.SqlDataProvider("connection string")); } } }Normally, I would reference the ConfigurationManager classes and then share my 'connectionStrings.config' and 'appSettings.config' files from my UI layer to keep the tests in sync with the settings that will be deployed, but in the above example I have left this out.
'prevent wrong case urls If Text.RegularExpressions.Regex.IsMatch(Request.Url.PathAndQuery.Replace("Default.aspx", "default.aspx"), "[A-Z]") Then Response.StatusCode = 301 Response.Status = "301 Moved Permanently" Dim loc As String = Request.Url.PathAndQuery.ToLower() If loc.EndsWith("default.aspx") Then loc = loc.Remove(loc.LastIndexOf("default.aspx"), "default.aspx".Length) Response.AddHeader("Location", loc) End IfThe code basically uses a 301 redirect to inform the client (spider/browser) to use the lower case equivelant of the URL. Another SEO measure is to not reveal 'default.aspx' as part of the URL (because that would be a duplicate of '/') - so the code also makes sure not to use this page name in the URL.
ddlYear.Items.Clear() For yr As Integer = Now.Date.Year - 100 To Now.Date.Year - 18 ddlYear.Items.Add(yr.ToString()) Next ddlYear.DataBind() '<-- This part tries to load SelectedValue - but will error!Error: 'DropDownList' has a SelectedValue which is invalid because it does not exist in the list of items. What you need to do, is pass in 'new ListItem()' to the Items.Add() function and specify that the string is the 'text' and 'value' for the list item. e.g.
ddlYear.Items.Clear() For yr As Integer = Now.Date.Year - 100 To Now.Date.Year - 18 ddlYear.Items.Add(New ListItem(yr.ToString(), yr.ToString())) Next ddlYear.DataBind()You should find that the drop down list will now load with your manually added items and will display the correct 'SelectedValue'!
Namespace Example.SessionObjects Public Class CurrentUserDetails Public Shared Property Name() As String Get Return CStr(HttpContext.Current.Session("CurrentUserName")) End Get Set(ByVal value As String) HttpContext.Current.Session("CurrentUserName") = value End Set End Property Public Shared Property Age() As Integer Get Return CInt(HttpContext.Current.Session("CurrentUserAge")) End Get Set(ByVal value As Integer) HttpContext.Current.Session("CurrentUserAge") = value End Set End Property End Class End NamespaceIf you are using ViewState, you will declare these properties as part of your UserControl, for example. Because you are wrapping access to the data as you would a private field, you gain all the benefits of that, such as validation, initialization, default values etc. As you can see, the above example still relies on the developer coming up with a unique key for each object, which is the problem addressed by this article. By nature, all of your strongly typed properties will be unique, since they live in a unique namespace, classname, propertyname - anything else would give you a compiler error. So you can use this unique name as your key name. The following code shows how you can use the 'StackFrame' object to get a reference to the currectly executing method (which will get get_PropertyName, or set_PropertyName) and use (along with name of the declaring type) to generate a unique key for the data store:
Public Property Thing() As Integer Get Dim sfm As Reflection.MethodBase = New StackFrame().GetMethod() Return CType(HttpContext.Current.Session(sfm.DeclaringType.FullName & sfm.Name.Remove(0, 4)), Integer) End Get Set(ByVal value As Integer) Dim sfm As Reflection.MethodBase = New StackFrame().GetMethod() HttpContext.Current.Session(sfm.DeclaringType.FullName & sfm.Name.Remove(0, 4)) = value End Set End PropertyThe reason I have used '.Remove(0, 4)' is to get rid of the 'get_' and 'set_' prefixes, so that the getter and setter function refer to the same key.