I have a ProfileType form with a user picture field. Is it possible to build two different forms out of the same type and entity on the same page? I want the user to be able to upload an avatar separately by ajax, which I have no problem with, but in the event of no js, I want accessibility and the ability to just use good old php, but in separate forms.
Yes. You need to create two forms with two different names:
$formFactory = $this->container->get('form.factory');
$form1 = $formFactory->createNamed(new MyType(), 'form1', $object);
$form2 = $formFactory->createNamed(new MyType(), 'form2', $object);
if ('POST' === $request->getMethod()) {
if ($request->request->has('form1')) {
$form1->bindRequest($request);
...
}
if ($request->request->has('form2')) {
$form2->bindRequest($request);
...
}
}
Related
I'm working on a project that includes a module that helps with electricity recharge, so what happens is that the user's data is already saved in the app and when they choose to recharge, the app opens up this webpage in a web view.
Currently, I'm using WebBridgeView for opening the webpage as:-
render() {
return (
<WebViewBridge
ref="webviewbridge"
onBridgeMessage={this.onBridgeMessage.bind(this)}
source={{uri: "https://currencypin.com/PrepaidMeterPaymentsV2.0/cartwiz?c=IN&p=5&pm=tm"}}/>
);
}
}
Now, what I want is that when the webpage opens, the form fields come prefilled with the custom data that I have. So that the only field that the user needs to fill on the page is the CAPTCHA.
I was following this article for achieving the same, but it actually assumes that the website is customizable. Which is not possible in my case because it belongs to a 3rd party vendor.
What are the ways to achieve this?
You have to use the injectedJavaScript prop from WebView.
First declare a jsCode variable:
const amount = 2
const jscode = `
if (document.getElementById('txtAmount') == null) {
// field not existing, deal with the error
} else {
document.getElementById('txtAmount').value = '${amount}';
}
`
Please notice the " ` " character. Used to put variables in strings.
Then use it like so:
<WebViewBridge
ref="webviewbridge"
onBridgeMessage={this.onBridgeMessage.bind(this)}
injectedJavaScript={jsCode}
My problem is to dynamically create forms through jquery.
The user should be able to dynamically generate inputs such that he can save multiple rows into the corresponding model/table in one go.
Now I don't know how do I generate the name attribute for the forms for multiple models. I suppose it should be something like ModelName[Property][] (but I would preferably want to do it in 'Yii way' instead of hardcoding the names)
To understand this better, Here I found a similar post in Yii Wiki.
using a single form to collect data for two or more models
How can this be modified for Yii2? So that the user should be able to fill in data for all the (dynamically generated) rows and submit them in one go.
Tutorial of tabular form for multiple models.
Try this, Reference
public function actionCreate()
{
$user = new User;
$profile = new Profile;
if ($user->load(Yii::$app->request->post()) && $profile->load(Yii::$app->request->post()) && Model::validateMultiple([$user, $profile])) {
$user->save(false); // skip validation as model is already validated
$profile->user_id = $user->id;
$profile->save(false);
return $this->redirect(['view', 'id' => $user->id]);
} else {
return $this->render('create', [
'user' => $user,
'profile' => $profile,
]);
}
}
I have a page built in ASP.NET MVC 4 that uses the jquery.validate.unobtrusive library for client side validation. There is an input that needs to be within a range of numbers. However, this range can change dynamically based on user interactions with other parts of the form.
The defaults validate just fine, however after updating the data-rule-range attribute, the validation and message are still triggered on the original values.
Here is the input on initial page load:
<input id="amount" data-rule-range="[1,350]" data-msg-range="Please enter an amount between ${0} and ${1}">
This validates correctly with the message Please enter an amount between $1 and $350 if a number greater than 350 is entered.
After an event fires elsewhere, the data-rule-range is updated and the element looks as such:
<input id="amount" data-rule-range="[1,600]" data-msg-range="Please enter an amount between ${0} and ${1}">
At this point if 500 is entered into the input it will fail validation with the same previous message stating it must be between $1 and $350. I have also tried removing the validator and unobtrusiveValidation from the form and parsing it again with no luck.
$('form').removeData('validator');
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
Is there a clean way to change the validation behavior based on the input attributes dynamically?
As Sparky pointed out changing default attributes dynamically will not be picked up after the validation plugin has been initialized. To best work around this without rewiring how we register validated fields and rules, I found it easiest to register a custom adapter in the unobtrusive library:
jQuery.validator.unobtrusive.adapters.add("amount", {}, function (options) {
options.rules["amount"] = true;
options.messages["amount"] = function () { return $(options.element).attr('data-val-amount'); };
});
jQuery.validator.addMethod("amount", function (val, el, params) {
try {
var max = $(el).attr('data-amount-max');
var min = $(el).attr('data-amount-min');
return val <= max && val >= min;
} catch (e) {
console.log("Attribute data-amount-max or data-amount-min missing from input");
return false;
}
});
Because the message is a function, it will be evaluated every time and always pick up the latest attribute value for data-val-amount. The downside to this solution is that everytime there is a change we need to change all three attributes on the input: data-amount-min, data-amount-max, and data-val-amount.
Finally here is the input markup on initial load. The only attribute that needs to be present on load is data-val-amount.
<input id="amount" data-val-amount="Please enter an amount between ${0} and ${1}" data-val="true">
You cannot change rules dynamically by changing the data attributes. That's because the jQuery Validate plugin is initialized with the existing attributes on page load... there is no way for the plugin to auto re-initialize after dynamic changes are made to the attributes.
You must use the .rules('add') and .rules('remove') methods provided by the developer.
See: http://jqueryvalidation.org/rules/
you can try this one:
// reset the form's validator
$("form").removeData("validator");
// change the range
$("#amount").attr("data-rule-range", "[1,600]");
// reapply the form's validator
$.validator.unobtrusive.parse(document);
charle's solution works! you cannot have model attributes to use it though, I build my inputs like:
#Html.TextBoxFor(m => Model.EnterValue, new
{
#class = "form-control",
id="xxxx"
data_val = "true",
data_val_range = String.Format(Messages.ValueTooBig, Model.ParamName),
data_val_range_max = 6,
data_val_range_min = 2,
data_val_regex_pattern = "^\\d+(,\\d{1,2})?$"
})
and then in javascript:
$("form").removeData("validator");
$("#xxxx").attr('data-val-range-max', 3)
$("#xxxx").attr('data-val-range-min', 0)
$.validator.unobtrusive.parse(document);
I have an MVC project with three roles: Users, Account Managers, and Administrators.
Administrators have their own MVC Area where they have full control over Users and Account Managers. I'm trying to implement functionality to allow Administrators to view the site as any User or Account Manager.
In the Admin Area of the site, I have a View of a list of Users and Account Managers. The list contains a "View Site As User" button for each record.
I have never done anything like this before, but the ViewAs Controller Action is currently set up to create a Session with the selected User's information, like so:
ViewBag.SiteSession = Session["SiteSession"] = new SiteSession()
{
ID = user.ID,
AccountID = user.AccountID,
DisplayName = user.DisplayName,
IsManager = user.IsAdmin,
IsAdmin = false
};
The View relevant to this Action has the Model defined as a string, and nothing else but an iframe with the Model as the src attribute, like so:
#model string
<iframe src="#Model" >
</iframe>
What I'm trying to do is render whichever portion of the site was requested in this iframe. When an Administrator clicks "View As User," I'd like to direct to Home. The URL is generated through this call:
Url.Action("Index", "Home", new { Area = "" }));
The Area is set to nothing to avoid rendering the Admin Area's Home.
Currently, this is not working. I don't know where to even begin, minus what I already have.
I'm looking for any suggestions. All help is greatly appreciated, as this doesn't seem like an easy task.
If you don't know how to help, it would also be appreciated if you could direct this question to somebody that can.
Again, thanks in advance.
The way that I've done this in the past has been to use the concept of an an actual user and an effective user. Most display actions use the effective user to generate their content. Typically I've implemented it as "impersonation" rather than "preview" so the user is actually navigating the site as the user rather than displaying in a separate window. In this case I simply set both in the current session. Things that require admin permission (like switching to/from impersonation) obviously use the real user.
If you wanted to do preview then I'd think about using a parameter on each request to set the effective user. The code would need to understand to add this parameter to all links so that you could navigate in the iframe without messing up navigation in the original interface.
As for removing the area from the url, I think what you have (setting to the empty string) should work. If it's not working you might want to try lowercase area, Url.Action("Index", "Home", new { area = "" }). I'm pretty sure that the RouteValueDictionary that gets created under the hood uses a case insensitive key comparison, though, so it shouldn't matter.
For this task, I ended up creating a separate controller, ViewAsController, which had a controller-wide [Authorize] attribute that only allowed users with the Admin role to access its actions.
In the Start action, a Session object containing the selected User's information is created, like so:
[HttpGet]
public ActionResult Start(int id)
{
var user = db.Users
.First(u => u.ID == id);
Session["SiteSession"] = new SiteSession()
{
//Session data...
};
return PartialView("_IFrame");
}
This Action returns a Partial View that I ended up displaying in a jQuery UI modal dialog window.
Here's the code for that Partial View:
#{
ViewBag.SiteSession = (SiteSession)Session["SiteSession"];
}
<h2>Viewing Site As #ViewBag.SiteSession.DisplayName</h2>
<div>
<iframe src="#Url.Action("Index", "Home", new { Area = "" })"></iframe>
</div>
As you can see, it's extremely bare, and that's exactly what it needs to be. The <iframe> acts as a browser in a browser, allowing the Admin user full access to whichever Actions the selected User would.
For the sake of detail, here's the jQuery that creates the dialog and opens it:
$(function () {
$("#viewAsDialog").dialog({
modal: true,
autoOpen: false,
resizable: true,
draggable: true,
closeOnEscape: false,
height: $(window).height() * .9,
width: 1000,
closeText: '',
close: function () {
$.post("#Url.Action("End", "ViewAs", new { Area = "Admin" })")
.success(function (result) {
});
}
});
});
function viewAs(result) {
$("#viewAsDialog").html(result);
$("#viewAsDialog").dialog("open");
}
You can see here that the dialog is initialized on document-ready, and is not opened until the AJAX call that retrieves the Partial View is successfully completed.
Once the Admin closes the dialog, the server calls the End action in the ViewAs Controller, destroying the session:
[HttpPost]
public ActionResult End()
{
Session["SiteSession"] = null;
return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK);
}
I have two forms on two different views. I would like to post the form input to the second view, and then back to the first form upon posting the second form.
I have set up a test with a route that looks like this :
Route::get('/test1', function() {
return View::make('test1');
});
Route::post('/test2', function() {
$flash = Input::get();
return View::make('test2')->with('flash', $flash);
});
Route::post('/test1', function() {
return View::make('test1')->with('flash', $flash);
});
I am only able to pass $flash once. I'm misunderstanding why I cannot pass it again. I feel like I have to extract it again?
You need to add a form field in /test2 and resubmit the $flash data in order to pass it to /test1 via POST. It's a new request, the app will lose the $flash var otherwise.
A different approach could be to store $flash in a session with Session::put('flash', $flash); and accessing it in the next request.
The best method is to store your data in session. It will be available across multiple request . Using Input::flash() will only be available until the next request. See the Laravel docs for Input::flash() and Session