by Andreas

JQuery / KnockoutJS – define default button within a DIV

We’ve previously covered several aspects of using Default buttons in ASP.NET and Silverlight (the latter is in Norwegian, but should be semi-readable through an automagically translated version – sponsored by Google Translate).

This time I thought I’d show you how I did this without the need of server side controls. Why? First of all, adding server side controls adds unnecessary overhead by increasing the view state. Second, in my complex client side user interface I’ve stuck to clean HTML, javascript / jQuery and KnockoutJS. I didn’t want to clog it up, and since the solution was as simple as it turned out to be I am glad I stuck to my guns.

I had a look at what ASP.NET generates when a default button is defined for a Panel control. As you may or may not know, the server side Panel control is rendered as a DIV when the page is returned to the client browser. And as expected, Javascript is used to invoke the defined default button click event so I thought I’d just steal this javascript function and call it myself from within my pure HTML DIV element. Not surprisingly, this worked and why shouldn’t it? I just did the exact same thing as the .NET framework does, and there’s not much voodoo or black magic in that.

So, I wanted to invoke the click event for a “Save Changes” button displayed within a Fancybox dialog, simply by clicking ENTER. First, I added the javascript function that I stole from the .NET server control rendering engine (I am pretty sure that’s not what it’s called):

function WebForm_FireDefaultButton(event, target) {
    if (event.keyCode == 13) {
        // this variable has to be added
        var __nonMSDOMBrowser =
            (window.navigator.appName.toLowerCase().indexOf('explorer') == -1);

        var src = event.srcElement || event.target;
        if (!src || (src.tagName.toLowerCase() != "textarea")) {
            var defaultButton;
            if (__nonMSDOMBrowser) {
                defaultButton = document.getElementById(target);
            }
            else {
                defaultButton = document.all[target];
            }
            if (defaultButton && typeof (defaultButton.click) != "undefined") {
                defaultButton.click();
                event.cancelBubble = true;
                if (event.stopPropagation) event.stopPropagation();
                return false;
            }
        }
    }
    return true;
}

Note that I had to add the __nonMSDOMBrowser variable. This is usually added as a global variable on the page, but I saw no reason to do so because none of my other functions care about it.

Then, within my dialog box DIV element I make sure the onkeypress calls the new function with the ID of the button as a parameter:

<div id="addContactDialogBody" class="modal-body" onkeypress="javascript:return WebForm_FireDefaultButton(event, 'btnSaveContact')">
      <!-- This is my user input form, containing a couple of input text fields -->
</div>
<div id="addContactDialogFooter" class="modal-footer">
    <!-- dont be confused, the data-bind attribute is KnockoutJS related and has nothing to do with this example -->
    <input id="btnSaveContact" type="button" value="Save" data-bind="click: addNewContact, visible: true" />
    <input id="btnCancelContact" type="button" value="Cancel" onclick="javascript: $.fancybox.close();" />
</div>

 

When the focus is on an element within the first DIV (removed for clarty in the example code), pressing the enter Key will invoke the click event (or in my case, a KnockoutJS click binding) for the button with id btnSaveContact.

by Andreas

KnockoutJS – how to force binding refresh / re-evaluation

Observable bindings in KnockoutJS get re-evaluated when the focus is changed in a form, for instance when you click a button or use TAB to move from one input field to the next. When KnockoutJS is used in combination with an ASP.NET panel control where a DefaultButton is defined, re-evaluation of observable bindings is not performed when the user clicks ENTER to trigger the button click event. As a result of this, a value entered in an input field that is bound to the view model will not be registered if the user doesn’t navigate (move focus) away from the field first. 

A work-around for this is to use jQuery (or pure javascript) and programmatically set the focus to a different control, for instance the button itself when the event is fired.

self.addNewTimeRegEntry = function () {
    // set focus to button control to force re-evaluation
    $("#<%= btnAddRegEntry.ClientID %>").focus();

    ... // continue as normal

The button click event is bound to the addNewTimeRegEntry function above (through the KnockoutJS framework), and by moving the focus all variables will be refreshed before further processing is done.

by Andreas

ASP.NET – Cross browser default button

We have previously covered default buttons both in Silverlight and generic ASP.NET (both in Norwegian, but the code samples know no language barriers). I thought I’d add a third post about this, because the generic ASP.NET default button only shows the simplest approach – which is not fully browser independent. It works great in Internet Explorer, but if you want to make sure it also works in other browsers like Chrome and Firefox a more thorough approach is required.

I solved this by creating a new class that inherits from System.Web.UI.WebControls.LinkButton. In the OnLoad event, a script is added to the page which handles the click event for the link button. I would add a reference to the source of the script if I had it, but unfortunately that’s long gone.

Create a new class called LinkButtonDefault.cs and add the following code:

using System;
using System.Web.UI.WebControls;

namespace My.Very.Own.Namespace
{
    public class LinkButtonDefault : LinkButton
    {
        protected override void OnLoad(System.EventArgs e)
        {
            Page.ClientScript.RegisterStartupScript(GetType(), "addClickFunctionScript",
                _addClickFunctionScript, true);

            string script = String.Format(_addClickScript, ClientID);
            Page.ClientScript.RegisterStartupScript(GetType(), "click_" + ClientID,
                script, true);
            base.OnLoad(e);
        }

        private const string _addClickScript = "addClickFunction('{0}');";

        private const string _addClickFunctionScript =
            @"  function addClickFunction(id) {{
            var b = document.getElementById(id);
            if (b && typeof(b.click) == 'undefined') b.click = function() {{
                var result = true; if (b.onclick) result = b.onclick();
                if (typeof(result) == 'undefined' || result) {{ eval(b.getAttribute('href')); }}
            }}}};";
    }
}

 

At the top of the page where you want your custom default link button, register the component and give it a tag prefix:

<%@ Register TagPrefix="UR" Namespace="My.Very.Own.Namespace" Assembly="Name.Of.Your.Assembly, Version=1.0.0.0,  Culture=neutral, PublicKeyToken=3011884815bf2cfd" %>

 

Replace namespace, assembly name and public key token with your corresponding values. Then create a button based on your inherited class (using the tag prefix defined in the previous step):

<UR:LinkButtonDefault ID="lnkContinue" runat="server" onclick="btnContinue_Click">

 

Wrap the button inside a panel, and set the DefaultButton parameter to your button:

<asp:Panel ID="Panel1" runat="server" DefaultButton="lnkContinue">
by Andreas

Silverlight 4 Default button

Silverlight har ikke den innebygde Default button propertien som støttes i for eksempel Panels i ASP.NET. For de utviklerne som lever et liv i skyggen sørger denne for at du kan peke på en control innenfor et panel hvor Click event skal kalles når brukeren trykker Enter. Et typisk eksempel er en login-side hvor Enter simulerer trykk på Log in-knappen.

Silverlight / XAML har ikke denne muligheten – merkelig nok. Det er derfor en del fremgangsmåter rundt på nettet, men denne her er grei og fungerer i Silverlight 4.

Denne koden er frekt og freidig (og sikkert uten samtykke) lånt av Patrick Cauldwell – men siden jeg gir ham cred så er det sikkert greit.

1. Opprett en klasse i prosjektet, kall denne DefaultButtonService

2. Kopier inn dette (oppdater namespace til ditt eget):


using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;

namespace SilverlightDemo
{
public static class DefaultButtonService
{
public static readonly DependencyProperty DefaultButtonProperty =
DependencyProperty.RegisterAttached(“DefaultButton”, typeof(string), typeof(DefaultButtonService), new PropertyMetadata(OnDefaultButtonChanged));

public static string GetDefaultButton(DependencyObject d)
{
return (string)d.GetValue(DefaultButtonProperty);
}

///
/// Sets the CommandParameter property.
///

public static void SetDefaultButton(DependencyObject d, string value)
{
d.SetValue(DefaultButtonProperty, value);
}

private static void OnDefaultButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tb = d as TextBox;
if (tb != null)
{
tb.KeyUp += new KeyEventHandler(tb_KeyUp);
}
}

static void tb_KeyUp(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
string name = (string)((DependencyObject)sender).GetValue(DefaultButtonProperty);
object root = App.Current.RootVisual;
object button = ((FrameworkElement)root).FindName(name);
if (button is Button)
{
ButtonAutomationPeer peer = new ButtonAutomationPeer((Button)button);

IInvokeProvider ip = (IInvokeProvider)peer;
ip.Invoke();
}
break;
}
}
}
}

3. Legg til et xmlns i XAML:

xmlns:my=”clr-namespace:SilverlightDemo”

4. Legg til en attributt på alle controls som skal trigge at knappen “btnDemo” skal trykkes, for eksempel en textbox:

by Stian

DefaultButton i ASP.NET

EnterbuttonForskjellige nettlesere behandler et enter-trykk på forskjellige måter avhengig av antall tekstbokser og antall knapper på siden. Dette gjør at vi må styre hvilke knapp som skal behandle et enter-trykk i web applikasjonen. Løsninger som å disable enter knappen med et javascript kan få uheldige konsekvenser som f.eks i en Text Area komponent hvor linjeskift ikke vil virke lenger.

Løsning du vil bruke i de fleste tilfellene er:
<form id=”form1″ runat=”server” defaultbutton=”btn1″>

Problemet med denne løsningen kom jeg akkurat over i et prosjekt hvor det som så mange andre plasser brukes Masterpages. Vi står derfor uten en form-tag i undersiden og vi må i stedet bruke Panel på denne måten:
<asp:Panel ID=”pnl1″ runat=”server” defaultbutton=”Button1″>

På denne måten kan du styre flere områder på websiden til å trigge forskjellige Buttons ved enter-trykk.