How to display and edit fractions in an MVC/Razor View - asp.net-mvc

I am trying to display and edit fractions in an MVC application. Previously in WPF I used the concept of a converter to take a double and format it as a fraction for the user and then take the users input and covert it back to a fraction. What would be the best way to do this in a Razor view?

I Admit it was harder than I thought. I'm still not sure if it's the best way to do it or not, but it works.
I defined a super simple Model:
public class MyModel
{
[DataType("Fraction")] //It's important to define DataType
public double MyDouble { get; set; }
}
and here is my simple controller:
public class HomeController : Controller
{
public ActionResult Index()
{
MyModel ViewModel = new MyModel { MyDouble = 0.06 };
return View(ViewModel);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
// Update db.
// Do what you want to do.
return View(model);
}
}
Index view (\Views\Home\Index.cshtml):
#model MvcApplication1.Models.MyModel
#{ViewBag.Title = "Index";}
#Html.DisplayFor(m => m.MyDouble)
<br />
#using (Html.BeginForm("Index", "Home"))
{
#Html.EditorFor(m => m.MyDouble)
<input type="submit" value="Submit" />
}
<script type="text/javascript">
function fraction_changing(hiddenFieldId, numeratorId, denominatorId) {
var numeratorValue = document.getElementById(numeratorId).value;
var denominatorValue = document.getElementById(denominatorId).value;
var hiddenField = document.getElementById(hiddenFieldId);
hiddenField.value = numeratorValue / denominatorValue;
}
</script>
You will find out about the above Javascript code in just a minute.
Display Template is also so easy. I just used Fraction class (By Syed Mehroz Alam) to convert double values to Fraction.
As you know, Templates (which are Partial Views) should be placed in "DisplayTemplates" and "EditorTemplates" folders under "Views" folder. I prefer to put them in "Views\Shared\" so all other Views are able to use it.
Fraction.cshtml (\Views\Shared**DisplayTemplates\Fraction.cshtml**)
#using Mehroz // Fraction class lives in Mehroz namespace
#inherits System.Web.Mvc.WebViewPage<double>
#{
Fraction fraction = new Fraction(Model);
}
#fraction.Numerator / #fraction.Denominator
Let's now take a look at the tricky part, which is Edit Template.
So here is Fraction.cshtml (\Views\Shared**EditorTemplates\Fraction.cshtml**)
#using Mehroz
#inherits System.Web.Mvc.WebViewPage<double>
#{
Fraction fraction = new Fraction(Model);
string numeratorStr = fraction.Numerator.ToString();
string denominatorStr = fraction.Denominator.ToString();
string unifier = Guid.NewGuid().ToString().Replace('-', '_');
string hiddenFieldElementId = string.Format("hiddenField{0}", unifier);
string numeratorElementId = string.Format("numerator{0}", unifier);
string denominatorElementId = string.Format("denominator{0}", unifier);
string onchangingFunctionSyntax =
string.Format("fraction_changing('{0}', '{1}', '{2}')",
hiddenFieldElementId,
numeratorElementId,
denominatorElementId);
}
#Html.HiddenFor(m => m, new { id = hiddenFieldElementId })
#Html.TextBox("Numerator", numeratorStr,
new { id = numeratorElementId, onchange = onchangingFunctionSyntax }) /
#Html.TextBox("Denominator", denominatorStr,
new { id = denominatorElementId, onchange = onchangingFunctionSyntax })
What this template really do is:
Converting double value (its model) to an instance of Fraction.
Showing numerator and denominator value of the Fraction in separate Text Input.
Recalculate the double value (by Javascript)
Because Javascript code needs unique Id to get an element by document.getElementById(id), our Edit Template has to generate these unique IDs for related elements.
You can download the code here: http://sdrv.ms/MlyDI2

The best and cleanest way to solve your issue is to develop a Display/Edit Template.
Here is a nice article about it:
Overriding DisplayFor and EditorFor to create custom outputs for MVC

Related

How to configure an MVC dropdown depending on which view calls it

I have two views, BatchReceipt and Receipt which utilise the same model. Until now they have used the same display template of ReceiptType. But I want to have one exclude certain items and the other to have the full list (so essentially a second .cshtml display template called ReceiptTypeFull). How do I configure each of these views in Visual Studio to utilise the different Display Templates?
Some additions to show the code being used:
I have file ReceiptType.cshtml being used as a DisplayTemplate which contains the following to setup the receipt dropdown
#using Clinton.Web.Helpers.EnumHelpers
#{
var item = EnumsHelper.GetNameFromEnumValue(Model);
}
I want to use a different DisplayTemplate, call it ReceiptTypeFull.cshtml
#using Clinton.Web.Helpers.EnumHelpersFull
#{
var item = EnumsHelper.GetNameFromEnumValue(Model);
}
#item
The difference is in calling the enumhelper or the enumhelperfull to vary the query populating the dropdown. My problem is that I cannot see how to redirect the view to use the different enumhelper/displaytemplate/
Thanks
I think I understand what you are getting at. You want to control which template is used for an Enum in the view.
I will explain using editor templates but it works the same way if you use display templates. You should be able to follow and apply for your scenario.
The idea is to use this overload of the editor html helper.
public static MvcHtmlString Editor(this HtmlHelper html, string expression, string templateName);
It is called like this
#Html.Editor("{property name}", "{template name}").
Below is an example to show it being used.
Suppose we have this enum
public enum MyItems
{
Item1 = 1,
Item2 = 2,
Item3 = 3
}
This helper
public static class MyEnumHelper
{
public static List<MyItems> GetAllItems()
{
return new List<MyItems>()
{
MyItems.Item1,
MyItems.Item2,
MyItems.Item3
};
}
public static List<MyItems> GetSomeItems()
{
return new List<MyItems>()
{
MyItems.Item1,
MyItems.Item2
};
}
}
This controller
public class HomeController : Controller
{
public ActionResult AllItems()
{
return View();
}
public ActionResult SomeItems()
{
return View();
}
}
We have these 2 editor templates, which are put in views/shared/editortemplates
First one called MyItems.cshtml which is the all one
#model MyItems?
#{
var values = MyEnumHelper.GetAllItems().Cast<object>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.ToString(),
Value = v.ToString()
});
}
#Html.DropDownList("", values)
Second one called MyItems2.cshtml which is the some one
#model MyItems?
#{
var values = MyEnumHelper.GetSomeItems().Cast<object>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.ToString(),
Value = v.ToString()
});
}
#Html.DropDownList("", values)
Then in the AllItems.cshtml to get the MyItems.cshtml template called we need
#model MyItemsViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.MyItem)
<submit typeof="submit" value="submit"/>
}
And in the SomeItems.cshtml to get some of the items by calling MyItems2.cshtml we use
#model MyItemsViewModel
#using (Html.BeginForm())
{
#Html.Editor("MyItem", "MyItems2") #* this bit answers your question *#
<submit typeof="submit" value="submit" />
}

Tempdata In Razor

learning MVC creating a simple date time signup form. i am trying to make two textboxes for the user. One for date and one for time.
I am going to save both values in a single dateTime field in my model.
So i need to figure out how to have a form field stored in tempdata to be accessed in the controller when posted. i can then combine the two text boxes to make a dateTime to store in my model.
I know how to get the tempdata in the controller, its just the razor syntax in the form i cant quite get.
Thanks in advance.
TempData is not fit for your purpose. You can not assign value in TempData/ViewData in a View. You can assign value in TempData/ViewData in controller only and access those value in View. For more information please refer this question: TempData moving from view to controler
I suggest you to use a ViewModel in your scenario having all properties what you need in your view. Please look into answer given by Mariusz at ASP.NET MVC - How exactly to use View Models
I think a better practice would be to have a view model containing all the information you want to pass from the form to the controller.
View Model:
public class MyViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public string Date { get; set; }
public string Time { get; set; }
}
Controller:
public ActionResult Signup()
{
var m = new MyViewModel();
return View(m);
}
[HttpPost]
public ActionResult Signup(MyViewModel m)
{
var username = m.Username;
var password = m.Password;
var date = m.Date;
var time = m.Time;
// ...
}
View:
#model MvcApplication5.Controllers.MyViewModel
#* ... *#
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Username)
#Html.PasswordFor(m => m.Password)
#Html.TextBoxFor(m => m.Date)
#Html.TextBoxFor(m => m.Time)
<input type="submit" value="Submit" />
}

How to get sequence/array index in Editor Template?

Case:
I have a list of items of Class X displayed using Editor Template for Class X.
Problem:
How can I get index of an item being processed on the inside of the Editor Template?
I've been using this HtmlExtension that returns only the needed id of an iteration. It's basically a regex on ViewData.TemplateInfo.HtmlFieldPrefix that's capturing the last number.
public static class HtmlExtensions
public static MvcHtmlString Index(this HtmlHelper html)
{
var prefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
var m = Regex.Match(prefix, #".+\[(\d+)\]");
if (m.Success && m.Groups.Count == 2)
return MvcHtmlString.Create(m.Groups[1].Value);
return null;
}
}
Can be used in an EditorFor-template like this:
#Html.Index()
Use a for loop instead of for each and pass the indexer into the EditorFor extension; razor should handle the rest.
#for(var i = 0; i < Model.count(); i++)
{
#Html.EditorFor(m => Model.ToArray()[i], new { index = i })
}
Update:
pass in the the index of the item using view data as show above.
In your editor template access the item via the ViewBag
<span> Item Index: #ViewBag.index </span>
Using the EditorTemplate is the best solution when viewing models that contain a list of something.
In order to find the index for the sub-model being rendered you can use the property that Razor sets by default:
ViewData.TemplateInfo.HtmlFieldPrefix
Say, for example, you have the following view models:
public class ParagraphVM
{
public int ParagraphId { get; set; }
public List<LineVM> Lines { get; set; }
}
and
public class LineVM
{
public int Id { get; set; }
public string Text {get; set;}
}
and you want to be able to edit all the "LineVM" within a "ParagraphVM". Then you would use an Editor Template so you would create a view at the following folder (if it doesn't exist) with the same name as the sub-model Views/Shared/EditorTemplates/LineVM.cshtml:
#model MyProject.Web.MVC.ViewModels.Paragraphs.LineVM
#{
//this will give you the List's element like Lines[index_number]
var field = ViewData.TemplateInfo.HtmlFieldPrefix;
}
<div id="#field">
#Html.EditorFor(l => l.Text)
</div>
Assuming you have a Controller's ActionResult that is returning a View and passing a ParagrapghVM viewmodel to a view, for example Views/Paragraph/_Paragraph.cshtml:
#model MyProject.Web.MVC.ViewModels.Paragraphs.ParagraphVM
#using (Html.BeginForm("Details", "Paragraphs", FormMethod.Post))
{
#Html.EditorFor(p => p.Lines)
}
This view would render as many editors for the list Lines as items contains that list.
So if, for example, the property list ParagraphVM.Lines contains 3 items it would render something like:
<div id="#Lines[0]">
<input id="Lines_0__Text name="Lines[0].Text"/>
</div>
<div id="#Lines[1]">
<input id="Lines_1__Text name="Lines[1].Text"/>
</div>
<div id="#Lines[2]">
<input id="Lines_2__Text name="Lines[2].Text"/>
</div>
With that you can know exactly what position each items is within the list and for example use some javascript to create a carousel or whatever you want to do with it. But remember that to edit that list you don't really need to know the position as Razor takes care of it for you. If you post back the model ParagraphVM, the list Lines will have the values bound (if any) without any additional work.
How about:
#using System
#using System.Text.RegularExpressions
var i = Convert.ToInt32(Regex.Matches(
ViewData.TemplateInfo.HtmlFieldPrefix,
#"\[([0-9]+)?\]")[0].Groups[1].ToString());
I think the easiest way is:
#Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, #"(?!\[)\d+(?=\])")
Or as helper:
public static string Index(this HtmlHelper html)
{
Match m = Regex.Match(html.ViewData.TemplateInfo.HtmlFieldPrefix, #"(?!\[)\d+(?=\])");
return m.Success ? m.Value : null;
}
Inspired by #Jona and #Ryan Penfold
You can use #Html.NameFor(m => m.AnyField). That expression will output the full name property including the index. You could extract the index there...

Tracking the number of times Ajax.ActionLink was called

In brief: is it possible to track the number of times an Ajax.ActionLink method was called?
Now for context. I've got a simple model:
public class Person {
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address {
public string City { get; set; }
public string Country { get; set; }
}
So, a person can have many addresses. On the Create page, I want the user to click a button that allows them to add as many Addresses as they want, dynamically.
I used this page as a reference in learning how to bind dynamically to a list: http://haacked.com/archive/2008/10/23/model-binding-toa-list.aspx,
With that as a reference, here are my classes:
HomeController:
//
// GET: /Home/
public ActionResult Index() {
return View();
}
[HttpPost]
public ActionResult Create(Person p) {
return View(p);
}
[OutputCache(NoStore = true, Duration = 0)]
public ActionResult AjaxAddAddress() {
TempData["key"] = DateTime.Now.Ticks.GetHashCode();
return PartialView("~/Views/Shared/EditorTemplates/Address.cshtml", new Address());
}
Index view:
#model ModelTest.Models.Person
<div>
#using (Html.BeginForm("Create", "Home")) {
<div>Name: #Html.TextBoxFor(m => m.Name)</div>
<div id="ajaxAddressBox"></div>
<p>#Ajax.ActionLink("Add Another Address", "AjaxAddAddress", new AjaxOptions {
UpdateTargetId = "ajaxAddressBox",
InsertionMode = InsertionMode.InsertAfter,
HttpMethod = "GET" })</p>
<input id="btnSubmit" type="submit" value="Create" />
}
</div>
Create View (just to confirm the model binded okay):
#model ModelTest.Models.Person
<div>
<p>You entered person: #Model.Name.</p>
<p>He has #Model.Addresses.Count total addresses.
#foreach (var c in Model.Addresses) {
<p>City: #c.City, Country: #c.Country</p>
}
</div>
Address editor template:
#model ModelTest.Models.Address
<p><input type="hidden" name="Addresses.Index" value="#TempData["key"]" />
City: #Html.TextBoxFor(x => x.City, new { Name = "Addresses[" + TempData["key"] + "].City" } )
Country: #Html.TextBoxFor(x => x.Country, new { Name = "Addresses[" + TempData["key"] + "].Country" })</p>
It seems to work ok, so I hope I'm doing this right so far. I'm new to MVC so please let me know if anything is totally wrong.
But I need it to do more. Ideally, I'd like to add a label that says "Address #(index)" for each line. But more important, I need to restrict the user to only adding, eg, 5 addresses. Either way, I'd like to track the number of times that Ajax.ActionLink, or the method AjaxAddAddress was called. Plus, in the future I'll need an edit page that also requires that restriction. Thus, if an existing person has 3 addresses, they can add only 2 more.
Any advice? It seems simple but I'm not sure how best to approach it. If I used a hidden field, how do you pass that value in Ajax.ActionLink and read it in my AjaxAddAddress method? Can you make a local client variable somehow?
I suppose a Session variable would work, but I always get nervous using that, not sure how long it lives or how reliable it is.
Here's one possible solution I came up with, with help from http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/.
Instead of using Ajax.ActionLink, I'm using Html.ActionLink and calling Ajax manually myself. That way, I can have it grab values right from Javascript. Whatever value I want, really: an expando, jquery.data, a hidden field, anything.
So my Ajax.ActionLink becomes:
#Html.ActionLink("Add Another Address", "AjaxAddAddress", null, new { id = "addItem" })
Then, within the same view I added this script:
<script type="text/javascript">
$("#addItem").click(function () {
$.ajax({
url: this.href + "?index=" + $("#ajaxAddressBox").children('div').size(),
cache: false,
success: function (html) {
$("#ajaxAddressBox").append(html);
}
});
return false;
});
</script>
I'm manually passing in an Index value to my AjaxAddAddresses method, and I'm basing that index value off the total number of div children currently in the ajaxAddressBox, or put another way, the current total number of addresses added. Thus, in the future when I build an Edit view, and it'll initially populate with existing addresses, this function will know how many addresses there are from the start.
AjaxAddAddresses becomes:
[OutputCache(NoStore = true, Duration = 0)]
public ActionResult AjaxAddAddress(int? index) {
if (index >= 5) return null;
TempData["key"] = DateTime.Now.Ticks.GetHashCode();
TempData["index"] = index + 1;
return PartialView("~/Views/Shared/EditorTemplates/Address.cshtml", new Address());
}
Thus, if the index is >= 5, I return null so that the user can't add more. (This could be done in the script block as well to save the wasted Ajax call, but at least when done server-side it can't be spoofed.)
And the Address Editor Template becomes:
#model ModelTest.Models.Address
<div><p><input type="hidden" name="Addresses.Index" value="#TempData["key"]" />
Address ##TempData["index"] ---
City: #Html.TextBoxFor(x => x.City, new { Name = "Addresses[" + TempData["key"] + "].City" } )
Country: #Html.TextBoxFor(x => x.Country, new { Name = "Addresses[" + TempData["key"] + "].Country" })</p></div>
Of course, other solutions are still welcome. This still feels needlessly complicated to me...
-ps, As it turns out, using a Session variable in my AjaxAddAddress method does work, but I can't shake the feeling that it could fail under some circumstances.

ModelMetadata.Watermark and MVC View Models

I'm struggling to display a watermark (so called placeholder) on my MVC3 form inputs.
There is already few posts down here talking including the quite focused one here:
Html5 Placeholders with .NET MVC 3 Razor EditorFor extension?
In these posts, advice is made to create tweaked html.TextBox templates.
In my case, my asset is that I should not need any editor template as I'm tweaking them inline.
Better than long talks, here is the relevant part of the actual code:
~/Models/myModel.cs
namespace myProject.Models {
public class myFormModel {
...
[Display(Name = "firstFieldName", Prompt = "firstFieldPrompt")]
public string firstFieldValue { get; set; }
...
}
}
~/Controllers/myFormSurfaceController.cs
namespace myProject.Controllers {
public class myFormSurfaceController : SurfaceController {
...
[ChildActionOnly]
public PartialViewResult myForm()
{
return PartialView("myPartialView", new myFormModel());
}
...
[HttpPost, ValidateAntiForgeryToken]
public ActionResult handleMyFormSubmit(myFormModel model) {...}
...
}
}
~/Views/myProject/Partial/myPartialView.cshtml
#model myFormModel
#{
using (Html.BeginUmbracoForm("handleMyFormSubmit", "myFormSurface", null, new Dictionary<string, object> { { "class", "myFormStyle" }, { "id", "myFormId" } }))
{
...
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", #placeholder = ViewData.ModelMetadata.Watermark })
...
}
}
Result is that the placeholder html tag is showing up correctly on my rendered webpage but is empty though Name tag is filled up correctly, even without DisplayName decoration set on my view model's property.
http://localhost/testpage
...
<input type="text" value="" placeholder="" name="firstFieldName" id="firstFieldName" class="myInputStyle">
...
What am I missing here ? I did try indeed to create both editor templates (MultilineText and String) in the correct folder (~/Views/Shared/EditorTemplates/) but I assume they are never called as I'm using "Html.TextBoxFor" and not "Html.TextBox"...
Other thing, if I remove "#placeholder = ViewData.ModelMetadata.Watermark" from the #Html.TextBoxFor call, I don't have any "placeholder" displayed on the rendered webpage. Which is good, this part of the call is definitively fine.
Thanks in advance for any help on that point...
Nicolas.
Edit:
What about if I create more variable in my model.
For instance:
public string firstFieldPrompt { get { return "bla"; } set { } }
and then
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", #placeholder = x => x.FirstFieldPrompt })
?
I realise this is an oldie, but you can use the ModelMetadata.FromLambdaExpression() method from within your view (without using templates), i.e.
#Html.TextBoxFor(x => x.firstFieldValue,
new {
#class = "myInputStyle",
#placeholder = ModelMetadata.FromLambdaExpression(x => x.firstFieldValue, ViewData).Watermark
})
Hope this helps someone :-)
The reason you get an empty watermark is that in your case (i.e. not using templates) ViewData actually refers to myFormModel (not myFormModel.firstFieldValue); you are essentially retrieving the watermark of your view model. Since models can't have watermarks ([Display] can't be applied to classes) ViewData.ModelMetadata.Watermark will always be empty for views.
As far as I can see, your only option here (if you don't want to use templates) is doing the watermark inline:
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", placeholder = "Your watermark text here" })
By the way, if want to use templates, you need to use the templated helpers #Html.EditorFor() and #Html.DisplayFor(). #Html.TextBoxFor() is just the strongly-typed version of #Html.TextBox(). It is not templated.

Resources