Thursday, 20 December 2012

Turning you ASP.Net Hyperlinks into images buttons using CSS and jQuery

First of all I’m no big jQuery or CSS geek I know a little and I search the net Smile

I was building a site for a client who wanted Image buttons not text links and you should know me if you have read past posts I believe Dynamic Data is about changing as little as possible in the pages I like to do it all if possible using the Metadata, so I thought is there a way of doing this using pure CSS, there is sort of but you are still left with the text of the hyperlink, that is where jQuery came in, and then I thought I need to add a CSS class to each button and again jQuery came to my aid. so here we go.

Note: I am using the latest Web Essentials by Mads Kristensen and using LESS for my style sheets, you are really missing out if you have VS2012 and are not using this experimental add-in for VS2012.

Note: If you don't have VS2012 the you can use Mindscape’s Web Workbench which offers SASS, LESS and CoffeeScript

So the first job is to somehow automatically add the CSS class to each of the buttons that we want to swap to image button.

function NAC_ReplaceHyperLinkWithImageButton() {
// array of button commands/names to affect
var toMatch = ["Details", "Edit", "Delete", "Update", "Cancel", "Insert", "Select", "New"];
// commands/names to replace
var toReplace = { "New": "Insert" };
$(document).ready(function () {
$("TABLE.DDDetailsTable a, TABLE.DDGridView a")
.each(function () {
// get the inner text
var innerText = $(this).text();
if ($.inArray(innerText, toMatch) > -1) {

// do replacement of commands to Replace
var found = toReplace[innerText];
// check there is a match in the lookup table
if (typeof found !== "undefined")
innerText = found;

// get the embedded text
$(this).addClass(innerText);

// add a tooltip
$(this).attr('Title', innerText);

// remove the hyperlinks text
$(this).text('');
}
});
});
}
// run script
NAC_ReplaceHyperLinkWithImageButton();
// bind script to AJAX bits
Sys.Application.add_load(NAC_ReplaceHyperLinkWithImageButton);

Listing 1 – jQuery for the swap out

So first of all we have an array this contains ALL the button names (the display text) that we want to affect, next we hook into the jQuery Read event then we are selecting command buttons List, Details, Edit and ListDetails pages using these two selectors:

"TABLE.DDDetailsTable a, TABLE.DDGridView a"

after that I am using the jQuery inArray function to check if the buttons text is one we want to affect. Then having gotten the innerText form the hyperlink we add the CSS class, then finally we remove the text so only the icon will show.

jQuery is so cool mixed with CSS.

ReplaceHyperlinkWithIcons

Figure 1- hyperlinks replace with buttons

Now people say LESS is more and in this case it is.

table.DDGridView a.Cancel,
table.DDDetailsTable a.Cancel
{
    width: 20px;
    height: 20px;
    display: inline-block;
    background-repeat: no-repeat;
    background-position: center center;
    background-image: url('../images/Cancel.png');
}

    table.DDGridView a.Cancel:hover,
    table.DDDetailsTable a.Cancel:hover
    {
        background-image: url('../images/Cancel-h.png');
    }

        table.DDGridView a.Cancel:hover:active,
        table.DDDetailsTable a.Cancel:hover:active
        {
            background-image: url('../images/Cancel-a.png');
        }

    table.DDGridView a.Cancel.aspNetDisabled,
    table.DDDetailsTable a.Cancel.aspNetDisabled,
    table.DDGridView a.Cancel.aspNetDisabled:hover,
    table.DDDetailsTable a.Cancel.aspNetDisabled:hover,
    table.DDGridView a.Cancel.aspNetDisabled:active,
    table.DDDetailsTable a.Cancel.aspNetDisabled:active
    {
        background-image: url('../images/Cancel-d.png');
    }

Listing 2 – the CSS

above in Listing 2 is the CSS for the Edit button as you can see we cover hyperlinks with a CSS class of “Edit” and we have three states normal hover and active that is when we click. Now for the LESS

/* ==== button mixin ==== */
.Button (@Name)
{
a.@{Name}
{
width: 20px;
height: 20px;
display: inline-block;
background-repeat: no-repeat;
background-position: center center;

background-image: url('../images/@{Name}.png');

&:hover
{
background-image: url('../images/@{Name}-h.png');

&:active
{
background-image: url('../images/@{Name}-a.png');
}
}
// link fix
&.aspNetDisabled,
&.aspNetDisabled:hover,
&.aspNetDisabled:active
{
background-image: url('../images/@{Name}-d.png');
}
// link fix
}
}

table.DDGridView,
table.DDDetailsTable
{
.Button(Cancel);
.Button(Delete);
.Button(Details);
.Button(Edit);
.Button(Update);
.Button(New);
.Button(Select);
.Button(Insert);
}

Listing 3 – LESS

In Listing 3 we have a MIXIN called Button and we pass in the name of the button we want, no repeating the same code and if you need to add an extra button it’s easy.

The only thing left is for me to add this to NuGet as a simple to apply package and you are away.

Updated: Package now available from NuGet here Replace ASP.Net Hyperlink With Image Button You will need to add a reference in you master page to the CSS and the JavaScript files which will be added to the Style and Scripts folder respectively.

Adding the NuGet package to an existing Dynamic Data site

Right Click the “References” node of the Web Application Project

Adding NuGet Package

Figure 1 – Adding NuGet Package to Project

Find the “Replace Hyperlink With Image Button” NuGet Package

Figure 2 – Find the “Replace Hyperlink With Image Button” NuGet Package

AddingScriptToHead

Figure 3 – Add the Style sheet and jQuery references to the head of the Master page 

Note: the highlighted sections, you will need to add a ~/ and ../ to the start od the CSS and Script links.

AddScriptToEndOfMasterPage

Figure 4 – Add a script tag just before the end of the BODY tag

Note: you can add your scripts and Style sheets your own way with bundling and minifying this is just an example.

Happy coding.

V1.0.4 now on NuGet you disabled icons if a link’s Enabled property is set to “false”;

Replace Hyperlink With Disabled Icons

Hopefully no more changes now.

Warning another change: v1.0.8 now has better icons and supports disabled buttons including removing the onclick event of the Delete button.

Friday, 14 December 2012

ASP.NET and Web Tools 2012.2 (Release Candidate)

 

Scott Hanselman announced ASP.NET and Web Tools 2012.2 (Release Candidate) today and it’s great for the first time it’s easy to deploy a DD Web Application Project (WAP) site precompiled and un-editable it’s just a check box now.

So install the new tools (get them from here Download Page and Release notes)

Note: If you already have publish settings then VS2012 will import them but they need to be in the root of your site.

New Publish Wizard

Figure 1 - New Publish Wizard

Navigate to the Settings Tab and check the “Precompile during publishing” checkbox and then click “Configure”

Disabled Editable markup.

Figure 2 - Disabled Editable markup.

un-check the “Allow precompiled site to be updatable” checkbox and your pages will no look like this Figure 3 if someone tries to edit them Smile

Precompiled aspx page no uneditable

Figure 3 - Precompiled aspx page no un-editable

And finally in VS2012 you can set the build configuration in each Publish profile, this works without having to change the build configuration in you project.

Warning: This sadly does not work for Dynamic Data yet it testes OK you already deployed to a site as it uses the place holders, I’m looking into it hopefully there will be a simple fix.

Thursday, 30 August 2012

O2 has major fail

O2 have just blocked external access to smtp.o2.co.uk this means you cannot send e-mail from your mobile if you are no connected to an O2 network and if you are abroad you will only be able to send email via your data account.

For me this is a major fail I havrelayeded on this from O2 for as long as I have been with them. I will be moving from O2 and finding a solution to sending e-mail that no ISP or Mobile provider will be able to block.

RIP O2

Steve



Well news is O2 don't seem to know what the issue is I just got told by first level support that it was not accessable from outside O2's network? but it is again I suspect I was being given a line...

Steve

Saturday, 26 May 2012

Creating a Tabbed Entity Template using Wijmo Open for Juice UI

Wijmo Open for Juice UI is quite cool and offers an alternative to using Ajax Control Toolkit which is still cool but Wijmo Open for Juice UI supports IE6+, Firefox 3+, Safari 3+, and Chrome browsers which should mean everything works.

I started customising Entity Templates here Custom Entity Templates – Dynamic Data 4 I decided to take this a little further and create a Tabbed UI for editing really long forms, I have done this with the Ajax Control Toolkit but thought I like to have a jQuery UI version which brings theming to the table.

Creating the Entity Template

Wijmo tabs in action

Figure 1 – Wijmo tabs in action

<asp:Panel ID="Panel1" runat="server">
    <ul>
        <li><a href="#tabs-1">Nunc tincidunt</a></li>
        <li><a href="#tabs-2">Proin dolor</a></li>
        <li><a href="#tabs-3">Aenean lacinia</a></li>
    </ul>
    <div id="tabs-1">
        <p>
            Proin elit arcu, rutrum commodo, vehicula tempus...
</p>
</div> <div id="tabs-2"> <p> Morbi tincidunt, dui sit amet facilisis feugiat...
</p> </div> <div id="tabs-3"> <p> Mauris eleifend est et turpis. Duis id erat...
</p> </div> </asp:Panel> <wijmo:WijTabs ID="Tabs1" runat="server" TargetControlID="Panel1"> </wijmo:WijTabs>

Listing 1 – mark-up to create the Wijmo tabs

As you can see there are two blocks the UL and the DIVs the UO is used to create the tabs and each DIV matched an LI element and is shown when the tab is selected.

This is not quite as easy to render in an Entity Template as was the AJAX tabs because we need to render two sets of elements two the page, first the UL then the DIVs and we also need the IDs of the DIVs for link in the LIs. so we have to first create the DIVs so we can get each ones client ID then create the UL and add the hyperlinks.

protected override void OnLoad(EventArgs e)
{
    var groupAttribute = Table.GetAttribute<GroupNamesAttribute>();
    if (groupAttribute == null)
        throw new InvalidOperationException("A GroupsAttribute is required for AJAX tab group to work.");

    var row = new HtmlTableRow();
    var td = new HtmlTableCell();
    row.Controls.Add(td);
    this.Controls.Add(row);
    // create tab container to hold each children column
    var panel = new Panel();
    panel.ID = "tabContainer_" + Table.Name;

    // add a tab panel for each children table
    var unorderedList = new HtmlGenericControl("ul");

    // SortedList of tabs
    var tabs = new SortedList<String, HtmlGenericControl>();

    // add DIV for each group
    foreach (var gi in groupAttribute.Groups)
    {
        var groupName = gi.Value;

        var tabDiv = new HtmlGenericControl("div");
        tabDiv.ClientIDMode = ClientIDMode.Static;
        tabDiv.ID = String.Format("{0}-{1}", Table.Name, groupName).Replace(" ", "-");

        // get columns for this group
        var columns = from c in Table.GetScaffoldColumns(Mode, ContainerType)
                      where c.GetAttributeOrDefault<GroupAttribute>().Index == gi.Key
                      orderby c.GetAttributeOrDefault<DisplayAttribute>().GetOrder()
                      select c;

        // add table for this tabs fields
        var htmlTable = new HtmlTable();
        htmlTable.Attributes.Add("class", "DDDetailsTable");
        htmlTable.CellPadding = 6;
        htmlTable.Attributes.Add("Name", groupName);

        // add fields
        foreach (MetaColumn column in columns)
        {
            // new row
            var htmlRow = new HtmlTableRow();
            htmlTable.Controls.Add(htmlRow);

            // add header cell
            var tdHeader = new HtmlTableCell();
            tdHeader.Attributes.Add("class", "DDLightHeader");
            tdHeader.InnerText = column.DisplayName;
            // add cell to row
            htmlRow.Controls.Add(tdHeader);

            // add data cell
            var tdData = new HtmlTableCell();
            // get field template
            var dynamicControl = new DynamicControl() 
                { 
                    Mode = Mode,
                    DataField = column.Name, 
                    ValidationGroup = this.ValidationGroup 
                };
            // add field template to cell
            tdData.Controls.Add(dynamicControl);
            // add cell to row
            htmlRow.Controls.Add(tdData);
        }

        // add the DynamicControl to the tab panel
        tabDiv.Controls.Add(htmlTable);

        // add the tab to list
        tabs.Add(groupName, tabDiv);
    }

    foreach (var gi in groupAttribute.Groups)
    {
        var groupName = gi.Value;

        // new 'LI'
        var tabLi = new HtmlGenericControl("li");

        // new hyperlink
        var hyperlink = new Literal();
        hyperlink.Text = String.Format("<a href=\"#{0}\" >{1}</a>", tabs[groupName].ClientID, groupName);

        // add hyperlink to 'LI'
        tabLi.Controls.Add(hyperlink);

        // add LIs items to UL
        unorderedList.Controls.Add(tabLi);
    }

    // add UL to panel
    panel.Controls.Add(unorderedList);

    // add DIVs to panel
    foreach (var gi in groupAttribute.Groups)
    {
        var groupName = gi.Value;
        panel.Controls.Add(tabs[groupName]);
    }

    // add the panel to the page
    td.Controls.Add(panel);

    // associate the panel with the Wijmo tabs
    var wijmoTabs1 = new WijTabs()
        {
            ID = "wijmoTabs1",
            TargetControlID = panel.ID
        };

    // add Wijmo tabs to the page
    td.Controls.Add(wijmoTabs1);
}

Listing 2 – the main code for the Entity Template

26-05-2012 12-13-50

Figure 2 – the finished Wijmo Tabs EntityTemplate

To setup you will need to have the Attributes and the Advanced Field Template Factory these are in the sample project. I will be putting on NuGet along with my other bits and pieces hopefully soon.

public static void RegisterRoutes(RouteCollection routes)
{
    // add new entity template factory that works with single files also
    DefaultModel.EntityTemplateFactory 
        = new AdvancedEntityTemplateFactory();

    DefaultModel.RegisterContext(typeof(Models.NorthwindEntities), 
        new ContextConfiguration() { ScaffoldAllTables = true });

    routes.Add(new DynamicDataRoute("{table}/{action}.aspx")
    {
        Constraints = new RouteValueDictionary(new 
        { 
            action = "List|Details|Edit|Insert" 
        }),
        Model = DefaultModel
    });
}

Listing 3 – setting up the project Global.asax.cs

[MetadataTypeAttribute(typeof(Employee.EmployeeMetadata))]
[EntityUIHint("WijmoTabs")]
[GroupNames("Personal","Company","Address")]
public partial class Employee
{
    internal sealed class EmployeeMetadata
    {
        public int EmployeeID { get; set; }

        [Group(0)]
        [Display(Order = 0)]
        public string Title { get; set; }
        [Group(0)]
        [Display(Order = 1)]
        public string TitleOfCourtesy { get; set; }
        [Group(0)]
        [Display(Order = 2)]
        public string FirstName { get; set; }
        [Group(0)]
        [Display(Order = 3)]
        public string LastName { get; set; }
        [Group(0)]
        [Display(Order = 4)]
        public Nullable<DateTime> BirthDate { get; set; }
        [Group(0)]
        [Display(Order = 5)]
        public string Extension { get; set; }
        [Group(0)]
        [Display(Order = 6)]
        public byte[] Photo { get; set; }
        [Group(0)]
        [Display(Order = 7)]
        public string HomePhone { get; set; }

        [Group(1)]
        [Display(Order = 8)]
        public Nullable<DateTime> HireDate { get; set; }
        [Group(1)]
        [Display(Order = 9)]
        public Employee Manager { get; set; }
        [Group(1)]
        [Display(Order = 10)]
        public Nullable<int> ReportsTo { get; set; }
        [Group(1)]
        [Display(Order = 10)]
        public EntityCollection<Employee> Staff { get; set; }
        [Group(1)]
        [Display(Order = 11)]
        public EntityCollection<Order> Orders { get; set; }
        [Group(1)]
        [Display(Order = 12)]
        public EntityCollection<Territory> Territories { get; set; }
        [Group(1)]
        [Display(Order = 13)]
        public string Notes { get; set; }

        [Group(2)]
        [Display(Order = 14)]
        public string Address { get; set; }
        [Group(2)]
        [Display(Order = 15)]
        public string City { get; set; }
        [Group(2)]
        [Display(Order = 16)]
        public string PostalCode { get; set; }
        [Group(2)]
        [Display(Order = 17)]
        public string Region { get; set; }
        [Group(2)]
        [Display(Order = 18)]
        public string Country { get; set; }

        [Display(AutoGenerateField = false)]
        public string PhotoPath { get; set; }
    }
}

Listing 4 – Sample Metadata

You will also need NuGet and add the Wijmo Open for Juice UI to your project.

Get Wijmo Open for Juice UI from NuGet

Later I will add all the features that you get with jQuery UI such as tab sorting and alignment see the online Juice Explorer for all the features.

Just for good measure I have added the Wijmo Accordion to the project

Wijmo Accordian Entity Termplate

Figure 3 – Wijmo Accordion Entity Template

You can of course get even more by getting Studio for Studio for ASP.NET Wijmo from ComponentOne this is the best thing for ASP.Net I have seem come out for a while Open-mouthed smile as everyone else seems to be concentrating on either pure client or MVC Sad smile

Download from my sky drive WijmoTabs.zip

Wednesday, 22 February 2012

Using SQL Server View in Visual Studio LightSwitch 2011

There are two issue resulting from SQL Server Views in VS LightSwitch 2011 inferred keys;

  1. Columns appearing as key that you do not want  to be inferred as keys.
  2. Forcing columns to be keys that are not inferred as keys.

You will see the results of these issues with SQL Server Views in different ways for me there were two forms:

  1. Strange Paging on child grids, you may see less row than are set on the pager i.e. page size was set to 10 but on pages you see 5, 6, 7 rows or more.
  2. Single item per page.
  3. Single item returned when there should be many.

Removing Inferred Columns

View from Northwind

Figure 1 – View from Northwind

As you can see in Figure 1 view from the Northwind database as imported in to LightSwitch you can see there are two columns that may cause you problems.

To take these out of the inferred key on this view we can apply a CAST see below:

CAST(dbo.Products.Discontinued AS BIT)
CAST(dbo.Categories.CategoryName AS NVARCHAR(15))

Adding CAST to Column

Figure 2 – Adding CAST to Column

So when we “Update Datasource” we get what we see in Figure 3

View after “Update Datasource”

Figure 3 – View after “Update Datasource”

Now we have remove the two offending columns from the inferred keys.

Forcing Columns to be Inferred

Here we need to do the opposite and the function we use for that is ISNULL (Transact-SQL) for this sample we will add the Supplier and Customer ID’s to the inferred keys, here the replacement value I am using is 0 because the two columns are INT but if a column was NVARCHAR(50) then the cast would be ISNULL(TextColumn, N‘’)

ISNULL(dbo.Products.SupplierID, 0)
ISNULL(dbo.Products.CategoryID, 0)

In Figure 4 we can see the result;

After Including the Supplier and Customer IDs in the Inferred Keys

Figure 4 – After Including the Supplier and Customer IDs in the Inferred Keys

Conclusion

This way you can for the keys you want to used in your view, there will be a small hit in the queries for using either of these techniques in your queries but hopefully not too much of a hit.

Note: These view may or may not have issues, but I have had various problems in LightSwitch relating both issues and used one the two techniques to override the default behaviour.
Note: Also I did not come up with these methods they are scattered around the LightSwitch Forums but for my own use and easy reference I have added them here.

Monday, 20 February 2012

Love Telerik Open Access ORM

Just watched the Q1 2012 “What’s New in Data Tools – OpenAccess ORM” and they now have a a Dynamic Data Wizard see screen show of the slide.

DD Wizard in Telerik OpenAccess ORM

Find it here Telrik OpenAccess ORM, Note this works with Most Databases and all Free databases Open-mouthed smile

This is cool!

Monday, 13 February 2012

Basic Auditing for Dynamic Data with Entity Framework 4.x

This is my first article of 2012 and I thought I had published an article on this previously but apparently not Crying face so I will now rectify that oversight.

The first this we need out audit fields, these are added to every entity we need to audit, next we need an Interface to allow us to fine entities with Audit fields.

AuditFields

Figure 1 – Audit Fields

public interface IAuditable
{
String CreatedByUserID { get; set; }
String CreatedDateTime { get; set; }
String UpdatedByUserID { get; set; }
String UpdatedDateTime { get; set; }
}

Listing 1 – IAuditable interface

We then need to add this to each entity that will be audited, this is just applied to you metadata classes on the Partial Class NOT the buddy class.

[MetadataType(typeof(AppAlterMetadata))]
public partial class AppAlter : IAuditable
{
internal class AppAlterMetadata
{
public Object ID { get; set; }

// other field deleted for brevety

// auditing fields
public Object CreatedByUserID { get; set; }
public Object CreatedDateTime { get; set; }
public Object UpdatedByUserID { get; set; }
public Object UpdatedDateTime { get; set; }
}
}

Listing 2 – the interface applied to each entity that requires auditing

Now for the code that does the auditing automatically,

public partial class MyEntities
{
/// <summary>
/// Called when [context created].
/// </summary>
partial void OnContextCreated()
{
// Register the handler for the SavingChanges event.
this.SavingChanges += new EventHandler(context_SavingChanges);
}


/// <summary>
/// Handles the SavingChanges event of the context control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private static void context_SavingChanges(object sender, EventArgs e)
{
var objects = ((ObjectContext)sender).ObjectStateManager;

// handle auditing
AuditingHelperUtility.ProcessAuditFields(objects.GetObjectStateEntries(EntityState.Added));
AuditingHelperUtility.ProcessAuditFields(objects.GetObjectStateEntries(EntityState.Modified), InsertMode: false);
}


/// <summary>
/// Auditing helper utility class
/// </summary>
internal static class AuditingHelperUtility
{
internal static void ProcessAuditFields(IEnumerable<Object> list, bool InsertMode = true)
{
foreach (ObjectStateEntry item in list)
{
var appUserID = GetUserId();
// deal with insert and update entities
var auditEntity = item.Entity as IAuditable;
if (auditEntity != null)
{

if (InsertMode)
{
auditEntity.CreatedByUserID = appUserID;
auditEntity.CreatedDateTime = DateTime.Now;
}

auditEntity.UpdatedByUserID = appUserID;
auditEntity.UpdatedDateTime = DateTime.Now;
}

}
}
}


public static String GetUserId()
{
return System.Web.HttpContext.Current.User.Identity.Name;
}
}

Listing 3 – the Audit code

Lets break this down into three sections

Section 1

Here we wire-up the SavingChanges handler in the OnContextCreated() partial method to do this we first need to create a partial class from out entities for you look in the EDMX code behind file you will see something like this;

EFClasses

Figure 2 – Entities classes

so we add a new class to the the project, make sure it has the same namespace as the EDMX code behind file (this is pretty much the same as for out metadata classes) and then we add the partial class same as the MyEntities (this will be the name you gave it when creating but it is there in the code behind you can’t miss it) class, see Listing 3.

The method is wired up with this line of code:

this.SavingChanges += new EventHandler(context_SavingChanges);

Section 2

Now in the context_SavingChanges method we simply get the ObjectStateManager  which has all the objects that are being added, updated and deleted, here we are only interested in the Added and Modified items. All we do is call our helper with each collection of objects.

Section 3

Looking at Listing 3 you will see the AuditingHelperUtility and it’s ProcessAuditFields method, here we first of all cast the each entity to the IAuditable interface and check for null if it isn't then set the appropriate properties and exit.

Finally

This can be expanded to cover many different requirements, I have maintained a separate audit table using this method with the addition of a little reflection.