MyCustomControl.razor
<input type="text" id="#id" />
#code {
[Parameter]
public string id { get; set; }
}
Test.Razor
#page "/test"
<button #onclick="#addCompoment">add text box</button>
<div class="simple-list-list">
#if (componentListTest == null)
{
<p>You have no items in your list</p>
}
else
{
<ul>
#foreach (var item in componentListTest)
{
#item<br/>
}
</ul>
}
</div>
#functions {
private List<RenderFragment> componentListTest { get; set; }
private int currentCount { get; set; }
private string TxtExample { get; set; }
protected void OnInit()
{
currentCount = 0;
componentListTest = new List<RenderFragment>();
}
protected void addCompoment()
{
if(componentListTest==null)
{
componentListTest = new List<RenderFragment>();
}
componentListTest.Add(CreateDynamicComponent(currentCount));
currentCount++;
}
RenderFragment CreateDynamicComponent(int counter) => builder =>
{
try
{
var seq = 0;
builder.OpenComponent(seq, typeof(MyCustomControl));
builder.AddAttribute(++seq, "id", "listed-" + counter);
builder.CloseComponent();
}
catch (Exception ex)
{
throw;
}
};
}
After Adding the textbox dynamically,how to retrieve all input data from the textbox (after clicking on the submit button.)
How to interact with dynamic component and fetch Value.
MyCustomControl is component, Append in Test Razor Page.
for these component create an attribute like bind-value to get input field data given by user
There are a couple of solutions to this type of issue, depending on the general design of your app, constraints, and such like. The following solution is simple. Generally speaking, it involves passing the value of the added text box to a parent component to be saved in a list object. The parent component has a button that displays the list of text when clicked.
The following is the definition of the child component:
MyCustomControl.razor
<input type="text" #bind="#Value" id="#ID" />
#code {
private string _value;
public string Value
{
get { return _value; }
set
{
if (_value != value)
{
_value = value;
if (SetValue.HasDelegate)
{
SetValue.InvokeAsync(value);
}
}
}
}
[Parameter]
public string ID { get; set; }
[Parameter]
public EventCallback<string> SetValue { get; set; }
}
Usage in a parent component
<button #onclick="#addCompoment">add text box</button>
<div class="simple-list-list">
#if (componentListTest == null)
{
<p>You have no items in your list</p>
}
else
{
<ul>
#foreach (var item in componentListTest)
{
#item
<br />
}
</ul>
}
</div>
<p><button #onclick="#ShowValues">Show values</button></p>
#if (Display)
{
<ul>
#foreach (var value in values)
{
<li>#value</li>
}
</ul>
}
#code {
public void SetValue(string value)
{
values.Add(value);
}
private List<RenderFragment> componentListTest { get; set; }
private List<string> values = new List<string>();
private int currentCount { get; set; }
protected override void OnInitialized()
{
currentCount = 0;
componentListTest = new List<RenderFragment>();
}
private bool Display;
private void ShowValues()
{
if (values.Any())
{
Display = true;
}
}
protected void addCompoment()
{
if (componentListTest == null)
{
componentListTest = new List<RenderFragment>();
}
componentListTest.Add(CreateDynamicComponent(currentCount));
currentCount++;
}
RenderFragment CreateDynamicComponent(int counter) => builder =>
{
try
{
builder.OpenComponent(0, typeof(MyCustomControl));
builder.AddAttribute(1, "id", "listed-" + counter);
builder.AddAttribute(2, "SetValue", Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, this.SetValue )));
builder.CloseComponent();
}
catch (Exception ex)
{
throw;
}
};
}
Note:
Notice the SetValue attribute I've added to the CreateDynamicComponent's builder. This provides a Component Parameter to MyCustomControl of type EventCallback<string> which is assigned to the SetValue parameter property:
[Parameter]
public EventCallback<string> SetValue { get; set; }
And it is used (trigger the method which is also called SetValue in the parent component. You can change the name if you like) to pass the changed value from the child component to the parent component.
Use code instead of functions.
Note that I've made some modifications in your code: OnInitialized instead of OnInit (obsolete), sequence numbers should not created the way you do. Refer to this article written by Steve Sanderson ...
Hope this helps...
Related
In a Blazor application, I want to use a component from two different pages. The component uses a service to maintain its state. But each page needs to have the component use a different state. Here's how I thought I would do it (using the default Counter component to demonstrate).
CounterService.cs
namespace TestTwoInterfaceToOneService.Shared
{
public interface ICounterServiceA
{
int CurrentCount { get; set; }
void IncrementCount();
}
public interface ICounterServiceB
{
int CurrentCount { get; set; }
void IncrementCount();
}
public class CounterService : ICounterServiceA, ICounterServiceB
{
public int CurrentCount { get; set; }
public void IncrementCount()
{
CurrentCount++;
}
}
}
In Program.cs add:
builder.Services.AddScoped<ICounterServiceA, CounterService>();
builder.Services.AddScoped<ICounterServiceB, CounterService>();
Counter.razor
<h1>Counter</h1>
<p>Current count: #CounterService.CurrentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
[Parameter]
public CounterService CounterService { get; set; }
private void IncrementCount()
{
CounterService.CurrentCount++;
}
}
PageA.razor
#page "/pagea"
#inject ICounterServiceA CounterServiceA
<h3>Page A</h3>
<Counter CounterService="CounterServiceA" /> #*<-- Error: Cannot convert from ICounterServiceB to CounterService*#
PageB.razor
#page "/pageb"
#inject ICounterServiceB CounterServiceB
<h3>Page B</h3>
<Counter CounterService="CounterServiceB" /> #*<-- Error: Cannot convert from ICounterServiceB to CounterService*#
I get the error 'Cannot convert from ICounterServiceB to CounterService' when I try to pass the service to the component. I've discovered that using 2 identical interfaces pointing to the same concrete implementation does indeed give me 2 scoped instances. However, I cannot figure out how to pass those instances into the component.
Is there a piece that I'm missing? Or, should this be done some other way?
SOLUTION
Using a combination of the answer from Henk and the comments from Brian, the solution I came up with is:
CounterService.cs
namespace TestTwoInterfaceToOneService.Shared
{
public class CounterService
{
public int CurrentCount { get; set; }
public void IncrementCount()
{
CurrentCount++;
}
public void ResetCount()
{
CurrentCount = 0;
}
}
}
CounterStateService.cs
using System.Collections.Generic;
namespace TestTwoInterfaceToOneService.Shared
{
public interface ICounterStateService
{
CounterService this[string key] { get; }
}
public class CounterStateService : ICounterStateService
{
private Dictionary<string, CounterService> counterServices = new();
public CounterService this[string key]
{
get
{
if (!counterServices.ContainsKey(key))
{
counterServices.Add(key, new CounterService());
}
return counterServices[key];
}
}
}
}
In Program.cs, add
builder.Services.AddScoped<ICounterStateService, CounterStateService>();
Counter.razor
<p>Current count: #CounterService.CurrentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Increment</button>
#code {
[Parameter]
public CounterService CounterService { get; set; }
private void IncrementCount()
{
CounterService.CurrentCount++;
}
}
PageA.razor
#page "/pagea"
#inject ICounterStateService CounterStateService
<h3>Page A</h3>
<Counter CounterService='CounterStateService["A"]' />
<button class="btn btn-primary" #onclick='CounterStateService["A"].ResetCount'>Reset Count</button>
PageB.razor
#page "/pageb"
#inject ICounterStateService CounterStateService
<h3>Page B</h3>
<Counter CounterService='CounterStateService["B"]' />
<button class="btn btn-primary" #onclick='CounterStateService["B"].ResetCount'>Reset Count</button>
Or, should this be done some other way?
Yes. Baking this usage pattern into the Type structure isn't going to adapt or scale well. What if you need a third page, or want to make it dynamic?
You could use a wrapper service and a scheme with a key to bind a State to a Component:
public class CounterService { ... } // no need to register
public class CounterStateService // register for injection
{
private Dictionary <string, CounterService> stateLookup = new();
public CounterService this[string key]
{
if (! stateLookup.tryGetValue (key, out CounterService service))
{
service = new CounterService();
stateLookup.Add(key, service);
}
return service;
}
}
and use it like
#page "/pagea"
#inject CounterStateService CounterStateService
<h3>Page A</h3>
<Counter CounterService="CounterStateService["A"]" />
I am trying to validate a nested model using custom validation. But the problem is AttributeAdapterBase.AddValidation function is never called on nested model. However it works well with simple class property
Custom required validation attribute:
public interface IId
{
long Id { get; set; }
}
public class Select2RequiredAttribute : RequiredAttribute
{
public Select2RequiredAttribute(string errorMessage = "") : base()
{
ErrorMessage = errorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Type t = value.GetType();
if (typeof(IId).IsAssignableFrom(t))
{
if ((value as IId).Id == 0)
{
return new ValidationResult(ErrorMessage);
}
}
else
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Attribute adapter base:
public class Select2RequiredAttributeAdapter : AttributeAdapterBase<Select2RequiredAttribute>
{
public Select2RequiredAttributeAdapter(Select2RequiredAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-select2-required", GetErrorMessage(context));
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
return Attribute.ErrorMessage ?? GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
Adapter provider:
public class Select2RequiredAdapterProvider : IValidationAttributeAdapterProvider
{
private readonly IValidationAttributeAdapterProvider _baseProvider = new ValidationAttributeAdapterProvider();
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
if (attribute is Select2RequiredAttribute)
{
return new Select2RequiredAttributeAdapter(attribute as Select2RequiredAttribute, stringLocalizer);
}
else
{
return _baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
}
}
}
Startup.cs:
services.AddSingleton<IValidationAttributeAdapterProvider, Select2RequiredAdapterProvider>();
Model classes:
public interface IBaseBriefViewModel : IId
{
string Name { get; set; }
}
public class BaseBriefViewModel : IBaseBriefViewModel
{
public virtual long Id { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public long Id { get; set; }
public string Name { get; set; }
[Select2Required("Branch is required.")]
public BaseBriefViewModel Branch { get; set; }
}
Branch select 2 partial view:
#model DataLibrary.ViewModels.BriefViewModels.BaseBriefViewModel
#{
var elementId = ViewData["ElementId"] != null && !string.IsNullOrEmpty(ViewData["ElementId"].ToString()) ? ViewData["ElementId"].ToString() : "branch-id";
}
<div class="form-group">
<label>Branch: <span class="text-danger"></span></label>
<div class="row">
<div class="#select2Class">
#Html.DropDownListFor(model => model.Id, new List<SelectListItem>() {
new SelectListItem()
{
Value = (Model!=null&&Model.Id>0)?Model.Id.ToString():"",
Text = (Model!=null&&Model.Id>0)?Model.Name:"",
Selected = (Model!=null&&Model.Id>0)?true:false,
}}, new { #id = elementId, #class = "form-control disable-field"})
#Html.ValidationMessageFor(model => model.Id, "", new { #class = "text-danger" })
</div>
</div>
</div>
<script>
$(function () {
var id = "#" + "#elementId";
var url = '/Branch/GetBranchsForSelect2';
var dataArray = function (params) {
params.page = params.page || 1;
return {
prefix: params.term,
pageSize: pageSize,
pageNumber: params.page,
};
};
Select2AutoCompleteAjax(id, url, dataArray, pageSize, "---Branch---");
});
</script>
All this code works well for server side. But for better user experience I want to show error before submitting form. How can I achieve this? I want to use this BaseBriefViewModel for a lot of Select2 in the project. So hard coding a static error message is not a good idea. What I really want to do is pass a error message from parent object. Like Branch is required in this specific case. Maybe in some other class I might pass Product is required
Any direction will be appreciated
At the moment this is not supported - but support is in planned. See dotnet github issue:
https://github.com/dotnet/runtime/issues/36093
I have Blazor WebAssembly application. I have a parent and child component that share a model class. When the child updates a value on the model, the child redraws itself, but the parent does not. So what exactly triggers a Blazor component to redraw itself? I can't find any documentation on this particular aspect of Blazor.
I have an POCO model class. In the real application this comes from a separate assembly. Models/AwesomenessModel.cs:
namespace BlazorApp.Models {
public class AwesomenessModel {
public int? Level { get; set; }
}
}
My index page. Pages/Index.razor:
#page "/"
#using BlazorApp.Components
#using BlazorApp.Models
<Awesomeness AwesomenessModel="#AwesomenessModel"></Awesomeness>
#code {
private AwesomenessModel AwesomenessModel = new AwesomenessModel();
}
The parent class. I have added a redraw button to verify that the content should have changed. Components/Awesomeness.razor:
#using BlazorApp.Models
#if (AwesomenessModel.Level.HasValue) {
<div>Awesomeness is #AwesomenessModel.Level.Value</div>
} else {
for (int i = 0; i < 10; i++) {
<AwesomenessSelector AwesomenessModel="#AwesomenessModel" Level="#i"></AwesomenessSelector>
}
}
<div><button class="btn btn-link" #onclick="RedrawClick">Redraw</button></div>
#code {
[Parameter]
public AwesomenessModel AwesomenessModel { get; set; }
private void RedrawClick() {
this.StateHasChanged();
}
}
The child component that does the actual change to the Awesomeness model. Components/AwesomenessSelector.razor:
#using BlazorApp.Models
#if (!AwesomenessModel.Level.HasValue) {
<div><button class="btn btn-link" #onclick="LevelClick">#Level</button></div>
}
#code {
[Parameter]
public AwesomenessModel AwesomenessModel { get; set; }
[Parameter]
public int Level { get; set; }
private void LevelClick() {
AwesomenessModel.Level = Level;
}
}
When I run the application and click on an awesomeness level, that level disappears (the AwesomenessSelector component redraws), but the rest are still visible and there is no 'Awesomeness is...' message (the Awesomeness component does not update). If I then click the Redraw button to force the Awesomeness component to redraw, I get the correct 'Awesomeness is...' message.
I'm probably not structuring the code in the correct way to get the parent component to redraw automatically. How should I structure it in this scenario?
Child Component add a EventCallback for OnLevelClick
#using BlazorApp.Models
#if (!AwesomenessModel.Level.HasValue) {
<div><button class="btn btn-link" #onclick="LevelClick">#Level</button></div>
}
#code {
[Parameter]
public AwesomenessModel AwesomenessModel { get; set; }
[Parameter]
public int Level { get; set; }
[Parameter]
public EventCallback<string> OnLevelClick { get; set; }
private void LevelClick() {
AwesomenessModel.Level = Level;
OnLevelClick.InvokeAsync();
}
}
parent Component add Handler for OnLevelClick
#using BlazorApp.Models
#if (AwesomenessModel.Level.HasValue) {
<div>Awesomeness is #AwesomenessModel.Level.Value</div>
} else {
for (int i = 0; i < 10; i++) {
<AwesomenessSelector AwesomenessModel="#AwesomenessModel" Level="#i" #OnLevelClick="OnLevelClick"></AwesomenessSelector>
}
}
#code {
[Parameter]
public AwesomenessModel AwesomenessModel { get; set; }
private void OnLevelClick() {
this.StateHasChanged();
}
}
hope this helps
You need StateHasChanged() on the parent control that doesn't know that the child has been updated. I believe you should create an event on the child that is triggered when you need to update the parent. Then on the parent when the event is triggered StateHasChanged(). All theory but should work.
I have a Create action that takes an entity object and a HttpPostedFileBase image. The image does not belong to the entity model.
I can save the entity object in the database and the file in disk, but I am not sure how to validate these business rules:
Image is required
Content type must be "image/png"
Must not exceed 1MB
A custom validation attribute is one way to go:
public class ValidateFileAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
var file = value as HttpPostedFileBase;
if (file == null)
{
return false;
}
if (file.ContentLength > 1 * 1024 * 1024)
{
return false;
}
try
{
using (var img = Image.FromStream(file.InputStream))
{
return img.RawFormat.Equals(ImageFormat.Png);
}
}
catch { }
return false;
}
}
and then apply on your model:
public class MyViewModel
{
[ValidateFile(ErrorMessage = "Please select a PNG image smaller than 1MB")]
public HttpPostedFileBase File { get; set; }
}
The controller might look like this:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// The uploaded image corresponds to our business rules => process it
var fileName = Path.GetFileName(model.File.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data"), fileName);
model.File.SaveAs(path);
return Content("Thanks for uploading", "text/plain");
}
}
and the view:
#model MyViewModel
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.LabelFor(x => x.File)
<input type="file" name="#Html.NameFor(x => x.File)" id="#Html.IdFor(x => x.File)" />
#Html.ValidationMessageFor(x => x.File)
<input type="submit" value="upload" />
}
Based on Darin Dimitrov's answer which I have found very helpful, I have an adapted version which allows checks for multiple file types, which is what I was initially looking for.
public override bool IsValid(object value)
{
bool isValid = false;
var file = value as HttpPostedFileBase;
if (file == null || file.ContentLength > 1 * 1024 * 1024)
{
return isValid;
}
if (IsFileTypeValid(file))
{
isValid = true;
}
return isValid;
}
private bool IsFileTypeValid(HttpPostedFileBase file)
{
bool isValid = false;
try
{
using (var img = Image.FromStream(file.InputStream))
{
if (IsOneOfValidFormats(img.RawFormat))
{
isValid = true;
}
}
}
catch
{
//Image is invalid
}
return isValid;
}
private bool IsOneOfValidFormats(ImageFormat rawFormat)
{
List<ImageFormat> formats = getValidFormats();
foreach (ImageFormat format in formats)
{
if(rawFormat.Equals(format))
{
return true;
}
}
return false;
}
private List<ImageFormat> getValidFormats()
{
List<ImageFormat> formats = new List<ImageFormat>();
formats.Add(ImageFormat.Png);
formats.Add(ImageFormat.Jpeg);
formats.Add(ImageFormat.Gif);
//add types here
return formats;
}
}
Here is a way to do it using viewmodel, take a look at whole code here
Asp.Net MVC file validation for size and type
Create a viewmodel as shown below with FileSize and FileTypes
public class ValidateFiles
{
[FileSize(10240)]
[FileTypes("doc,docx,xlsx")]
public HttpPostedFileBase File { get; set; }
}
Create custom attributes
public class FileSizeAttribute : ValidationAttribute
{
private readonly int _maxSize;
public FileSizeAttribute(int maxSize)
{
_maxSize = maxSize;
}
//.....
//.....
}
public class FileTypesAttribute : ValidationAttribute
{
private readonly List<string> _types;
public FileTypesAttribute(string types)
{
_types = types.Split(',').ToList();
}
//....
//...
}
And file length validation in asp.net core:
public async Task<IActionResult> MyAction()
{
var form = await Request.ReadFormAsync();
if (form.Files != null && form.Files.Count == 1)
{
var file = form.Files[0];
if (file.Length > 1 * 1024 * 1024)
{
ModelState.AddModelError(String.Empty, "Maximum file size is 1 Mb.");
}
}
// action code goes here
}
You may want to consider saving the image to database also:
using (MemoryStream mstream = new MemoryStream())
{
if (context.Request.Browser.Browser == "IE")
context.Request.Files[0].InputStream.CopyTo(mstream);
else
context.Request.InputStream.CopyTo(mstream);
if (ValidateIcon(mstream))
{
Icon icon = new Icon() { ImageData = mstream.ToArray(), MimeType = context.Request.ContentType };
this.iconRepository.SaveOrUpdate(icon);
}
}
I use this with NHibernate - entity defined:
public Icon(int id, byte[] imageData, string mimeType)
{
this.Id = id;
this.ImageData = imageData;
this.MimeType = mimeType;
}
public virtual byte[] ImageData { get; set; }
public virtual string MimeType { get; set; }
Then you can return the image as a FileContentResult:
public FileContentResult GetIcon(int? iconId)
{
try
{
if (!iconId.HasValue) return null;
Icon icon = this.iconRepository.Get(iconId.Value);
return File(icon.ImageData, icon.MimeType);
}
catch (Exception ex)
{
Log.ErrorFormat("ImageController: GetIcon Critical Error: {0}", ex);
return null;
}
}
Note that this is using ajax submit. Easier to access the data stream otherwise.
I have a POJO named "FlashCard" which has a field named "links" which is collection (set) of Link objects. When I submit a FORM to my Action all the POJO fields are populated with values from the form except the collection of "links". I have no idea why this isn't getting populated.
Any advice on how to resolve this problem or how to better troubleshoot it would be much appreciated.
Also, my POJO's collection is a Set. Does it matter (or complicate things) that I'm using a Set and not a List?
I'm including a simplified version of my code below.
Here's my POJO:
public class FlashCard implements java.io.Serializable {
private int flashCardId;
private String question;
private String answer;
private Set<Link> links = new HashSet<Link>(0);
public FlashCard() {
}
public FlashCard(String question, String answer) {
this.question = question;
this.answer = answer;
}
public FlashCard(String question, String answer, Set<Link> links) {
this.question = question;
this.answer = answer;
this.links = links;
}
public int getFlashCardId() {
return this.flashCardId;
}
public void setFlashCardId(int flashCardId) {
this.flashCardId = flashCardId;
}
public String getQuestion() {
return this.question;
}
public void setQuestion(String question) {
this.question = question;
}
public String getAnswer() {
return this.answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
public Set<Link> getLinks() {
return this.links;
}
public void setLinks(Set<Link> links) {
this.links = links;
}
}
Here's the POJO for the Link object:
public class Link implements java.io.Serializable {
private int linkId;
private String url;
private Set<FlashCard> flashcards = new HashSet<FlashCard>(0);
public Link() {
}
public Link(String url) {
this.url = url;
}
public Link(String url, Set<FlashCard> flashcards) {
this.url = url;
this.flashcards = flashcards;
}
public int getLinkId() {
return this.linkId;
}
public void setLinkId(int linkId) {
this.linkId = linkId;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public Set<FlashCard> getFlashcards() {
return this.flashcards;
}
public void setFlashcards(Set<FlashCard> flashcards) {
this.flashcards = flashcards;
}
}
Here's the relevant part of the Action
public class FlashCardAction extends FlashCardsAppBaseAction implements ModelDriven<FlashCard>, Preparable, SessionAware {
static Logger logger = Logger.getLogger(FlashCardAction.class);
FlashCard flashCard = new FlashCard();
Map <String,Object> httpSession;
Session session;
FlashCardPersister fcPersister;
public Map<String, Object> getHttpSession() {
return httpSession;
}
public FlashCard getFlashCard() {
return this.flashCard;
}
public void setFlashCard(FlashCard flashCard) {
this.flashCard = flashCard;
}
public void validate() {
logger.debug("Entering validate()");
if ( flashCard.getQuestion().length() == 0 ){
addFieldError("flashCard.question", getText("error.flashcard.question"));
}
if ( flashCard.getAnswer().length() == 0 ) {
addFieldError("flashCard.answer", getText("error.flashcard.answer"));
}
}
public String saveOrUpdate() {
logger.debug("Entering saveOrUpdate()");
// assume we'll fail
boolean result = false;
// are we creating a New Flash Card or Updating and existing one
// for now, let's assume we are creating a New Flash Card
boolean newFlashCard = true;
// if this is an Update of an existing Flash CArd then we'll have a Flash Card Id other than 0
if (this.flashCard.getFlashCardId() != 0) {
newFlashCard = false;
}
try {
result = fcPersister.saveOrUpdateFlashCard(this.flashCard, session);
// did we save a new FlashCard successfully?
if (result == true && newFlashCard) {
logger.debug("Flash Card created successfully");
this.addActionMessage(getText("actionmessage.flashcard.created"));
}
// did we update an existing Flash Card successfully?
else if (result == true && newFlashCard == false) {
logger.debug("Flash Card updated successfully");
this.addActionMessage(getText("actionmessage.flashcard.updated"));
}
// such a failure
else {
logger.error("unable to create or update FlashCard");
return "error";
}
return "success";
} catch (Exception e) {
logger.error("Exception in createFlashCard():", e);
return "error";
}
}
#Override
public FlashCard getModel() {
return this.flashCard;
}
#Override
public void setSession(Map<String, Object> httpSession) {
this.httpSession = httpSession;
}
#Override
public void prepare() throws Exception {
logger.debug("Entering prepare()");
// get a handle to a Hibernate session
session = getHibernateSession();
// get a handle to the FlashCard persistance utility class
fcPersister = new FlashCardPersister();
}
}
And lastly here's the JSP
<%#page import="com.opensymphony.xwork2.ActionContext"%>
<%#page import="com.opensymphony.xwork2.ActionSupport"%>
<%# page contentType="text/html; charset=UTF-8"%>
<%# taglib prefix="s" uri="/struts-tags"%>
<%# taglib prefix="sjr" uri="/struts-jquery-richtext-tags"%>
<h3><s:text name="label.flashcard.title"/></h3>
<s:actionerror theme="jquery" />
<s:actionmessage theme="jquery"/>
<s:fielderror theme="jquery"/>
<s:form action="saveOrUpdate" method="post">
<s:hidden name="flashCard.flashCardId" />
<s:textfield name="flashCard.question" key="label.flashcard.question" size="66" />
<sjr:tinymce
id="flashCard.answer"
name="flashCard.answer"
key="label.flashcard.answer"
rows="20"
cols="50"
editorTheme="simple"
/>
<s:textfield name="flashCard.links.url" key="label.flashcard.link" size="66" />
<tr>
<td>
<s:submit label="label.flashcard.submit" align="center" theme="simple" />
</td>
<td>
<s:submit key="label.flashcard.cancel" name="redirectAction:list" theme="simple" />
</td>
</tr>
</s:form>
<%((ActionSupport)ActionContext.getContext().getActionInvocation().getAction()).clearErrorsAndMessages();%>
First of all I don't think you can use Set here, because Sets are unordered and you can't get an item from a set by an index or key like List and Map. The only way is to iterate through the set and get the items.
Second assuming you're using a collection other than set, in:
<s:textfield name="flashCard.links.url" key="label.flashcard.link" size="66"/>
You try to set the value of the text field to url field of links which is a collection and doesn't have such a field. So you need to get the specific item from the collection you're editing and pass the value. Like:
<s:textfield name="flashCard.links[0].url" key="label.flashcard.link" size="66"/>
But since you can't get the specific item you are editing I suggest you create a link field in your Action and set the updated link to it. Then you can perform a logic to relace the updated link with obsolete one in you flashcards. Hope this helps.
Since you are using modeldriven and the model is FlashCard, i think the following
<sjr:tinymce
id="flashCard.answer"
name="flashCard.answer"
key="label.flashcard.answer"
rows="20"
cols="50"
editorTheme="simple"/>
should be changed to
<sjr:tinymce
id="flashCard.answer"
name="answer"
key="label.flashcard.answer"
rows="20"
cols="50"
value="answer"
editorTheme="simple"/>
the name field should be given without the prefix flashcard.also you should provide the 'value' attribute in order for it to be pre-populated.