I did a AJAX History in a Dynamic Data Website article back in February 2009 to make filter respect the back button in the browser, this however was not enough for my customers they wanted the filter to be remembered even when they came back to the page without using the back button.
So I decided to store the filter info in Session state for this sample sample (but you could also use a database I have where the client wanted the filters to be remembered across sessions and by user).
I’m adding this extension to the previous sample from Five Cool Filters for Dynamic Data 4 (which now has a couple more filters added) to give history across all the filters.
Extension Methods
So to the code there are two extension methods for this sample AddFilterValueToSession which you can guess adds a filters current value to session and GetFilterValuesFromSession which retrieves an Dictionary of values for the specified table.
/// <summary> /// Adds the filters current value to the session. /// </summary> /// <param name="page">The page.</param> /// <param name="column">The column.</param> /// <param name="value">The value.</param> public static void AddFilterValueToSession(this Page page, MetaColumn column, Object value) { Dictionary<String, Object> filterValues; var objectName = column.Table.DataContextPropertyName; // get correct column name var columnName = column.Name; if (column is MetaForeignKeyColumn) columnName = ((MetaForeignKeyColumn)column).ForeignKeyNames.ToCommaSeparatedString(); // check to see if we already have a session object if (page.Session[objectName] != null) filterValues = (Dictionary<String, Object>)page.Session[objectName]; else filterValues = new Dictionary<String, Object>(); // add new filter value to session object if (filterValues.Keys.Contains(columnName)) filterValues[columnName] = value; else filterValues.Add(columnName, value); // add back to session if (page.Session[objectName] != null) page.Session[objectName] = filterValues; else page.Session.Add(objectName, filterValues); }
Listing 1 – AddFilterValueToSession
If you follow the code and comments you will see that first we try to get the session object for this table from session and if it is not there we simply create one. Then we add/update the value for this column and finally save back to session.
/// <summary> /// Gets the filter values from session. /// </summary> /// <param name="page">The page.</param> /// <param name="table">The table.</param> /// <param name="defaultValues">The default values.</param> /// <returns>An IDictionary of filter values from the session.</returns> public static IDictionary<String, Object> GetFilterValuesFromSession(this Page page, MetaTable table, IDictionary<String, Object> defaultValues) { var queryString = new StringBuilder(); var objectName = table.DataContextPropertyName; if (page.Session[objectName] != null) { var sessionFilterValues = new Dictionary<String, Object>((Dictionary<String, Object>)page.Session[objectName]); foreach (string key in defaultValues.Keys) { if (!sessionFilterValues.Keys.Contains(key) || sessionFilterValues[key] == null) sessionFilterValues.Add(key, defaultValues[key]); else sessionFilterValues[key] = defaultValues[key]; } var t = (Dictionary<String, Object>)page.Session[objectName]; return sessionFilterValues; } else return defaultValues; }
Listing 2 – GetFilterValuesFromSession
In Listing 2 we simply get the Dictionary object back from session and then return it.
Applying to the Filters
Listing 3 is a for the ForeignKey filter, here call the AddFilterValueToSession extension method passing in the Column and value from the DropdownList.
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { OnFilterChanged(); Page.AddFilterValueToSession(Column, DropDownList1.SelectedValue); }
Listing 3 – ForeignKey filter
Every filter in that sample has this applied and there are several different examples in there.
Extracting the Filter Values
Now all we need is to get the values and pass them back to the filters each time we arrive at a List page. In Listing 4 I have updated the Page_Init method, I first attempt to get the filter values from session if none are found I fall back to default List page behaviour.
protected void Page_Init(object sender, EventArgs e) { table = DynamicDataRouteHandler.GetRequestMetaTable(Context); var defaultValues = Page.GetFilterValuesFromSession(table, table.GetColumnValuesFromRoute(Context)); GridView1.SetMetaTable(table, defaultValues); GridDataSource.EntityTypeFilter = table.EntityType.Name; }
Listing 4 – Updating the List page
Now if we switch back to the ForeignKey filter and the Page_Init method Listing 5 you will see that I have not had to change anything here, because we are adding the default values in the List page they will arrive in each filter via the DefaultValue property.
protected void Page_Init(object sender, EventArgs e) { if (!Page.IsPostBack) { if (!Column.IsRequired) { DropDownList1.Items.Add(new ListItem("[Not Set]", NullValueString)); } PopulateListControl(DropDownList1); // Set the initial value if there is one string initialValue = DefaultValue; if (!String.IsNullOrEmpty(initialValue)) { DropDownList1.SelectedValue = initialValue; // optional add auto filters to history Page.AddFilterValueToSession(Column, initialValue); } } }
Listing 5 – updating the filter
I’ll show one complex example of setting up the values in a filter, we will look at me DateRange filter for this. First look at Listing 6 here I am concatenating both values together separated by ‘|’.
protected void btnRangeButton_Click(object sender, EventArgs e) { var button = (Button)sender; if (button.ID == btnClear.ID) { txbDateFrom.Text = String.Empty; txbDateTo.Text = String.Empty; } OnFilterChanged(); Page.AddFilterValueToSession(Column, String.Format("{0}|{1}", txbDateFrom.Text, txbDateTo.Text)); }
Listing 6 – DateRange button click event
Then if you look at the DateRange filters Page_Init method all need to do is check I have a value, reveres the concatenation and put the values in the correct textboxes.
protected void Page_Init(object sender, EventArgs e) { // set correct date time format txbDateFrom_CalendarExtender.Format = DATE_FORMAT; txbDateTo_CalendarExtender.Format = DATE_FORMAT; if (!Column.ColumnType.Equals(typeof(DateTime))) throw new InvalidOperationException(String.Format("A date range filter was loaded for column '{0}' but the column has an incompatible type '{1}'.", Column.Name, Column.ColumnType)); if (DefaultValue != null) { var values = DefaultValue.Split(new char[] { '|' }); if (values.Length == 2) { txbDateFrom.Text = values[0]; txbDateTo.Text = values[1]; } } }
Listing 7 – DateRange Page_Init method
Clear Filters
Last we add a button to clear the filters
<div class="DD"> <asp:ValidationSummary ID="ValidationSummary1" runat="server" EnableClientScript="true" HeaderText="List of validation errors" CssClass="DDValidator" /> <asp:DynamicValidator runat="server" ID="GridViewValidator" ControlToValidate="GridView1" Display="None" CssClass="DDValidator" /> <asp:QueryableFilterRepeater runat="server" ID="FilterRepeater"> <ItemTemplate> <asp:Label runat="server" Text='<%# Eval("DisplayName") %>' OnPreRender="Label_PreRender" /> <asp:DynamicFilter runat="server" ID="DynamicFilter" OnFilterChanged="DynamicFilter_FilterChanged" /> <br /> </ItemTemplate> </asp:QueryableFilterRepeater> <asp:Button ID="ClearFiltersButton" runat="server" Text="Clear Filters" CssClass="DDControl" OnClick="ClearFiltersButton_Click" /> <br /> </div>
Listing 8 – Clear filters button
and in the post back of the button
protected void ClearFiltersButton_Click(object sender, EventArgs e) { Page.ClearTableFilters(table); Response.Redirect(table.ListActionPath); }
Listing 9 – Clears filters button method
and lastly we need the method the clears the filters for us,
public static void ClearTableFilters(this Page page, MetaTable table) { var objectName = table.DataContextPropertyName; if (page.Session[objectName] != null) page.Session[objectName] = null; }
Listing 10 – Clear filters extension method
Lastly we will need to hide the button when there are no filters.
protected override void OnPreRenderComplete(EventArgs e) { RouteValueDictionary routeValues = new RouteValueDictionary(GridView1.GetDefaultValues()); InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert, routeValues); if (FilterRepeater.Controls.Count == 0) ClearFiltersButton.Visible = false; base.OnPreRenderComplete(e); }
Listing 11 – hiding the clear filters button
So then all done then.
12 comments:
Hi, Steve! It is very elegant solution.
And, what you think about following changes.
Thanks!
ValZ
protected void Page_Init(object sender, EventArgs e)
{
table = DynamicDataRouteHandler.GetRequestMetaTable(Context);
var defaultValues = Page.GetFilterValuesFromSession(table, (Dictionary)table.GetColumnValuesFromRoute(Context));
GridView1.SetMetaTable(table, defaultValues);
GridDataSource.EntityTypeFilter = table.EntityType.Name;
}
public static Dictionary GetFilterValuesFromSession(this Page page, MetaTable table, Dictionary defaultValues)
{
var queryString = new StringBuilder();
var objectName = table.DataContextPropertyName;
Dictionary filterValues;
if (page.Session[objectName] != null)
{
filterValues = (Dictionary)page.Session[objectName];
foreach (string key in defaultValues.Keys)
{
if (filterValues[key] == null)
filterValues.Add(key, defaultValues[key]);
else
filterValues[key] = defaultValues[key];
}
return filterValues;
}
else
return defaultValues;
}
Hi ValZ, you solution is correct I have not considered integrating redirects from other ListPages with filter values. I will add to the sample well done.
Steve.
Hi ValZ, I've updated appropriatly.
Steve
Thanks Steve, your solution helped me... once again!
LB
Great job Steve! These filters help me a lot.
I'm working on a big update and an oss project hope to be oput early 2012 :)
Steve
Hi
I like this implementation but I'm having one problem with it. If a user have selected some filters, goes into edit and then click on the back button in the browser, the previosly selected filters are not selected. However if I update with F5 the previously selected filters gets selected. Is there any awy to make the filters being selected when the user press on the browsers back button?
yes there is a version of that on my blog here AJAX History in a Dynamic Data Website have fun.
Steve
So I will have to combine them if I would like the browser back button support plus the ability to save the selected filters during the whole session?
Yes I would take the principals of one and apply them to the other.
Steve
Hi
I'm trying to implement a multi select filter on a foreign key column. I have managed to accomplish this but but I don't know how to save and load the selected values. In your example you just save one value for each filter, is it possible to to the same with more values for the same column? Do you know how this could be accomplished? Is it possible to give more than one value to the defaultvalue parameter of the SetMetaTable method?
I have done this e-mail me and I will send you my MultiForeignKey Filter
Steve
Post a Comment