2007-07-10

Vad Asp.Net-utvecklare bör kunna om JavaScript

K. Scott Allen har skrivit en utmärkt artikel om "What ASP.NET Developers Should Know About JavaScript"

Obligatorisk läsning om man ska koda lite mer avancerad JavaScript på klientsidan.

  • Varje objekt i JavaScript är ett dictionary
  • Vare funktion i JavaScript är även ett objekt
  • Varje objekt har en prototyp.

Förvirrad? Läs artikeln!

Utöka en befintlig AjaxControlToolkit Extender

I en tidigare bloggpost visade jag hur man skapar en AjaxControlToolkitExtender från början. Den här gången tänkte jag visa hur man utökar en befintlig extender med lite ytterligare funktionalitet. Det kan vara bra att läsa den tidigare bloggposten först.

HoverAddsCssClassExtender

Extendern HoverAddsCssClassExtender hakas liksom alla extendrar på en befinlig kontroll på sidan, t.ex. en Panel. När muspekaren förs över kontrollen kommer dess class-property (som innehåller css-klassnamn) att utökas med ytterligare ett css-klassnamn. När musen lämnar kontrollen återställs värdet. En klassisk onmouseover-, eller hover-effekt alltså. Traditionellt så brukar man byta css-klassnamnet mot något helt annat, men det är smartare att lägga på ytterligare en regel.

Ett litet exempel kan nog förtydliga vad som händer och hur den används:

<asp:Panel runat="server" ID="Panel1" CssClass="squared">
Hover me!
</asp:Panel>
<
ajaxExtender:HoverAddsCssClassExtender runat="server"
ID="HoverAddsCssClassExtender1" TargetControlID="Panel1" HoverCssClassName="hover" />

På klienten kommer Panel1 att vara en div vars class-property är satt till "squared". När musen förs över Panel1 kommer dess class-property att sättas till "squared hover", dvs. värdet från HoverCssClassName läggs till sist, för att återställas till "squared" när musen försvinner.


HoverExtender


Hur får man då till det här enklast? Jo det finns en extender som heter HoverExtender som följer med AjaxControlToolkit och som är lite odokumenterad. Den ligger som grund till DropDownExtender och HoverMenuExtender men den fungerar alldeles utmärkt att använda fristående. Det den gör att den kör ett av användaren specificerat script på mouseover och ett annat script på mouseout. Man kan även specificera att det ska dröja ett tag innan någon av scripten körs. Detta kan t.ex. utnyttjas till att tillåta att muspekaren passerar över kontrollen utan att scriptet körs (eller lämnar kontrollen som hastigast utan att scriptet körs direkt). Bara genom att låta muspekaren dröja kvar ett tag körs scriptet.


Vi kommer att låta vår extender ärva funktionalitet av HoverExtender. Men bara på klientsidan.


Skapa HoverAddsCssClassExtender


Nog pratat. Låt oss börja koda. Jag kommer inte att gå in i detalj hur du sätter upp projektet utan utgå ifrån att du har ett projekt där du kan skapa extendern. Mer information om hur du sätter upp ett sånt projekt finns i den tidigare bloggposten. Namnge projektet HoverAddsCssClass


Ny AjaxControlToolkitExtender


Du måste inte skapa Ajax Control Project utan kan skapa en extender i ett vanligt klassbibliotek. För att skapa en ny extender i ett befintligt projekt: Högerklicka på projektet och välj Add > New Item... och välj ASP.NET AJAX Extender Control längst ner under My Templates. Ange HoverAddsCssClass som namn. Se till att du har en referens till AjaxControlToolkit.


Nedan kommer jag förutsätta att du skapat ett nytt projekt som heter HoverAddsCssClass med tre filer. HoverAddsCssClassBehavior.js, HoverAddsCssClassDesigner.cs och HoverAddsCssClassExtender.cs. Det första man ALLTID ska göra är att se till att js-filen har Build Action=Embedded Resource (finns under properties för den filen).


Radera designern


Vi kommer inte att behöva någon egen designer så ta bort HoverAddsCssClassDesigner.cs ur projektet. Default-designern duger utmärkt. (Designern används i design-läget inne i Visual Studio). Gå samtidigt in och radera hänvisningen till den i HoverAddsCssClassExtender.cs.

namespace HoverAddsCssClass
{
[Designer(typeof(HoverAddsCssClassDesigner))]
[ClientScriptResource("HoverAddsCssClass.HoverAddsCssClassBehavior",
"HoverAddsCssClass.HoverAddsCssClassBehavior.js")]
[TargetControlType(typeof(Control))]
public class HoverAddsCssClassExtender : ExtenderControlBase
{
// TODO: Add your property accessors here.

Ärv av HoverExtender – men bara på klientsidan


Normalt ärver en extender (som används på server-sidan) från ExtenderControlBase som syns i koden ovan, men den kan bytas ut mot vilken extender som helst i AjaxControlToolkit (och alla andra klasser som implementerar IExtenderControl). Då vi vill återanvända funktionalitet som HoverExtender har skulle vi kunna byta ut arvet mot HoverExtender och få en massa saker gratis. Men eftersom vi inte vill exponera alla de properties som HoverExtender har så struntar vi i arvet. Istället lägger vi till de properties vi vill ha.


På klientsidan däremot vill vi utnyttja HoverBehavior (som är klientdelen av HoverExtender). Även här används arv. Längst ner i filen HoverAddsCssClassBehavior.js hittar du:

HoverAddsCssClass.HoverAddsCssClassBehavior.registerClass(
'HoverAddsCssClass.HoverAddsCssClassBehavior', AjaxControlToolkit.BehaviorBase);

Klassen HoverAddsCssClass.HoverAddsCssClassBehavior registreras och den ärver av AjaxControlToolkit.BehaviorBase, men det finns inget som säger att den måste ärva från den klassen. Vi ändrar det till att vara HoverBehavior istället

HoverAddsCssClass.HoverAddsCssClassBehavior.registerClass(
'HoverAddsCssClass.HoverAddsCssClassBehavior', AjaxControlToolkit.HoverBehavior);

Vi måste även se till att koden för HoverBehavior laddas ner till klienten. Det gör vi med hjälp av attributet RequiredScript på serversidans extender.

[ClientScriptResource("HoverAddsCssClass.HoverAddsCssClassBehavior", 
"HoverAddsCssClass.HoverAddsCssClassBehavior.js")]
[TargetControlType(typeof(Control))]
[RequiredScript(typeof(HoverExtender))]
public class HoverAddsCssClassExtender : Hover
{


Vi säger med RequiredScript-raden ovan att vi vill få ner alla script-filer som HoverExtender använder.


HoverCssClassName – Property


Vi behöver en property HoverCssClassName så att man kan ange css-klassnamnet som ska läggas till då musen förs över elementet. Vi börjar i extendern med att lägga till propertyn. Ta samtidigt bort MyProperty som redan ligger i filen.

[ExtenderControlProperty()]
[DefaultValue("")]
public string HoverCssClassName
{
get
{
return GetPropertyValue("HoverCssClassName", String.Empty);
}
set
{
SetPropertyValue("HoverCssClassName", value);
}
}


Över till klientsidans behavior-kod.

HoverAddsCssClass.HoverAddsCssClassBehavior = function(element) {
HoverAddsCssClass.HoverAddsCssClassBehavior.initializeBase(this, [element]);

this._hoverCssClassName = "";
}
...
dispose : function() {
// TODO: add your cleanup code here

HoverAddsCssClass.HoverAddsCssClassBehavior.
callBaseMethod(this, 'dispose');
},

get_HoverCssClassName : function() {
return this._hoverCssClassName;
},
set_HoverCssClassName : function(value) {
this._hoverCssClassName = value;
this.raisePropertyChanged('HoverCssClassName');
},


Hämta och spara värdet i en lokalt deklarerad variabel.


Hantera mouseover


HoverBehavior, klientsidans behavior som vi ärver av, har två properties hoverScript och unhoverScript, på vilka man anger javascriptkod i form av en strängar. Denna kod kommer att tolkas och köras vid mouseover resp. mouseout (och även focus resp. blur). Det här utnyttjar vi till att köra två av våra egna metoder för att lägga till och ta bort css-klassnamnet.


Eftersom vi ärver av HoverBehavior har vi tillgång till de metoder som används för att sätta dess properties. På samma sätt som vår HoverCssClassName har en metod set_HoverCssClassName för att ange värdet har hoverScript och unhoverScript de två metoderna set_hoverScript och set_unhoverScript. Så vi anropar dem från vår initialize-kod:

initialize : function() {
HoverAddsCssClass.HoverAddsCssClassBehavior.
callBaseMethod(this, 'initialize');

this.set_hoverScript("this._addCss()");
this.set_unhoverScript("this._removeCss()");
},

och vi lägger till de två metoder som initialize hänvisar till:

_addCss : function()
{
var targetElement=this.get_element();
if(targetElement && !Sys.UI.DomElement.containsCssClass(targetElement,this._hoverCssClassName))
{
Sys.UI.DomElement.addCssClass(targetElement,this._hoverCssClassName);
}
},

_removeCss : function()
{
var targetElement=this.get_element();
if(targetElement && Sys.UI.DomElement.containsCssClass(targetElement,this._hoverCssClassName))
{
Sys.UI.DomElement.removeCssClass(targetElement,this._hoverCssClassName);
}
}

I _addCss() börjar vi med att plocka ut det element som extendern hakades på. Om ett sånt element fanns (ska alltid finnas, men bäst att kolla ändå för att slippa fel om den ändå inte skulle finnas) och dess css-klassnamnsträng inte redan innehöll vår _hoverCssClassName lägg till det till css-klassnamnet.


_removeCss() fungerar på liknande sätt. Om vi har ett target element och dess css-klassnamnsträng innehåller _hoverCssClassName, ta bort det.


Vi nyttjar här tre funktioner som Asp.Net Ajax för med sig:



I och med det är allt klart.


Testsida


Läs den tidigare bloggposten för information om hur man skapar en testsajt. I Default.aspx knackar du in följande:

<head id="Head1" runat="server">
<title>Untitled Page</title>
<style type="text/css">
.squared
{
width:200px;
height:200px;
border: dashed 4px black;
background-color: #eee;
text-align:center;
}

.hover
{
background-color: #228;
color:white;
}
</style>

</head>
<
body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:Panel runat="server" ID="Panel1" CssClass="squared">
Hover me!
</asp:Panel>
<myExtenders:HoverAddsCssClassExtender runat="server" ID="HoverAddsCssClassExtender1" TargetControlID="Panel1" HoverCssClassName="hover" />
</form>
</
body>
</
html>

Sen är det bara att trycka F5 och testa.


Utöka med HoverDelay & UnhoverDelay


HoverBehavior har ytterligare properties som vi enkelt kan utnyttja: hoverDelay och unhoverDelay. Genom att sätta dessa specificerar vi hur lång tid i millisekunder man måste befinna oss över elementet innan hoverScript körs, resp. hur länge vi måste befinna oss utanför för att unhoverScript ska köras. Att lägga till stöd för dessa två värden är enkelt. På klientsidan ärver vi ju av HoverBehavior så där är dessa properties redan exponerade. Det räcker således med att göra det i serversidans extender.

[ExtenderControlProperty]
[DefaultValue(0)]
[ClientPropertyName("hoverDelay")]
public int HoverDelay
{
get
{
return GetPropertyValue("HoverDelay", 0);
}
set
{
SetPropertyValue("HoverDelay", value);
}
}

[ExtenderControlProperty()]
[ClientPropertyName("unhoverDelay")]
[DefaultValue(0)]
public int UnhoverDelay
{
get
{
return GetPropertyValue("UnhoverDelay", 0);
}
set
{
SetPropertyValue("UnhoverDelay", value);
}
}

Composition istället för arv


I det här exemplet använde vi arv på klientsidan för att få den funktionalitet vi behövde. Ett annat sätt är använda composition, vilket innebär att att man istället för att ärva in önskat beteende skapar en privat HoverBehavior inne i vår behavior (och då ärver man från AjaxControlToolkit.BehaviorBase som vanligt). Man får då själv se till att förändringar av mina properties slår igenom även på HoverBehavior.


När och varför man ska välja arv eller composition ligger utanför denna genomgång och även hur man skapar med Composition. Är du nyfiken på hur man gör kolla in t.ex. HoverMenuBehavior i AjaxControlToolkit som använder composition för PopupBehavior och HoverBehavior.


Ladda ner projektet


Ladda ner projektet

2007-05-21

Infoga Do-nothing-kommentarer

Infoga alltid en //Do nothing kommentar på ställen där avsikten är att inget ska utföras.

Anta t.ex. att du har en konstructor i en Control som ärver av WebControl och det enda du vill göra är att ändra vilken tag som ska användas.
public MyControl()
:base(HtmlTextWriterTag.Div)
{
}
När någon annan senare läser koden (eller du själv en månad senare när du glömt allt) är risken stor att man undrar om koden är klar eller om det saknas något i konstruktorn eftersom den är tom. Genom att knacka in en kommentar tydliggör du för dig själv och andra att den verkligen ska vara tom.
public MyControl()
:base(HtmlTextWriterTag.Div)
{
//Do nothing
}

Jag har en code snippet för det här så jag behöver bara skriva don och trycka tab två gånger så infogas //Do nothing.

2007-05-20

Skapa en AjaxControlToolkit Extender

I den här genomgången ska vi skapa en AjaxControlToolkit Extender som man hakar på en TextBox. När användaren skriver in ett datum i något av de tillåtna formaten kommer datumet att formateras om till ett angivet format. Om användaren skriver in "070519" kommer det att formateras om till "2007-05-19". Detta sker på klienten utan något anrop till webbservern.


För att kunna göra detta måste du ha laddat ner och installerat ASP.NET 2.0 AJAX Extensions 1.0 och ASP.NET AJAX Control Toolkit från http://ajax.asp.net/downloads/.


Länk till projektet finns längst ner


Klientkoden – översikt


Microsoft har utökat Date-proptotypen med metoden Date.parseLocale(value, validFormats) som givet en sträng returnerar ett datum om värdet matchade något av de angivna formaten. De har också utökat med en format(displayFormat) metod för att formattera ett datum.


Med dessa två metoder har vi allt vi behöver. Genom att hantera change-eventet på en input-textbox får vi reda på när dess värde ändrats. När värdet ändrats skickas det till parseLocale som antingen returnerar null, eller ett datum. Om ett datum returneras så formatteras det med format och den resulterande strängen åker tillbaka till input-textboxen.


Nytt Ajax Control Project


Börja med att skapa ett nytt ASP.NET AJAX Control Project. (File > New > Project. Välj Visual C# och under My Templates välj ASP.NET AJAX Control Project). Ge projektet ett valfritt namn, t.ex. DateParser. Ett nytt projekt sätts upp med tre filer: DateParserBehavior.js, DateParserDesigner.cs och DateParserExtender.cs. (Dessa filer kan du även få i ett befintligt projekt genom att högerklicka på projektet och välja Add New Item och sen välja ASP.NET AJAX Control.)


En extender består av en server del, DateParserExtender.cs, och en klientdel, DateParserBehavior.js. Genom att sätta properties i server-kontrollen kan vi påverka hur klientdelens beteende ska vara. Den tredje filen är en designer-klass men den behöver man sällan in och peta i.


Det första man måste göra när man lagt till en ny extender är att verifiera att javascript-filen kommer att bäddas in som en resurs. Kolla att propertyn Build Action är satt till Embedded Resorce.
Javascript file, Build Action: Embedded Resource


Om du glömmer att bädda in den som en resurs kommer du att få ett meddelande om att resursen inte finns när du försökèr köra applikationen.



Serverkontrollen


DateParser Extendern (i DateParserExtender.cs) ska ha två properties: ValidFormats och DisplayFormat för att man ska kunna ange tillåtna format samt hur det formatterade datumet ska se ut. DateParserExtender.cs utgör själva server-kontrollen och den ärver av ExtenderControlBase från AjaxControlToolkit. Inledningsvis innehåller vår nya kontroll en property; MyProperty som fungerar som en mall för hur properties ska skrivas.

[ExtenderControlProperty]
[DefaultValue("")]
public string MyProperty
{
get
{
return GetPropertyValue("MyProperty", "");
}
set
{
SetPropertyValue("MyProperty", value);
}
}


Attributet ExtenderControlProperty signalerar att det här är en property vars värde ska ner till klienten. Om attributet saknas kommer värdet aldrig att föras över till Behaviorn (man kan alltså ha "vanliga" properties vars värden inte direkt påverkar klient-beteendet).



DefaultValue-attributet har att göra med hur koden för att skapa den här kontrollen ska se ut (och ligger utanför detta ämne) och ska sättas till propertyns defaultvärde.



För att hämta och sätta värden används metoderna GetPropertyValue resp. SetPropertyValue. I samband med att man hämtar värdet anger man det värde som ska gälla ifall propertyn inte har satts tidigare. Se till att det motsvarar DefaultValue-attributet.



Våra properties blir då alltså:

private const string _Default_ValidFormats = "yyMMdd;yyyyMM;ddyyyy-MM-dd;yy-MM-dd;yyyyMM-dd;yyyy-MMdd;yyMM-dd;yy-MMdd;dd MMM yyyy;MM/dd/yy;MM/dd/YYYY";
private const string _Default_DisplayFormat = "yyyy-MM-dd";

[ExtenderControlProperty]
[DefaultValue(_Default_DisplayFormat)]
public string DisplayFormat
{
get
{
return GetPropertyValue("DisplayFormat", _Default_DisplayFormat);
}
set
{
SetPropertyValue("DisplayFormat", value);
}
}

[ExtenderControlProperty]
[DefaultValue(_Default_ValidFormats)]
public string ValidFormats
{
get
{
return GetPropertyValue("ValidFormats", _Default_ValidFormats);
}
set
{
SetPropertyValue("ValidFormats", value);
}
}


Vi använder konstanter för defaultvärdena för att se till att attributet och GetPropertyValue använder samma värde (sen är det dessutom snyggare att göra så och vi gillar ju att skriva snygg kod). Då var serverdelen klar.



Klientens Behavior

En behavior kan ses som en klass. Den har en konstruktor, där dess privata variabler definieras; den har properties; publika och privata metoder samt en initialize och en dispose-metod. När en behavior skapas körs först konstruktorn, eventuell properties från servern sätts, varpå initialize körs. I initialize bygger man upp det som behaviorn behöver för att fungera, registrerar events, etc. När behaviorn ska tas bort körs dess dispose-metod. Här avregistrerar man sig från event och rensar upp efter sig. Observera att dispose kan komma att anropas flera gånger så det gäller att se till att koden klarar det.

Properties



Även filen DateParserBehavior.js innehåller lite exempelkod för att visa hur den ska kodas. En property på klientsidan består av tre delar: en privat variabel som skapas i konstruktorn högt upp i filen, en get-metod och en set-metod som man använder för att sätta värdet på propertyn. Så för propertyn MyProperty så skriver man inte obj.MyProperty="value"; istället gör man anropet obj.set_MyProperty("value");.



Så för våra properties behöver vi två variabler för att hålla deras värden. Vi anger också default-värden.

DateParser.DateParserBehavior = function(element) {

DateParser.DateParserBehavior.initializeBase(this, [element]);

// Property variables
this._displayFormat = "yyyy-MM-dd";
this._validFormats = "yyMMdd;yyyyMMdd;yyyy-MM-dd;yy-MM-dd;yyyyMM-dd;yyyy-MMdd;yyMM-dd;yy-MMdd;dd MMM yyyy;MM/dd/yy;MM/dd/YYYY";
}
Efter dispose-metoden lägger vi in get- och set-metoderna
dispose : function() {
// TODO: add your cleanup code here

DateParser.DateParserBehavior.callBaseMethod(this, 'dispose');
},


// Properties ------------------------------------------------
get_DisplayFormat : function() {
return this._displayFormat;
},
set_DisplayFormat : function(value) {
this._displayFormat = value;
},


get_ValidFormats : function() {
return this._validFormats;
},
set_ValidFormats : function(value) {
this._validFormats = value;
}
De privata variablerna (signaleras med att de börjar med ett understreck) kan namnges som man vill men namnen på get- och set-metoderna måste motsvara server-kontrollens properties. Eftersom vi har en ExtenderControlProperty-markerad property i server-kontrollen som heter ValidFormats måste vi även ha en get_ValidFormats och en set_ValidFormats metod på klienten.

ParseDate, sträng till datum



Innan vi skriver kod för eventhanteringen behöver vi en hjälpmetod för att anropa Date.parseLocale(value,formats). Se mitt tidigare inlägg om varför detta behövs.

_parseDate : function(value,formats)
{
//Create an array that initially will contain value.
//Add the elements from formats after the value.
var args=[value];
Array.addRange(args,formats);

//args contains now: [value, formats[0], formats[1], ... ]
//Call the parseLocale method using our args array as parameters
return Date.parseLocale.apply(Date,args);
}


Uppdatera inputboxen



Metoden som kommer att anropas när eventet change höjs:

_updateTargetElement : function()
{
var targetElement = this.get_element();
var value=targetElement.value;
if(value)
{
var parsedDate = this._parseDate(value,this._validFormatsArr);
if(parsedDate)
{
targetElement.value=parsedDate.format(this._displayFormat);
}
//Else, on parse error, do nothing.
}
}


Metoden börjar med att ta reda på vårt targetElement, dvs. vår inputbox, genom att anropa metoden get_element() (en metod vi ärvt från vår basklass). Om värdet har satts, försök att tolka det som ett datum. Om vi lyckades tolka det som ett datum, formattera det enligt specificerat format och uppdatera inputboxens value. Det enda som behöver en förklaring är den privata variabeln this._validFormatsArr.



Tillåtna format som array



Vi anger formatet som en semikolonseparerad sträng av godkända format, t.ex. "yyyyMMdd;yyMMdd". Strängen kommer vi att göra om till en array ["yyyyMMdd","yyMMdd"] och det är alltså det this._validFormatsArr innehåller. Vi deklarerar den i konstruktorn ihop med de andra.

    // Property variables
this._displayFormat = "yyyy-MM-dd";
this._validFormats = "yyMMdd;yyyyMMdd;yyyy-MM-dd;yy-MM-dd;yyyyMM-dd;yyyy-MMdd;yyMM-dd;yy-MMdd;dd MMM yyyy;MM/dd/yy;MM/dd/YYYY";

// Internal variables
this._validFormatsArr=null;


Arrayen fylls i Initialize-metoden.

initialize : function() {
DateParser.DateParserBehavior.callBaseMethod(this, 'initialize');

this._validFormatsArr=this._validFormats.split(";");
},


Event-hantering



Det sista som återstår är att registrera en eventhanterare för inputboxens change-event. Mönstret för event består typiskt av fyra delar (ibland vill man göra på andra sätt men det är överkurs):





  1. Skapa en funktion för eventhantering

  2. Deklarera en privat variabel i konstruktorn som kommer att innehålla en delegat (kan betraktas som en funktionspekare) för din eventhanterare.

  3. I initialize skapar man delegaten och registrerar den på eventet.

  4. I dispose avregistrerar man delegaten från eventet och sätter den privata variablen till null.


I vårt exempel blir det då så här.





  1. Skapa en funktion för eventhantering
    //Event handlers -----------------------------------------------
    _onValueChange : function(e)
    {
    this._updateTargetElement();
    }


  2. Deklarera en privat variabel i konstruktorn som kommer att innehålla en delegat (kan betraktas som en funktionspekare) för din eventhanterare.
    //Event handlers
    this._valueChangeHandler=null;


  3. I initialize skapar man delegaten och registrerar den på eventet.
    initialize : function() {
    DateParser.DateParserBehavior.callBaseMethod(this, 'initialize');

    var targetElement = this.get_element();

    this._validFormatsArr=this._validFormats.split(";");

    //Register Event handlers
    this._valueChangeHandler = Function.createDelegate(this, this._onValueChange);
    $addHandler(targetElement, 'change', this._valueChangeHandler);

    },


  4. I dispose avregistrerar man delegaten från eventet och sätter den privata variablen till null.
    dispose : function() {
    //Reminder: Might be called several times.

    var targetElement = this.get_element();

    if (this._valueChangeHandler)
    {
    $removeHandler(targetElement, 'change', this._valueChangeHandler);
    this._valueChangeHandler = null;
    }


    DateParser.DateParserBehavior.callBaseMethod(this, 'dispose');
    },


Sisådär då borde allt vara klart för en testtur.



Testprojekt



För att kunna testa behöver vi en testsajt. Lägg till en ny Ajax-webbsajt (File > Add > New Web Site ... > ASP.NET AJAX-enabled Web site). Lägg till en referens till vår DateParser (högerklicka på webbsajten i Solution Explorer och välj Add Reference > Projects > Date Parser > OK).



Öppna default.aspx och dra in en TextBox från toolboxen. Växla till source-läge och lägg till följande direktiv ovanför DOCTYPE-raden för att göra vår extender tillgänglig på sidan:

<%@ Register tagprefix="myExtenders" Namespace="DateParser" Assembly="DateParser" %>


Nedanför TextBox1 lägger du till:

<myExtenders:DateParserExtender ID="TextBox1Extender"
runat="server" TargetControlID="TextBox1" />


Sätt webbprojektet till att vara startprojekt och kör igång genom att trycka F5. Om allt fungerar som det ska, ska du kunna mata in 070519 i rutan, trycka tab och det formatteras om till 2007-05-19. Om du missat att säga till att JavaScript-filen ska bäddas in som en resurs är det nu du kommer att få ett felmeddelande om att resursen saknas i assembly:t.



Om du vill byta datumformat är det bara att göra det på DateParserExtendern:

<myExtenders:DateParserExtender ID="TextBox1Extender"
runat="server" TargetControlID="TextBox1"
DisplayFormat="MMM ddd dd yyyy" ValidFormats="yyMMdd" />


Dessa format kan du använda: http://msdn2.microsoft.com/en-us/library/bb79761a-ca08-44ee-b142-b06b3e2fc22b.aspx



För att slippa lägga till ett register-direktiv på alla sidor kan du lägga till följande i Web.Config.

<system.web>
<pages>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add tagPrefix="myExtenders" namespace="DateParser" assembly="DateParser"/>
</controls>
</pages>


En bra sak att alltid lägga in i sina behaviors är en metod för att trigga beteendet. Om beteendet är att poppa upp en ruta när något händer, lägg till en metod som poppar upp den. I vårt fall behöver vi en metod för att parse:a inputboxen och formattera om den, så vi lägger till följande publika metod:

//Public Methods -----------------------------------------------
updateTargetElement : function()
{
this._updateTargetElement();
}

Metoder som denna gör det möjligt att via kod trigga ett beteende och det är ibland väldigt praktiskt. Tyvärr är det något de flesta AjaxControlToolkit Extenders saknar.



Ladda ner projektet

2007-05-19

Anropa Date.parseLocale() med en array

I dokumentation till javascript-metoden Date.parseLocale(value,formats) (som Microsoft lagt till med Ajax) står det att formats är en array av format. Det är inte sant. Den tar formaten som flera argument:

Date.parseLocale(value, format1, format2, ...)
Hur gör man då om man har en array med format och inte vill loopa över arrayen och göra ett anrop till parseLocale för varje format? Ett sätt att göra det på är att använda standard-javascript-metoden apply(thisObj, argArray). Den kan köras på ett funktionsobjekt och tar som första argument det objekt som ska bli this inne i funktionen. Som andra argument tar den en array med alla argument till funktionen.
Så vi behöver bygga en array bestående av [value, format1, format2, ...] och sen köra apply. Så här:
function myParseLocale(value,formats)
{
var args=[value];
Array.addRange(args,formats);
return Date.parseLocale.apply(Date,args);
}



Date.parseLocale är en metod som givet en sträng försöker tolka det som ett datum enligt ett av de inskickade formaten. Om den lyckas returneras ett Date-objekt.

2007-05-18

Skicka värden till behaviors

En property i en Ajax Extender ser oftast ut så här:
[ExtenderControlProperty]
[DefaultValue("")]
public string MyProperty
{
get { ... }
set { ... }
}
Med en matchande property i klientsidans behavior:
get_MyProperty : function() { ... },
set_MyProperty : function(value) { ... }

Värden som anges på serversidans MyProperty kommer genom lite magi (nåja) att hamna på klientsidan och vara åtkomlig där.

Ibland vill man skicka över värden, eller sätta egenskaper på klientens behavior utan att ha en matchande property på Extender-kontrollen. För att åstadkomma detta så override:a RenderScriptAttributes() och lägg till klientegenskaper med AddProperty().
protected override void RenderScriptAttributes(ScriptBehaviorDescriptor descriptor)
{
base.RenderScriptAttributes(descriptor);
string horizontalAlignment=GetHorizontalAlignment();
descriptor.AddProperty("HorizontalAlignment", horizontalAlignment);
}

Voilà, propertyn HorizontalAlignmenti klientens behavior kommer att få ett värde utan att vi skapade en property på serversidan.

Namn på properties i Ajax extenders

Properties för Ajax-extenders som ärver av ExtenderControlBase ser typiskt ut så här:
[ExtenderControlProperty]
[DefaultValue("")]
public string MyProperty
{
get
{
return GetPropertyValue("MyProperty", "");
}
set
{
SetPropertyValue("MyProperty", value);
}
}


I behavior-klassen på klientsidan måste en property med samma namn finnas:
get_MyProperty : function() {
return this._myProperty;
},
set_MyProperty : function(value) {
this._myProperty = value;
}


Men om man nu vill att propertyn ska ha ett namn på serversidan och ett annat på klientsidan? Kul att du frågade. Riktigt enkelt. Markera propertyn på serversidan med attributet ClientPropertyName. Så om vi vill att MyProperty ska heta myProp på klientsidan, skriver man:
[ExtenderControlProperty]
[DefaultValue("")]
[ClientPropertyName("myProp")]
public string MyProperty
{
get
{
return GetPropertyValue("MyProperty", "");
}
set
{
SetPropertyValue("MyProperty", value);
}
}


På klientsidan kan vi då skriva:
get_myProp : function() {
return this._myProperty;
},
set_myProp : function(value) {
this._myProperty = value;
}


Mer om attributen:
http://ajax.asp.net/ajaxtoolkit/Walkthrough/ExtenderClasses.aspx

2007-05-10

OOP i JavaScript

MSDN Magazine, maj 2007 har en intressant artikel om hur man programmerar objektorienterat i JavaScript. Ett absolut måste att läsa om man använder Microsofts Asp.Net Ajax och vill ha lite koll på vad som händer eftersom hela kodbiblioteket på klienten är skrivet med dessa tekniker.

http://msdn.microsoft.com/msdnmag/issues/07/05/JavaScript/default.aspx

2007-03-22

Validators fungerar inte med UpdatePanel

Validatorer fungerar ibland rätt dåligt när man stoppar dem i en UpdatePanel.

I Ajx asp.net 1.0 releasen (den som finns nu i skrivande stund) så var tanken att man skulle fått till det här genom att byta ut de vanliga validatorerna mot ajax-enablade varianter. Så blev det inte. I väntan på nästa releas så finns det en fix tillgänglig.

Kolla här: http://blogs.msdn.com/mattgi/archive/2007/01/23/asp-net-ajax-validators.aspx