I am trying to get into functionnal programming. For the last year I have been reading and making a few aborted attempt at programing in fsharp.

My last attempt was to create a web application in fsharp. The problem is that I am missing some basic baby step before being able to do what I want. It is the same thing as learning a new language, you need to know how to say beer before even being able to get into some cool social gathering…

Well then, I am quite fond of a board game called diplomacy, mecanism are easy, or so they seems at first , let’s try to model this. First step, I am trying to create some basic board game. I need the seven countries :

type Country =
    | France
    | England
    | Germany
    | Italy
    | AustriaHungary
    | Russia
    | Turkey

Territories can be land, coast or sea and might or not have a center, this center can itself be neutral or belong to some nation.

Therefore I have modeled so far the control and the center by :

type controlledBy =
    | SomeCountry of Country
    | NoOne 

type Center =
    | SomeCenter of controlledBy
    | NoCenter

and the territories are then detailed as follows :

type territory =
    |Land of string*string*Center
    |Sea of string*string*Center
    |Coast of string*string*Center

This allows me to setup a board game as follows ( it is an example not the whole game is here defined ) :

let territories = [Land("PAR","Paris", SomeCenter( SomeCountry(France)) );
                 Coast("PIC","Picardie", NoCenter);
                 Land("BOU","Bourgogne", NoCenter);
                 Coast("BRE","Brest", SomeCenter( SomeCountry(France)));
                 Coast("GAS","Gascogne", NoCenter);
                 Coast("MAR","Marseille",SomeCenter( SomeCountry(France)));
                 Sea("MID","Ocean Atlantique",NoCenter);
                 Sea("LIO","Golfe du Lion",NoCenter);
                 Sea("MAN","la Manche",NoCenter);
                 Coast("LON", "London" , SomeCenter( SomeCountry(England)));
                 Coast("WAL", "Wales" , NoCenter);
                 Coast("YOR", "YorkShire" , NoCenter);
                 Coast("LIV", "Liverpool" , SomeCenter( SomeCountry(England)));
                 Coast("EDI", "Edinburgh" , SomeCenter( SomeCountry(England)));
                 Sea("ATL", "Atlantique Nord" , NoCenter);
                 ]

In order to get something out of this model, I have decided to write some short description out of each part of my model so far :

Countries :

let printCountry country =
    match country with 
    | France -> "France"
    | England -> "Angleterre"
    | Germany -> "Allemagne"
    | Italy -> "Italie"
    | AustriaHungary -> "Autriche - Hongrie"
    | Russia -> "Russie"
    | Turkey -> "Turquie"

Control of Center :

let printControlledBy control =
    match control with
    | SomeCountry(country)  -> printCountry country
    | NoOne -> "Personne"

Center as follows :

let printCenter center =
    match center with
    | SomeCenter(controlled) -> sprintf " a un centre qui appartient à %s" (printControlledBy(controlled))
    | NoCenter -> sprintf "n'a pas de centre"

and finally territories like this :

let printTerritory territory = 
    match territory with
    | Land(pre,name,center) -> sprintf "Le territoire %s (%s) est terrestre et %s." name pre (printCenter(center))
    | Sea(pre, name, center) ->  sprintf "Le territoire %s (%s) est marin et %s." name pre (printCenter(center))
    | Coast(pre, name, center) ->  sprintf "Le territoire %s (%s) est cotier et %s." name pre (printCenter(center))

I have then only to map my boardgame to the print function in fsi:

territories |> List.map printTerritory;;

These are just baby steps and , it allowed me to dive into the syntax of disciminated unions and option. I do hope I am on the way of going more functionnal than OO. The mindshift is not an easy one to take, it is somewhat hard to shift from some stateful way of thinking to something more on the move, but I ‘ll give it my best try.

 

Publié par : yoannr | 13 janvier 2013

Reminder about rules & aggregates

Some piece of wisdom from Philip Jander, coming from the following discussion on the ddd/cqrs google group :

 

Let me chime in on this.

This set of problems naturally arises whenever one develops a 
non-trivial domain model. It is obvious, that there is no immediate 
« easy » solution.

Here is my take on this: the problem simply stems from the fact that we 
are considering a collaboration. There are three entities involved: 
Person, Department and Registration. So the possible points of contact are:

a) have Person expose its Age (to be used by Department or Registration)
b) have Person expose an IsOlderThan guard method/function (to be used 
by Department or Registration)
c) move the rule into Person
d) use the readmodel to inject the age via command to the Department or 
Registration
e) have person provide the age

If we are within one context, I personally rule out option d. The reason 
is that coupling via readmodel is even worse than exposing a property. 
It still couples but the coupling is hidden. This is a bit different if 
we are talking about separate contexts, there the age information would 
simple become part of the external interface. But within one context, 
using the readmodel kindof defeats the idea of having a domain model.

So I am left with three options within the domain model. All of them 
couple Person and Department or Registration. Which I consider perfectly 
ok, what would be the point of having a domain model if the objects may 
not interact?
Hence, I will consider different principles in deciding what to choose. 
First, I like to look up Nicola et al’s Streamlined object modeling for 
such questions. The relevant passage is:

« A validation rule verifies a property value against a standard that is 
not dependent on the properties of the other potential collaborators. 
The object with the clearest access to the standard is the owner of the 
collaboration rule. » [SOM p55]
This seems to be either the Department or the Registration, depending on 
formulation of requirements. At any rate this immediately rules out 
option c. Person must not know about rules of the department.

Now SOM make heavy use of getters and setters. They would go for option 
a. Since I instead intend to follow TDA, I cannot use a, which leaves me 
with b or e.
But, actually IsOlderThan is a getter, too. And here I differ from 
Ernst’s implementation. So I play the TDA card again and dismiss option 
b as just a copy of a.

Which leaves me with my desired strategy « have person provide the age ». 
But to whom? Now the registration is the entity mediating the 
collaboration, therefore I suggest that Person checks its own rules, 
department checks its own rules and registration checks the rules 
governing the collaboration. Since they are potentially complex, I 
actually move them into a factory.

In (pseudo)code:

Department{
     RegisterPerson(Person person){
         GuardIfICanRegisterAtAll();

         var registration = new RegistrationFactory();
         registration.SetDepartment(this);

         person.RequestRegistration(registration);

         registration.Do();
     }
}

Person{
     RequestRegistration(RegistrationFactory registration){
         GuardIfICanRegisterAtAll();
         registration.SetPerson(this, this._age);
     }
}

RegistrationFactory{
     SetDepartment(Department department){
         _department = department.Id;
     }
     SetPerson(Person person, Age age){
         _person = person.Id;
         _age = age;
     }

     Do(){
         EnsurePreconditions();
         AgeRule();
         new Registration(RegistrationHandle.Create(), _department, 
_person); // which in turn emits the appropriate event which is 
collected by a UoW.
     }

     EnsurePreconditions(){
         if (_department==null) throw new 
RegistrationFailedDueToInternalError(« Department not provided »);
         if (_person==null) throw new 
RegistrationFailedDueToInternalError(« Person not provided »);
         if (_age==null) throw new 
RegistrationFailedDueToInternalError(« Age not provided »);
     }

     AgeRule(){
         if (_age < Age.FromYears(25)) throw new 
RegistrationFailedPersonTooYoung();   // <– here is the thingy
     }
}

and the command handler

Handle(RegisterPersonInDepartment cmd)
{
        var person = repository.Get<Person>(cmd.PersonId);
        var department = repository.Get<Department>(cmd.DepartmentId);

        department.RegisterPerson(person);
}

(using cqrs/event sourcing with if-it-doesn’t-throw-it-succeeds 
semantics and auto-registration of the dirty aggregate)

This way, encapsulation is preserved, TDA is observed and the rules are 
with their respective owners. Requiring person to provide the age to the 
registration factory is fine – this simply is part of the factory’s 
contract. Person is of course free to deny the collaboration.

 

If we are within one context, I personally rule out option d. The reason is that coupling via readmodel is even worse than exposing a property.

Why?

Glad you asked 🙂

I have five issues with using the readmodel.

#1 is that I believe the business logic of one bounded context using a domain model should be fully encapsulated in that domain model. That includes the interactions and collaborations. Having a path – even it is only for data access – outside of the domain model for the sole purpose of not explicitly accessing that data within the model doesn’t make a lot of sense to me.

#2 is testing. I want to be able to test any domain by itself. And that includes the collaborations which are usually an important part of the model’s behavior. But I do *not* want to have my read model projections be a part of that. 

#3: I once learned that all dependencies in which a domain model participates should point inward to the domain model. This includes both afferent and efferent ones. Having to depend on the readmodel violates that.

#4: Let me ask, what is the reason to invoke the readmodel? Usually I hear « aggregates should not depend on each other ». Frankly, that is nonsense. A domain model consists of classes that of course have interdependencies. While good model design tries to reduce and qualify these dependencies, having *none* is not of value by itself. An aggregate’s fence doesn’t say ‘no access’ but rather ‘take me as a unit or not at all’ to other aggregates.

#5 is that I did it. That part of the system became a black hole pulling more and more of first data and then logic away from the domain model. Over the course of two years it gradually shifted from « nice shortcut » to « nightmare ».

Of course, one can do it, and it will work. But successfully only for some time or for non-complex systems or for systems that do not change. I.o.w. for systems that do not really call for a domain model 🙂

A domain model’s purpose is to encapsulate business logic. The choice between using a read-model or another AR doesn’t change that.

I feel that this statement is incorrect. « Using a read-model » breaks the encapsulation of the domain model as the readmodel is outside of its boundaries. « Another AR » is within these boundaries.

Seems fine. I would have chosen a class design that would avoid having invalid states, to avoid EnsurePreconditions, but beyond that the approach seems reasonable, but perhaps overkill? I’m surprised Yves doesn’t call it « all that » 🙂

Yes, honestly I didn’t put much thought to that factory. There are possibly ways to do that better 🙂

Publié par : yoannr | 30 août 2012

Learning EDA/CQRS/ES- Abstraction, commands and events

In order not to redo things several times I wanted to factorize common stuff. For Instance, let’s say we have a cow and a horse. The cow produces milk, the horse runs fast, but both eat grass.

public class Herbivorous
{
   protected int Quantity;

   public void EatGrass(int quantity)
   {
      var evt= Build.GrassEaten
                    .WithQuantity(quantity);
      RaiseEvent(evt);
   }

   public void Apply(GrassEaten evt)
   {
       _quantity= evt.Quantity;
   }
}

public class Horse : Herbivorous
{
   private bool _HasFastRun;

   public void RunFast()
   {
      var evt= Build.FastRun;
      RaiseEvent(evt);
   }

   public void Apply(FastRunevt)
   {
       _HasFastRun= true;
   }
}

public class Cow: Herbivorous
{
   private bool _IsMilkProduced;

   public void ProduceMilk()
   {
      var evt= Build.MilkProduced;
      RaiseEvent(evt);
   }

   public void Apply(MilkProduced evt)
   {
       _IsMilkProduced= true;
   }
}

To eat Grass, my application receives a command in Json or xml, or whatever that deserialise into this class:

namespace Herbivorous
{
   public class EatGrass : CommandBase
   {
      public Guid IdHerbivorous {get; set;}
      public Guid CommitId {get; set;}
      public long Version {get; set;}
      public int Quantity {get; set;}
   }
}

The command handler should be :

public class EatGrassHandler : CommandHandler
{
   public override CommandValidation Execute(EatGrass cmd)
   {
      Contract.Requires(cmd != null);
      Herbivorous herbivorous= EventRepository.GetById(cmd.Id);
      if (herbivorous.IsNull())
         throw new AggregateRootInstanceNotFoundException();
      herbivorous.EatGrass(cmd.Quantity);
      EventRepository.Save(herbivorous, cmd.CommitId);
   }
}

so far so good. I get a Herbivorous object , I have access to its EatGrass function, whether it is a horse or a cow doesn’t matter really. The only problem is here :

EventRepository.GetById(cmd.Id)

Indeed, let’s imagine we have a cow that has produced milk during the morning and now wants to eat grass. The EventRepository contains an event MilkProduced, and then come the command EatGrass. With the CommandHandler, we are no longer in the presence of a cow and the herbivorious doesn’t know anything about producing milk . what should it do?

Should I have explicit contextual command like :

namespace Herbivorous.Cow
    {
       public class EatGrass : CommandBase
       {
          public Guid IdHerbivorous {get; set;}
          public Guid CommitId {get; set;}
          public long Version {get; set;}
          public int Quantity {get; set;}
       }
       public class ProduceMilk : CommandBase
       {
          public Guid IdHerbivorous {get; set;}
          public Guid CommitId {get; set;}
          public long Version {get; set;}
       }
    }

This would mean that the external component that asks my herbivorous to eat grass should know that in this bounded context we talk about a cow. in the former command, we were talking about a general Herbivorous behavior, so the context of my call was not important. We knew that we needed herbivorous to eat grass that was all.

This is my main problem possible leakage of domain specific from one bounded context to another. Actually It might mean too that I cannot support abstraction when working on interfaces between several applications. I wonder…

Or I could just accept any event when rebuilding my herbivorous. It means that if it does not find a method to apply this event to, he will goes just fine trying to apply the next one of its stream. This is the real simple solution, but it does not put me at ease to know that events might not be applied ( and not producing errors) when rehydrating my object.

One tough problem that has been on  my arms for the last month is how to deal with the ordering of events within on single bc and between different bcs communicating together. I am using RabbitMq and learning the abc of messaging with it.  I might missing some magic ( who said basic? 🙂 ) tricks, and this is a post to order a bit more my own thoughts.

My application is composed of different bounded contexts:

  • Schedule
  • Contractor
  • Control
  • Accounting

Let’s assume Schedule generate an event  InterventionScheduled :

This event goes into the event store and then get publish eventually. That goes fine , usually some denormalizer waits in the dark to get the event and denormalize it into the read database.

My problem arose when two events are fired that should have order between them. Let’s call them e1 and e2. For sake of clarity e1 should be denormalized before e2.

public class EventProjection : IEventHandler<e1>,
                               IEventHandler<e2>;
{
   public void Handle(e1 @event)
   {
      //Denormalize e1 in the database
   }
   public void Handle(e2 @event)
   {
      //Denormalize e1 in the database
   }
}

First attempt : We have two queues queue_e1, queue_e2. The two events are arriving very fast and so are present into those two queues at the same time.

Projection might get the event in queue_e2 and thus denormalize e2 before getting to e1. This can lead to problems , error, inconsistent state and so on.. not so good.. or so it appears at first glance.

second attempt : What if I had only one queue per bounded context, this way I could guarantee the order of processing of my events.

public class ProjectionHandlerService : ProjectionHandlerServiceBase</pre>
{
   public override void Subscribe(IBus bus)
   {
      bus.Subscribe<e1>(Execute);
      bus.Subscribe<e2>(Execute);
   }
}
<pre>

This does not resolve our issue though the events are now in the correct order. The subscribing to event to a single queue creates a round robin cycle.

I could create a queue per projection and then directly bind my projection to eat the event coming from this queue.  but that means that, the knowledge of my projection are leaking into the messaging system.

So back to square one, what is the real business impact of having different queues for each event. I would say that the percentage of chance of having two events not following the same order should low enough.  Not all events have to behave this way. Most of them are simple events not having anything to do with one another, or they might but their  creation are spread upon hours, not milliseconds.

I always have the possibility to know when there is a problem and solve it in a casual way. So I will come back to my first attempt with easynetQ which was working fine, and deal  with errors when (if ?)  they come…


					
Publié par : yoannr | 18 juillet 2012

Learning EDA/CQRS/ES- playing with the User Interface

I have started 6-7 month ago , a project of mine to study domain driven design and CQRS. This I do on my free time, so progress are slow. But the good thing is that between two development phases I have time to think. It can make things even slower because quite often, I decide to redo everything from scratch.

I am not going to dive into every little things I have been since the last months. May be I will, but here I just want to write down the past research I have been doing.

Since I started doing, CQRS and DDD, I tried to unlearn everything I have learnt so far, and put that knowledge to test.  Besides, I quite often have understood things « à la CQRS » to be very simple and learnt afterwards that I was being misleaded.

For instance, I thought that once you had your bunch of command , everything you had to do was to send them your UI and then back again to be processed by one command handler. But it doesn’t work that way.  You command might have mandatory fields that are not initialized. For instance,  description of an item is to be empty, but is mandatory when the user does the action DoStuff.  Other stuff might be part part of the UI responsible to produce an command, but have nothing to do with the command in itself ( example list of items to populate a drop down list, the command is only interested in the  value selected).

So far, I have find this path only to deal with the UI.

For my specific purpose, I am on my way to divide my interface between a general interface based on a projection for instance Stuff, and little task based user interface dedicated to do one thing like DoStuff.

The projection based interface undergo the following flow :

Projection
-> Stuff (c# object)  MVC Model
-> Json generated by MVC
-> Stuff  (object javascript, push array)
-> template Html

the only task  this view can do is a filter on the projection, to do this , one command is created in javascript and then send via Json to the MVC controller which return another bunch of Stuff object via the same way.

Whereas the task based ui is like this :

Projection
->Stuff (c# object)  MVC Model
-> Mvc strong typed view

and the generation of the command is as follows :

pageHtml, javascript
-> command javascript ( & validation)
-> json
-> deserialised by ASP.net mvc to give command (from the actual commandProject)

Thus, to Execute a specific command, I have so far :

  • C# MVC model for the task base UI
  • Javascript command (with command validation)
  • C# Command of the command Project (with command validation )

The command is present in 3 different places. I would like to reduce this number of model but I do not know yet how.

The javascript validation is in two parts :

  1.  validation supporting interaction with the user of the web interface
  2. validation of the command in javascript in order to verify that the object created was complete

The C# command should be created via a builder. This builder uses the constructor that ensures the command to be in a valid state.  I am using right now, an automatic (and by default) creation of the command and therefore it uses a ctor(), so no invariants will be checked , this is to be done in the near future.

 

Publié par : yoannr | 1 novembre 2011

Introduction à CQRS/ES

CQRS est un acronyme pour Command Query Responsibility Segregation.C’est à dire la séparation de  responsabilité des commandes et des query. Le principe est très simple et peut être enonce comme : Poser une question ne doit pas changer la réponse.  

Voici à quoi ressemble une application classique :
application classique

Le domaine va  devoir gérer aussi bien l’entrée d’information que la sortie. Or comme vous le savez, les requêtes de sorties sont souvent quelque chose de compliqué. Assez souvent on a besoin d’utiliser et de créer des objects qui ne serviront qu’a un usage bien particulier. Or le domaine doit gérer ces particularismes et les intégrer dans ses pérégrinations.

C’est la qu’intervient CQRS, en séparant les commandes des query on arrive a un schema plus simple car bien delimite :
Command sous CQRS

Le client envoie une commande (l’équivalent du DTO du schema classique) à l’application. Cette command correspond a un fonction void. Si je prends l’exemple d’une application twitter, on pourrait avoir une comand comme ceci :

 public class PostNewTweetCommand : CommandBase
    {
        public string Message { get; set; }
        public string Who { get; set; }
    }
L’application reçoit cette Command et sait la traiter. En fonction des règles de validation et de disponibilité de l’infrastructure (réseau, ..) on peut imaginer que cette action sera un succès ou entraînera une exception. Au client de gérer cet état de fait.
Le cote query est quant à lui gérer séparement via une interface très légère qui s’occupe d’optimiser les query.

CQRS coté query

La database est attaquée directement par la couche mince d’accès aux données comme par exemple linq2sql ou  autre chose d’encore plus léger, et permet ainsi une optimisation des query sans alourdir le cote command.

Credits pour les exemples et les images :

http://www.dddcqrs.com par Greg Young

le projet framework nCQRS


Publié par : yoannr | 26 septembre 2011

FlexBox according to my needs

In order to share some tought, this post will be in english , sorry for french readers.

I needed to change the Jquery Flexbox to get a select that could be filtered like a textbox without removing any items from the list if the little arrow on the right was clicked.

I had to change the FlexBox Jquery script in order to get this. (full source at the end of this article if the link does not work anymore)

 

 

And then I packed it up in an usercontrol ASCX in order to ghet my control ready for my use :

page aspx :

<%@ControlLanguage= »C# »AutoEventWireup= »true »CodeBehind= »AutoCompleteDropDownList.ascx.cs »

    Inherits= »Super.Web.userControl.AutoCompleteDropDownList »%>

 

<divrunat=serverid=C_DDL_AutoComplete></div>

 

<scripttype= »text/javascript »>

 

    jQuery.noConflict()(function($){

        $(document).ready(function() {

            $(‘#<%= C_DDL_AutoComplete.ClientID  %>).flexbox({

                « results »: <%= JsonDataSource %>,

                 « total »: <%= JsonDataSourceTotal %>

            }, {

                watermark : <%= WaterMark %>,

                width: <%= Width %>,

                allowInput: <%= AutoComplete.ToString().ToLower() %>,

                paging : false,

                onSelect: function() { $(‘#<%= C_Hid_SelectedValue.ClientID %>).val(this.value);}  //onselect

               }); //flexbox

             var idSelected = $(‘#<%= C_Hid_SelectedValue.ClientID %>).val();

             $(‘#<%= C_DDL_AutoComplete.ClientID  %>).setIdValue(idSelected);

 

             Data= <%= JsonDataSource %>;

             DataJson = $.parseJSON(Data);

             for(index=0;index<DataJson.length;index++)

             {

                if(DataJson[index].id==idSelected)

                {

                    $(‘#<%= C_DDL_AutoComplete.ClientID  %>).setValue(DataJson[index].name);

                    break; ;

                }

             }

            

            

        });//ready

       

    });//No conflict

  

</script>

 

<asp:HiddenFieldrunat=serverID=C_Hid_SelectedValue/>

and its code behind :

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using Super.Core;

 

namespace Super.Web.userControl

{

 

    publicpartialclassAutoCompleteDropDownList : UserControl

    {

        privateint _Width = 350;

        privatebool _AutoComplete = true;

 

        protectedstring JsonDataSource;

        protectedstring JsonDataSourceTotal;

 

 

        public  IEnumerable<EntitaVisualizzazione> DataSource;

        ///<summary>

        /// [{ « id »: « myid » , « name » : « myName » },

        /// { « id »: « myid2 » , « name » : « myName2 » },

        /// { « id »: « myid3 » , « name » : « myName3 » }]

        ///</summary>

        publicstring DataSourceJsonClient { get; set; }

        ///<summary>

        /// Number of elements provided in DataSourceJsonClient

        ///</summary>

        publicint? DataSourceJsonClientTotal { get; set; }

        publicint Width

        {

            get { return _Width; }

            set { _Width = value; }

        }

        publicbool AutoComplete

        {

            get { return _AutoComplete; }

            set { _AutoComplete = value; }

        }

        publicstring WaterMark { get; set; }

        publicstring SelectedValue

        {

            get { return C_Hid_SelectedValue.Value; }

            set { C_Hid_SelectedValue.Value=value; }

        }

 

 

        protectedoverridevoid OnPreRender(EventArgs e)

        {

            base.OnPreRender(e);

 

            JsonDataSource = string.Empty;

            if (!string.IsNullOrEmpty(DataSourceJsonClient) && DataSource != null)

            {

                thrownewException(« DataSourceJson and DataSource cannot be filled at the same time »);

            }

 

 

            if (!string.IsNullOrEmpty(DataSourceJsonClient))

            {

                if (!DataSourceJsonClientTotal.HasValue)

                    thrownewException(« DataSourceJsonClientTotal must have a value, when DataSourceJsonClient is filled »);

 

                if (!string.IsNullOrEmpty(SelectedValue))

                {

                    if (!DataSourceJsonClient.ToLower().Contains(string.Format( » id : ‘{0}' »,SelectedValue).ToLower()))

                        thrownewException(string.Format(« The SelectedValue ‘{0}’ was not found in the Items of the DataSourceJsonClient », SelectedValue));

                }

 

                JsonDataSource = DataSourceJsonClient;

                JsonDataSourceTotal = DataSourceJsonClientTotal.Value.ToString();

            }

            else

            {

                if (DataSource != null)

                {

                    if (!string.IsNullOrEmpty(SelectedValue))

                    {

                        if (!DataSource.Any(x => x.Id == SelectedValue))

                            thrownewException(string.Format(« The SelectedValue ‘{0}’ was not found in the Items of the Datasource », SelectedValue));

                    }

 

                    JsonDataSource = « [« ;

                    foreach (IEntitaVisualizzazione entita in DataSource)

                    {

                        JsonDataSource += string.Format(@ »{{ «  »id » »: «  »{0} » », «  »name » »: «  »{1} » » }}, », entita.Id, entita.Descrizione);

                    }

                    if (!string.IsNullOrEmpty(JsonDataSource))

                        JsonDataSource = JsonDataSource.Substring(0, JsonDataSource.Length – 1);

 

                    JsonDataSource += « ] »;

 

                    JsonDataSourceTotal = DataSource.Count().ToString();

 

                }

            }

           

        }

    }

}

and here is my final use in a standard aspx page :

<Super:AutoCompleteDropDownList runat= »server » ID= »C_DDL_Lotti »
DataSource= »<%# Entities %> »></Super:AutoCompleteDropDownList>

and its code behind :

protected IEnumerable<MyEntity> Entities;

Entities = GetMyListFromDatabse()
.Select(x => new MyEntity()
{
Id = x.Id.ToString(),
Descrizione = x.DescrizioneComplete
});

 

 

FlexBox Jquery script :

/*!

* jQuery FlexBox $Version: 0.9.6 $

*

* Copyright (c) 2008-2010 Noah Heldman and Fairway Technologies (http://www.fairwaytech.com/flexbox)

* Licensed under Ms-PL (http://www.codeplex.com/flexbox/license)

*

* $Date: 2010-11-24 01:02:00 PM $

* $Rev: 0.9.6.1 $

*/

(function ($) {

$.flexbox = function (div, o) {

 

// TODO: in straight type-ahead mode (showResults: false), if noMatchingResults, dropdown appears after new match

// TODO: consider having options.mode (select, which replaces html select; combobox; suggest; others?)

// TODO: on resize (at least when wrapping within a table), the arrow is pushed down to the next line

// TODO: check for boundary/value problems (such as minChars of -1) and alert them

// TODO: add options for advanced paging template

// TODO: general cleanup and refactoring, commenting

// TODO: detailed Exception handling, logging

// TODO: FF2, up arrow from bottom has erratic scroll behavior (if multiple flexboxes on page)

// TODO: FF2 (and maybe IE7): if maxVisibleRows == number of returned rows, height is a bit off (maybe set to auto?)

// TODO: escape key only works from input box (this might be okay)

// TODO: make .getJSON parameters (object and callback function) configurable (e.g. when calling yahoo image search)

// TODO: escape key reverts to previous value (FF only?) (is this a good thing?)

 

// TEST: highlightMatches uses the case of whatever you typed in to replace the match string, which can look funny

// TEST: handle pageDown and pageUp keys when scrolling through results

// TEST: allow client-side paging (return all data initially, set paging:{pageSize:#}, and ensure maxCacheBytes is > 0)

// TEST: accept json object as first parameter to flexbox instead of page source, and have it work like a combobox

// TEST: implement no results template

// TEST: implement noResultsText and class

// TEST: watermark color should be configurable (and so should default input color)

// TEST: exception handling and alerts for common mistakes

// TEST: first example should use defaults ONLY

// TEST: add property initialValue, so you can set it when the flexbox loads

// TEST: handle hidden input value for form submissions

// TEST: how can we allow programmatically setting the field value (and therefore hidden value).  add jquery function?

// TEST: use pageSize parameter as threshold to switch from no paging to paging based on results

// TEST: if you type in an input value that matches the html, it might display html code (try typing « class » in the input box)

// TEST: don’t require all paging subprops (let default override)

// TEST: when tabbing from one ffb to another, the previous ffb results flash…

// TEST: IE7: when two non-paging ffbs right after each other, with only a clear-both div between them, the bottom ffb jumps down when selecting a value, then jumps back up on mouseover

// TEST: FF2, make sure we scroll to top before showing results (maxVisibleRows only)

// TEST: if maxVisibleRows is hiding the value the user types in to the input, scroll to that value (is this even possible?)

// TEST: make sure caching supports multiple ffbs uniquely

// TEST: when entering a number in the paging input box, the results are displayed twice

 

var timeout = false,      // hold timeout ID for suggestion results to appear

cache = [],                   // simple array with cacheData key values, MRU is the first element

cacheData = [],         // associative array holding actual cached data

cacheSize = 0,                // size of cache in bytes (cache up to o.maxCacheBytes bytes)

delim = ‘\u25CA’,       // use an obscure unicode character (lozenge) as the cache key delimiter

scrolling = false,

pageSize = o.paging && o.paging.pageSize ? o.paging.pageSize : 0,

retrievingRemoteData = false,

$div = $(div).css(‘position’, ‘relative’).css(‘z-index’, 0);

 

// The hiddenField MUST be appended to the div before the input, or IE7 does not shift the dropdown below the input field (it overlaps)

var $hdn = $(‘<input type= »hidden »/>’)

.attr(‘id’, $div.attr(‘id’) + ‘_hidden’)

.attr(‘name’, $div.attr(‘id’))

.val(o.initialValue)

.appendTo($div);

var $input = $(‘<input/>’)

.attr(‘id’, $div.attr(‘id’) + ‘_input’)

.attr(‘autocomplete’, ‘off’)

.addClass(o.inputClass)

.css(‘width’, o.width + ‘px’)

.appendTo($div)

.click(function (e) {

if (o.watermark !==  » && this.value === o.watermark)

this.value =  »;

else

this.select();

})

.focus(function (e) {

$(this).removeClass(‘watermark’);

})

.blur(function (e) {

if (this.value ===  ») $hdn.val( »);

setTimeout(function () { if (!$input.data(‘active’)) hideResults(); }, 200);

})

.keydown(processKeyDown);

 

if (o.initialValue !==  »)

$input.val(o.initialValue).removeClass(‘watermark’);

else

$input.val(o.watermark).addClass(‘watermark’);

 

var arrowWidth = 0;

if (o.showArrow && o.showResults) {

var arrowClick = function () {

if ($ctr.is(‘:visible’)) {

hideResults();

}

else {

$input.focus();

if (o.watermark !==  » && $input.val() === o.watermark)

$input.val( »);

else

$input.select();

if (timeout)

clearTimeout(timeout);

timeout = setTimeout(function () { flexbox(1, true, o.arrowQuery); }, o.queryDelay);

}

};

var $arrow = $(‘<span></span>’)

.attr(‘id’, $div.attr(‘id’) + ‘_arrow’)

.addClass(o.arrowClass)

.addClass(‘out’)

.hover(function () {

$(this).removeClass(‘out’).addClass(‘over’);

}, function () {

$(this).removeClass(‘over’).addClass(‘out’);

})

.mousedown(function () {

$(this).removeClass(‘over’).addClass(‘active’);

})

.mouseup(function () {

$(this).removeClass(‘active’).addClass(‘over’);

})

.click(arrowClick)

.appendTo($div);

arrowWidth = $arrow.width();

$input.css(‘width’, (o.width – arrowWidth) + ‘px’);

}

if (o.selectBehavior)

{ o.selectFirstMatch = false; $input.click(arrowClick); } // simulate <select> behavior

 

// Handle presence of CSS Universal Selector (*) that defines padding by verifying what the browser thinks the outerHeight is.

// In FF, the outerHeight() will not pick up the correct input field padding

var inputPad = $input.outerHeight() – $input.height() – 2;

var inputWidth = $input.outerWidth() – 2;

var top = $input.outerHeight();

 

if (inputPad === 0) {

inputWidth += 4;

top += 4;

}

else if (inputPad !== 4) {

inputWidth += inputPad;

top += inputPad;

}

 

var $ctr = $(‘<div></div>’)

.attr(‘id’, $div.attr(‘id’) + ‘_ctr’)

.css(‘width’, inputWidth + arrowWidth)

.css(‘top’, top)

.css(‘left’, 0)

.addClass(o.containerClass)

.appendTo($div)

.mousedown(function (e) {

$input.data(‘active’, true);

})

.hide();

 

var $content = $(‘<div></div>’)

.addClass(o.contentClass)

.appendTo($ctr)

.scroll(function () {

scrolling = true;

});

 

var $paging = $(‘<div></div>’).appendTo($ctr);

$div.css(‘height’, $input.outerHeight());

 

function processKeyDown(e) {

// handle modifiers

var mod = 0;

if (typeof (e.ctrlKey) !== ‘undefined’) {

if (e.ctrlKey) mod |= 1;

if (e.shiftKey) mod |= 2;

} else {

if (e.modifiers & Event.CONTROL_MASK) mod |= 1;

if (e.modifiers & Event.SHIFT_MASK) mod |= 2;

}

// if the keyCode is one of the modifiers, bail out (we’ll catch it on the next keypress)

if (/16$|17$/.test(e.keyCode)) return; // 16 = Shift, 17 = Ctrl

 

var tab = e.keyCode === 9, esc = e.keyCode === 27;

var tabWithModifiers = e.keyCode === 9 && mod > 0;

var backspace = e.keyCode === 8; // we will end up extending the delay time for backspaces…

 

// tab is a special case, since we want to bubble events…

if (tab) if (getCurr()) selectCurr();

 

// handling up/down/escape/right arrow/left arrow requires results to be visible

// handling enter requires that AND a result to be selected

if ((/27$|38$|33$|34$/.test(e.keyCode) && $ctr.is(‘:visible’)) ||

(/13$|40$/.test(e.keyCode)) || !o.allowInput) {

 

if (e.preventDefault) e.preventDefault();

if (e.stopPropagation) e.stopPropagation();

 

e.cancelBubble = true;

e.returnValue = false;

 

switch (e.keyCode) {

case 38: // up arrow

prevResult();

break;

case 40: // down arrow

if ($ctr.is(‘:visible’)) nextResult();

else flexboxDelay(true);

break;

case 13: // enter

if (getCurr()) selectCurr();

else flexboxDelay(true);

break;

case 27: //   escape

hideResults();

break;

case 34: // page down

if (!retrievingRemoteData) {

if (o.paging) $(‘#’ + $div.attr(‘id’) + ‘n’).click();

else nextPage();

}

break;

case 33: // page up

if (!retrievingRemoteData) {

if (o.paging) $(‘#’ + $div.attr(‘id’) + ‘p’).click();

else prevPage();

}

break;

default:

if (!o.allowInput) { return; }

}

} else if (!esc && !tab && !tabWithModifiers) { // skip esc and tab key and any modifiers

flexboxDelay(false, backspace);

}

}

 

function flexboxDelay(simulateArrowClick, increaseDelay) {

if (timeout) clearTimeout(timeout);

var delay = increaseDelay ? o.queryDelay * 5 : o.queryDelay;

timeout = setTimeout(function () { flexbox(1, simulateArrowClick,  »); }, delay);

}

 

function flexbox(p, arrowOrPagingClicked, prevQuery) {

if (arrowOrPagingClicked) prevQuery =  »;

var q = prevQuery && prevQuery.length > 0 ? prevQuery : $.trim($input.val());

 

if (q.length >= o.minChars || arrowOrPagingClicked) {

// If we are getting data from the server, set the height of the content box so it doesn’t shrink when navigating between pages, due to the $content.html( ») below…

if ($content.outerHeight() > 0)

$content.css(‘height’, $content.outerHeight());

$content.html( »).attr(‘scrollTop’, 0);

 

var cached = checkCache(q, p);

 

if ((cached && !arrowOrPagingClicked && o.selectBehavior) || (cached && !o.selectBehavior)) {

$content.css(‘height’, ‘auto’);

displayItems(cached.data, q);

showPaging(p, cached.t);

}

else {

var params = { q: q, p: p, s: pageSize, contentType: ‘application/json; charset=utf-8’ };

var callback = function (data, overrideQuery) {

if (overrideQuery === true) q = overrideQuery; // must compare to boolean because by default, the string value « success » is passed when the jQuery $.getJSON method’s callback is called

var totalResults = parseInt(data[o.totalProperty]);

 

// Handle client-side paging, if any paging configuration options were specified

if (isNaN(totalResults) && o.paging) {

if (o.maxCacheBytes <= 0) alert(‘The « maxCacheBytes » configuration option must be greater\nthan zero when implementing client-side paging.’);

totalResults = data[o.resultsProperty].length;

 

var pages = totalResults / pageSize;

if (totalResults % pageSize > 0) pages = parseInt(++pages);

 

for (var i = 1; i <= pages; i++) {

var pageData = {};

pageData[o.totalProperty] = totalResults;

pageData[o.resultsProperty] = data[o.resultsProperty].splice(0, pageSize);

if (i === 1) totalSize = displayItems(pageData, q);

updateCache(q, i, pageSize, totalResults, pageData, totalSize);

}

}

else {

var totalSize = displayItems(data, q);

updateCache(q, p, pageSize, totalResults, data, totalSize);

}

showPaging(p, totalResults);

$content.css(‘height’, ‘auto’);

retrievingRemoteData = false;

};

if (typeof (o.source) === ‘object’) {

if ((!o.allowInput || o.selectBehavior) && arrowOrPagingClicked)

callback(o.source);

else

callback(filter(o.source, params));

 

}

else {

retrievingRemoteData = true;

if (o.method.toUpperCase() == ‘POST’) $.post(o.source, params, callback, ‘json’);

else $.getJSON(o.source, params, callback);

}

}

} else

hideResults();

}

 

function filter(data, params) {

var filtered = {};

filtered[o.resultsProperty] = [];

filtered[o.totalProperty] = 0;

var index = 0;

 

for (var i = 0; i < data[o.resultsProperty].length; i++) {

var indexOfMatch = data[o.resultsProperty][i][o.displayValue].toLowerCase().indexOf(params.q.toLowerCase());

if ((o.matchAny && indexOfMatch !== -1) || (!o.matchAny && indexOfMatch === 0)) {

filtered[o.resultsProperty][index++] = data[o.resultsProperty][i];

filtered[o.totalProperty] += 1;

}

}

if (o.paging) {

var start = (params.p – 1) * params.s;

var howMany = (start + params.s) > filtered[o.totalProperty] ? filtered[o.totalProperty] – start : params.s;

filtered[o.resultsProperty] = filtered[o.resultsProperty].splice(start, howMany);

}

return filtered;

}

 

function showPaging(p, totalResults) {

$paging.html( »).removeClass(o.paging.cssClass); // clear out for threshold scenarios

if (o.showResults && o.paging && totalResults > pageSize) {

var pages = totalResults / pageSize;

if (totalResults % pageSize > 0) pages = parseInt(++pages);

outputPagingLinks(pages, p, totalResults);

}

}

 

function handleKeyPress(e, page, totalPages) {

if (/^13$|^39$|^37$/.test(e.keyCode)) {

if (e.preventDefault)

e.preventDefault();

if (e.stopPropagation)

e.stopPropagation();

 

e.cancelBubble = true;

e.returnValue = false;

 

switch (e.keyCode) {

case 13: // Enter

if (/^\d+$/.test(page) && page > 0 && page <= totalPages)

flexbox(page, true);

else

alert(‘Please enter a page number between 1 and ‘ + totalPages);

// TODO: make this alert a function call, and a customizable parameter

break;

case 39: // right arrow

$(‘#’ + $div.attr(‘id’) + ‘n’).click();

break;

case 37: // left arrow

$(‘#’ + $div.attr(‘id’) + ‘p’).click();

break;

}

}

}

 

function handlePagingClick(e) {

flexbox(parseInt($(this).attr(‘page’)), true, $input.attr(‘pq’)); // pq == previous query

return false;

}

 

function outputPagingLinks(totalPages, currentPage, totalResults) {

// TODO: make these configurable images

var first = ‘&lt;&lt;’,

prev = ‘&lt;’,

next = ‘&gt;’,

last = ‘&gt;&gt;’,

more = ‘…’;

 

$paging.addClass(o.paging.cssClass);

 

// set up our base page link element

var $link = $(‘<a/>’)

.attr(‘href’, ‘#’)

.addClass(‘page’)

.click(handlePagingClick),

$span = $(‘<span></span>’).addClass(‘page’),

divId = $div.attr(‘id’);

 

// show first page

if (currentPage > 1) {

$link.clone(true).attr(‘id’, divId + ‘f’).attr(‘page’, 1).html(first).appendTo($paging);

$link.clone(true).attr(‘id’, divId + ‘p’).attr(‘page’, currentPage – 1).html(prev).appendTo($paging);

}

else {

$span.clone(true).html(first).appendTo($paging);

$span.clone(true).html(prev).appendTo($paging);

}

 

if (o.paging.style === ‘links’) {

var maxPageLinks = o.paging.maxPageLinks;

// show page numbers

if (totalPages <= maxPageLinks) {

for (var i = 1; i <= totalPages; i++) {

if (i === currentPage) {

$span.clone(true).html(currentPage).appendTo($paging);

}

else {

$link.clone(true).attr(‘page’, i).html(i).appendTo($paging);

}

}

}

else {

if ((currentPage + parseInt(maxPageLinks / 2)) > totalPages) {

startPage = totalPages – maxPageLinks + 1;

}

else {

startPage = currentPage – parseInt(maxPageLinks / 2);

}

 

if (startPage > 1) {

$link.clone(true).attr(‘page’, startPage – 1).html(more).appendTo($paging);

}

else {

startPage = 1;

}

 

for (var i = startPage; i < startPage + maxPageLinks; i++) {

if (i === currentPage) {

$span.clone(true).html(i).appendTo($paging);

}

else {

$link.clone(true).attr(‘page’, i).html(i).appendTo($paging);

}

}

 

if (totalPages > (startPage + maxPageLinks)) {

$link.clone(true).attr(‘page’, i).html(more).appendTo($paging);

}

}

}

else if (o.paging.style === ‘input’) {

var $pagingBox = $(‘<input/>’)

.addClass(‘box’)

.click(function (e) {

this.select();

})

.keypress(function (e) {

return handleKeyPress(e, this.value, totalPages);

})

.val(currentPage)

.appendTo($paging);

}

 

if (currentPage < totalPages) {

$link.clone(true).attr(‘id’, divId + ‘n’).attr(‘page’, +currentPage + 1).html(next).appendTo($paging);

$link.clone(true).attr(‘id’, divId + ‘l’).attr(‘page’, totalPages).html(last).appendTo($paging);

}

else {

$span.clone(true).html(next).appendTo($paging);

$span.clone(true).html(last).appendTo($paging);

}

var startingResult = (currentPage – 1) * pageSize + 1;

var endingResult = (startingResult > (totalResults – pageSize)) ? totalResults : startingResult + pageSize – 1;

 

if (o.paging.showSummary) {

var summaryData = {

« start »: startingResult,

« end »: endingResult,

« total »: totalResults,

« page »: currentPage,

« pages »: totalPages

};

var html = o.paging.summaryTemplate.applyTemplate(summaryData);

$(‘<br/>’).appendTo($paging);

$(‘<span></span>’)

.addClass(o.paging.summaryClass)

.html(html)

.appendTo($paging);

}

}

 

function checkCache(q, p) {

var key = q + delim + p; // use null character as delimiter

if (cacheData[key]) {

for (var i = 0; i < cache.length; i++) { // TODO: is it possible to not loop here?

if (cache[i] === key) {

// pull out the matching element (splice), and add it to the beginning of the array (unshift)

cache.unshift(cache.splice(i, 1)[0]);

return cacheData[key];

}

}

}

return false;

}

 

function updateCache(q, p, s, t, data, size) {

if (o.maxCacheBytes > 0) {

while (cache.length && (cacheSize + size > o.maxCacheBytes)) {

var cached = cache.pop();

cacheSize -= cached.size;

}

var key = q + delim + p; // use null character as delimiter

cacheData[key] = {

q: q,

p: p,

s: s,

t: t,

size: size,

data: data

}; // add the data to the cache at the hash key location

cache.push(key); // add the key to the MRU list

cacheSize += size;

}

}

 

function displayItems(d, q) {

var totalSize = 0, itemCount = 0;

 

if (!d)

return;

 

$hdn.val($input.val());

if (parseInt(d[o.totalProperty]) === 0 && o.noResultsText && o.noResultsText.length > 0) {

$content.addClass(o.noResultsClass).html(o.noResultsText);

$ctr.show();

return;

} else $content.removeClass(o.noResultsClass);

 

for (var i = 0; i < d[o.resultsProperty].length; i++) {

var data = d[o.resultsProperty][i],

result = o.resultTemplate.applyTemplate(data),

exactMatch = q === result,

selectedMatch = false,

hasHtmlTags = false,

match = data[o.displayValue];

 

if (!exactMatch && o.highlightMatches && q !==  ») {

var pattern = q,

highlightStart = match.toLowerCase().indexOf(q.toLowerCase()),

replaceString = ‘<span>’ + match.substr(highlightStart, q.length) + ‘</span>’;

if (result.match(‘<(.|\n)*?>’)) { // see if the content contains html tags

hasHtmlTags = true;

pattern = ‘(>)([^<]*?)(‘ + q + ‘)((.|\n)*?)(<)’; // TODO: look for a better way

replaceString = ‘$1$2<span>$3</span>$4$6’;

}

result = result.replace(new RegExp(pattern, o.highlightMatchesRegExModifier), replaceString);

}

 

// write the value of the first match to the input box, and select the remainder,

// but only if autoCompleteFirstMatch is set, and there are no html tags in the response

if (o.autoCompleteFirstMatch && !hasHtmlTags && i === 0) {

if (q.length > 0 && match.toLowerCase().indexOf(q.toLowerCase()) === 0) {

$input.attr(‘pq’, q); // pq == previous query

$hdn.val(data[o.hiddenValue]);

$input.val(data[o.displayValue]);

selectedMatch = selectRange(q.length, $input.val().length);

}

}

 

if (!o.showResults) return;

 

$row = $(‘<div></div>’)

.attr(‘id’, data[o.hiddenValue])

.attr(‘val’, data[o.displayValue])

.addClass(‘row’)

.html(result)

.appendTo($content);

 

if (exactMatch || (++itemCount == 1 && o.selectFirstMatch) || selectedMatch) {

$row.addClass(o.selectClass);

}

totalSize += result.length;

}

 

if (totalSize === 0) {

hideResults();

return;

}

 

$ctr.parent().css(‘z-index’, 11000);

$ctr.show();

 

$content

.children(‘div’)

.mouseover(function () {

$content.children(‘div’).removeClass(o.selectClass);

$(this).addClass(o.selectClass);

})

.mouseup(function (e) {

e.preventDefault();

e.stopPropagation();

selectCurr();

});

 

if (o.maxVisibleRows > 0) {

var maxHeight = $row.outerHeight() * o.maxVisibleRows;

$content.css(‘max-height’, maxHeight);

}

 

return totalSize;

}

 

function selectRange(s, l) {

var tb = $input[0];

if (tb.createTextRange) {

var r = tb.createTextRange();

r.moveStart(‘character’, s);

r.moveEnd(‘character’, l – tb.value.length);

r.select();

} else if (tb.setSelectionRange) {

tb.setSelectionRange(s, l);

}

tb.focus();

return true;

}

 

String.prototype.applyTemplate = function (d) {

try {

if (d ===  ») return this;

return this.replace(/{([^{}]*)}/g,

function (a, b) {

var r;

if (b.indexOf(‘.’) !== -1) { // handle dot notation in {}, such as {Thumbnail.Url}

var ary = b.split(‘.’);

var obj = d;

for (var i = 0; i < ary.length; i++)

obj = obj[ary[i]];

r = obj;

}

else

r = d[b];

if (typeof r === ‘string’ || typeof r === ‘number’) return r; else throw (a);

}

);

} catch (ex) {

alert(‘Invalid JSON property ‘ + ex + ‘ found when trying to apply resultTemplate or paging.summaryTemplate.\nPlease check your spelling and try again.’);

}

};

 

function hideResults() {

$input.data(‘active’, false); // for input blur

$div.css(‘z-index’, 0);

$ctr.hide();

}

 

function getCurr() {

if (!$ctr.is(‘:visible’))

return false;

 

var $curr = $content.children(‘div.’ + o.selectClass);

 

if (!$curr.length)

$curr = false;

 

return $curr;

}

 

function selectCurr() {

$curr = getCurr();

 

if ($curr) {

$hdn.val($curr.attr(‘id’));

$input.val($curr.attr(‘val’)).focus();

hideResults();

 

if (o.onSelect) {

o.onSelect.apply($hdn[0]);

}

}

}

 

function supportsGetBoxObjectFor() {

try {

document.getBoxObjectFor(document.body);

return true;

}

catch (e) {

return false;

}

}

 

function supportsGetBoundingClientRect() {

try {

document.body.getBoundingClientRect();

return true;

}

catch (e) {

return false;

}

}

 

function nextPage() {

$curr = getCurr();

 

if ($curr && $curr.next().length > 0) {

$curr.removeClass(o.selectClass);

 

for (var i = 0; i < o.maxVisibleRows; i++) {

if ($curr.next().length > 0) {

$curr = $curr.next();

}

}

 

$curr.addClass(o.selectClass);

var scrollPos = $content.attr(‘scrollTop’);

$content.attr(‘scrollTop’, scrollPos + $content.height());

}

else if (!$curr)

$content.children(‘div:first-child’).addClass(o.selectClass);

}

 

function prevPage() {

$curr = getCurr();

 

if ($curr && $curr.prev().length > 0) {

$curr.removeClass(o.selectClass);

 

for (var i = 0; i < o.maxVisibleRows; i++) {

if ($curr.prev().length > 0) {

$curr = $curr.prev();

}

}

 

$curr.addClass(o.selectClass);

var scrollPos = $content.attr(‘scrollTop’);

$content.attr(‘scrollTop’, scrollPos – $content.height());

}

else if (!$curr)

$content.children(‘div:last-child’).addClass(o.selectClass);

}

 

function nextResult() {

$curr = getCurr();

 

if ($curr && $curr.next().length > 0) {

$curr.removeClass(o.selectClass).next().addClass(o.selectClass);

var scrollPos = $content.attr(‘scrollTop’),

curr = $curr[0], parentBottom, bottom, height;

if (supportsGetBoxObjectFor()) {

parentBottom = document.getBoxObjectFor($content[0]).y + $content.attr(‘offsetHeight’);

bottom = document.getBoxObjectFor(curr).y + $curr.attr(‘offsetHeight’);

height = document.getBoxObjectFor(curr).height;

}

else if (supportsGetBoundingClientRect()) {

parentBottom = $content[0].getBoundingClientRect().bottom;

var rect = curr.getBoundingClientRect();

bottom = rect.bottom;

height = bottom – rect.top;

}

if (bottom >= parentBottom)

$content.attr(‘scrollTop’, scrollPos + height);

}

else if (!$curr)

$content.children(‘div:first-child’).addClass(o.selectClass);

}

 

function prevResult() {

$curr = getCurr();

 

if ($curr && $curr.prev().length > 0) {

$curr.removeClass(o.selectClass).prev().addClass(o.selectClass);

var scrollPos = $content.attr(‘scrollTop’),

curr = $curr[0],

parent = $curr.parent()[0],

parentTop, top, height;

if (supportsGetBoxObjectFor()) {

height = document.getBoxObjectFor(curr).height;

parentTop = document.getBoxObjectFor($content[0]).y – (height * 2); // TODO: this is not working when i add another control…

top = document.getBoxObjectFor(curr).y – document.getBoxObjectFor($content[0]).y;

}

else if (supportsGetBoundingClientRect()) {

parentTop = parent.getBoundingClientRect().top;

var rect = curr.getBoundingClientRect();

top = rect.top;

height = rect.bottom – top;

}

if (top <= parentTop)

$content.attr(‘scrollTop’, scrollPos – height);

}

else if (!$curr)

$content.children(‘div:last-child’).addClass(o.selectClass);

}

};

 

$.fn.flexbox = function (source, options) {

if (!source)

return;

 

try {

var defaults = $.fn.flexbox.defaults;

var o = $.extend({}, defaults, options);

 

for (var prop in o) {

if (defaults[prop] === undefined) throw (‘Invalid option specified: ‘ + prop + ‘\nPlease check your spelling and try again.’);

}

o.source = source;

 

if (options) {

o.paging = (options.paging || options.paging == null) ? $.extend({}, defaults.paging, options.paging) : false;

 

for (var prop in o.paging) {

if (defaults.paging[prop] === undefined) throw (‘Invalid option specified: ‘ + prop + ‘\nPlease check your spelling and try again.’);

}

 

if (options.displayValue && !options.hiddenValue) {

o.hiddenValue = options.displayValue;

}

}

 

this.each(function () {

new $.flexbox(this, o);

});

 

return this;

} catch (ex) {

if (typeof ex === ‘object’) alert(ex.message); else alert(ex);

}

};

 

// plugin defaults – added as a property on our plugin function so they can be set independently

$.fn.flexbox.defaults = {

source : [],

method: ‘GET’, // One of ‘GET’ or ‘POST’

queryDelay: 100, // num of milliseconds before query is run.

allowInput: true, // set to false to disallow the user from typing in queries

selectBehavior: true, // set to false to disallow the select behavior

containerClass: ‘ffb’,

contentClass: ‘content’,

selectClass: ‘ffb-sel’,

inputClass: ‘ffb-input’,

arrowClass: ‘ffb-arrow’,

matchClass: ‘ffb-match’,

noResultsText: ‘No matching results’, // text to show when no results match the query

noResultsClass: ‘ffb-no-results’, // class to apply to noResultsText

showResults: true, // whether to show results at all, or just typeahead

selectFirstMatch: true, // whether to highlight the first matching value

autoCompleteFirstMatch: false, // whether to complete the first matching value in the input box

highlightMatches: true, // whether all matches within the string should be highlighted with matchClass

highlightMatchesRegExModifier: ‘i’, // ‘i’ for case-insensitive, ‘g’ for global (all occurrences), or combine

matchAny: true, // for client-side filtering ONLY, match any occurrence of the search term in the result (e.g. « ar » would find « area » and « cart »)

minChars: 1, // the minimum number of characters the user must enter before a search is executed

showArrow: true, // set to false to simulate google suggest

arrowQuery:  », // the query to run when the arrow is clicked

onSelect: false, // function to run when a result is selected

maxCacheBytes: 32768, // in bytes, 0 means caching is disabled

resultTemplate: ‘{name}’, // html template for each row (put json properties in curly braces)

displayValue: ‘name’, // json element whose value is displayed on select

hiddenValue: ‘id’, // json element whose value is submitted when form is submitted

initialValue:  », // what should the value of the input field be when the form is loaded?

watermark:  », // text that appears when flexbox is loaded, if no initialValue is specified.  style with css class ‘.ffb-input.watermark’

width: 200, // total width of flexbox.  auto-adjusts based on showArrow value

resultsProperty: ‘results’, // json property in response that references array of results

totalProperty: ‘total’, // json property in response that references the total results (for paging)

maxVisibleRows: 0, // default is 0, which means it is ignored.  use either this, or paging.pageSize

paging: {

style: ‘input’, // or ‘links’

cssClass: ‘paging’, // prefix with containerClass (e.g. .ffb .paging)

pageSize: 10, // acts as a threshold.  if <= pageSize results, paging doesn’t appear

maxPageLinks: 5, // used only if style is ‘links’

showSummary: true, // whether to show ‘displaying 1-10 of 200 results’ text

summaryClass: ‘summary’, // class for ‘displaying 1-10 of 200 results’, prefix with containerClass

summaryTemplate: ‘Displaying {start}-{end} of {total} results’ // can use {page} and {pages} as well

}

};

 

$.fn.setValue = function (val) {

var id = ‘#’ + this.attr(‘id’);

var input = $(id + ‘_input’);

input.val(val).removeClass(‘watermark’);

};

 

$.fn.setIdValue = function (val) {

var id = ‘#’ + this.attr(‘id’);

$(id + « _hidden »).val(val);

};

})(jQuery);

Publié par : yoannr | 17 juin 2011

Gestion des Exceptions, Unit Testing, MsTests

J‘aimerai avoir une fonction de ce type la pour tester mon code :

static void Throws<T>(Action action, string expectedMessage) where T : Exception

Malheureusement celle ci n’existe pas dans la collection Assert. J ai donc vu de part le net qu’il me fallait créer ma propre classe MyAssert avec cette méthode. Et tant qu’à faire je me suis dit que j’allais le faire en TDD.

Throws<T> va contenir des accès direct  à Assert, et par conséquent sera difficilement testable (encore que maintenant que j’ècris ce lignes je trouve que je me suis compliquée la vie, mais bon… Sourire )

Je me susi donc créer cette classe :

public class AssertAnswer
        {
            public bool Success { get; set; }
            public string Message { get; set; }
        }

et un fonction qui contiendrait le corps de ma logique :

public static AssertAnswer AssertAction<T>(Action action, string expectedMessage) where T : Exception
{
}

C’est cette fonction que je compte tester. J’écris donc mes tests :

private void ThrowNullReferenceException(string msg)
      {
          throw new NullReferenceException(msg);
      }
private void DoNotThrowException(string msg)
      {
          //do nothing
      }

[TestMethod]
        public void MyAssert_AssertAction_Exception_Awaited()
        {
            string msg = "test";
            var actual = MyAssert.AssertAction<System.NullReferenceException>(() => ThrowNullReferenceException(msg), msg);

            Assert.AreEqual(true, actual.Success);
            Assert.AreEqual(msg, actual.Message);
        }

[TestMethod]
        public void MyAssert_AssertAction_Different_Exception()
        {
            string msg = "test";
            var actual = MyAssert.AssertAction<System.ArgumentException>(() => ThrowNullReferenceException(msg), msg);

            Assert.AreEqual(false, actual.Success);
            Assert.AreEqual("A different Exception was thrown System.NullReferenceException.", actual.Message);
        }

[TestMethod]
        public void MyAssert_AssertAction_No_Exception()
        {
            string msg = "test";
            var actual = MyAssert.AssertAction<System.ArgumentException>(() => DoNotThrowException(msg), msg);

            Assert.AreEqual(false, actual.Success);
            Assert.AreEqual("Exception of type System.ArgumentException should be thrown.", actual.Message);
        }

Ces trois tests couvrent l’ensemble des cas de figure qui peuvent se passer.

Il ne reste plus qu’à écrire le code  de la fonction :

public static AssertAnswer AssertAction<T>(Action action, string expectedMessage) where T : Exception
       {
           AssertAnswer answer = new AssertAnswer();

           try
           {
               action.Invoke();

               answer.Success = false;
               answer.Message = string.Format("Exception of type {0} should be thrown.", typeof(T));
           }
           catch (T exc)
           {
               answer.Success = true;
               answer.Message = expectedMessage;
           }
           catch (Exception e)
           {
               answer.Success = false;
               answer.Message = string.Format("A different Exception was thrown {0}.", e.GetType());
           }

           return answer;
       }

Mes tests passent je peux continuer , et revenir a mon besoin initial , en réécrivant d’autres tests pour vraiment tester la fonction que je compte appeler plus tard :

[TestMethod]
       public void MyAssert_Throws_Exception_Awaited()
       {
           string msg = "test";
           MyAssert.Throws<System.NullReferenceException>(() => ThrowNullReferenceException(msg), msg);
       }

       [TestMethod]
       public void MyAssert_Throws_Different_Exception()
       {
           try
           {
               string msg = "test";
               MyAssert.Throws<System.ArgumentException>(() => ThrowNullReferenceException(msg), msg);

               Assert.Fail();
           }
           catch (Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException e)
           {

           }
       }

       [TestMethod]
       public void MyAssert_Throws_No_Exception()
       {
           try
           {
               string msg = "test";
               MyAssert.Throws<System.ArgumentException>(() => DoNotThrowException(msg), msg);

               Assert.Fail();
           }
           catch (Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException e)
           {

           }
       }

 

et ensuite le code le fonction pour réussir ces tests :

public static void Throws<T>(Action action, string expectedMessage) where T : Exception
       {
           AssertAnswer answer = AssertAction<T>(action, expectedMessage);

           Assert.IsTrue(answer.Success);
           Assert.AreEqual(expectedMessage, answer.Message);
       }

 

et voila, ma fonction est prête à être utilisé dans d’autres tests. Maintenant , il est vrai que j’aurais pu m’épargner la création de la fonction AssertAction et tester directement le code de ma fonction, mais que voulez vous, il faut bien débuter un jour et faire quelques erreurs.. d’ailleurs si vous en voyez d’autres n’hésitez pas..

++

Publié par : yoannr | 30 mars 2010

Jstree , Json et IEnumerable & Extension Method…

Bonjour,

Je suis toujours à ma découverte de JQuery, et dernièrement je me suis frotté á la problématique du treeview avec Jquery. Il n’y a pas encore de tree officiel dans Jquery et j’ai opté pour un des arbres qui est présent sur la road map de JQuery, j’ai nommé jstree (http://www.jstree.com/).

Ce tree permet de faire tout ce dont j’ai envie, créer des nodes insérer, effectuer des postback, travailler sur du HTML, du Json. Bref il me semble vraiment complet et assez convivial dans son ergonomie.

La documentation est assez bien faite et elle m’a amené a vouloir développer mon arbre en prenant comme source du Json et en mode asynchrone. Pour ce faire rien de plus simple il y a un exemple dans la doc  que j’ai remanié pour avoir cela:

<script type= »text/javascript » class= »source »>
$(function()
{
$(« #MyTree »).tree(
{
data:
{
type: « json »,
async: true,
opts:
{
url: « /localisation/GetTree »
}
}
});
});
</script>
<div id= »MyTree »>
</div>

l’arbre “myTree”  prend en donnée du Json, de maniére asynchrone et sa source de donnée sera un appel À l’URL « /localisation/GetTree”. Localisation est evidemment le nom de mon controlleur et GetTree le nom de l’action associée:

public string GetTree(int? id)
{

Localisation localisation = CreateLocalisation();

//fetch all groups
localisation.Groups = servicelocalisation.GetLocalisationGroups();

string returnvalue = string.Empty;
returnvalue = localisation.Groups.ToJsTreeJson(g=>g.ID.Value, g=>g.Name, g=> g.ChildGroups);

return returnvalue;
}

Le service localisation va récupérer nos donnée et nous rendre une List<LocalisationGroup>  stockée dans localisation.Groups .

J’ai ensuite développé une extension méthode afin de transformer mon IEnumerable<T> en Json afin que mon jsTree puisse interpréter correctement ma liste de groupe.

Si on détaille cette extension méthode, on voit trois choses entrée en argument :

  • g=>g.ID.Value
  • g=>g.Name
  • g=> g.ChildGroups

Ces trois expression lambdas vont nous aider a retrouver nos informations dans un LocalisationGroup. Elles vont servir respectivement a retrouver l’identifiant de notre groupe, le nom de notre groupe, et les groupes enfant de notre groupe.

Passons de l’autre coté du miroir et regardons de plus prés cette extension method:

public static string ToJsTreeJson<TElement>(
this IEnumerable<TElement> elements,
Func<TElement, int> GetIDElement,
Func<TElement, string> GetDataElement,
Func<TElement, IEnumerable<TElement>> GetChildrenElements)
{
string returnvalue = string.Empty;

JsTree tree = new JsTree();
foreach (TElement element in elements)
{
tree.RootNodes.Add(CreateJstreeNode(element,GetIDElement, GetDataElement,GetChildrenElements));
}

returnvalue = tree.Serialize();

return returnvalue;
}

Les choses se compliquent? pas tant que ca…  C’est une extension méthode donc la liste de groupe que j’avais au départ est en fait passé en argument et nous allons la retrouver sous le nom éléments. Et que vois t’on? Que pour chaque élément de cette liste je vais ajouter un treenode à notre tree. Normal c’est ce que l’on veut faire. Et pour ce faire on utilise la méthode CreateJstreeNode.

Cette méthode prend en argument presque la même chose que notre extension methode:

private static  JsTreeNode CreateJstreeNode<TElement>(
TElement element ,
Func<TElement, int> GetIDElement,
Func<TElement, string> GetDataElement,
Func<TElement, IEnumerable<TElement>> GetChildrenElements)
{
JsTreeNode node = new JsTreeNode();

node.data = GetDataElement(element);
node.attributes = new jsAttribute();
node.attributes.id =  GetIDElement(element).ToString();
foreach (TElement e in GetChildrenElements(element))
{
node.children.Add(CreateJstreeNode(e,GetIDElement, GetDataElement, GetChildrenElements));
}

return node;
}

Sauf que au lieu de s’attacher à voir ce qui se passe dans une liste d’éléments, cette dernière ne s’intéresse qu’au noeud qu’on lui donne en argument. Et grâce aux fonctions données précédemment (g=>g.ID.Value, g=>g.Name, g=> g.ChildGroups), elle va être capable de comprendre comment décortiquer notre objet afin d’instancier un JsTreeNode .
Pour finir, cette méthode s’appelle récursivement afin de détailler tous les éléments enfants qui pourraient appartenir à un élément.

Pour chaque élément de notre liste cette méthode va être appelée et ensuite notre objet JsTree sera rempli. Il ne suffria plus alors qu’à lui demander de se sérialiser afin de produire le json correspondant.

Afin d’être exhaustif voici le modèle utilisé pour représenter mon jstree :

public class JsTree
{
delegate string GetData();
public IList<JsTreeNode> RootNodes { get; set; }

public JsTree()
{
RootNodes = new List<JsTreeNode>();
}

public string Serialize()
{
string returnvalue = « [« ;
foreach (JsTreeNode node in RootNodes)
{
returnvalue += JSONSerializer.Serialize<JsTreeNode>(node)+ », »;
}
if (returnvalue.Length > 1)
{
returnvalue = returnvalue.Substring(0, returnvalue.Length – 1);
}
returnvalue += « ] »;

return returnvalue;
}
}

public class jsAttribute
{
public string id { get; set; }
}

public class JsTreeNode
{
private List<JsTreeNode> _children;

public string data { get; set; }
public jsAttribute attributes { get; set; }
public List<JsTreeNode> children
{
get
{
if (_children == null)
_children = new List<JsTreeNode>();
return _children;
}
set
{
_children = value;
}
}
}

et voilà pour le Json Serializer :

public class JSONSerializer
{
public string unescapedUrl { get; set; }

public static T Deserialise<T>(string json)
{
T obj = Activator.CreateInstance<T>();
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
obj = (T)serializer.ReadObject(ms);
return obj;
}
}

public static string Serialize<T>(T obj)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
return Encoding.Default.GetString(ms.ToArray());
}
}
}

Comme toujours, si vous avez des questions ou des commentaires sur ma manière de procéder , ne vous gêner pas, ce blog est un peu fait pour ca!

a++

Publié par : yoannr | 15 juin 2009

Serialisation xml, les accésseurs

Dans le cas d’une banale désérialisation de fichier xml j’ai déclaré ma classe comme ceci :

public class Testimonials
{

private int _NumberOfTestimonial;
private List<Testimonial> _ListOfTestimonials;

[System.Xml.Serialization.XmlElement(« NumberOfTestimonial »)]
public int NumberOfTestimonial
{
get { return _NumberOfTestimonial; }
set { NumberOfTestimonial = value; }
}

[System.Xml.Serialization.XmlArray(« listOfTestimonials »)]
[System.Xml.Serialization.XmlArrayItem(« testimonial », typeof(Testimonial))]
public List<Testimonial> ListOfTestimonials
{
get { return _ListOfTestimonials; }
set { _ListOfTestimonials = value; }
}

public Testimonials()
{
}

}

et ceci afin d’illustrer le fichier xml suivant :

<testimonials>
<NumberOfTestimonial>2</NumberOfTestimonial>
<listOfTestimonials>
<testimonial >
<id>1</id>
<imageUrl>/images/testimonials/img_bodz.jpg</imageUrl>
<text>bla bla bla</text>
</testimonial>
<testimonial >
<id>2</id>
<imageUrl>/images/testimonials/img_coud.jpg</imageUrl>

<text><h3> Barbara</h3>Bla bla bla</text>
</testimonial>
</listOfTestimonials>
</testimonials>

lors du lancement de mon site , j’obtenais l’erreur suivante lors d’appel à ma fonction deserialize:

An unhandled exception of type ‘System.StackOverflowException’ occurred in App_Code.otapn_w9.dll

Cela provenait de :

set { NumberOfTestimonial = value; }

En effet, le set fait appel  au getteur, ce qui résulte en un System.StackOverflowException.

Correction faite :

public int NumberOfTestimonial
{
get { return _NumberOfTestimonial; }
set { _NumberOfTestimonial = value; }
}

tout se passe bien …

Older Posts »

Catégories