I am trying to determine the best/easiest way to prepopulate certain checkboxes created using Struts2 form tags. My application is a "normal" three tier setup, using Struts2 on the controller layer.
Before I really, really dig deep here, does the tag support creating the list of all possible checkboxes, then populating it (say, via the below action)?
Sample action:
public class UserManagementAction extends ActionSupport implements Preparable {
private List<String> allRoles;
private List<String> rolesToPrepopulate;
// get/set methods
public void prepare() throws Exception {
// populate the allRoles and rolesToPrepopulate lists
}
public String execute() throws Exception {
return INPUT;
}
(Note: assume that struts.xml has been configured with which JSP to return for INPUT)
Thanks for any help.
Jason
What I would do is a new object class and use it as for checkboxes.
For example:
public class StrutsCheckbox {
private Integer id;
private Boolean selected;
...
}
And in prepare() method you can set selected field as you wish (and also id to all of them).
Next in JSP:
<s:iterator value="allRoles">
<s:checkbox name="selected" id="selected" fieldValue="%{id}" value="%{selected}"/>
</s:iterator>
And then in submit action Collection selected will be filled with ids.
public class UserManagementAction extends ActionSupport implements Preparable {
private List<StrutsCheckbox> allRoles;
private List<StrutsCheckbox> rolesToPrepopulate;
private List<Integer> selectedCheckboxes;
// get/set methods
public void prepare() throws Exception {
// populate the allRoles and rolesToPrepopulate lists
// fill and set allRoles and/or rolesToPrepopulate
}
public String execute() throws Exception {
return INPUT;
}
public String submit() throws Exception {
// list selectedCheckboxes is filled with selected fields id's
return INPUT;
}
Maybe with some corrections it will work, but the main idea is here.
Related
I have a legacy MongoDB database (collection) where there is single value stored as an array List . But its only a one value that must be, in UI selected with ComboBox. So I have a model bean
class Project {
List<Company> companies;
}
And I would like to bind it and edit with VAADIN ComboBox. Initialy I thought I can use some customer converter for ComboBox but can't get it to work. Combo should edit the first value in the List (Company bean) and store back into companies field as an array to remain compatible. Is it event possible to do it and if so can you give me some hint how to accomplish this?
EDIT: Enhanced explanation
MongoDB model:
class Project {
List<Company> companies;
}
Vaadin UI:
ComboBox companies;
... 'companies' ComboBox is attached to BeanItemContainer which is List ... selection is therefore the only one Company bean, but should be stored, for compatibility reasons, as List with only one item. So basicly ComboBox should be able to read existing List value as single Company, allow selection and store it as List with this one selecten Company.
After the last question update the answer below may not be correct. Waiting for OP to provide details so the answer can be corrected.
You can use a sort of a delegation approach using a ProjectDelegator wrapper. This will allow you to bind the form to the Project name (using the project field) as well as the Company name (using the getCompany() getter)
1. The UI class
#PreserveOnRefresh
#SpringUI
public class MyVaadinUI extends UI {
#Override
protected void init(VaadinRequest request) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
setContent(layout);
// add the form to the UI
layout.addComponent(new MyForm(new ProjectDelegator(new Project("myProject", new Company("myCompany")))));
}
}
2. The form
// "wrapping" the form in a custom component. not really needed but a nice touch
public class MyForm extends CustomComponent {
public MyForm(ProjectDelegator projectDelegator) {
FormLayout layout = new FormLayout();
// use a binder to create fields and bind the members using reflection
BeanFieldGroup<ProjectDelegator> binder = new BeanFieldGroup<>(ProjectDelegator.class);
// bind the project name using the "project" field
layout.addComponent(binder.buildAndBind("Project", "project.name"));
// bind the company name using the "getCompany" method
layout.addComponent(binder.buildAndBind("Company", "company.name"));
// add a "save" button
layout.addComponent(new Button("Save", new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent clickEvent) {
try {
// commit changes
binder.commit();
} catch (FieldGroup.CommitException e) {
// didn't expect this! what gives?!
System.out.println("Could not save data: [" + e.getMessage() + "]");
}
}
}));
// set the delegator as the binder data source
binder.setItemDataSource(projectDelegator);
setCompositionRoot(layout);
}
}
3. The "delegator"
// delegator class to adapt from/to list with only 1 item
public class ProjectDelegator {
// nice constant to express the intent as clearly as possible
private static final int MY_ONLY_COMPANY = 0;
// our delegate
private Project project;
public ProjectDelegator(Project project) {
this.project = project;
}
// BeanFieldGroup will use this by reflection to bind the "company.name" field
public Company getCompany() {
// delegate the accessing to the origina product
return project.getCompanies().get(MY_ONLY_COMPANY);
}
// accessor; can if not required at a later time
public Project getProject() {
return project;
}
}
4. The legacy model classes
// our problem class
public class Project {
private String name;
private List<Company> companies = new ArrayList<>();
public Project(String name, Company company) {
this.name = name;
companies.add(company);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Company> getCompanies() {
return companies;
}
public void setCompanies(List<Company> companies) {
this.companies = companies;
}
}
// the "indirect" problem :)
public class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("Setting actual company name to [" + name + "]");
this.name = name;
}
}
5. Resulting UI
6. Pressing the save button
Setting actual company name to [myNewCompany]
I have an applicant model that contains a list of tags:
public class Applicant
{
public virtual IList<Tag> Tags { get; protected set; }
}
When the form is submitted, there is an input field that contains a comma-delimited list of tags the user has input. I have a custom model binder to convert this list to a collection:
public class TagListModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var incomingData = bindingContext.ValueProvider.GetValue("tags").AttemptedValue;
IList<Tag> tags = incomingData.Split(',').Select(data => new Tag { TagName = data.Trim() }).ToList();
return tags;
}
}
However, when my model is populated and passed into the controller action on POST, the Tags property is still an empty list. Any idea why it isn't populating the list correctly?
The problem is you have the protected set accessor in Tags property. If you change that into public as below things will work fine.
public class Applicant
{
public virtual IList<Tag> Tags { get; set; }
}
A model binder only binds submitted values. It does not bind values rendered in the view.
You need to create a custom EditorTemplate to render the tags as you need them.
MVC can already bind to a List, I would recommend using the built in technology that already does what you need.
I didn't notice any code about adding the binder, did you add your ModelBinder to the Binders?
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof(IList<Tag>), new TagListModelBinder());
}
<p:editor value="#{editorBean.value}" widgetVar="editor" width="686"
height="390" language="en" align="center">
</p:editor>
Following is my rich-text editor bean picked up from primefaces
#ManagedBean(name = "editorBean")
#SessionScoped
public class EditorBean {
private static final String MANAGED_BEAN_NAME = "editorBean";
private String value;
public static EditorBean getCurrentInstance() {
return (EditorBean) FacesContext.getCurrentInstance()
.getExternalContext().getRequestMap().get(MANAGED_BEAN_NAME);
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
Apart from this I have another bean say A. I have a method inside A that populates a HTML table. What I want is when the user opens the editor, it should be pre-populated with that HTML table data and of course the changes should get reflected into (String: value). Therefore, you can say that I am trying to tie up both the values together. I think it needs to be done with DI but somehow its not working. If someone can guide or quote an example, it would be really helpful.
One way to do it is rewrite your getValue() method to pick up the value from bean A.
And yes, the reference to your A bean should come from DI:
//injecting a reference to A
#ManagedPropery(value="#{A}") //or whatever is the name of your bean
private A beanA;
public void setBeanA(A beanA) {
this.beanA = beanA;
}
Or, with CDI, just:
#Inject private A beanA
Finally, your getValue method
public String getValue() {
return beanA.getValue()
}
Below, in CreateTest, uponsuccessful, I want to redirect to Tests from CreateTest.
I want to do something like the following:
public ActionResult Tests(int ID, string projectName)
{
TestModel model = new TestModel (ID, projectName);
return View(model);
}
[HttpPost]
public ActionResult CreateTest(TestModel model)
{
try
{
return RedirectToAction("Tests");
}
catch (Exception e)
{
ModelState.AddModelError("Error", e.Message);
return View(model);
}
}
You might need to provide the arguments when redirecting:
return RedirectToAction("Tests", new {
ID = model.ID,
projectName = model.ProjectName
});
and the url you will be redirecting to will now look something like this:
/Foo/Tests?ID=123&projectName=abc
I know this is a bit old but...
What I've done in the past is have a "MessageArea" class exposed as a property on my base controller that all my controllers ultimately inherit from. The property actually stores the class instance in TempData. The MessageArea has a method to Add() which takes a string message and an enum Type (e.g. Success, Error, Warning, Information).
I then have a partial that renders whatever messages are in MessageArea with appropriate styling according to the type of the message.
I have a HTMLHelper extension method RenderMessageArea() so in any view I can simple say #Html.RenderMessageArea(), the method and partial take care of nulls and nothing is output if there are no messages.
Because data stored in TempData only survives 1 request it is ideal for cases where you want your action to redirect but have 1 or more messages shown on the destination page, e.g. an error, not authorised page etc... Or if you add an item but then return to the index list page.
Obviously you could implement something similar to pass other data. Ultimately I'd say this is a better solution to the original question than the accepted answer.
EDIT, EXAMPLE:
public class MessageAreaModel {
public MessageAreaModel() {
Messages = new List<Message>();
}
public List<Message> Messages { get; private set; }
public static void AddMessage(string text, MessageIcon icon, TempDatadictionary tempData) {
AddMessage(new Message(icon, text), tempData);
}
public static void AddMessage(Message message, TempDataDictionary tempData) {
var msgArea = GetAreaModelOrNew(tempData);
msgArea.Messages.Add(message);
tempData[TempDataKey] = msgArea;
}
private static MessageAreaModel GetAreaModelOrNew(TempDataDictionary tempData) {
return tempData[TempDataKey] as MessageAreaModel ?? new MessageAreaModel();
}
The above class can then be used to add messages from your UI layer used by the controllers.
Then add an HtmlHelper extension like so:
public static void RenderMessageArea(this HtmlHelper html) {
html.RenderPartial("MessageArea",
(MessageAreaModel)html.ViewContext.TempData[MessageAreaModel.TempDataKey] ?? MessageAreaModel.Empty);
html.ViewContext.TempData.Remove(MessageAreaModel.TempDataKey);
}
The above is not fully completed code there are various bells and whistles I've left out but you get the impression.
Make the int nullable:
public ActionResult Tests(int? ID, string projectName){
//...
}
I have a View for creating a customer that contains numerous textboxes. After the user tabs out of each textbox I want to use JQuery to call a Controller method that will check in the DataBase and look for any possible matches, the controller will then send content and I will use jQuery to dynamically show the possible matches (Similar to what Stack Overflow does when you enter in your question and shows Related Questions).
My question is, I have 15 textboxes and would like to send that data from each back with each call. I'd like to avoid having my Controller method with a signature like
Public ActionResult CheckMatches(string param1, string param2... string param15)
Is there an easier way to pass multiple paramers as a single object, like FormCollection?
All you need to do is create a type with properties the same name as the names of your textboxes:
public class CheckMatchesAguments
{
public string Param1 { get; set; }
public string Param2 { get; set; }
// etc.
}
Then change your action to:
public ActionResult CheckMatches(CheckMatchesAguments arguments)
That's all!
Be warned, though: If CheckMatchesAguments has any non-nullable properties (e.g., ints), then values for those properties must be in the FormCollection, or the default model binder won't bind anything in the type. To fix this, either include those properties, too, in the form, or make the properties nullable.
Javascript:
var data = { foo: "fizz", bar: "buzz" };
$.get("urlOfAction", data, callback, "html")
Action:
public ActionResult FooAction(MegaParameterWithLotOfStuff param)
And a custom model binder:
public class MegaParameterWithLotOfStuffBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext,
string modelName, Type modelType,
ModelStateDictionary modelState)
{
var param = new MegaParameterWithLotOfStuff();
param.Foo = controllerContext.
HttpContext.Request.Form["foo"];
param.Bar = controllerContext.
HttpContext.Request.Form["bar"];
return customer;
}
}
Global.asax:
protected void Application_Start()
{
ModelBinders.Binders[typeof(MegaParameterWithLotOfStuff)] =
new MegaParameterWithLotOfStuffBinder();
}
Or binding filter+action combo:
public class BindMegaParamAttribute: ActionFilterAttribute
{
public override void OnActionExecuting
(ActionExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
var param = new MegaParameterWithLotOfStuff();
param.Foo = httpContext.Request.Form["foo"];
param.Bar = httpContext.Request.Form["bar"];
filterContext.ActionParameters["param"] = param;
base.OnActionExecuted(filterContext);
}
}
Action:
[BindMegaParam]
public ActionResult FooAction(MegaParameterWithLotOfStuff param)