Toggling viewability of data with stimulus controller not firing - ruby-on-rails-7

Context: providing a UI where visitor can toggle their input password from hidden to viewable via a stimulus controller: pwdtoggle_controller.js
import { Controller } from "#hotwired/stimulus";
export default class extends Controller {
static targets = ["show", "unhide"];
password() {
// console.log(`${this.value.textContent}`);
// console.log(`${this.input.type}`);
if (this.value.textContent === "show") {
this.value.textContent = "hide";
this.input.type = "text";
} else {
this.value.textContent = "show";
this.input.type = "password";
}
}
get value() {
return this.showTarget;
}
get input() {
return this.unhideTarget;
}
}
However the form or controller must be mistaken. The browser console does not register any errors, but also does not show any behaviour when the link is clicked upon. The signin form view :
<div data-controller="pwdtoggle">
<%= f.password_field :password, placeholder: t('user.password'), class: "unhide", data: {target: "toggle.unhide"} %>
<div class="toogle__password">
<a data-target="toggle.show" data-action="click->toggle#password">show</a>
</div>
</div>
renders in HTML as
<div data-controller="pwdtoggle">
<input placeholder="password" class="unhide" data-target="toggle.unhide" type="password" name="user[password]" id="user_password" data-ddg-inputtype="credentials.password">
<div class="toogle__password">
<a data-target="toggle.show" data-action="click->toggle#password">show</a>
</div>
</div>
rails 7.0.4
ruby 3.1.2
stimulus-rails (1.1.1)
Where is the above mistaken?

Related

How to redirect a ASP.NET Core MVC partial view after file download

I have an asp.net core MVC partial view called ExportPagePartial that allows user to export a page from the system and downloads it. In the HttpGet controller action I show the partial view (as a Modal pop-up) to get user input.
Modal Popup
<a class="dropdown-item" asp-action="ExportPagePartial" asp-route-userId="#Model.UserId" asp-route-businessAccountId="#Model.BusinessAccountId" asp-route-projectId="#Model.ProjectId" asp-route-pageId="#Model.PageId" data-toggle="modal" data-target="#ModalPlaceholder" title="Export page."><i class="fas fa-cloud-download-alt"></i> Export</a>
Controller Get Action
[HttpGet]
public IActionResult ExportPagePartial(string userId, string businessAccountId, string projectId, string pageId)
{
ExportPageViewModel model = new ExportPageViewModel()
{
// Set properties
};
return PartialView(nameof(ExportPagePartial), model);
}
Once the user hits Export button from the Modal pop-up partial view (which is a form submit action) the following HTTPPost action is correctly called.
In this action I have to get the file from the Web Api and then download it via the browser, however after download is complete i want to close the partial view. Once the download is complete the partial view is still visible.
The return action never works and partial modal pop-up view does not close
return RedirectToAction(nameof(BlahRedirectAction));
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExportPagePartial(ExportPageViewModel model)
{
// Call Web API to get the file
string downloadUrl = "blah_blah_url";
using (HttpResponseMessage httpResponse = await WebApiClient.HttpClient.PostAsJsonAsync(downloadUrl, unprotectedExportInput))
{
if (!httpResponse.IsSuccessStatusCode)
{
throw new InvalidOperationException(await httpResponse.Content.ReadAsStringAsync());
}
// Download the file now.
ActionContext actionContext = new ActionContext(HttpContext, ControllerContext.RouteData, ControllerContext.ActionDescriptor, ModelState);
FileStreamResult fileContent = File(await httpResponse.Content.ReadAsStreamAsync(), httpResponse.Content.Headers.ContentType.MediaType, httpResponse.Content.Headers.ContentDisposition.FileName);
await fileContent.ExecuteResultAsync(actionContext);
}
// Redirect to main pain
// The view never redirects and partial view is still visible
return RedirectToAction(nameof(BlahRedirectAction));
}
fileContent.ExecuteResultAsync(actionContext);
This is because when you download the file, ExportPagePartial has determined the return flow, and will not perform the RedirectToAction.
I suggest that you change the post method that triggers ExportPagePartial to ajax to achieve, so that you can successfully execute ExportPagePartial and after the method, redirect the page to what you want in js.
Here is a complete code of my demo based on your code:
public class ExportTestController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpGet]
public IActionResult ExportPagePartial(string userId, string businessAccountId, string projectId, string pageId)
{
ExportPageViewModel model = new ExportPageViewModel()
{
Id = 1,
Gender = "male",
Name = "aaa",
Number = "1231244"
};
return PartialView(nameof(ExportPagePartial), model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExportPagePartial(ExportPageViewModel model)
{
// Call Web API to get the file
string downloadUrl = "blah_blah_url";
using (HttpResponseMessage httpResponse = await WebApiClient.HttpClient.PostAsJsonAsync(downloadUrl, unprotectedExportInput))
{
if (!httpResponse.IsSuccessStatusCode)
{
throw new InvalidOperationException(await httpResponse.Content.ReadAsStringAsync());
}
// Download the file now.
ActionContext actionContext = new ActionContext(HttpContext, ControllerContext.RouteData, ControllerContext.ActionDescriptor, ModelState);
FileStreamResult fileContent = File(await httpResponse.Content.ReadAsStreamAsync(), httpResponse.Content.Headers.ContentType.MediaType, httpResponse.Content.Headers.ContentDisposition.FileName);
await fileContent.ExecuteResultAsync(actionContext);
}
// Redirect to main pain
// The view never redirects and partial view is still visible
return RedirectToAction(nameof(BlahRedirectAction));
}
Index.cshtml:
#{
ViewData["Title"] = "Index";
Layout = null;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script>
$(function () {
$("a").click(function () {
var route = $(this).attr("href");
$('#partial').load(route);
})
$("form").submit(function () {
$.ajax({
url: $("form").attr('action'),
type: 'Post',
data: $("form").serializeArray(),
success: function () {
//$("#ModalPlaceholder").hide();
window.location.href = "/ExportTest/BlahRedirectAction";
}
});
})
})
</script>
<a class="dropdown-item" asp-action="ExportPagePartial"
asp-route-userId="1" asp-route-businessAccountId="1"
asp-route-projectId="1" asp-route-pageId="1"
data-toggle="modal" data-target="#ModalPlaceholder" title="Export page."><i class="fas fa-cloud-download-alt"></i> Export</a>
<div class="modal fade" id="ModalPlaceholder" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<form asp-action="ExportPagePartial" method="post">
<div id="partial">
</div>
</form>
</div>
ExportPagePartial.cshtml:
#model ExportPageViewModel
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label asp-for="Id" class="control-label">#Model.Id</label>
<input asp-for="Id" class="form-control" hidden />
</div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Gender" class="control-label"></label>
<input asp-for="Gender" class="form-control" />
<span asp-validation-for="Gender" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Number" class="control-label"></label>
<input asp-for="Number" class="form-control" />
<span asp-validation-for="Number" class="text-danger"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" >Save changes</button>
</div>
</div>
</div>
Here is the test result:

Asp net core 2.2, updating usename not working

I want my user to be able to change his username so i made this:
<form asp-action="UpdateUserProfile" asp-controller="Account" method="post">
<div class="account-details-item">
<h2 class="center-text text-left account-details-item-title">UserName:</h2>
<input name="username" id="username-text" readonly="readonly" class="center-text account-details-item-value" asp-for="User.UserName" value=#Model.User.UserName>
<a id="btn-username" class="account-details-item-btn" >Edit</a>
</div>
<div class="account-details-item">
<h2 class="center-text text-left account-details-item-title">Email:</h2>
<input name="email" readonly="readonly" id="email-text" class="center-text account-details-item-value email" asp-for="User.Email" value=#Model.User.Email />
<a id="btn-email" class="account-details-item-btn" >Edit</a>
</div>
<div class="account-details-item">
<h2 class="center-text text-left account-details-item-title">Phone number:</h2>
<input name="phonenumber" readonly="readonly" id="phone-text" class="center-text account-details-item-value" asp-for="User.PhoneNumber" value=#Model.User.PhoneNumber>
<a id="btn-phone" class="account-details-item-btn" >Edit</a>
</div>
<div class="btns-container">
<div class="btn-item"><a asp-action="Index" asp-controller="Cart" asp-route-id=#Model.User.CartId>Go To Cart</a></div>
<div id="save-btn" class="btn-item"><button type="submit">Save Changes</button></div>
</div>
</form>
And in AccountController:
[HttpPost]
public IActionResult UpdateUserProfile()
{
var username = Request.Form["username"];
var phonenumber = Request.Form["phonenumber"];
var email = Request.Form["email"];
var user = _userService.GetUser(User);
if(`enter code here`IsUsernameDiffrent(username))
{
_userService.UpdateUsername(User, username);
_userManager.UpdateAsync(user.Result);
}
else if(IsEmailDiffrent(email))
{
_userService.UpdateEmail(User, email);
_userManager.UpdateAsync(user.Result);
}
else if (IsPhoneNumberDiffrent(phonenumber))
{
_userService.UpdatePhoneNumber(User, phonenumber);
_userManager.UpdateAsync(user.Result);
}
return RedirectToAction("Index");
}
And in Service Class:
public async void UpdateUsername(ClaimsPrincipal user, string newUsername)
{
var currentUser = await GetUser(user);
currentUser.UserName = newUsername;
_dbContext.SaveChanges();
}
The issue is that if user change his username he still have to login with the old one,
changes are made in database but whenever i try to login with new username it says "Invalid login attempt"
I have an Update User Action in my project and it does work in mine, and its little different from yours, but you can try to change it like that:
[HttpPost]
public async Task<IActionResult> UpdateUserProfile()
{
var user = _userService.GetUser(User);
if (user == null)
{
return StatusCode(404);
}
var username = Request.Form["username"];
if(IsUsernameDifferent(username))
{
user.UserName = username;
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction("Action", "Controller");
}
return View();
}
return View();
}
I feel that more code must be seen to analyze the issue. But, make sure you are using transaction scope in your services when you are making changes to the data in the database like update or delete.
Using this technique will ensure that the code is consistent. Changes to the database will not happen unless everything inside the scope is successful. For example, if you are updating and then deleting, what if while deleting an error occurs. Then your data will be updated in the database but not deleted. This is to make sure that both happens successfully or nothing happens at all.
Refer this link.
https://learn.microsoft.com/en-us/ef/core/saving/transactions

Pass parameter form MVC to Stimulsoft report

The View code is as follows:
#using Stimulsoft.Report.Mvc;
#using Stimulsoft.Report;
#{
ViewBag.Title = "ListPouyaProject";
Layout = "~/Views/Shared/_mainView.cshtml";
}
<section class="content">
<!-- Default box -->
<div class="box">
<div class="box-body">
<div class="form-group">
Start Date: <input type="text" id="date1" name="date1" onclick="PersianDatePicker.Show(this, '1392/03/22');" />
End Date : <input type="text" id="date2" name="date2" onclick="PersianDatePicker.Show(this, '1397/03/22');" />
</div>
<div class="form-group">
#Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions()
{
Actions =
{
GetReport = "Report4_ListPouyaProject",
ViewerEvent = "ViewerEvent"
}
})
</div>
</div>
</div>
</section>
The Controller code is as follows:
public ActionResult Report4_ListPouyaProject()
{
var report = new StiReport();
report.Load(Server.MapPath("~/Content/Reports/ListPouyaProject.mrt"));
return StiMvcViewer.GetReportResult(report);
}
public ActionResult ListPouyaProject()
{
return View();
}
public ActionResult ViewerEvent()
{
return StiMvcViewer.ViewerEventResult();
}
I want to pass the date1 and date2 variables to the controller from view.
To do this, we need to add the following commands to the contoroller :
report.CompiledReport.DataSources["spm_report_4_ListPouyaProject"].Parameters["StartDate"].ParameterValue = DateTime.Parse(date1);
report.CompiledReport.DataSources["spm_report_4_ListPouyaProject"].Parameters["EndDate"].ParameterValue = DateTime.Parse(date2);
How to pass the parameters date1 and date2 from view to controller?
First, you need to add the StiMvcViewer component to the view page. Also, you need to pass the StiMvcViewerOptions object to the constructor. The minimum required options are two actions - GetReport and ViewerEvent, they are located in the Actions options group.
#using Stimulsoft.Report.MVC;
#Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions()
{
Actions =
{
GetReport = "GetReport",
ViewerEvent = "ViewerEvent"
}
})
<div style="width: 150px;">
#Html.ActionLink("Simple List", "Index", new { id = "1" })
<br />Report Snapshot
</div>
and in controoller :
public ActionResult GetReport(int? id)
{
// Create the report object
StiReport report = new StiReport();
switch (id)
{
// Load report snapshot
case 1:
// Load report
// Load report snapshot
report.LoadDocument(Server.MapPath("~/Content/Reports/SimpleList.mdc"));
break;
}
// Load data from XML file for report template
if (!report.IsDocument)
{
DataSet data = new DataSet("Demo");
data.ReadXml(Server.MapPath("~/Content/Data/Demo.xml"));
report.RegData(data);
}
return StiMvcViewer.GetReportResult(report);
}
public ActionResult ViewerEvent()
{
return StiMvcViewer.ViewerEventResult();
}

MVC 4 Html.Editor ignores EditorTemplate

I'm trying to implement some custom EditorTemplates but they're only being rendered by my Create view, and not the Edit one.
Model
public class Page {
public int PageID { get; set; }
[DataType(DataType.Html)]
[AllowHtml]
// I tried including [UIHint("Html")] but this made no difference
public string Content { get; set; }
...
}
/Views/Shared/EditorTemplates/Html.cshtml
#model string
#Html.TextArea("", Model, new { #class = "html"})
/Views/Shared/EditorTemplates/Object.cshtml
#if (ViewData.TemplateInfo.TemplateDepth > 1)
{
#ViewData.ModelMetadata.SimpleDisplayText
} else {
#Html.ValidationSummary(false)
foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit
&& !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml) {
#Html.Editor(prop.PropertyName)
#prop.DataTypeName
} else {
<div class="form-field">
#if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())) {
#Html.Label(prop.PropertyName)
}
#Html.Editor(prop.PropertyName)
</div>
}
}
}
/Views/Page/Create.cshtml ( This correctly renders Html.cshtml )
#model MvcDisplayTemplates.Models.Page
#using (Html.BeginForm()) {
#Html.EditorForModel(Model)
<p><input type="submit" value="Create" /></p>
}
/Views/Page/Edit.cshtml ( This simply renders the default single line text editor )
#model MvcDisplayTemplates.Models.Page
#using (Html.BeginForm()) {
#Html.EditorForModel(Model)
<p><input type="submit" value="Save" /></p>
}
Interestingly, if I use EditorFor on Edit.cshtml then Html.cshtml is actually rendered. e.g.
#Html.EditorFor(model => model.Content)
UPDATE: If I delete object.cshtml then Html.cshtml is also rendered correctly. So this does seem to be an issue in Object.cshtml. It just seems odd that it works on one view but not another
I fixed by explicitly setting the template in Object.cshtml
#Html.Editor(prop.PropertyName, prop.TemplateHint ?? prop.DataTypeName)
Still not clear why it previously worked in one view but not the other though.

MVC 1.0 Ajax.BeginForm() submit inside an Html.BeginForm()

I have a View for creating a new account in my application. This view starts with Html.BeginForm() and hits the right controller (Create) no problems.
I decided to add an Ajax.BeginForm() so that I could make sure an account with the same name doesn't already exist in my application.
When I click the submit using either button it goes to the same controller (Create). To try and differentiate which submit button was clicked, I put in a check to see if the request is Ajax then try to run a different code path. But Request.IsAjaxRequest() doesn't fire. What is my best bet to implement this functionality in an existing form with MS Ajax?
<% using (Html.BeginForm()) {%>
..............
<% using(Ajax.BeginForm("Echo",
new AjaxOptions() { UpdateTargetId = "EchoTarget" }))
{ %>
Echo the following text:
<%=Html.TextBox("echo", null, new { size = 40 })%>
<input type="submit" value="Echo" />
<% } %>
<div id="EchoTarget">
controller code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(User newUser)
{
if (Request.IsAjaxRequest())
{
return Content("*you hit the ajax button");
}
else
{ //regular create code here.....
}
</div>
If you insist on multiple form usage..use Javascript in a some function like this
<SCRIPT>
function InnerFormSubmitter(dataForm, actionForm) {
actionForm.innerHTML = dataForm.innerHTML;
actionForm.submit();
}
</SCRIPT>
<form name="yourButton" action="ValidateSomething" method="post"></form>
<form name="mainForm" action="SavedData" method="post">
<input type="textbox" name="text1">
<input type="textbox" name="text2">
<button name="validateUserButton" id="FormButton" onChange=
"InnerFormSubmitter (this.form, document.getElementById('yourButton'))">
</button>
</form>
Hope this helps!
Addendum on jQuery usage for your scenario:
Since you wanted a link:
Check Availability
function isValidUser(userId) {
var url = "<CONTROLLER>/<ACTION>/" + userId;
$.post(url, function(data) {
if (data) {
// callback to show valid user
} else {
// callback to show error/permission
}
});
}
And you controller should have:
[AcceptVerbs("POST")]
public bool IsValidUser(int id) {
// check availability
bool allow = CheckUser();
// if allow then insert
if (allow) {
//insert user
return true;
} else {
return false;
}
}
Further Update on jQuery:
Instead of
document.getElementById('UserIdent').value
you can use
$('#UserIdent').val();
Update on JSON usage
The JsonResult class should be used in the Controller and $.getJson function in the View.
$(function() {
$('#yourLinkOrButton').click(function() {
$.getJSON("<CONTROLLER>/GetUserAvailability/", null, function(data) {
$("#yourDivorLablel").<yourFunctionToUpdateDiv>(data);
});
});
public JsonResult GetUserAvailability()
{
//do all validation and retrieval
//return JsonResult type
}
You cannot nest forms, ever, in any HTML page, no matter how you generate the form. It isn't valid HTML, and browsers may not handle it properly. You must make the forms siblings rather than children.

Resources