While working on this asp.net mvc project at work i couldn’t help notice that there are lots of code that is getting copy-pasted from one controller to the other, when i am adding a new controller. now as you know mvc fm comes with a set of T4 templates that lets you add views and controllers in such a way that some code in these views and controllers are generated for you. this is grate. but you can customize the original templates to your needs and make them generate more of the code for you. (please note that i am talking about controllers that are responsible for mostly CURD on a given single model.)

First of all you will need to identify what code gets repeated from controller to controller in your asp.net mvc project. and then you can get these stuff out in to a template and start working on it.

Locating the T4 Templates

you can find your original templates in [Visual Studio Install Directory]\Common7\IDE\ItemTemplates\[CSharp | VisualBasic]\Web\MVC\CodeTemplates\ and the you can copy them to your project folder. you can start editing these copies and they will be used when you invoke your templates(ex:- by clicking on the Add Controller sub menu within the IDE). for more info please refer to T4 Templates: A Quick-Start Guide for ASP.NET MVC Developers )

Customizing

In my case i wanted my controllers to have all the basic actions like List, Details, Create, Edit and my custom actions like Grid, GridData(take a look at A Grid with Ajax/Pagination/Sorting/Filtering on ASP.net MVC with ExtJS and Enitiy Framework).

i needed to get the type of the model that a given controller is associated with. and then i have to traverse it’s properties(through reflection…etc) and do some string manipulation to generate custom method signatures and custom anonymous types. i also wanted to filter the list of properties that will be used in these parameters and anon types.

Action method signatures

action method signatures can be different from controller to controller. as in…


//Different Properties, in different models
//
//Create action of a Product Controller
public ActionResult Create(string ID, string Name, string Description, string Price)
//
//Create action of a User Controller
public ActionResult Create(string ID, string Name, string Age)

//Different Primary Keys in different models
//
public ActionResult Details(int UID)
//
public ActionResult Details(int UID, int SiteUID)

Method calls to models


//Different Primary Keys
//
public ActionResult Details(int UID)
        {
            return View(Category.GetModelByPrimaryKey(new {UID}));
        }
//
public ActionResult Details(int UID, int SiteUID)
        {
            return View(Category.GetModelByPrimaryKey(new {UID, SiteUID}));
        }

//Different Properties for different models
//
public ActionResult Create(string ID, string Name)
        {
            Category.Create(new {ID,Name}, User.Identity.Name)
            return View();
        }
//
public ActionResult Create(string ID, string Name, int PropOne, string PropTwo)
        {
            Category.Create(new {ID,Name, PropOne, PropTwo}, User.Identity.Name)
            return View();
        }

But unlike in the view here in the controller you can’t just access the model’s type through MvcTextTemplateHost.ViewDataType. when you are generating a controller this property is null. so i thought of using reflection get the model for the current controller. you can use the ControllerRootName property of MvcTextTemplateHost(available through mvcHost variable in the default template) to get the model name(ex: ProductController would give Product).


<#
Type controllerType = Assembly.LoadFile("D:\\path\\to\\your\\model\\assemblys\\dll\\file.dll").GetType("Example.Models." + mvcHost.ControllerRootName);
#>

Following is the full source code of a generated controller and the modified template that was used to generate it. I have reused some methods like IsBindableType and GetEntityKeyProperties(i have modified this a bit) that can be found in some View templates in mvc framework. The generated controller can be used to do CURD operations on the model and provide a backend(providing data to the grid through json) for a ExtJS grid! :) (A Grid with Ajax/Pagination/Sorting/Filtering on ASP.net MVC with ExtJS and Enitiy Framework)

Generated Controller


//
//A Generated Controller
//
using System;
using System.Collections;
using System.Web.Mvc;
using ExampleInc.Filters;
using ExampleInc.Lib.Helpers;
using ExampleInc.Models;
using ExampleInc.Models.UoW;

namespace ExampleInc.Controllers
{
	[Authorize]
    [NavigationFilter]
    public class ExampleModelController : Controller
    {
        //
        // GET: /ExampleModel/
        public ActionResult Index()
        {
            return RedirectToAction("List");
        }

        //
        // GET: /ExampleModel/Details/5
        public ActionResult Details(Int32 SiteUID,Int32 UID)
        {
            return View(ExampleModel.GetModelByPrimaryKey(new {SiteUID,UID}));
        }

        //
        // GET: /ExampleModel/Create
        public ActionResult Create()
        {
            return View();
        } 

        //
        // POST: /ExampleModel/Create
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create(Int32 SiteUID ,Int32 UID ,String ID ,String Name ,Decimal Turnover ,String StreetAddress ,String City ,String PostalCode ,String Telephone1 ,String Telephone2 ,String Email ,String Fax ,String Notes ,String Capacity ,Int16 NumberOfMealsPerDay ,Decimal SellingPricePerMeal ,Decimal BudgetPricePerMeal ,String BestTimeToCall ,Byte TradingDaysPerWeek ,Byte TradingWeeksPerYear ,Boolean AccountActive ,Int32 PrimaryContact_SiteUID ,Int32 PrimaryContactUID ,DateTime LastCallStartTime ,Boolean IsProfileCall )
        {
            if (ViewData.ModelState.IsValid)
            {
                // Attempt to add the user

                if (ExampleModel.Create(new {SiteUID ,UID ,ID ,Name ,Turnover ,StreetAddress ,City ,PostalCode ,Telephone1 ,Telephone2 ,Email ,Fax ,Notes ,Capacity ,NumberOfMealsPerDay ,SellingPricePerMeal ,BudgetPricePerMeal ,BestTimeToCall ,TradingDaysPerWeek ,TradingWeeksPerYear ,AccountActive ,PrimaryContact_SiteUID ,PrimaryContactUID ,LastCallStartTime ,IsProfileCall }, User.Identity.Name))
                {
                    TempData["StatusBar"] += "ExampleModel '" + ID + "' successfully added.";
                    return RedirectToAction("List");
                }

                ModelState.AddModelError("_FORM", "Error");
            }

            // If we got this far, something failed, redisplay form
            return View();
        }

        //
        // GET: /ExampleModel/Edit/5
        public ActionResult Edit(Int32 SiteUID,Int32 UID)
        {
            return View(ExampleModel.GetModelByPrimaryKey(new {SiteUID,UID}));
        }

        //
        // POST: /ExampleModel/Edit/5
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Int32 SiteUID ,Int32 UID ,String ID ,String Name ,Decimal Turnover ,String StreetAddress ,String City ,String PostalCode ,String Telephone1 ,String Telephone2 ,String Email ,String Fax ,String Notes ,String Capacity ,Int16 NumberOfMealsPerDay ,Decimal SellingPricePerMeal ,Decimal BudgetPricePerMeal ,String BestTimeToCall ,Byte TradingDaysPerWeek ,Byte TradingWeeksPerYear ,Boolean AccountActive ,Int32 PrimaryContact_SiteUID ,Int32 PrimaryContactUID ,DateTime LastCallStartTime ,Boolean IsProfileCall )
        {
            if (ViewData.ModelState.IsValid)
            {
                if (ExampleModel.Update(new {SiteUID,UID}, new {SiteUID ,UID ,ID ,Name ,Turnover ,StreetAddress ,City ,PostalCode ,Telephone1 ,Telephone2 ,Email ,Fax ,Notes ,Capacity ,NumberOfMealsPerDay ,SellingPricePerMeal ,BudgetPricePerMeal ,BestTimeToCall ,TradingDaysPerWeek ,TradingWeeksPerYear ,AccountActive ,PrimaryContact_SiteUID ,PrimaryContactUID ,LastCallStartTime ,IsProfileCall }, User.Identity.Name))
                {
                    TempData["StatusBar"] += "ExampleModel '" + ID + "' successfully updated.";
                    return RedirectToAction("List");
                }

                ModelState.AddModelError("_FORM", "Error");
            }

            // If we got this far, something failed, redisplay form
            return View(ExampleModel.GetModelByPrimaryKey(new {SiteUID,UID}));
        }

        public ActionResult Delete(Int32 SiteUID,Int32 UID)
        {
            if (ExampleModel.Delete(new {SiteUID,UID}))
            {
                TempData["StatusBar"] += "ExampleModel successfully deleted.";
            }
            else
            {
                TempData["StatusBar"] += "ExampleModel delete operation failed!.";
            }
            return RedirectToAction("List");
        }

        public ViewResult List()
        {
            return View();
        }

        public ViewResult Grid(string RowClickEventHandlerFunctionName)
        {
            ViewData["RowClickEventHandlerFunctionName"] = RowClickEventHandlerFunctionName;
            return View();
        }

        public JsonResult GridData()
        {
            int totalOjectCount;
            var ExampleModelsList = EmExtJSGridFilterHelper.GetResults(Request, DataMan.ObjectContext.ExampleModel, out totalOjectCount);
            var list = new ArrayList();
            foreach (var ExampleModel in ExampleModelsList) //populate data containers with read data
            {
                list.Add(new
                             {
                                 ExampleModel.SiteUID ,ExampleModel.UID ,ExampleModel.ID ,ExampleModel.Name ,ExampleModel.Turnover ,ExampleModel.StreetAddress ,ExampleModel.City ,ExampleModel.PostalCode ,ExampleModel.Telephone1 ,ExampleModel.Telephone2 ,ExampleModel.Email ,ExampleModel.Fax ,ExampleModel.Notes ,ExampleModel.Capacity ,ExampleModel.NumberOfMealsPerDay ,ExampleModel.SellingPricePerMeal ,ExampleModel.BudgetPricePerMeal ,ExampleModel.BestTimeToCall ,ExampleModel.TradingDaysPerWeek ,ExampleModel.TradingWeeksPerYear ,ExampleModel.AccountActive ,ExampleModel.PrimaryContact_SiteUID ,ExampleModel.PrimaryContactUID ,ExampleModel.LastCallStartTime ,ExampleModel.IsProfileCall
                             });
            }
            return Json(new {dataitExampleInc = list.ToArray(), totalItExampleInc = totalOjectCount});
        }
    }
}

T4 Template used to Generate the Controller


<#@ template language="C#" HostSpecific="True" #>
<#@ output extension="cs" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="System.Data.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Data.Objects.DataClasses" #>
<#@ import namespace="System.Data.Linq.Mapping" #>
<#
MvcTextTemplateHost mvcHost = (MvcTextTemplateHost)(Host);
#>
<#
Type controllerType = Assembly.LoadFile("D:\\work_items\\ExampleInc\\ExampleInc-Alt-21\\ExampleInc.Models\\bin\\Debug\\ExampleInc.Models.dll").GetType("ExampleInc.Models." + mvcHost.ControllerRootName);

PropertyInfo[] piArray = controllerType.GetProperties(BindingFlags.Public|BindingFlags.Instance|BindingFlags.DeclaredOnly);

string actionMethodParamerters = "", anonTypePropValString = "", gridDataAnonTypePropValString = "";
foreach (PropertyInfo pi in piArray)
		{
			Type currentPropertyType = GetUnderlyingType(pi.PropertyType);

			if(IsBindableType(currentPropertyType) && IsNeededProp(pi.Name))
			{
				actionMethodParamerters += currentPropertyType.Name + " " + pi.Name + " ,";
				anonTypePropValString += pi.Name + " ,";
				gridDataAnonTypePropValString += mvcHost.ControllerRootName.ToLower() + "." + pi.Name + " ,";
			}
		}
actionMethodParamerters = actionMethodParamerters.Remove(actionMethodParamerters.Length - 1); //remove the last extra ","
anonTypePropValString = anonTypePropValString.Remove(anonTypePropValString.Length - 1); //remove the last extra ","
gridDataAnonTypePropValString = gridDataAnonTypePropValString.Remove(gridDataAnonTypePropValString.Length - 1); //remove the last extra ","
#>
using System;
using System.Collections;
using System.Web.Mvc;
using ExampleInc.Filters;
using ExampleInc.Lib.Helpers;
using ExampleInc.Models;
using ExampleInc.Models.UoW;

namespace <#= mvcHost.Namespace #>
{
	[Authorize]
    [NavigationFilter]
    public class <#= mvcHost.ControllerName #> : Controller
    {
        //
        // GET: /<#= mvcHost.ControllerRootName #>/
        public ActionResult Index()
        {
            return RedirectToAction("List");
        }

<#
if(mvcHost.AddActionMethods) {
#>
        //
        // GET: /<#= mvcHost.ControllerRootName #>/Details/5
        public ActionResult Details(<#= GetMethodParamertersString(controllerType) #>)
        {
            return View(<#= mvcHost.ControllerRootName #>.GetModelByPrimaryKey(new {<#= GetDataAnonTypePropValString(controllerType) #>}));
        }

        //
        // GET: /<#= mvcHost.ControllerRootName #>/Create
        public ActionResult Create()
        {
            return View();
        } 

        //
        // POST: /<#= mvcHost.ControllerRootName #>/Create
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create(<#= actionMethodParamerters #>)
        {
            if (ViewData.ModelState.IsValid)
            {
                // Attempt to add the user

                if (<#= mvcHost.ControllerRootName #>.Create(new {<#= anonTypePropValString #>}, User.Identity.Name))
                {
                    TempData["StatusBar"] += "<#= mvcHost.ControllerRootName #> '" + ID + "' successfully added.";
                    return RedirectToAction("List");
                }

                ModelState.AddModelError("_FORM", "Error");
            }

            // If we got this far, something failed, redisplay form
            return View();
        }

        //
        // GET: /<#= mvcHost.ControllerRootName #>/Edit/5
        public ActionResult Edit(<#= GetMethodParamertersString(controllerType) #>)
        {
            return View(<#= mvcHost.ControllerRootName #>.GetModelByPrimaryKey(new {<#= GetDataAnonTypePropValString(controllerType) #>}));
        }

        //
        // POST: /<#= mvcHost.ControllerRootName #>/Edit/5
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(<#= actionMethodParamerters #>)
        {
            if (ViewData.ModelState.IsValid)
            {
                if (<#= mvcHost.ControllerRootName #>.Update(new {<#= GetDataAnonTypePropValString(controllerType) #>}, new {<#= anonTypePropValString #>}, User.Identity.Name))
                {
                    TempData["StatusBar"] += "<#= mvcHost.ControllerRootName #> '" + ID + "' successfully updated.";
                    return RedirectToAction("List");
                }

                ModelState.AddModelError("_FORM", "Error");
            }

            // If we got this far, something failed, redisplay form
            return View(<#= mvcHost.ControllerRootName #>.GetModelByPrimaryKey(new {<#= GetDataAnonTypePropValString(controllerType) #>}));
        }

        public ActionResult Delete(<#= GetMethodParamertersString(controllerType) #>)
        {
            if (<#= mvcHost.ControllerRootName #>.Delete(new {<#= GetDataAnonTypePropValString(controllerType) #>}))
            {
                TempData["StatusBar"] += "<#= mvcHost.ControllerRootName #> successfully deleted.";
            }
            else
            {
                TempData["StatusBar"] += "<#= mvcHost.ControllerRootName #> delete operation failed!.";
            }
            return RedirectToAction("List");
        }

        public ViewResult List()
        {
            return View();
        }

        public ViewResult Grid(string RowClickEventHandlerFunctionName)
        {
            ViewData["RowClickEventHandlerFunctionName"] = RowClickEventHandlerFunctionName;
            return View();
        }

        public JsonResult GridData()
        {
            int totalOjectCount;
            var <#= mvcHost.ControllerRootName.ToLower() #>sList = EmExtJSGridFilterHelper.GetResults(Request, DataMan.ObjectContext.<#= mvcHost.ControllerRootName #>, out totalOjectCount);
            var list = new ArrayList();
            foreach (var <#= mvcHost.ControllerRootName.ToLower() #> in <#= mvcHost.ControllerRootName.ToLower() #>sList) //populate data containers with read data
            {
                list.Add(new
                             {
                                 <#= gridDataAnonTypePropValString #>
                             });
            }
            return Json(new {dataitems = list.ToArray(), totalItems = totalOjectCount});
        }
<#
}
#>
    }
}
<#+
public static List<PropertyInfo> GetEntityKeyProperties(Type type)
{
	List<PropertyInfo> keyProperties = new List<PropertyInfo>();

	PropertyInfo[] properties = type.GetProperties();

	foreach (PropertyInfo pi in properties)
	{
		System.Object[] attributes = pi.GetCustomAttributes(true);

		foreach (object attribute in attributes)
		{
			if (attribute is EdmScalarPropertyAttribute)
			{
				if ((attribute as EdmScalarPropertyAttribute).EntityKeyProperty == true)
				{
					keyProperties.Add(pi);
				}
			} else if(attribute is ColumnAttribute) {
				if ((attribute as ColumnAttribute).IsPrimaryKey == true)
				{
					keyProperties.Add(pi);
				}
			}
		}
	}

	return keyProperties;
}

public bool IsBindableType(Type type)
{
	bool isBindable = false;

	if (type.IsPrimitive || type.Equals(typeof(string)) || type.Equals(typeof(DateTime)) || type.Equals(typeof(decimal)) || type.Equals(typeof(Guid)) || type.Equals(typeof(DateTimeOffset)) || type.Equals(typeof(TimeSpan)))
	{
		isBindable = true;
	}

	return isBindable;
}

public bool IsNeededProp(string propName)
        {
            switch (propName)
            {
                case "CreationDate":
                case "LastModifiedDate":
                case "LastModifiedBy":
                case "EffectiveDate":
                case "ExpiryDate":
                case "ServerEntryDate":
                    return false;
            }
            return true;
        }

public string GetMethodParamertersString(Type viewDataType)
        {
			List<PropertyInfo> primaryKeys = GetEntityKeyProperties(viewDataType);

			if(primaryKeys.Count > 0) {
				string result = "";
				foreach(PropertyInfo pk in primaryKeys)
				{
					result += String.Format("{0} {1},", GetUnderlyingType(pk.PropertyType).Name , pk.Name);
				}

				return result.Remove(result.Length - 1); //remove the last extra ","
			} else {
				return "UID=Model.PrimaryKey";
			}
        }

public string GetDataAnonTypePropValString(Type viewDataType)
        {
			List<PropertyInfo> primaryKeys = GetEntityKeyProperties(viewDataType);

			if(primaryKeys.Count > 0) {
				string result = "";
				foreach(PropertyInfo pk in primaryKeys)
				{
					result += String.Format("{0},", pk.Name);
				}

				return result.Remove(result.Length - 1); //remove the last extra ","
			} else {
				return "UID=Model.PrimaryKey";
			}
        }

public Type GetUnderlyingType(Type type)
{
	Type currentPropertyType = type;
			Type currentUnderlyingType = System.Nullable.GetUnderlyingType(currentPropertyType);
			if(currentUnderlyingType != null) {
				currentPropertyType = currentUnderlyingType;
			}

			return currentPropertyType;
}
#>

hope this will help someone/myself in the feuture :)

a bit too complex title eh? sorry about that. i am finally blogging about something after a long while. its a good idea to use the revision number of your source tree in thee release versions of your software. because it will enable you to ‘connect and identify’ your release versions with your source tree’s different revisions. this is how i went about ‘automating’ it. note that I use svn as my source control system, and VS(2008) as my IDE at work.

What we are trying to do

Read the AssemblyInfo.cs file in a project and replace the(/a part of the) version number for that project with it’s svn revision number.

What you need

Code


<!-- Import of the MSBuildCommunityTask targets -->
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />

  <!-- to AssemblyInfo to include svn revision number -->
<Target Name="BeforeBuild">
	<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="$(ProgramFiles)\VisualSVN\bin">
	   <Output TaskParameter="Revision" PropertyName="Revision" />
        </SvnVersion>

	<FileUpdate Files="Properties\AssemblyInfo.cs"
                Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)"
                ReplacementText="$1.$2.$3.$(Revision)" />
</Target>

Steps

  • Locate and open your project file. ex:- exampleProject.csproj in a editor.
  • Edit(SvnVersion node’s ToolPath attribute) the path of the svn bin folder, where you find the executables(ex: svn.exe, svnversion.exe…).
  • go the very end and add the following code to the file. be sure to add this code before the </project> tag closing. so that your code is still inside the <project> node.
  • Save the exampleProject.csproj file.
  • it’s done! now every time you build your project, it’s AssemblyInfo.cs file’s version will contain the svn revision number as it’s last part(x.x.x.svn-revison)

How

Let me start by saying that I am no expert in MSBuild(reffer MSDN for more info). MSBuild is the build system used by VS to build your projects. when you create a project file it is basically a MSBuild file. the build file tell MSBuild how your project should be build/compiled and about any other actions that needs to taken to insure a succesful build. when we needed to include the svn revision number of our project in the AssemblyInfo.cs file we simply added a build target called AssenblyInfo. inside that we uses two MSBuild CommunityTasks(”The MSBuild Community Tasks Project is an open source project for MSBuild tasks. The goal of the project is to provide a collection of open source tasks for MSBuild.“).

  • SvnVersion – “Summarize the local revision(s) of a working copy.” – we use this to get the svn revision of our project. the result(revision) is stroed in a property called Revision.
  • FileUpdate – “Replace text in file(s) using a Regular Expression.” – we use this task to replace the revision portion of the version string(x.x.x.revision) with the content of the Revision property.

and that’s it. pretty simple eh!

References

i recently needed to use Enum.GetValues on a .netcf project. but unfortunately it is not there in .netcf. googling gives a lot of solutions that we can use in .netcf. but i couldn’t find one that worked exactly as Enum.GetValue did.

Retrieves an array of the values of the constants in a specified enumeration.

is the method description for the Enum.GetValue, taken from msdn.com. most of the solutions online returned only an array of the constants. not their values. so i came up with the following. now of course this is inspired from lot of smart people’s code that i took from around the net sometime back. ;)


private static string EnumValuesToString(object e)
        {
            var eVal = (int) e;
            var allValues = GetValues(typeof (Testnum));
            var strings = new List<string>();

            foreach (var value in allValues)
            {
                if (value > eVal)
                {
                    break;
                }
                if ((eVal & value) != value)
                {
                    continue;
                }
                strings.Add(value.ToString());
            }

            return string.Join(",", strings.ToArray());
        }

        private static int[] GetValues(Type enumType)
        {
            if (enumType.BaseType ==
                typeof (Enum))
            {
                //get the public static fields (members of the enum)
                var fi = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);
                //create a new enum array
                var values = new int[fi.Length];
                //populate with the values
                for (var iEnum = 0; iEnum < fi.Length; iEnum++)
                {
                    values[iEnum] = (int) fi[iEnum].GetValue(null);
                }
                //return the array
                return values;
            }

            //the type supplied does not derive from enum
            throw new ArgumentException("enumType parameter is not a System.Enum");
        }

usage would go like


        [Flags]
        public enum Testenum
        {
            fld1 = 1,
            fld2 = 2,
            fld3 = 4,
            fld4 = 8,
            fld5 = 16
        }

        #endregion

        public ClassTest()
        {
            EnumValuesToString(Testenum.fld1 | Testenum.fld2 | Testenum.fld4);
        }

may this help me/you/him or her in the future :)

noob alert!: I am pretty new to RoR. so i might not be solving the problem with the best possible solution.

Scenario 1: many-to-many + one-to-many

Task: I have two models. Contact and ContactSource. a Contact belongs to a ContactSource. and a Contact can be synced with one or more ContactSource(s). i need to get the ContactSource(s) with whom a given Contact is synced ex:- Contact1.synced_with => [ContactSource1, ContactSource2...]

Solution:  for the first relationship(one-to-many) we can use belongs_to. and for second we can use has_many with :through


class Contact  :contacts_synced_with_contact_sources

end

class ContactsSyncedWithContactSource < ActiveRecord::Base
belongs_to :contact
belongs_to :contact_source
end

In the above code we have no problem accessing the data relevant to the two relationships. because: Contact.contact_source would point to the Contact Source who owns the Contact while Contact.contact_sources would point to the ContactSources with whom the Contact is synced with. but look at the next problem….

Scenario 2: many-to-many + many-to-many

Task: I have two models. Book and Student. a student can own many books, while a book(this does not refer to a individual copy of a book, but to all the copies of a book) can be owned by many students. and a student have a collection of books that he loves.

Problem: here we have two many to many relationships within two business entities. if you try to solve this with has_many :through or has_and_belongs_to_many, you would run in to a problem. in the first problem you referred to the ContactSources by using the property accessor contact_sources. but here if you try using student1.books how would ActiveRecord know what to return?(book the student love or the books he own?).

you could solve this problem by using the :source option(is that what you call these in ruby? :P ) in has_many… it would like this…

has_many :books_i_love, :source => "book", :through => :student_love_books

you can give a custom property accessor name such as books_i_love but then you have to use the :source => “book” to let rails know about what model(s) you are trying to access.

if you do not use the :source => “book” you would get a very helpful error message like this

ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :books_i_love or :books_i_love in model StudentLoveBook. Try 'has_many :books_i_love, :through => :student_love_books, :source => '. Is it one of :student or :book?

source follows…


class Student  "book", :through => :student_love_books

  has_many :student_own_books
  has_many :books, :through => :student_own_books
end

class Book < ActiveRecord::Base
end

class StudentLoveBook < ActiveRecord::Base
  belongs_to :student
  belongs_to :book
end

class StudentLoveBook < ActiveRecord::Base
  belongs_to :student
  belongs_to :book
end

class CreateBooks < ActiveRecord::Migration
  def self.up
    create_table :books do |t|
      t.string :name

      t.timestamps
    end
  end

  def self.down
    drop_table :books
  end
end

class CreateStudents < ActiveRecord::Migration
  def self.up
    create_table :students do |t|
      t.string :name

      t.timestamps
    end
  end

  def self.down
    drop_table :students
  end
end

class CreateStudentOwnBooks < ActiveRecord::Migration
  def self.up
    create_table :student_own_books do |t|
    t.column :student_id, :integer
    t.column :book_id, :integer
      t.timestamps
    end
  end

  def self.down
    drop_table :student_own_books
  end
end

class CreateStudentOwnBooks < ActiveRecord::Migration
  def self.up
    create_table :student_own_books do |t|
    t.column :student_id, :integer
    t.column :book_id, :integer
      t.timestamps
    end
  end

  def self.down
    drop_table :student_own_books
  end
end

hope this would help someone/myself in the future :)

Think: In the Entity Framework…

you have two models like this:

Item{ID, Name, Category}, Category{ID, Name}

With a Relationship like this:

Item belongs to a Category. and a Category may have zero or more Items.

if you try to sort the Items by their Category(which doesn’t make sense in the first place! :P ) by doing something like:

YourObjectContext.Items.OrderBy(”it.Category”)

you would get the error that is specified in the title of this post. .

but if all you want to do is sort all the Items by the Name of their respective Categories. you should do something like this:

YourObjectContext.Items.OrderBy(”it.Category.Name”)

it will work just fine. you can use this doted notation to specify properties of properties(nested properties). this can be used with OrderBy’s Where caluses…etc this is a faily simple thing i know :) but it was bit tricky to find this out, for me atleast.

arch linux - kde - yakuake

arch linux - kde - yakuake

Background

I’ve been using linux for like 4 years now. in the beginning i was just trying it out. back then i used almost any thing i could find. redhat/knoppix/suse/debian…etc but back then i was only trying them out. but for the last one/two year(s) I’ve been with ubuntu. the reasons were simple. it just worked! but recently i felt so bored with my system. so last weekend i download a Arch iso and started installing the distro.

Why Arch Linux

  • simple
  • packages are build for i686, thus they will be more optimized(good for my ‘old’ laptop)
  • pacman is good(I’m a huge fan of apt, so a good package manager was a must)
  • good documentation/support/community
  • i can choose what i want to install, i have control over what’s getting into my system. ie: the system is really ‘mine’ when its done.
arch linux kde desktop

arch linux kde desktop

Installation

i read a bit of the beginners guide available in the Arch Wiki. but soon got bored :P so i followed along the installation script. it was pretty straight forward. but i wanted to use ext4 as my root file system. as i was trying out a new distro. i figured this is the best time to try out the new file system. after all there was a lot of talk going around about ext4 been much better than ext3(naturally…). i was installing the system on my laptop. which is a bit old. so i thought ext4 would help me with the speed. and after all one of the reasons i choose to go with arch was that all it’s packages were build for i686 platform. which will usually make em run faster.

Arch and Ext4

as of now(16/feb/2009) official arch installing media doesn’t support ext4. meaning you wouldn’t be able to just make a ext4 partition the way you would have made a ext3… because the tools needed for it are not included in the installation media. but its not that hard to get it working. the best way to do is(ASFAIK) installing the system in to a ext3 partition. and then converting the partition(s) ext4. when you need to convert your root partition you have to use a live cd to do it. its pretty straight forward. please refer to the arch wiki for more info.

btw i tried to upgrade the live cd(arch installation media) before installing the system so i could get all the ext4 supported tools..etc but that was too good to be true. it broke the system. and when i tried to get around the broken stuff it eventually lead to a kernel panic! :P but its ok. its nice to have experiences right? :)

KDE

i am so amazed with the latest release of KDE! it rocks. i have been a gnome(default in ubuntu) user for a while now. never had much time to look at kde. even when i got a chance to work with it. it felt a bit uncomfortable so i never really wanted to try it out. but lately i’ve been more and more curious about KDE. so this time i choose to go with it. wow. it was really nice. its visually appealing. but wait its not the main thing. its rock solid! i have only 512MB ram. but still it works grate. no slow downs or what so ever. i feel good about the way it looks and works. but there are somethings that i am still a bit alien to. but its ok. i am getting used to KDE. its grate. i suggest you give it a try too. you never know, you might like it too.

archlinux - kde - firefox - KFirefox theme

archlinux - kde - firefox - KFirefox theme

Where is Firefox?

i found it a bit hard to believe that firefox is not the default browser in KDE at first when i installed it. but then again konqueror is a decent browser too. anyways i switched to firefox. when you start up firefox you would notice something. that is it looks damn UGLY! i think this is because it is not a kde application. but no worries you can get it to look better by installing KFirefox theme. it gives a nice look. and FF would look much more like a kde app.

kde 4.2 - kickoff application luncher

kde 4.2 - kickoff application launcher

Getting Stuff to Work

unlike ubuntu arch expects you to read stuff and install what you need by your self. its not hard. but it will need some of your time. you will need do some googling at some point or other and find out about things that you need and install them by your self. for example when i tried to connect to my gmail/gtalk account using kopete. it just didn’t work. reading around a bit reviled that it need a SSL library installed in order to work with gtalk. i had to install these libraries by my self. but of course its just a ‘pacman -S package’ away! :)

Arch WiKi Rocks!

arch wiki is a very rich source of information when you trying to solve your problems about arch. it will really teach you a lot of things about your system. whenever you want install some package or whenever you want to learn about how to do something in arch. its best to check out the wiki. as it have answers/articles for most your common problems. it will save you a lot of time.

Final Thoughts

i’m kinda happy that i choose to go ahead and install this new distro. i now have a good system running in my laptop. arch linux is a grate distro. you might want to give it a try. its not for everyone. if you don’t won’t to mess with your system and just wants something that just works. go for ubuntu/fedora/mint…etc. but if you want a system that is pretty simple and configurable to your liking. arch is a good choice. you can make a lot of choices when you choose arch. unlike in ubuntu where a lot things are already decided for you(this is good if you just want everything to work…).

I am new to all these JS libraries like jQuery and ExtJS(there are many more out there…). when i got started with asp.net mvc i looked in to jQuery. and i was absolutely amazed with all the functionality it offered with very simple and neat syntax! a while ago i did a post on “Ajax Enabling MVCContrib Grid’s Pagination with jQuery”. In which i added AJAX/pagination support to MVCContrib’s Grid. but with time i needed more and more features from the grid. like sorting and filtering(through AJAX of course). then i came across the wonderful JS library called ExtJS. it have grate UI components done and ready to be used. i love all ExtJS stuff that is related to UI. and their components seems to be very extensible as well.

Ext JS Overview

Ext JS is a cross-browser JavaScript library for building rich Internet applications. It includes:

  • High performance, customizable UI widgets
  • Well designed and extensible Component model
  • An intuitive, easy to use API
  • Commercial and Open Source licenses available

i liked there Grid and it had all the features i needed. so i decided to go ahead with it.

extjs_grid_screenshot

the main points i had to look at:

  • including a ExtJS distribution with my asp.net mvc project which is already using jQuery
  • inserting the relevant JS to render the grid in my views
  • creating the necessary controller actions to support the grid’s actions(get data/sort/filter data…etc)

Including a ExtJS distribution

ExtJS JS library can use other libraries(such as jQuery…) as its base library. so been a fan of jQuery, i choose to build a custom version of ExtJS which uses jQuery as its base.  this version of the library includes the jQuery adapter within it self. but you still need to include the jQuery library by your self. it is NOT included in the custom version that you built for your self.  so in your site.master(you can put these where ever you like, not necessary in the site.master. just make sure these are included where ever you use them) you have to include the libraries in the following order: include jQuery first, then include any jQuery plugins you use and finally include ExtJS(any of your custom JS files should be included afterward).

now you are done including the JS libraries necessary . but ExtJS’s UI components need a few more things to be included in your site.master for them to work correctly. you need to include the necessary style sheets/images used by ExtJS components. to get these download ExtJS SDK. this includes all the style sheets and the images among other things. you will need to include the necessary plugins as well.

  • ext-2.2resourcescssext-all.css
  • ext-2.2resourcesimages – images used by ExtJS components
  • all JS files under ext-2.2examplesgrid-filteringgrid – includes necessary plugins needed for the grid filtering functionality

now you are ready to roll. note that we can use what ever jQuery we want when we are working with extjs components. and as we are using the jQuery core(and also because we are already using jQuery in out mvc project) the size of the ExtJS library is much lower.

Rendering the grid

looking at the samples(look under ext-2.2examplesgrid-filtering) included in the extjs sdk. its not that hard to figure out how to render the grid using javascript. the extjs API reference would be helpful as well.

i ran across a bug in the GridView component when i tried to use disable ‘autoHeight’ ‘feature’ of the grid. how ever there was a fix available for it in the the extjs forums.  following is the JS code that i used in my view(in which the grid resides). i just included them in a script tag within the view. but the best way to use them would be to extract them in to some helper methods and then using the helpers.

some notes about ExtJs grids

  • a grid is ‘bound’ to a data store.
  • when rendering a grid you will have to create a data record, column modal and a data reader(JsonReader in this case).
  • column modal – how the columns are structured
  • data record – how a data record(or a person object, in the context of this example) is structured. what fields does it contain…etc
  • data reader – where to get/ how to read data
  • filters and paging is connected to the grid in a ‘pluggable’ manner

        //Sugguested Fix for the bug: "Grid autoHeight disables horizontal scrolling too"
        Ext.override(Ext.grid.GridView, {
            layout: function() {
                if (!this.mainBody) {
                    return;
                }
                var g = this.grid;
                var c = g.getGridEl(), cm = this.cm,
                expandCol = g.autoExpandColumn,
                gv = this;
                var csize = c.getSize(true);
                var vw = csize.width;
                if (vw < 20 || csize.height < 20) {
                    return;
                }
                if (g.autoHeight) {
                    csize.height = this.mainHd.getHeight() + this.mainBody.getHeight();
                    if (!this.forceFit) {
                        csize.height += this.scrollOffset;
                    }
                }
                this.el.setSize(csize.width, csize.height);
                var hdHeight = this.mainHd.getHeight();
                var vh = csize.height - (hdHeight);
                this.scroller.setSize(vw, vh);
                if (this.innerHd) {
                    this.innerHd.style.width = (vw) + 'px';
                }
                if (this.forceFit) {
                    if (this.lastViewWidth != vw) {
                        this.fitColumns(false, false);
                        this.lastViewWidth = vw;
                    }
                } else {
                    this.autoExpand();
                }
                this.onLayout(vw, vh);
            }
        });
        //end bug fix here
        //////////////////

        Ext.onReady(function() {
            Ext.QuickTips.init(); //for enabling tool tips

            Ext.menu.RangeMenu.prototype.icons = {
                gt: '/Scripts/extjs/plugins/grid-filter/img/greater_then.png',
                lt: '/Scripts/extjs/plugins/grid-filter/img/less_then.png',
                eq: '/Scripts/extjs/plugins/grid-filter/img/equals.png'
            };
            Ext.grid.filter.StringFilter.prototype.icon = '/Scripts/extjs/plugins/grid-filter/img/find.png';

            createAndShowGrid();
        });

        function createAndShowGrid() {

            order = Ext.data.Record.create([
	        { name: 'Id' },
	        { name: 'FirstName' },
	        { name: 'Email' }
        ]);

            orderReader = new Ext.data.JsonReader({
                root: 'dataitems',
                totalProperty: 'totalItems' //number of total records
            },
		    order
	    );
            //data store creation
            ds = new Ext.data.Store({
                proxy: new Ext.data.HttpProxy({
                    url: '/Person/ListJson'
                }),
                reader: orderReader,
                sortInfo: { field: 'Id', direction: "ASC" },
                remoteSort: true
            });

            columnModel = new Ext.grid.ColumnModel([{
                header: 'Person Id',
                dataIndex: 'Id'
            }, {
                header: 'Person Name',
                dataIndex: 'FirstName'
            }, {
                header: 'Person Email',
                dataIndex: 'Email'
            }
	    ]);

            columnModel.defaultSortable = true;

            var filters = new Ext.grid.GridFilters({
                filters: [
	                { type: 'string', dataIndex: 'Id' },
	                { type: 'string', dataIndex: 'FirstName' },
	                { type: 'string', dataIndex: 'Email' }
	            ]
            });

            var pagingBar = new Ext.PagingToolbar({
                pageSize: 10,
                store: ds,
                displayInfo: true,
                displayMsg: 'Displaying Persons {0} - {1} of {2}',
                emptyMsg: "No Persons to display",
                plugins: filters
            });

            grid = new Ext.grid.GridPanel({
                store: ds,
                cm: columnModel,
                width: 500,
                autoHeight: true,
                title: 'Persons List',
                frame: true,
                loadMask: true,
                stripeRows: true,
                plugins: filters,
                bbar: pagingBar // paging bar on the bottom
            });

            grid.render('personGrid');
            grid.store.load({ params: { start: 0, limit: 10} });
        }
<div id="personGrid"

Controller actions to support the grid’s actions(get data/sort/filter data…etc)

A ExtJS grid can be fed data in various ways. i am going use a controller action to give the grid the data it needs to render it self. the data would be in json format. we would have a controller action like the following, that returns a JsonResult. we can use the Json method in the mvc framework to serialize the data to json with ease. i am doing it by constructing a anonymous type that hosts the data and then serializing it to json. please note that the following example uses the Entity Framework to deal with the back-end data. although this is example uses the EF. you can easily port it to use any other ORM technology(Linq…etc).

source of /Person/ListJson action


public JsonResult ListJson()
        {
            int totalOjectCount;
            var personsList = ExtJSGridFilterHelper.GetResults(Request, new ObjectEntities().Person,out totalOjectCount);
            var list = new ArrayList();
            foreach (var person in personsList) //populate data containers with read data
            {
                list.Add(new
                             {
                                 Id = person.ID,
                                 FirstName = person.FirstName,
                                 person.Email
                             });
            }
            return Json(new { dataitems = list.ToArray(), totalItems = totalOjectCount });
        }

note that the json result that is returned, basically contains a array of items and a another property called totalItems. this total items property tell the grid, the number of(obviously ) total items that it will have to display. this is helpfule for the grid when pagination is enabled. and you should make sure that this property is specified in your json output if you want the pagination to work correctly.

example json output that is produced by the above action


{"dataitems":[{"Id":"5","FirstName":"jhon Lalic","Email":"jhon@gmail.com"},{"Id":"6","FirstName":"Maureen Joesph","Email"
:"maureen@gmail.com"}],"totalItems":2}

this contains two records. and the totalItems property.

ExtJSGridFilterHelper is a helper class that harbours most of the logic related to implementing sorting/paging and filtering of data. i am not going to explain what the source code is doing, step by step. but you can always read the source ;)

parsing of parameters sent by the grid…(for the full source code please look at the source of ExtJSGridFilterHelper)

parameters related to paging

  • start
  • limit

parameters related to the sorting

  • sort => the name of the property to be sorted(as given in the client side(aka field name))
  • dir => direction

if (sort != null) //do sorting
            {
                objectsList = query.OrderBy("it." + sort).ToList();
                if (dir == "DESC") objectsList.Reverse();
            }
            else
            {
                objectsList = query.ToList();
            }

parameters related to the filters


public Filter(int id, HttpRequestBase request)
            {
                Id = id;
                Field = request.Params[string.Format("filter[{0}][field]", id)];
                DataType = request.Params[string.Format("filter[{0}][data][type]", id)];
                DataValue = request.Params[string.Format("filter[{0}][data][value]", id)];
                Datacomparison = request.Params[string.Format("filter[{0}][data][comparison]", id)];
            }

how filters are processed, extract from the getExpression() method

</pre>
<pre>public FilterExpressionResult getExpression()
            {
                string expressionString = null;
                var expressionParams = new List(); //paramerters collection
                switch (DataType)
                {
                    case "string":
                        expressionString = string.Format("(it.{0} like '{1}%')", Field, DataValue);
                        break;
                    case "boolean":
                        expressionString = string.Format("(it.{0} = {1})", Field, (DataValue == "true") ? 1 : 0);
                        break;
                    case "numeric":
                        switch (Datacomparison)
                        {
                            case "gt":
                                Datacomparison = ">";
                                break;
                            case "lt":
                                Datacomparison = "";
                                break;
                            case "lt":
                                Datacomparison = "<";
                                break;
                            default:
                                Datacomparison = "=";
                                break;
                        }

                        expressionParams.Add(new ObjectParameter("Param" + Id, DateTime.Parse(DataValue)));
                        expressionString = string.Format("(it.{0} {2} {1})", Field, "@" + "Param" + Id, Datacomparison);
                        break;
                    case "list":
                        var split = DataValue.Split(new[] { ',' });
                        var r = new string[split.Length];
                        for (var i = 0; i < split.Length; i++)
                        {
                            r[i] = string.Format("(it.{0} = '{1}')", Field, split[i]);
                        }
                        expressionString = string.Format("({0})", string.Join("OR", r));
                        break;
                }
<pre>

source of ExtJSGridFilterHelper


public class ExtJSGridFilterHelper
    {
        public static List GetResults(HttpRequestBase request, ObjectQuery query, out int totalOjectCount)
        {
            var sort = request.Params["sort"];
            var dir = request.Params["dir"];

            //get non null values
            int intStart, intLimit;
            if (!int.TryParse(request.Params["start"], out intStart)) intStart = 0;
            if (!int.TryParse(request.Params["limit"], out intLimit)) intLimit = 10;

            List objectsList;

            totalOjectCount = query.Count();
            var i = 0;
            var exspressions = new List();
            var parameters = new List();
            while (true) //check for filters starting from 0, if one exists move to the next one(0 -> 1...), .
            {
                if (!Filter.checkExistence(i, request.Params)) break; //...if not stop looking further
                var expression = new Filter(i, request).getExpression();
                exspressions.Add(expression.Expression);
                parameters.AddRange(expression.Parameters);
                i++; //keep track of index
            }
            var exspression = string.Format("({0})", string.Join("AND", exspressions.ToArray()));
            //build the final expression
            if (exspression != "()") query = query.Where(exspression, parameters.ToArray()); //filter collection on the expression

            if (sort != null) //do sorting
            {
                objectsList = query.OrderBy("it." + sort).ToList();
                if (dir == "DESC") objectsList.Reverse();
            }
            else
            {
                objectsList = query.ToList();
            }

            if (intStart + intLimit > objectsList.Count)
                intLimit = objectsList.Count - intStart; //make sure the range we select is valid
            objectsList = objectsList.GetRange(intStart, intLimit);
            return objectsList;
        }

        #region Nested type: Filter

        public class Filter
        {
            public string Datacomparison { get; set; }
            public string DataType { get; set; }
            public string DataValue { get; set; }
            public string Field { get; set; }

            public int Id { get; set; }

            public static bool checkExistence(int filterIndex, NameValueCollection @params)
            {
                return (@params[string.Format("filter[{0}][field]", filterIndex)] != null);
            }

            public Filter(int id, HttpRequestBase request)
            {
                Id = id;
                Field = request.Params[string.Format("filter[{0}][field]", id)];
                DataType = request.Params[string.Format("filter[{0}][data][type]", id)];
                DataValue = request.Params[string.Format("filter[{0}][data][value]", id)];
                Datacomparison = request.Params[string.Format("filter[{0}][data][comparison]", id)];
            }

            public FilterExpressionResult getExpression()
            {
                string expressionString = null;
                var expressionParams = new List(); //paramerters collection
                switch (DataType)
                {
                    case "string":
                        expressionString = string.Format("(it.{0} like '{1}%')", Field, DataValue);
                        break;
                    case "boolean":
                        expressionString = string.Format("(it.{0} = {1})", Field, (DataValue == "true") ? 1 : 0);
                        break;
                    case "numeric":
                        switch (Datacomparison)
                        {
                            case "gt":
                                Datacomparison = ">";
                                break;
                            case "lt":
                                Datacomparison = "";
                                break;
                            case "lt":
                                Datacomparison = "<";
                                break;
                            default:
                                Datacomparison = "=";
                                break;
                        }

                        expressionParams.Add(new ObjectParameter("Param" + Id, DateTime.Parse(DataValue)));
                        expressionString = string.Format("(it.{0} {2} {1})", Field, "@" + "Param" + Id, Datacomparison);
                        break;
                    case "list":
                        var split = DataValue.Split(new[] { ',' });
                        var r = new string[split.Length];
                        for (var i = 0; i < split.Length; i++)
                        {
                            r[i] = string.Format("(it.{0} = '{1}')", Field, split[i]);
                        }
                        expressionString = string.Format("({0})", string.Join("OR", r));
                        break;
                }
                return expressionString != null
                           ? new FilterExpressionResult { Expression = expressionString, Parameters = expressionParams }
                           : null;
            }

            #region Nested type: FilterExpressionResult

            public class FilterExpressionResult
            {
                public string Expression { get; set; }
                public List Parameters { get; set; }
            }

            #endregion
        }

        #endregion
    }

ah! that’s it for now. i might post some helpers that can be used in your views when you want to render the grid. the advantage of using this would be, so you won’t need to repeat all that javascript code in each and every view that you want to display a grid in. hope this will be helpful to someone. cheers!

niceme gm script screenshot 1

'simple google' gm script screenshot 1

niceme screenshot 2

'simple google' gm script screenshot 2

we all agree that most of the google web apps our there are simple. i love simple things too. in fact i am a firm ‘believer’ of the KISS philosophy. any ways getting in to the subject. google search have been very simple so far. and it still is very much simple. but i feel at times that it could be much more simpler that it is. i don’t need it’s menu bar, and i don’t want to see all those other links it have on its page…etc so with my new found love jQuery, i thought of making it a bit more simpler. i just removed(set style to display : none; so its there, but its hidden from the user’s view) all most every thing other than the google logo and the text box in the page from the users view. it was pretty straight forward actually. here the code.


// ==UserScript==
// @name           simplegoogle
// @namespace      thekindofme
// @include        http://www.google.lk/
// @include    http://www.google.lk/webhp*

// ==/UserScript==

//hide the whole document till GM runs letsJQuery()
document.body.style.display = "none";

// Add jQuery
    var GM_JQ = document.createElement('script');
    GM_JQ.src = 'http://code.jquery.com/jquery-latest.js';
    GM_JQ.type = 'text/javascript';
    document.getElementsByTagName('head')[0].appendChild(GM_JQ);

// Check if jQuery's loaded
    function GM_wait() {
        if(typeof unsafeWindow.jQuery == 'undefined') {window.setTimeout(GM_wait,100);}
        else { $ = unsafeWindow.jQuery; letsJQuery(); }
    }
    GM_wait();

// All your GM code must be inside this function
    function letsJQuery() {
        $("body > div").hide();
        $("center > *").hide();
        $("center > div > div").hide();
        $("center > div").show().css("margin-top", "20px"); //the google logo

        $("center > form").show();
        $("center > form > table > tbody > tr > td").hide();
        $("center > form > table > tbody > tr > td:eq(1)").show();
        $("center > form > table > tbody > tr > td:eq(1) > input").hide();

        var searchTextBox = $("center > form > table > tbody > tr > td:eq(1) > input:eq(1)").css("padding", "2px 6px 4px 6px").css("color", "#555555").css("background-color", "#E2FFE4").css("border", "#dddddd 2px solid").css("-moz-border-radius", "10px").css("margin-top", "15px");
        $("body").show();

        arrangesearchTextBoxLayout(searchTextBox);

        $(window).resize(function(){
            arrangesearchTextBoxLayout(searchTextBox);
            });

        searchTextBox.show().focus();
    }

    function arrangesearchTextBoxLayout(searchTextBox){
        searchTextBox.css("width", ($(window).width() /2)).css("font-size",($(window).height() /22) + "px");

    }

keep it simple stupid :)

The MVCContrib project(at http://www.codeplex.com/MVCContrib ) provides a nice Grid ‘control’(and a lot more of course…) for use with MVC framework. Currently it does not support some what magical generation of javascript. so that you can have a ajaxified grid control. but with jQuery its pretty easy to ajaxify it. Today i finished ajaxifiying the paging of the grid, with a little bit of jQuery. So this is how i did it. i am sure that there must be betters ways of doing it. but this is what i came up with :P

Structure

First of all for this to work you will need some ’structure’ in your app. let’s take a little example. if you want to display a list of People in your application using a grid. you will have a controller for people: PeopleController. this would have a two actions. one would be List. while the other one would be ListForGrid. List action would be a plain action that will just return a normal view: List.aspx. while ListForGrid would return a asp ‘control’ or a partial view: ListForGrid.ascx. ListForGrid.ascx would know how to render a grid. it will use the normal Grid helpers to render a grid that displays people. List.aspx will contain an empty div:grid_holder and some java script that would add the Ajax goodness.

How it works

when a user asks for the People/List. the List.aspx would be rendered. and upon the document.ready() it would call ListForGrid action via ajax and load the resulting html in to the page. ListForGrid uses the ListForGrid.ascx to render the grid. ListForGrid action will also accept the necessary parameters to support Grid’s pagination(ListForGrid(int? page)).

this is all good. but still we have not archived our main goal. the rendered grid will have pagination links that would take us back to theListForGrid action directly. so if we click on them we would go to that resulting view with out any ajax ‘magic’. to get the grid to render them with ajax we are going to have to use jQuery to ‘hook’ in to the click event’s of the pagination related links( first | prev | next | last) that are in the rendered grid. and we will ‘intercept’ any events(clicks) and load the grid through ajax as necessary. so if you click on the next link in the grid we will stop the page from going to the relevant page, we will ‘read’ what ‘location’ that he is trying to take us to and we will load that through ajax.

now if you don’t understand what i am trying to tell you, please take a look at the source code bellow. i am sure you will understand that :)

relavant JS/jQuery source code

$gridHolder = "#grid_holder";
$ListForGridURL = "/People/ListForGrid";

//load the grid when the document loads. this code will run only once
$(document).ready(function() {
    $($gridHolder).load($ListForGridURL, null, function() {
        //now the grid is loaded call ajazPaging,
        //this will hook on to the click events
        ajaxPaging();
    });
});

//hook on to the click events(in a recursive way) of all pagination
//related links, and when clicked load the updated grid through AJAX
function ajaxPaging() {
    //this will zero in on the needed <a> elements in the Grid
    $(".paginationRight > a")
        .click(function(event) {
            //stop the browser from going to the relevant URL
            event.preventDefault();
            //this.href will give us the href value of the current
            //element, which have the URL from which we should update our grid
            $($gridHolder).load(this.href, null, function() {
                //call the function recursively so that the same code would
                //run when the user click on the pagination links after the loading happens
                ajaxPaging();
            });
        });
}

Update: with the release of jQuery 1.3 you can write this in a more simpler way using the live(). more info

Live Events

jQuery now supports “live events” – events that can be bound to all current – and future – elements. Using event delegation, and a seamless jQuery-style API, the result is both easy to use and very fast.


$gridHolder = "#grid_holder";
$ListForGridURL = "/People/ListForGrid";

//load the grid when the document loads. this code will run only once
$(document).ready(function() {
    $($gridHolder).load($ListForGridURL, null, function() {
        //now the grid is loaded call ajazPaging,
        //this will hook on to the click events

    //hook on to the click events of all pagination
    //related links, and when clicked load the updated grid through AJAX
    //this will zero in on the needed <a> elements in the Grid
    $(".paginationRight > a")
        .live("click", function(event) {
            //stop the browser from going to the relevant URL
            event.preventDefault();
            //this.href will give us the href value of the current
            //element, which have the URL from which we should update our grid
            $($gridHolder).load(this.href);
        });
    });
});
rest of the sample source code(controller, actions and views…)

<em>PeopleController </em>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

using MvcContrib.Pagination;
using MvcContrib;

namespace MvcLocalization.Controllers
{
    public class PeopleController : Controller
    {
        public ActionResult List()
        {
            return View();
        }

        public ActionResult ListForGrid(int? page)
        {
            ViewData["people"] = GetListOfPeopel().AsPagination(page ?? 1, 1);
            return View("UserListGrid");
        }
    }
}

<em>
List.aspx</em>

<h2>People</h2>
<div id="grid_holder"></div>

        $gridHolder = "#grid_holder";
        $ListForGridURL = "/People/ListForGrid";

        //load the grid when the document loads. this code will run only once
        $(document).ready(function() {
            $($gridHolder).load($ListForGridURL, null, function() {
                //now the grid is loaded call ajazPaging, this will hook on to the click events
                ajaxPaging();
            });
        });

//hook on to the click events(in a recursive way) of all pagination
related links, and when clicked load the updated grid through AJAX
        function ajaxPaging() {
            //this will zero in on the needed <a> elements in the Grid
            $(".paginationRight > a")
                 .click(function(event) {
                       //stop the browser from going to the relevant URL
                     event.preventDefault();
                    //this.href will give us the href value of the current <a> element, which have the URL from which we should update our grid
                     $($gridHolder).load(this.href, null, function() {
                          //call the function recursively so that the same code would run when the user click on the pagination links after the loading happens
                         ajaxPaging();
                     });
                 });
        }

    

<em>ListForGrid.ascx</em>

<%
    Html.Grid(
        "people",
            column =>
            {
                column.For(p => p.Name);
                column.For(p => p.Age);
                column.For(p => p.Address);
            }
            );
%>

Aha! that’s it. jQuery rocks eh!
hope this will be useful to someone/myself.

Note 1: if you are completely new to MVCContrib Grid, you might want to read this
Note 2: a article on “Using MVCContrib Grid in a Web 2.0 World with jquery and AJAX” (btw this article does not cover implementing paging through ajax…)

I love OO for a reason. i don’t go around saying that i love OO so that everybody think i know a thing or two about software architecture. but because it have dividends. when you have a good design in your software, things become easy. some planning and some structure can make a lot of things very easy. if you follow OO principals you will have better and simple code. its very simple! you don’t follow OO principals, or any other principal(that have some acceptance, or that you knows to work well) you will end up with UGLY CODE! it might be easy to write ugly code. it might feel very ‘efficient’, ‘high performence’, ‘robust’ code at first. but in the end you will not have much performance gains(well may be about 0.001%, because you decided not to use setters and getters, to save method calls!!!). it will only make thing look bad an ugly. and that’s not all. i can assure you that your code will end up so complicated and full with ‘booby trap type bugs’ waiting to surface and explode.

believe me i know that gut feeling you get when you are writing ugly, unstructured code. :) when you are writing stuff your mind will be ‘hot enough’ to handle all the complications that arise. so you will be able to wire up things, and put a few FLAGS! here are there and get things working smoothly. and you might feel smart too! but think for a minute. you are not supposed to write code to show how deep the ’stack of your brain’s cache’ is. it will be hard to understand in the future for your self. and what about others who look in to it? do you really think they want to read complicated code? it is not good to write complicated code. source code is supposed to be human friendly. you don’t need to become a machine. we have more than enough machine’s! think in a higher level people! we have enough processors/RAM and shit! brains are more precious than that stuff!

wow you want to write micro code? no i am not talking about short and straight forward code. i am talking about micro code! why would to want to write something in one line if it takes 2 minutes for someone to understand it, while the same thing could be written in 2 lines, making it easy for people to read. i mean sure it will be the same for the machine(in a given context). i think you should write code that is readable. making it too short while compromising human readability is bad.

every tiny nano/1000000000 second that you TRY to save for your compiler will cost the programmers 5 mins. think about it. is it worth it?

so now all of you(who are stupid and jobless to read this! :D ) know that this is a rant. now why i am ranting like this? well today i had to do some debugging in a project whose code was ‘tightly coupled’. yeah like really tight!!! the bug was in the UI layer, but i had to go thought the custom controller code(UI), UI code that uses the controllers, business logic… to understand how the “UI” works! the business class that was related to this incident had a property setter that was there just to support a certain feature in UI :O i am so shocked! this is so tightly coupled. but what can i do. i got to debug it anyways :)

huh… if you have to debug tightly coupled code my advice to you is… first of all claim down. and tell your self that sometimes you got to do things that you don’t like. and start debugging.

Next Page »