I'm new to MVC and php framework, so please excuse me for this simple question...
I like to have my views without big chunk of php code but I have a case where I don't really know how to do it properly.
Basically some object has 20+ different states and the state is given by the model.
Now I have a :
switch($object->getState())
{
case 0:
$sText = '...';
break;
case 1:
$sText = '... on the'.$object->getDate();
break;
...
case 20:
$sText = '...';
break;
}
?>
<img src="<?echo $object->getState()?>.png" alt = "<?echo $sText;?>"
title = "<?echo $sText;?>" />
How can I do that without the 40+ lines of php in the view ? I don't want to have to repeat the <img> tag 20 times. For me the text should belongs to the view, not the model.
Maybe a view helper that will assign the text to the state ?
A view helper isn't going to make your code any less ambiguous or better MVC. The text for the view might not belong to the Model, but the logic for determining the text definitely doesn't belong to the view. There's nothing wrong with writing a method like $model->getViewTextForState($object->getState()) - that's basically the same approach as you'd use for string localization for numerous languages.
Think of it this way - the alt text for the view really does belong to the model because the model is responsible for marshalling ALL of the data. If some text in the view is variable, then it is literally model data, just like the image name you're producing from the $object->getState() method. The image name and the alt text for it are data and should be provided to the view from the model with a single line access method
The switch could be in your action, and you could use translation strings like this
$this->sText = 'object_state_' . $object->getState()
then in your view, translating $sText would do the trick.
Related
I am using data validation rules on a Google Spreadsheet.
In my scenario, I need users to entry only valid values. I use the 'Reject input' to force them to write only validated content.
However, the 'Reject input' option works for manually entried data only, but it does not work if the user pastes content into the cell from a different source (e.g. a MS Excel document). In that case, a warning is still shown but the invalid value is written.
In other words, I need the 'Reject input' option to work also with pasted content.
OR... another approach would be to programmatically check the validity of the value according the Datavalidation rule for that cell.
Any ideas?
Thank you in advance.
I had a little play with this.
I had inconsistent behavior from google.
On occasion when I ctrl-c and ctrl-p, the target cell lost its data validation!
To do this programmatically
Write myfunction(e)
Set it to run when the spreadsheet is edited, do this by Resources>Current Project's Triggers
Query e to see what has happened.
Use the following to gather parameters
var sheet = e.source.getActiveSheet();
var sheetname = sheet.getSheetName();
var a_range = sheet.getActiveRange();
var activecell = e.source.getActiveCell();
var col = activecell.getColumn();
var row = activecell.getRow();
You may wish to check a_range to make sure they have not copied and pasted multiple cells.
Find out if the edit happened in an area that you have data validated;
if (sheetname == "mySheet") {
// checking they have not just cleared the cell
if (col == # && activecell.getValue() != "") {
THIS IS WHERE YOU CHECK THE activecell.getValue() AGAINST YOUR DATA VALIDATION
AND USE
activecell.setValue("") ;
to clear the cell if you want to reject the value
}
}
The obvious problem with this is that it is essentially repeating programmatically what the data validation should already be doing.
So you have to keep two sets of validation rules synchronized. You could just delete the in sheet data validation but I find that useful for providing the user feedback. Also is the data validation you are using provides content it is practical to leave it in place.
It would be great if there was a way of detecting that ctrl-p had been used or one of the paste-special options and only run the script in those cases. I would really like to know the answer to that. Can't find anything to help you there.
Note also that if someone inserts a row, this will not trigger any data validation and the onEdit() trigger will not detect it. It only works when the sheet is edited and by this I think it means there is a content change.
onChange() should detect insertion, it is described as;
Specifies a trigger that will fire when the spreadsheet's content or
structure is changed.
I am posting another answer because this is a programmatic solution.
It has a lot of problems and is pretty slow but I am demonstrating the process not the efficiency of the code.
It is slow. It will be possible to make this run faster.
It assumes that a single cell is pasted.
It does not cater for inserting of rows or columns.
This is what I noticed
The onEdit(event) has certain properties that are accessible. I could not be sure I got a full listing and one would be appreciated. Look at the Spreadsheet Edit Events section here.
The property of interest is "e.value".
I noticed that if you typed into a cell e.value = "value types" but if you pasted or Paste->special then e.value = undefined. This is also true for if you delete a cell content, I am not sure of other cases.
This is a solution
Here is a spreadsheet and script that detects if the user has typed, pasted or deleted into a specific cell. It also detects a user select from Data validation.
Type, paste or delete into the gold cell C3 or select the dropdown green cell C4.
You will need to request access, if you can't wait just copy & paste the code, set a trigger and play with it.
Example
Code
Set the trigger onEdit() to call this or rename it to onEdit(event)
You can attach it to a blank sheet and it will write to cells(5,3) and (6,3).
function detectPaste(event) {
var sheet = event.source.getActiveSheet();
var input_type =" ";
if (event.value == undefined) { // paste has occured
var activecell = event.source.getActiveCell();
if (activecell.getValue() == "") { // either delete or paste of empty cell
sheet.getRange(5,3).setValue("What a good User you are!");
sheet.getRange(6,3).setValue(event.value);
input_type = "delete"
}
else {
activecell.setValue("");
sheet.getRange(5,3).setValue("You pasted so I removed it");
sheet.getRange(6,3).setValue(event.value);
input_type = "paste";
}
}
else { // valid input
sheet.getRange(5,3).setValue("What a good User you are!");
sheet.getRange(6,3).setValue(event.value);
input_type = "type or select";
}
SpreadsheetApp.getActiveSpreadsheet().toast('Input type = ' + input_type, 'User Input detected ', 3);
}
I am trying to alternate a CSS class in a <fieldset>. Normally a #helper function would work fine (see the #helper below), however I have two (2) circumstances that won't allow it to work properly.
First, I am using a Input.Edit.cshtml field template in ~/Views/EditorTemplates/Fields.
This basically means that as I build up a form, the Input.Edit.cshtml file is being called each time for as many <input>'s as I have in my form.
Second, I am using a condition to check the path of the URL in order to only apply this CSS alternating class on pages below a certain path. Specifically, I want to apply this change to pages under my ~/Services path. On all other pages I do not want the change applied. I check this condition using Request.Url.AbsoluteUri as you see below.
This is where I think my problem lies, as the alternating code is applied but then because it is called again for the same condition on the same page it is applied incorrectly due to my logic.
Now it could be that I am just stuck with the problem.
Here is the code:
#{
string CurrentUrl = Request.Url.AbsoluteUri;
}
#helper ResponsiveCss(string cssClass)
{
if (ViewBag.count == null) { ViewBag.count = 0; }
<text>class="#(ViewBag.count % 2 == 1 ? cssClass : "one-half last")"</text>
ViewBag.count++;
}
#if (CurrentUrl.Contains("Services"))
{
<fieldset #ResponsiveCss("one-half")>
//Label and Input code
</fieldset>
}
What should happen is that the class="one-half" is applied on the first <fieldset> that is created in the form, and then class="one-half last" on the second that is created.
Instead what is happening is that class="one-half" is NOT being applied on the first <fieldset> that is created, but rather ALL <fieldset>'s are being created with class="one-half last".
Sorry if that is not clear. Any thoughts on if I can make this work given the circumstances (and how)? Thanks.
Hopefully this solves your problem.
Alternating style
Instead of alternating the class name, you can use the nth-of-type or nth-child pseudo-classes.
fieldset:nth-of-type(odd) {}
fieldset:nth-of-type(even) {}
See this example: http://jsfiddle.net/cx9UG/
Note: These pseudo-classes can also be used in JQuery selector and document selector (querySelector and querySelectorAll)
CSS depending on URL
This is really CSS depending on the view. Just create two CSS bundles then apply in to the appropriate views.
View with alternating style #Styles.Render("~/Content/alternating")
View without alternating style #Styles.Render("~/Content/mono")
The EditorTemplate should not need to know the URL.
I'm not using a Grid, just using the MvcContrib Pager. I have a partial view created for the Pager (so I can display it at top and bottom of the results easily), and it calls the #Html. Pager method as so:
#Html.Pager(Model.PagedPrograms).First("<<").Last(">>").Next(">").Previous("<").Format("Item {0} - {1} of {2} ")
This works without additional tweaking as long as all parameters are passed to the page via QueryString, since Pager knows to rebuild those back on the URLs.
I'd like to give the user the option to change the page size (say 20, 50, All) ... I can easily handle that on the controller end, and I could write something like
#if (Model is Foo) {
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, 20), "20")
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, 50), "50");
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, -1), "All");
}
But I would have to do that for each Model type that might use this Pager... I might be overthinking this or coming at this completely backwards, but I thought I'd ask and see if anyone had insight.
Currently the Pager is only called from a view which takes IPagedProgramList (provides IPagination<ProgramDTO> { get; }), and I have two ViewModels implementing that interface (a simple search and an advanced search). But if this project grows and we add new ViewModels that use that Interface I would have to update the Pager partial view, and that seems bad / doesn't scale / etc.
So a nod to Ek0nomik who got me thinking outside the box on this one.
Step 1: Make sure all pages that are going to use the Pager controller are passing all parameters via GET not POST. Use RedirectToAction if you must accept post somewhere and just translate all the parameters into primitive types for the GET method.
Step 2: Don't worry about adding .Link() to the Pager. As long as everything's coming in via GET, you're fine. It will look at the URL for the page and adjust the page number parameter as it needs to when you're going forward/back.
Step 3 (optional but recommended): For consistency across your application, somewhere (probably your Global.ascx.cs file) you should define a list of the page sizes you will support. In my case I used Dictionary<int,string> so that I could pass -1 as the PageSize value but display All (and of course the data layer must know that -1 means disable paging).
Step 4: Add something like this to your pager partial view:
<ul class="pageSizeSelector">
#foreach (KeyValuePair<int,string> kvp in MvcApplication.AVAIL_PAGE_SIZES)
{
<li>#kvp.Value</li>
}
</ul>
Step 5: The javascript function changePageSize is so simple, I couldn't believe I hadn't thought of this first (note: no jQuery required... just in case you're not already using it, you don't need to just for this).
function changePageSize(size) {
var PSpattern = /PageSize=\d+/i;
var url = window.location.href;
url = url.replace(PSpattern, "PageSize=" + size);
window.location.href = url;
}
Step 6 (optional, unless you're an Internet Troll): Profit!
Some background information: I have a form for creating a question and its possible answers. The Question class contains a property which is a list of answers. I have created a view model for this form. I have a "Delete selected answer" button on the form and a dropdownlist to select the answer to delete. I post back the form view model to the controler, and if the user pressed the specific button I remove this answer from the Question and return the model to the create page. This is what I put inside the controler action:
if (questionForm.DeleteSelectedAnswerButton != null)
{
questionForm.Question.PossibleAnswers.RemoveAt(questionForm.AnswerNoToDelete);
questionForm.DeleteSelectedAnswerButton = null;
questionForm.AnswerNoToDelete = 0;
return View(questionForm);
}
Now, I try to delete answer 2 out of 4, the AnswerNoToDelete is equal to 1, as I step through the code I see that the questionForm.Question.PossibleAnswers is a list that is correctly missing the second answer, but when I return to my view, the last answer is the one that is always missing (judging from the text that is in the text boxes). Any ideas why this might be happening? My view has a for loop (from zero to less than the length of possible answers) to render a textbox for each answer text.
Am I doing something fundamentally wrong by building a model this way. Would it be better if my possible answers were one level up: questionForm.PossibleAnswers?
Try removing the value from the modelstate as well because html helpers will first use modelstate when binding and after that your view model values. And you have to do this for every value that you modify in your action:
if (questionForm.DeleteSelectedAnswerButton != null)
{
ModelState.Remove("Question.PossibleAnswers[" + questionForm.AnswerNoToDelete + "]");
questionForm.Question.PossibleAnswers.RemoveAt(questionForm.AnswerNoToDelete);
ModelState.Remove("DeleteSelectedAnswerButton");
questionForm.DeleteSelectedAnswerButton = null;
ModelState.Remove("AnswerNoToDelete");
questionForm.AnswerNoToDelete = 0;
return View(questionForm);
}
What would be the best way of going about this? I have a method in the presenter that populates various textboxes using a switch statement, but also needs to make sure that only these textboxes are visible, eg.:
switch (operation.CalculationType) {
case CalcType.Type1:
textbox1.Visible = true
_view.TextBox1 = "some value";
break;
case CalcType.Type2:
textbox1.Visible = true;
textbox2.Visible = true;
_view.TextBox1 = "some value";
_view.TextBox2 = "another value";
break;
I'm not fond of the idea of exposing a Visible property for each control on the form (theoretically this could lead to exposing all sorts of properties, which just seems wrong to me). Another idea I had was to create a method or event that the presenter calls, telling the form to show/hide the controls, but that kind of means replicating the logic in the presenter.
So what's the "proper" way of doing something this?
Thanks
If the variable _view is not an interface you should make it one, implement it and then add a method or methods that setup which textboxes are visible. This way it is clear in the code what you are trying to do and it not tied to that particular form implementation.
Inteface IFormView
Sub DisplayType1
Sub DisplayType2
....
End Interface