I had an issue similar to that posted in this related question:
mvc 4 textbox not updating on postback
Because I wanted to be a bit more targeted than "nuke it all" I am using ModelState.Remove("propertyname") instead to just affect specific values in my model.
However even this is too broad for what I want, because this removes any validation information, etc. that might have been accrued for this property.
How can I update the specific posted back value without losing all this other useful state information?
The below code displays the current behaviour, (with unwanted behaviour in bold):
submitting "test" returns a model with "replacement", but a form with
"test"
submitting "testlong" returns a model with "replacement", but a form
with "testlong", and displays a validation message
submitting "remove" returns a model with "replacement", and form with
"replacement"
submitting "removelong" returns a model with "replacement", and form
with "replacement" and no validation message
Model:
public class TestViewModel
{
[StringLength(7, ErrorMessage = "{0} must be less than {1} characters")]
public string A { get; set; }
}
Action:
public ActionResult Test(TestViewModel model)
{
if(model.A == "remove" || model.A == "removelong")
{
ModelState.Remove("A");
}
model.A = "replacement";
return View(model);
}
View:
#model TestNamespace.TestViewModel
#{ Layout = null; }
<!DOCTYPE html>
<html>
<head></head>
<body>
<form>
#Html.ValidationMessageFor(m=>m.A)
#Html.TextBoxFor(m => m.A)
</form>
</body>
</html>
Some examples where this would be useful:
Telling the user what they got wrong, fixing it for them, and telling
them we fixed it.
An application that logs validation errors for analysis, and always
does so as the last step before the view so as to capture validation
errors added during the Action.
Manager/Customer mandated logic which is nonsensical, but still required despite advice to that effect.
There is no clearly provided way to do this, due to the fact that in 99% of cases this is an X-Y problem (e.g. the reason for wanting this is flawed).
However if you really want to do this, you can do it with the following instead of Model.Remove("propertyName"):
ModelState propertyState = ModelState["propertyName"];
if (propertyState != null)
{
propertyState.Value = null;
}
or in another form
ModelState propertyState;
if (ModelState.TryGetValue("propertyName"))
{
propertyState.Value = null;
}
Related
On an xpage I calculate the message for an xp:confirm control:
var arr = viewScope.get("attachmentsAll");
if(arr.length>0){
return "";
}else{
return arr.length + " Are you sure want to upload the file?";
}
the viewScope is being updated after the event has been executed. I check this via a xp:text and I notice that this assumption is true.
<xp:text escape="true" id="computedField1"><xp:this.value><![CDATA[#{javascript:var arr = viewScope.get("attachmentsAll")
return arr.length + " number?"}]]></xp:this.value></xp:text>
The xp:confirm and xp:text reside in the same panel that is being partially updated after the event.
Can anyone explain me why the value for the viewScope variable is updated in the xp:text control and not in the xp:confirm control?
The main idea of my answer to your previous question was to put hidden input with computed value. What if you try to use <xp:this.script> instead of xp:confirm, and get the confirmation message from that hidden input the same way?
Update
The reason and the alternative solution that does not require to make any changes to existing xpage
It came out that the back-end instance of xp:confirm evaluates the new message correctly. The new value is even being sent to the browser with the response to ajax request. But one of the functions of XSP client module is built so that it won't update the querySubmit listener function if there already exists one with the same name. So, we are stuck with the old confirmation function that contains the old message. There is a way to override this behavior without breaking any other features. I have tried this and it works for me.
Create a new JavaScript library (client-side). Add the code:
if (!XSP._pushListenerTuned) {
XSP.__pushListener = XSP._pushListener;
XSP._pushListener = function x__pl(listeners, formId, clientId, scriptId, listener) {
if (scriptId && scriptId.endsWith("_confirm")) {
for (var i = 0; i < listeners.length; i++) {
if (scriptId == listeners[i].scriptId) {
listeners.splice(i, 1);
}
}
listeners.push(new this._SubmitListener(formId, listener, clientId, scriptId));
} else {
XSP.__pushListener(listeners, formId, clientId, scriptId, listener);
}
}
XSP._pushListenerTuned = true;
}
Attach your new library as resource globally via theme or as page resource on required page. I guess placing the above code as scriptBlock on a required page should also work. Now, any xp:confirm component on any page (if you used theme resource), or on particular page and everywhere after visiting this page (if you used page resource or scriptBlock), will work as naturally expected.
I've noticed that my child-view gets rendered twice for some reasons.
Here is a simple test-case:
Let's assume I've two controllers, ControllerA and ControllerB. I'm forwarding the action forwardTestAction from ControllerA to ControllerB and display the view returned from ControllerB as a child-view.
ControllerA::forwardTestAction()
public function forwardTestAction()
{
$childView = $this->forward()->dispatch(ControllerB::class, [
'action' => 'forward-test',
]);
$mainView = new ViewModel();
$mainView->addChild($childView, 'content');
return $mainView;
}
ControllerB::forwardTestAction()
public function forwardTestAction()
{
$number = new \stdClass();
$number->a = 10.4;
$view = new ViewModel([
'number' => $number,
]);
return $view;
}
Template forward-test.phtml of ControllerA
<?= $content; ?>
Template forward-test.phtml of ControllerB
<?php
$number->a = $this->currencyFormat($number->a);
echo $number->a;
The currencyFormat plugin throws an exception, because number gets formatted twice. The reason is because the child-view itself gets rendered twice.
That's a really weird behaviour and causes not only performance issues but also many other problems e.g. numberFormatting etc.
I'm doing something wrong?
Also I should mention that the html only gets outputted once, but the template gets rendered twice. Maybe it's just a weird constellation of my current setup. I also posted an issue on Github and they were not able to reproduce it.
I have a webMethods CAF task that has a big form with a save button and a submit button. Many elements on the form have validation. The user needs to be able to hit Save and have the form submitted to the back end model so it can be saved as task data, without firing validation. Hitting Submit should fire validation.
How can I configure the page to do this. It's such a normal requirement, and I'm stuck!
It's not much fun.
Give your Save button a nice ID. Say, saveButton
Create a getter in your Java code that returns a boolean. Inside it, return true if the button's ID is one of the submitted fields, otherwise false:
private boolean validationRequired() {
return mapValueEndsWith((Map<String, String>)getRequestParam(),
new String[] {
"saveButton", // Your save button
"anotherButton", // Perhaps another button also shouldn't validate
"myForm:aThirdButton" // perhaps you want to be specific to a form
});
}
In every field that should be required, except on Save, bind the Validation->required attribute to your validationRequired getter.
That's it! Very tedious with a lot of fields on the screen, but it works.
P.s. what's mapValueEndswith? Just a utility; removed whitespace for compactness' sake:
private boolean mapValueEndsWith(Map<String, String> haystack, String[] needles) {
for(String needle : needles) if(mapValueEndsWith(haystack, needle)) return true;
return false;
}
private boolean mapValueEndsWith(Map<String, String> haystack, String needle) {
for(String value : haystack.values()) if(value.endsWith(needle)) return true;
return false;
}
As I see the approach provided, works only if the form contains only string type fields. If there are any other data types like integer, float, data-time are mapped to UI fields and conversion is used then this might fail if wrong data is entered in those fields.
Edit: searching appears to work in a very basic manner, searching for whatever text is entered in all the String member fields of the relevant model, with no special search operators.
If there's any way to customize how the search works, I'd like to know. Right now it's looking like I'll just have to ignore the built-in search stuff and roll my own.
Since adding the notes field, it's started returning nothing instead of everything.
The only thing that was worked so far is entering "test" in the search box, which returns the record with "test" in the notes field. Searching for "notes:test" or "notes=test" doesn't work, nor does any attempt to search the agent or status fields. I've tried things like "1400" which is a value I've verified is present in the agent field via phpMyAdmin, and things like "0" or "VALID" for the status field, which is what all the records have.
model fields:
public class AgentShift extends Model {
public enum Status { VALID, RESCHEDULED, EXTENDED, DELETED } // REDUCED?
#Required #Min(1000) #Max(99999)
public int agent;
public Status status = Status.VALID;
#Required
#Columns(columns={#Column(name="start_time"),#Column(name="end_time")})
#Type(type="org.joda.time.contrib.hibernate.PersistentInterval")
public Interval scheduled;
#Type(type="org.joda.time.contrib.hibernate.PersistentLocalTimeAsTime")
public LocalTime agent_in;
#Type(type="org.joda.time.contrib.hibernate.PersistentLocalTimeAsTime")
public LocalTime agent_out;
public String notes;
}
modified crud list page:
#{extends 'main.html' /}
#{set title: 'Agent Shifts' /}
<div id="crudList" class="${type.name}">
<h2 id="crudListTitle">&{'crud.list.title', type.name}</h2>
<div id="crudListSearch">
#{crud.search /}
</div>
<div id="crudListTable">
#{crud.table fields:['id','agent','scheduled','status','notes'] }
#{crud.custom 'scheduled'}
#{if object.scheduled.getStart().toDateMidnight().equals(object.scheduled.getEnd().toDateMidnight())}
${org.joda.time.format.DateTimeFormat.forStyle("SS").printTo(out, object.scheduled.getStart())} to
${org.joda.time.format.DateTimeFormat.forStyle("-S").printTo(out, object.scheduled.getEnd())}
#{/if}
#{else}
${org.joda.time.format.DateTimeFormat.forStyle("SS").printTo(out, object.scheduled.getStart())} to
${org.joda.time.format.DateTimeFormat.forStyle("SS").printTo(out, object.scheduled.getEnd())}
#{/else}
#{/crud.custom}
*{
#{crud.custom 'entered_by'}
#{if object.entered_by}
${object.entered_by}
#{/if}
#{else} ?
#{/else}
#{/crud.custom}
#{crud.custom 'created'}
${org.joda.time.format.DateTimeFormat.forStyle("SS").printTo(out, object.created)}
#{/crud.custom}
}*
#{/crud.table}
</div>
<div id="crudListPagination">
#{crud.pagination /}
</div>
<p id="crudListAdd">
&{'crud.add', type.modelName}
</p>
</div>
Here you can see how to customize the CRUD pages, including how to show certain fields of the object in the screen.
The search strings are supposed to be in the format:
<field name>:<value>
For example:
name:Liam
That search would filter all objects whose name contains Liam. The field name must be a field displayed in the list page. I'm not sure if it works with #Lob, but usually that's not a field you want to display in the list page so it shouldn't be an issue.
I have to say that in Play 1.1 I had some issues with some columns, in which starting the search didn't work (it raised an error) and I couldn't solve it. It doesn't seem to happen in 1.2.1 (I have no idea if it is a fix or simply a change I did without noticing)
EDIT:
On the updated question, the list page seems right.
One bold idea: have you checked that the database has the proper columns? I remember I had some issues with Hibernate not playing fair when I changed some models and a few columns where not updated properly, causing odd behavior. It may be worth it to remove the table completely and let Play recreate it.
If that doesn't solve it most likely this is a Play bug in the CRUD controller, you would need to find the source.
My first concern would be the lack on a rel annotation on Interval, and the usage of LocalTime and the enum Status. It should not matter, but... I'm afraid I can only suggest you to do an incremental test to locale the issue:
remove everything except agent and notes, and try the search.
If it fails, raise a bug on Play's Lighthouse as it shouldn't
If it works, keep adding fields one at a time and retesting the search. This will be fast with Play, and you may detect the annotation/field causing the issue and report on Play's Lighthouse
You can use the elastic search module.
1) Define Controller:
#ElasticSearchController.For(ElasticSearchSampleModel.class)
public class ElasticSearchExample extends ElasticSearchController {
}
2) Define standard JPA model with #ElasticSearchable annotation:
#Entity
#ElasticSearchable
public class ElasticSearchSampleModel extends Model {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The field1. */
public String field1;
/** The field2. */
public String field2;
/**
* To String
*
* #see play.db.jpa.JPABase#toString()
*/
#Override
public String toString() {
return "ElasticSearchSampleModel [field1=" + this.field1 + ", field2=" + this.field2 + "]";
}
}
You can find more information at http://geeks.aretotally.in/play-framework-module-elastic-search-distributed-searching-with-json-http-rest-or-java.
Im using subsonic 2.2
I tried asking this question another way but didnt get the answer i was looking for.
Basically i ususally include validation at page level or in my code behind for my user controls or aspx pages. However i haev seen some small bits of info advising this can be done within partial classes generated from subsonic.
So my question is, where do i put these, are there particular events i add my validation / business logic into such as inserting, or updating. - If so, and validation isnt met, how do i stop the insert or update. And if anyone has a code example of how this looks it would be great to start me off.
Any info greatly appreciated.
First you should create a partial class for you DAL object you want to use.
In my project I have a folder Generated where the generated classes live in and I have another folder Extended.
Let's say you have a Subsonic generated class Product. Create a new file Product.cs in your Extended (or whatever) folder an create a partial class Product and ensure that the namespace matches the subsonic generated classes namespace.
namespace Your.Namespace.DAL
{
public partial class Product
{
}
}
Now you have the ability to extend the product class. The interesting part ist that subsonic offers some methods to override.
namespace Your.Namespace.DAL
{
public partial class Product
{
public override bool Validate()
{
ValidateColumnSettings();
if (string.IsNullOrEmpty(this.ProductName))
this.Errors.Add("ProductName cannot be empty");
return Errors.Count == 0;
}
// another way
protected override void BeforeValidate()
{
if (string.IsNullOrEmpty(this.ProductName))
throw new Exception("ProductName cannot be empty");
}
protected override void BeforeInsert()
{
this.ProductUUID = Guid.NewGuid().ToString();
}
protected override void BeforeUpdate()
{
this.Total = this.Net + this.Tax;
}
protected override void AfterCommit()
{
DB.Update<ProductSales>()
.Set(ProductSales.ProductName).EqualTo(this.ProductName)
.Where(ProductSales.ProductId).IsEqualTo(this.ProductId)
.Execute();
}
}
}
In response to Dan's question:
First, have a look here: http://github.com/subsonic/SubSonic-2.0/blob/master/SubSonic/ActiveRecord/ActiveRecord.cs
In this file lives the whole logic I showed in my other post.
Validate: Is called during Save(), if Validate() returns false an exception is thrown.
Get's only called if the Property ValidateWhenSaving (which is a constant so you have to recompile SubSonic to change it) is true (default)
BeforeValidate: Is called during Save() when ValidateWhenSaving is true. Does nothing by default
BeforeInsert: Is called during Save() if the record is new. Does nothing by default.
BeforeUpdate: Is called during Save() if the record is new. Does nothing by default.
AfterCommit: Is called after sucessfully inserting/updating a record. Does nothing by default.
In my Validate() example, I first let the default ValidatColumnSettings() method run, which will add errors like "Maximum String lenght exceeded for column ProductName" if product name is longer than the value defined in the database. Then I add another errorstring if ProductName is empty and return false if the overall error count is bigger than zero.
This will throw an exception during Save() so you can't store the record in the DB.
I would suggest you call Validate() yourself and if it returns false you display the elements of this.Errors at the bottom of the page (the easy way) or (more elegant) you create a Dictionary<string, string> where the key is the columnname and the value is the reason.
private Dictionary<string, string> CustomErrors = new Dictionary<string, string>
protected override bool Validate()
{
this.CustomErrors.Clear();
ValidateColumnSettings();
if (string.IsNullOrEmpty(this.ProductName))
this.CustomErrors.Add(this.Columns.ProductName, "cannot be empty");
if (this.UnitPrice < 0)
this.CustomErrors.Add(this.Columns.UnitPrice, "has to be 0 or bigger");
return this.CustomErrors.Count == 0 && Errors.Count == 0;
}
Then if Validate() returns false you can add the reason directly besides/below the right field in your webpage.
If Validate() returns true you can safely call Save() but keep in mind that Save() could throw other errors during persistance like "Dublicate Key ...";
Thanks for the response, but can you confirm this for me as im alittle confused, if your validating the column (ProductName) value within validate() or the beforevalidate() is string empty or NULL, doesnt this mean that the insert / update has already been actioned, as otherwise it wouldnt know that youve tried to insert or update a null value from the UI / aspx fields within the page to the column??
Also, within asp.net insert or updating events we use e.cancel = true to stop the insert update, if beforevalidate failes does it automatically stop the action to insert or update?
If this is the case, isnt it eaiser to add page level validation to stop the insert or update being fired in the first place.
I guess im alittle confused at the lifecyle for these methods and when they come into play