Telerik Styles with multiple views in ASP.NET MVC - c#

In a previous post I had trouble setting the styles on my views in the same way that they do it on the telerik demo site by selecting the style from a dropdown. This was answered however I now have a new but related problem.
I have multiple pages on my site (as is common to MVC) and each of these pages have many telerik controls on them. Allowing the user to style the site using predefined css done for me is great and saves me a lot of work.
The problem is that when I move from page to page, the combo box that telerik uses resets to its default value, thus resetting the styles on the site everytime the user changes a pages (not ideal).
I have tried using sessions to store the state, but there is no way to detect postback like with standard aspx development. So I cant do something like:
if(isPostBack)
{
if(string.isNullOrEmpty(Session["MyTheme"]+""))
{
Session["MyTheme"]="black";
}
else
{
Session["myTheme"]=//Some combo box selected value
}
}
Even if I could, the combo box onChange event is handled via JavaScript but the Telerik control requires C# Razor and try as I mgiht, I cant get them to talk and share a simple value.
Ultimately all I want to do, is allow the user pick a theme from the combo box and from then on, that theme is remembered throughout the site until they next change it.
I have tried query strings and sessions, but neither work as I cant access them in JavaScript. Aparently they are used on the server side only.
I have tried cookies but that doesnt work because I cant access them in C# Razor. Aparently they are client side only.
Below is my code:
<head>
#(
Html.Telerik().StyleSheetRegistrar()
.DefaultGroup(group => group
.Add("telerik.common.css")
.Add(string.IsNullOrEmpty(#Html.ViewContext.HttpContext.Request.QueryString["theme"]) ? "telerik.black.css" : "telerik."+#Html.ViewContext.HttpContext.Request.QueryString["theme"]+".css").Combined(true).Compress(true)
))
</head>
<body>
#(
/* TELERIK COMBOBOX */
Html.Telerik().ComboBox()
.Name("cbxTheme")
.SelectedIndex(0)
.ClientEvents(events => events.OnChange("cbxTheme_onChange"))
//.BindTo((IEnumerable<DropDownItem>)ViewData["Files"])
.Items(item =>
{
item.Add().Text("black");
item.Add().Text("common");
item.Add().Text("default");
item.Add().Text("forest");
item.Add().Text("hay");
item.Add().Text("metro");
item.Add().Text("office2007");
item.Add().Text("office2010black");
item.Add().Text("office2010blue");
item.Add().Text("office2010silver");
item.Add().Text("outlook");
item.Add().Text("rtl");
item.Add().Text("simple");
item.Add().Text("sitefinity");
item.Add().Text("sunset");
item.Add().Text("telerik");
item.Add().Text("transparent");
item.Add().Text("vista");
item.Add().Text("web20");
item.Add().Text("webblue");
item.Add().Text("windows7");
})
)
#(Html.Telerik().ScriptRegistrar().DefaultGroup(group => group.Combined(true).Compress(true)))
</body>
<script type="text/javascript">
function cbxTheme_onChange()
{
var selectedItemText = $("#cbxTheme").data("tComboBox").text();
//var selectedItemValue = $("#cbxTheme").data("tComboBox").value();
window.location.href = window.location.protocol
+ '//'
+ window.location.host
+ window.location.pathname
+ '?theme='
+ selectedItemText;
}
</script>
As I explained, for the most part it works fine. Execpt when I click on a likn to another page. Then everything gets set back to a preset default.
Ideally what I am looking for is a way to do a postback when a new item is selected in the combo box (like in the JavaScript). The style is changed so the whole page needs to be refreshed anyway. This works. But when I move to another page, it resets to a default style. So I need a way to store the selected style either client side or server side (preferred as my pages are loaded this way).
I have read this can be done by using a controller but it is not clear how. I would like the controller method if possible, because I am going to use a controller to load a list of CSS styles dynamically allowing the user to download additional styles and they will be added to the list automatically. So anything along this line would be great.

You can create a static class with a static property which will act as a global property.
public static class MyTheme
{
public static string MyGlobalTheme { get; set; }
}
or you could you the Application class.
Application["MyTheme"] = "black";
You could put this code in _Layout.cshtml. If your project name is TProj and your static class is in a folder called Objects, it would look like this.
_Layout.cshtml
#{
if (#Html.ViewContext.HttpContext.Request.QueryString["theme"] != null)
{
TProj.Objects.MyTheme.MyGlobalTheme = Html.ViewContext.HttpContext.Request.QueryString["theme"];
}
else
{
if (TProj.Objects.MyTheme.MyGlobalTheme == null)
{
TProj.Objects.MyTheme.MyGlobalTheme = "black";
}
}
}
Now in after this in _Layout.cshtml, you can use the string #TreasuryReportsMvc.Objects.MyTheme.MyGlobalTheme, which should stay the same even when you go to another page. _Layout.cshtml may not be the best place for this logic. You should think about where it makes the most sense for your project.
Be aware that global variables are frowned on by many. This question has a good discussion of Asp.Net MVC global variables.

Related

How can I respond to events on a page in an ASP.NET MVC app that need to access data within the View?

In my ASP.NET MVC project, I need to dynamically manipulate some html elements when the user makes a couple of selections. I assume this needs to be done in code-behind C# (as opposed to in jquery), because how I respond is using data that is retrieved from a SQL Server database and is part of the View (I don't think jQuery would/could know about this MVC/.NET-specific data).
Specifically, when a user clicks a checkbox on the page, provided a selection has been made from an html-select element, my code needs to spring into action to populate other elements on the page.
The View (.cshtml) is getting the data in a Razor code block like so:
#model WebAppRptScheduler.Models.HomeModel
#using System.Data
#{
DataTable dtUnitReportPairEmailVals = Model.UnitReportPairEmailVals DataTable;
var unitRptPairEmailVals = from x in dtUnitReportPairEmailVals.AsEnumerable()
select new
{
emailAddr = x.Field<string>("EmailAddr")
};
This data comes from the Controller:
DataTable UnitReportPairEmailValsDT = new DataTable();
UnitReportPairEmailValsDT = SQL.ExecuteSQLReturnDataTable(SQL.UnitReportPairEmailQuery, CommandType.Text, null);
model.UnitReportPairEmailVals = UnitReportPairEmailValsDT;
Does this (event handling) code belong in the Controller? If so, how is the method declared/decorated so that there is a connection between it and the html elements whose events it needs to monitor?
This is what needs to happen:
User selects an option from an html select
User clicks a checkbox
The code determines that both an option has been selected, and a checkbox checked; it then uses the data to populate other controls, something like this:
#if (unitRptPairEmailVals.Count > 0)
{
email1.Text = unitRptPairEmailVals[0];
}
#if (unitRptPairEmailVals.Count > 1)
{
email2.Text = unitRptPairEmailVals[1];
}
#if (unitRptPairEmailVals.Count > 2)
{
email3.Text = unitRptPairEmailVals[2];
}
The above would populate the text/value of these html inputs:
<label class="margin4horizontal">Email 1</label><input type="text" name="email1" id="email1" />
<label class="margin4horizontal">Email 2</label><input type="text" name="email2" id="email2" />
<label class="margin4horizontal">Email 3</label><input type="text" name="email3" id="email3" />
BTW, if I'm barking up the wrong tree, or am even in the wrong forest altogether, please let me know what the preferred approach is.
UPDATE
Once I pieced together several different answers to this and related questions, I wrote up a tip on how to do this here.
Update: I was answering on mobile last night, and here is a clearer edit and some sample code...
Asp.net MVC has controllers, which contain actions. When you navigation to a URL (HTTP GET), ASP.net translates this via routes into a specific controller action to invoke, then the result of that action is sent back to the browser as plain HTML.
Also when you submit a form (HTTP POST) to specific URL it translates to invoking a controller action via routes. and a ModelBinder will read the body of that HTTP POST and converts it into your Model class. Unlike the classic web forms that used ViewState to track page controls, state, and events.
What you want to achieve can be done in 2 different ways...
Using JavaScript and JQuery. You can issue an Ajax request (GET, POST, etc) to a URL that exists in your ASP.net MVC routes table to execute an action and the results will be returned back to your JavaScript without navigating to another page.
Posting an HTML Form back to the server will allow ASP.net MVC to read the new/changed values in your HTML controls and pass those new values to the controller action, which in turn can render the same view again with the new values and behaviour.

Custom data annotation to hide property based on another's value

Having a nightmare figuring this out and I am now flat against the brick wall
Using MVC framework I'm building essentially a simple form.
I'm keeping the view as simple as possible and using just editorForModel()
So within this forms model I'm using data annotations to add some more customisations to the properties.
I would like to create a custom data annotation, one that will hide the property it has been placed against based on the value of another property.
E.g
I have a drop down with yes or no.
And a field for name.
When the drop down is no it hides the name field in the form.
I can achieve this with JS but it would be nice going forward to have this as a simple data annotation for the model.
E.g [HideField("dropdown", "no")]
Thanks in advance
You're going to want to handle this on the client side, not server side (using an Attribute).
If you're already using jQuery, something like this would do. Here is the jsFiddle.
$(function () {
var dropdown = $('#dropdown').change(function (event) {
if ($(this).val() == "no") {
$('#fieldToHide').hide();
} else {
$('#fieldToHide').show();
}
});
});
The reason you want to do it on the client side is pretty simple, any other method would require a post back with every change of the dropdown to check the value..
** EDIT **
From comment:
Is it possible to inject this JavaScript into the page... ...Rather than having the essentially the same script duplicated on each page
This could be rewritten, a little more generic, so it could be reused on different pages. Add it to yoursite.js (which you should already have..) and then simply wire it up on each page as desired. Updated jsFiddle.
Site.JS
function hideIf(selectField, hideField, hideValue) {
$(selectField).change(function (event) {
if ($(this).val() == hideValue) {
$(hideField).hide();
} else {
$(hideField).show();
}
});
}
And then in each page you want to use it, wire it up like this:
$(function () {
hideIf("#dropdown", "#fieldToHide", "no");
});
This way you could make multiple calls to hideIf on different select fields without rewriting the script above..

Manage form data on click of asp.net back button (other than handling though Session or Viewstate)

I have a form on search page having textboxes, dropdowns, checkboxes. After selection of Search Criteria and clicking on Search button search results displays on another page. I am using a asp button on search result page to go back to search page and I want to retain the form data (textboxes, selection of dropdowns/checkboxes etc. which I have made earlier for search). I have implemented it through Session and Viewstate so I want to know some other way to manage this.
Many Thanks for you replies,
Harish Bhatt
There are many things You can do.
1.Cross Page Scripting
One of the problems faced by us in ASP.NET 1.x versions is the inability to transfer data between pages easily. Since ASP.NET 1.x pages postback to the same page by default, and you cannot do a post to another page (unless you remove the runat="server" and other messy things), accessing the controls in the previous page becomes very difficult unlike classic ASP where you have a action tag to specify the new page and you can use the Request.Form to access the previous page values.
There is a more effective way of accessing the Controls in the previous page in ASP.NET 2.0. Its using the PreviousPage property of the Page.
Say we have a page Default.aspx with a Textbox "Text1" and a Button "Button1".
We can access the controls in Default.aspx from another page by the following steps:-
A. Setting the PostBackUrl property of the Button to the New Page, as follows:-
<asp:Button ID="button1" Runat=server Text="submit" PostBackUrl="~/NewPage.aspx" />
B. Then, in the NewPage.aspx.cs, you can access the TextBox control on Default.aspx as follows:-
public void page_load()
{
if(!IsPostBack)
{
TextBox tb = (TextBox)PreviousPage.FindControl("Text1");
Response.Write(tb.Text);}
}
Note that a new TextBox tb is declared and typecasted using the PreviousPage and FindControl of the ID of the Control in the Previous Page. However, to retain the value across postbacks within the new page, its better to assign the text to a TextBox or Label in the NewPage.aspx such that its value is not lost across postbacks. The reason behind is that, the PreviousPage becomes invalid once you postback in the same page.
2.Sessions
You can refer the below link for help
http://asp.net-tutorials.com/state/sessions/
example
Session["Value"]="Any Data";
You can access it in any page
3.Cookies
Creating a Cookie Object
HttpCookie _userInfoCookies = new HttpCookie("UserInfo");
Setting values inside it
_userInfoCookies["UserName"] = "Abhijit";
_userInfoCookies["UserColor"] = "Red";
_userInfoCookies["Expire"] = "5 Days";
//Adding cookies to current web response
Response.Cookies.Add(_userInfoCookies);
You can use a query string that holds the IDs of the selected items in textbox, dropdown, check box, etc., like this:
Search.aspx?textId=3&dropdownId=7
You would also need to pass these values to your results page, so they can be passed back to the search page when you want to get back.
Results.aspx?textId=3&dropdownId=7
Note: This alternative becomes unwieldy as the number of parameters increases, but it is a viable alternative to Session cache.
I recommend storing clientstate information in a the database -- they are occasionally useful that way! You can define the corresponding rows and columns to represent the state info you need to track or just put all of your state info into a persisted stream (using json, xml etc.) as long as your don't to to search or perform db operations on the streamed values. Putting this in a database makes it easy to migrate to a web farm, or add failover, etc. The clientstate database database does not have to be the same database as the rest of your application for more scalability. If you are using sessionstate, you can also configurate asp.net to keep session state in a database to accomplish the same thing. Personally I store the heavily used clientstates in custom table, and use the general streamed method for lower volume clientstate info.
The trick is to use the sessionid as the key (or the sessionid + XKEY) where XKEY is needed if you need to store multiple copies of state info in the same table. e.g., on a storefront app XKEY might represent page # or suchlike. I also use this to generate a unique XKEY and pass the XKEY in the uri query parameterlist -- you only have to persist XKEY from page to page, not a long list of parameters.
I have already described the situation above here I was trying to maintain the Form data of search page on click of Back button from search result page, details page and other pages. To maintain this I used the Class SearchCriteria. Below is the sample code for Class
public class SearchCriteria
{
private string _strName;
private string _strCode;
public string strName
{
set { _strName = value; }
get { return _strName; }
}
public string strCode
{
set { _strCode = value; }
get { return _strCode; }
}
}
I have assigned all Search Criteria to this class and kept that in Session so that I can access this anywhere.
SearchCriteria objSearch = new SearchCriteria();
objSearch.strName = txtFirstName.Text;
objSearch.strCode = txtCode.Text ;
if (Session["SearchCriteria"] != null)
{
Session.Remove("SearchCriteria");
Session["SearchCriteria"] = objSearch;
}
else
{
Session["SearchCriteria"] = objSearch;
}
One Click of Back button from search result, details and other pages I am redirecting to Search Page with the QueryString PostBack=1
protected void btnBack_Click(object sender, EventArgs e)
{
Response.Redirect("Default.aspx?PostBack=1", false);
}
And on the Page_Load of Search page I checked for the 'QueryString' PostBack and based on the QueryString, I have assigned the Search Criteria to the Controls from Session.
if (Request.QueryString["PostBack"] != null)
{
SearchCriteria objSearch = new SearchCriteria();
if (Session["SearchCriteria"] != null)
{
objSearch = (SearchCriteria)Session["SearchCriteria"];
txtFirstName.Text = objSearch.strName;
txtCode.Text = objSearch.strCode;
}
}
I don't know what is recommended for different situations but like to know. Please share your ideas.
Many Thanks to Ashish Bahu, his answer helped me a lot on this.
I was looking for any other good way to do this without using Session or Viewstate but didn't found anything and finally end up with the above solution, If someone is having any other good solution for this please share. Thanks!

Adding dynamic generic html control to page destroys bound dropdownlist

I encountered some weird behaviour today and I was hoping someone could shed some light on it for me because I'm perplexed.
I have a couple of methods I use to interact with the ui for the sole purpose of displaying error/success/warning messages to the user.
Here is one of them
public static void Confirm(string text)
{
var page = (Page)HttpContext.Current.Handler;
var uiConfirm = new HtmlGenericControl("div")
{
ID = "uiNotify",
InnerHtml = text
};
uiConfirm.Attributes.Add("class", "ui-confirm");
page.Master.FindControl("form1").Controls.AddAt(2, uiConfirm);
}
This works perfectly fine except for one nuance I encountered this morning and I was hoping someone could shed some light on it for me.
I am working on your run of the mill profile editing page. In this page, I am binding a couple of dropdownlists (country, province/state) on page load. I have a submit at the bottom and a click event that fires to update the information, then call the method above to notify the user that their information was successfully updated. This works the first time you click the submit button; the page posts back, the information gets updated in the database, the dynamically added div gets popped in, confirm message is displayed and all is good. However, if you then click the submit button again, it fails stating SelectedItem on the dropdowns I'm binding in the page load is null (Object reference not set to an instance of an object). The dropdown is actually wiped for some reason on the second postback, but not the first.
In sheer desperation after trying everything else, I decided to take out the call to the confirm method... and strangely enough the error disappears and I can update the information on the page as many times as I like.
If I add a generic control statically to the page I'm working on, and change my method slightly so that instead of adding a generic control to the form dynamically it just finds the generic control on the page, that does no produce the same error.
The problem also goes away if I remove the two dropdowns from the page or just stop interacting with them.
Why on earth would adding a dynamic control to the form wipe my dropdowns on postback?
I think you should consider using the PlaceHolder class in your MasterPage, the AddAt(2, uiConfirm) is going to bite you and probably is:
Markup:
.......
<asp:PlaceHolder id="PlaceHolder1"
runat="server"/>
......
Code-behind:
public static void Confirm(string text)
{
var page = (Page)HttpContext.Current.Handler;
var uiConfirm = new HtmlGenericControl("div")
{
ID = "uiNotify",
InnerHtml = text
};
uiConfirm.Attributes.Add("class", "ui-confirm");
//may need to change depending on where you put your placeholder
Control placeHolder = page.Master.FindControl("PlaceHolder1");
placeHolder.Controls.Clear();
placeHolder.Controls.Add(uiConfirm);
}

What's the best place to put default content in an MVC application?

I'm working on a sort of a CMS/Wiki application to help me experiment with the new Asp.Net MVC framework, and I'm trying to wrap my head around some of the code organization.
Right now, I have three views that cover displaying an article: Index, Edit, and Rename. All three views display the contents of the current page, or placeholder content stating that the page does not exist.
This is currently accomplished with the following code in the action method for each view:
MyPage myPage = null;
if (!string.IsNullOrEmpty(pageName)) {
myPage = mRepository.GetMyPage(pageName);
}
//Page does not exist.
if (myPage != null) {
ViewData["pageContent"] = myPage.GetParsedSource(new PageState());
ViewData["pageSource"] = myPage.Source;
ViewData["title"] = myPage.Title;
}
else {
ViewData["title"] = pageName;
ViewData["pageContent"] = "Page does not exist, feel free to create it!";
ViewData["pageSource"] = "";
}
ViewData["pageName"] = pageName;
My question is, where should this logic actually go?
1) The Controller (as it is now), which requires the above code to be replicated across action methods?
2) The Model, defaulting values for pageSource to the verbiage shown above? This would have the downside of moving display text into the model.
3) The View, using a null coalescing operator to convert null ViewData entries to their defaults?
4) In the Views, but add additional controllers to handle cases where the pageName does not exist.
EDIT:
Hopefully this should clarify things a little. The flow of the application is as follows:
When the user enters a URL (i.e. /pages/page_title), they arrive at a screen which displays the content of the article, along with hyperlinks labeled "edit" and "rename."
Clicking edit displays a page which contains the article content, as well as form controls to edit the article's source.
Clicking rename displays a page which contains the article content, as well as form controls to edit the article's name.
I would have several actions:
Lookup
Display
Create
Edit
Rename
In your default Lookup controller action (which gets hit when the user asks for, say, "/wiki/article-title"), you can redirect (RedirectToAction()) to the appropriate action as necessary. That encapsulates your Create logic into its own controller, and can also be called directly (RESTful). Same with the others. That also allows you to keep your views very, very stupid (always a good thing).
I would keep it in the controller but extract it out so that you don't have to replicate the code in each of the actions.
Maybe set some defaults in the controller's constructor and then have a separate private method (ie. not an action method) that takes your MyPage object and sets the viewdata that is shared between your actions.

Categories

Resources