<g:remoteForm> redirect is not happening - ajax

I am using a to handle a login. In the case of incorrect credentials, I use Ajax to print an error message on the same web page but in the case of success I would like to forward to another web page. What is happening is that even in the case of success it is printing results on the same page. I know that this has partially to do with the fact that you can't send a redirect to Ajax. However, still a newbie to know how to go about it. Any suggestions?
Here is my gsp section having to do with this form:
<g:formRemote name="subForm" url="[controller:'admin', action:'authenticate']" update = "error_message">
<br><br><label>User Name (email): </label><g:textField name = "username" /><br><br>
<label>Password: </label><g:field name = "password" type = "password" /><br><br><br><br>
<div id = "error_message" style = "text-align: center"> </div>
<div style = "text-align: center">(for TWC employees only)</div>
<g:submitButton id = "submit_button" name="Submit"/>
</g:formRemote>
and here is the controller method 'authenticate':
def authenticate = {
try {
MongoClient mongoClient = new MongoClient("localhost", 27017)
DB db = mongoClient.getDB("admin");
def userName = params.username
def passWord = params.password
boolean auth = db.authenticate(userName, passWord.toCharArray())
if (auth)
redirect (action: loggedin)
else {
render "Login or Password incorrect!"
}
}
catch (UnknownHostException e) {
e.printStackTrace();
}
catch (MongoException e) {
e.printStackTrace();
}
}
def displayerror = {
render "Login or Password incorrect!"
}
def loggedin = {}
As it is, I can't get the gsp corresponding to the 'loggedin' method to display. Any ideas?

Minor adjustments needed to previous poster's most helpful suggestions. This is the code that will actually solve the issue.
<g:formRemote name="subForm" url="[controller:'admin', action:'authenticate']" onSuccess="doResult(data)">
<br><br><label>User Name (email): </label><g:textField name = "username" /><br><br>
<label>Password: </label><g:field name = "password" type = "password" /><br><br><br><br>
<div id = "error_message" style = "text-align: center"> </div>
<div style = "text-align: center">(for TWC employees only)</div>
<g:submitButton id = "submit_button" name="Submit"/>
</g:formRemote>
javascript below:
function doResult(data) {
if (data.success == true) {
window.location.href = data.url;
} else {
$("#error_message").html(data.message);
}
}
controller code section below
//success case
render(contentType: 'text/json') {
[success: true, url: createLink(controller: 'whateverController', action: 'whateverAction')]
}
}
else {
render(contentType: 'text/json') {
["success": false, "message": 'Login or Password is incorrect.']
}
importing JSON converter in last set of code isn't needed either.

You are correct that you can't send a redirect using ajax. What you can do, however, is send something back in your ajax response that you can read and redirect if needed.
Instead of just updating the div with the response from your ajax call you will need to send back some JSON data and use the onSuccess attribute of the formRemote tag to pass the results to a function which can act accordingly.
I would suggest you start by reading over the documentation for the formRemote tag, then consider something like the following:
<g:formRemote name="subForm" url="[controller:'admin', action:'authenticate']" onSuccess="doResult(e)">
<br><br><label>User Name (email): </label><g:textField name="username" /><br><br>
<label>Password: </label><g:field name="password" type="password" /><br><br><br><br>
<div id="error_message" style="text-align: center"> </div>
<div style="text-align: center">(for TWC employees only)</div>
<g:submitButton id="submit_button" name="Submit"/>
</g:formRemote>
Notice in the above that onSuccess is now set on the formRemote tag and update is removed. The response from the form submission will now be passed to the javascript function doResult.
This is what the function might look like:
<script>
function doResult(response) {
var result = eval('(' + response.responseText + ')');
if (result.success == true) {
window.location.href = result.url;
} else {
$("#error_message").html(result.message);
}
}
</script>
The only thing left is to change how your controller responds to the form submission. First you will need to add the import for import grails.converters.JSON into your controller. Then change the way it responds. It might look like this:
import import grails.converters.JSON
...
// in the case of an error
render [success: false, message: "Login or Password incorrect!"] as JSON
return
...
// in the case of success
render [success: true, url: createLink(controller: 'whateverController', action: 'whateverAction')] as JSON
return
It may seem like a lot to take in all at once, but once you do it a few times it becomes quite simple. One thing that helps a lot is to read the Grails documentation. It's long, but it's very well written and will help a lot.

Related

Why Does This AJAX.Helper Post call get a Enity Framework Error, but the "Get" doesn't?

As a learning project, I have a MVC & Typescript project and a Web 2.0 & entity framework project, the MVC project is trying to talk to the Web 2.0 project and I have a weird error.
This is my Web API 2.0 Player Controller:
public class PlayerController : ApiController
{
// GET api/<controller>/5
public Player Get(int? id)
{
if (id == null || id == -1)
{
var player = new Player();
LeaderBoardContext.Current.Players.Add(player);
LeaderBoardContext.Current.SaveChanges();
return player;
}
return LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == id);
}
// PUT: api/Scores/5
[ResponseType(typeof(void))]
public IHttpActionResult PostPlayer(LearningCancerAPICalls.Models.Player player)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var model = LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == player.PlayerId);
LeaderBoardContext.Current.Entry<Player>(player).State = EntityState.Modified;
try
{
LeaderBoardContext.Current.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
}
return StatusCode(HttpStatusCode.NoContent);
}
}
Its gone through a few iterations by this point, at one point it was initialising its own DB context at the top of the file but that was mysteriously null during the post. So now i'm using the style that we use in other projects which looks like this:
public static LeaderBoardContext Current
{
get
{
try
{
//added because 'HttpContext.Current.Items["_EntityContext"] ' was mysteriously comming back null....
if (HttpContext.Current.Items["_EntityContext"] == null)
{
HttpContext.Current.Items["_EntityContext"] = new LeaderBoardContext();
}
var obj = HttpContext.Current?.Items["_EntityContext"] as LeaderBoardContext;
return obj;
}
catch (Exception) //should only get here if using background task
{
return null;
}
}
}
So the first weirdness is in the post the context insisted on being null, but forcing it not to be null through the convoluted method above hasn't improved the situation much. Notice the first EF call that I have now put in to basically be the same as the GET:
var model = LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == player.PlayerId);
I have called the GET in both styles (with -1, with valid ID) and it works fine, but the POST has so far led to this error:
Which I would usually associate with a badly initialised EF project, but the GET works! it does exactly what it should do. I have even tried posting to a EF scafold controller with a different model and had the same problem!
The major difference between the two (apart from GET/POST) is the way I call them, this is how I use the GET:
var playerId = -1;
var activeUser:Player;
function initPlayerOnGameStart() {
if (host === undefined) {
host = 'http://localhost:52316';
}
if (playerId === undefined) {
playerId = -1;
}
var uri = host + '/api/Player/' + playerId;
jQuery.getJSON(uri).done(data => {
activeUser = data;
playerId = activeUser.PlayerId;
});
}
In a pure Typescript Json call. To do the POST I am experimenting with AJAX.Helper:
#model LearningCancerAPICalls.Models.Player
<a id="contact-us">Share Score!</a>
<div id="contact-form" class="hidden" title="Online Request Form">
#using (Ajax.BeginForm("", "", null, new AjaxOptions
{
HttpMethod = "POST", Url = "/api/Player",
OnSuccess ="OnSuccess",
OnFailure ="OnFailure"
}, new { id = "formId", name = "frmStandingAdd" }))
{
#Html.LabelFor(m => m.PlayerName);
#Html.TextBoxFor(m => m.PlayerName);
#Html.LabelFor(m => m.Email);
#Html.TextBoxFor(m => m.Email);
#Html.HiddenFor(m => m.PlayerId);
#Html.Hidden( "PlayerId");
<input type="submit" name="submit" value="Ok" />
}
</div>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script>
function OnSuccess() {
alert('Success');
}
function OnFailure(ajaxContext) {
alert('Failure');
}
</script>
Where I set PlayerID from the typescript. This successfully calls the post but crashes on the first use of EF. The other peculiar thing is that if I put a debug on the post. The model doesnt seem correct, as in, when I hover over it, it shows itself as a Player model, there has been no casting error, but it does not let me expand its properties. If I use variables or the imediate window to inspect variables then they are all fine. But I thought it was worth mentioning.
I am going to try a pure ajax call later to see if it resolves it, but I don't understand why the Ajax.helper would be at fault here, it technically does its job and the error is not related to the model that I can see.
UPDATE 1
So I tried the pure ajax call:
Html:
Name: <input type="text" name="fname" id="userName"><br />
<button onclick="postJustPlayer()"> Ok </button>
Typescript
function postJustPlayer() {
let level = jQuery("#chooseGridLevel").val();
let name = jQuery("#userName").val();
let uri = host + '/api/Player';
let player: Player = <Player>{};
player.Email = "Testing";
player.PlayerName = name;
jQuery.post(uri, player);
}
And this WORKS!?? I have no idea why the pure jQuery works, surely as far as EF is concerned it does the exact same thing? why would an AJAX.helper post be any different...
Solved it! This was a true puzzle, only solved when I delved into the network data (tools ftw).
For other newbies to web stuff I will explain how I found the route of this problem. In Chrome Dev Tools there is a Network tab that will show your web requests & responses. So by opening it after clicking my OK Button I can see this for my pure AJAX call:
I could then compare this to when I clicked "Submit" on my ajax form:
I Copy and paste these both into KDiff3, which highlighted one VERY important difference the local host address!
You will notice in the pure ajax request I specified the host, this is because as I mentioned, my web api project and my website project are separate, therefore they are on separate hosts!
So, in reality, the AJAX helper call should never have worked, but as it happens the day before I decided I needed a model from my API project in my website project and at the time thought "I probably shouldn't include my API project as a reference in my main website, but just for now....". So this lead to the API call with the wrong host being valid! With of course the fundamental difference that EF was not set up on THAT host.
So poor old ajax helper got plenty of my cursing for an error that only a special kind of idiot set up could lead to. Changing ajax helper to use the full path:
#model LearningCancerAPICalls.Models.Player
<a id="contact-us">Share Score!</a>
<div id="contact-form" class="hidden" title="Online Request Form">
#using (Ajax.BeginForm("", "", null, new AjaxOptions
{
HttpMethod = "POST", Url = "http://localhost:52316/api/Player",
OnSuccess ="OnSuccess",
OnFailure ="OnFailure"
}, new { id = "formId", name = "frmStandingAdd" }))
{
#Html.LabelFor(m => m.PlayerName);
#Html.TextBoxFor(m => m.PlayerName);
#Html.LabelFor(m => m.Email);
#Html.TextBoxFor(m => m.Email);
#Html.HiddenFor(m => m.PlayerId);
#Html.Hidden( "PlayerId");
<input type="submit" name="submit" value="Ok" />
}
</div>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script>
function OnSuccess() {
alert('Success');
}
function OnFailure(ajaxContext) {
alert('Failure');
}
</script>
Solved the problem! Thank you for anyone who scratched their head over this one, hopefully, this breakdown of a weird bug will be useful to someone.

Using $.ajax to send data via HttpPost in aspnet5, is not working

Ok basically I have a column, and when you click the header a couple of input tags appear dynamically, to change column name and submit it. I want to change it and show it right away using ajax. The code to do this is below, and sorry but the code is in coffee script. Also I am using ASPNET5 RC1 with MVC6.
SumbitColumnForm = () ->
$('.panel-heading').on 'click', 'input.ColumnTitleSumbit', (event) ->
event.preventDefault();
columnName = $('.NewColumnName').val().trim()
columnNumber = $(this).parent().parent().attr 'id'
newColumnData = "{
'ColumnName': 'columnName'
'ColumnNumber': 'columnNumber'
}"
$.ajax
url: '/Board/ChangeColumnName',
type: 'POST'
dataType: 'json',
data: newColumnData,
success: (data) ->
#res = $.parseJSON(data);
alert "Hit the Success part";
error: (xhr, err) ->
alert("readyState: " + xhr.readyState + "\nstatus: " + xhr.status);
alert("responseText: " + xhr.responseText);
The controller actions are
[Route("{p_BoardName}")]
public IActionResult Show(string p_BoardName)
{
m_Board.BoardName = p_BoardName;
ViewData["BoardName"] = m_Board.BoardName;
return View(m_Board);
}
[HttpPost]
public IActionResult ChangeColumnName(ColumnModel newColumnData)
{
string name = newColumnData.ColumnName;
m_Board.ColumnList[newColumnData.ColumnNumber].ColumnName = name;
return View("Show", m_Board);
}
Also the modelS if needed
public class ColumnModel
{
private string m_name;
private int m_columnNumber;
public int ColumnNumber { get { return m_columnNumber; } }
public string ColumnName { get { return m_name;} set { m_name = value; } }
public ColumnModel(string p_Name, int p_ColumnNumber)
{
m_name = p_Name;
m_columnNumber = p_ColumnNumber;
}
}
public class BoardModel
{
private string m_BoardName;
private List<ColumnModel> m_ColumnList;
[Required]
public string BoardName { get { return m_BoardName; } set { m_BoardName = value; } }
public List<ColumnModel> ColumnList
{
get { return m_ColumnList; }
set { m_ColumnList = value; }
}
}
So I have debugged both the Javascript and the controller action ChangeColumnName, and it is not hitting it. The javascript also does not hit the success part of the ajax call also. I have tried everything that comes to mind and done a lot of googling but it just won't work. I feel like it is because of the way I am sending the data for the $.ajax call but really I just don't know why this doesn't work.
Also if i want to update the column name, by removing the input and adding the normal HTML to display the name, am I right to return a view in the action method ChangeColumnName? I mean will it reload the page or add it dynamically. Will I have to add JS code in the success attribute of the $.ajax call to change it dynamically?
Any help will be greatly appreciated. Thank you.
Edit: Added the board model
Edit2: Updating showing the html code as a request of Stephen.
Using bootstrap column/grid design, and also bootstrap panels. I basically have column shown by html
<div id="1" class="panel panel-default BoardColumn">
<div class="panel-heading">
<h3 class="panel-title">Something1</h3>
</div>
</div>
I want to change the header class 'panel-title' and update it dynamically without reloading the page. I have set up another ajax call, so when the header is clicked a few input tags are added and and the html is changed to the following. In minimal explaining I have done this using jquery append and remove functions.
<div id="1" class="panel panel-default BoardColumn">
<div class="panel-heading">
<input class="PreviousColumnName" type="hidden" style="display: none;" value="Something1">
<input class="NewColumnName" name="ColumnName">
<input class="ColumnTitleSumbit" type="submit" value="Continue">
</div>
</div>
Now I want to get the value of the input with class name 'NewcolumnName', update the current column name in the model and place its new name. When the submit button is clicked, I want to remove the input tags and go back to the original html before that, showings it new column name without reloading.

Calling multiple action methods (using ajax) and showing the result of last in a new tab

I have a form in which I need to call two action methods, one after the other. This is how the flow goes.
First I check if the prerequisite data is entered by the user. If not then I show a message that user needs to enter the data first.
If all the prerequisite data is entered, I call an action method which return data. If there is no data returned then I show a message "No data found" on the same page.
If data is returned then I call another action method present in a different controller, which returns a view with all the data, in a new tab.
The View:
#using (Ajax.BeginForm("Index", "OrderListItems", null, new AjaxOptions { OnBegin = "verifyRequiredData"}, new { #id = "formCreateOrderListReport", #target = "_blank" }))
{
//Contains controls and a button
}
The Script in this View:
function verifyRequiredData() {
if ($("#dtScheduledDate").val() == "") {
$('#dvValidationSummary').html("");
var errorMessage = "";
errorMessage = "<span>Please correct the following errors:</span><ul>";
errorMessage += "<li>Please enter Scheduled date</li>";
$('#dvValidationSummary').append(errorMessage);
$('#dvValidationSummary').removeClass('validation-summary-valid').addClass('validation-summary-errors');
return false;
}
else {
$('#dvValidationSummary').addClass('validation-summary-valid').removeClass('validation-summary-errors');
$('#dvValidationSummary').html("");
$.ajax({
type: "GET",
url: '#Url.Action("GetOrderListReport", "OrderList")',
data: {
ScheduledDate: $("#dtScheduledDate").val(),
Crews: $('#selAddCrewMembers').val(),
Priorities: $('#selPriority').val(),
ServiceTypes: $('#selServiceTypes').val(),
IsMeterInfoRequired: $('#chkPrintMeterInfo').val()
},
cache: false,
success: function (data) {
debugger;
if (data !== "No data found") {
//var newUrl = '#Url.Action("Index", "OrderListItems")';
//window.open(newUrl, '_blank');
return true;
} else {
//Show message "No data found"
return false;
}
}
});
return false;
}
}
The "GetOrderListReport" Action method in "OrderList" Controller:
public ActionResult GetOrderListReport(OrderListModel model)
{
var contract = new OrderReportDrilldownParamDataContract
{
ScheduledDate = model.ScheduledDate
//Setting other properties as well
};
var result = OrderDataModel.GetOrderList(contract);
if (string.IsNullOrWhiteSpace(result) || string.IsNullOrEmpty(result))
{
return Json("No data found", JsonRequestBehavior.AllowGet);
}
var deserializedData = SO.Core.ExtensionMethods.DeserializeObjectFromJson<OrderReportDrilldownDataContract>(result);
// send it to index method for list
TempData["DataContract"] = deserializedData;
return Json(deserializedData, JsonRequestBehavior.AllowGet);
}
The last action method present in OrderListItems Controller, the result of which needs to be shown in a new tab:
public ActionResult Index()
{
var deserializedData = TempData["DataContract"] as OrderReportDrilldownDataContract;
var model = new OrderListItemViewModel(deserializedData);
return View(model);
}
The problem is that I am not seeing this data in a new tab, although I have used #target = "_blank" in the Ajax.BeginForm. I have also tried to use window.open(newUrl, '_blank') as can be seen above. But still the result is not shown in a new tab.
Please assist as to where I am going wrong?
If you are using the Ajax.BeginForm you shouldn't also be doing an ajax post, as the unobtrusive ajax library will automatically perform an ajax post when submitting the form.
Also, if you use a view model with data annotation validations and client unobtrusive validations, then there would be no need for you to manually validate the data in the begin ajax callback as the form won't be submitted if any validation errors are found.
The only javascript code you need to add in this scenario is a piece of code for the ajax success callback. That will look as the one you currently have, but you need to take into account that opening in new tabs depends on the browser and user settings. It may even be considered as a pop-up by the browser and blocked, requiring the user intervention to allow them as in IE8. You can give it a try on this fiddle.
So this would be your model:
public class OrderListModel
{
[Required]
public DateTime ScheduledDate { get; set; }
//the other properties of the OrderListModel
}
The form will be posted using unobtrusive Ajax to the GetOrderListReport of the OrderList controller. On the sucess callback you will check for the response and when it is different from "No data found", you will then manually open the OrderListItems page on a new tab.
This would be your view:
#model someNamespace.OrderListModel
<script type="text/javascript">
function ViewOrderListItems(data){
debugger;
if (data !== "No data found") {
var newUrl = '#Url.Action("Index", "OrderListItems")';
//this will work or not depending on browser and user settings.
//passing _newtab may work in Firefox too.
window.open(newUrl, '_blank');
} else {
//Show message "No data found" somewhere in the current page
}
}
</script>
#using (Ajax.BeginForm("GetOrderListReport", "OrderList", null,
new AjaxOptions { OnSucces= "ViewOrderListItems"},
new { #id = "formCreateOrderListReport" }))
{
#Html.ValidationSummary(false)
//input and submit buttons
//for inputs, make sure to use the helpers like #Html.TextBoxFor(), #Html.CheckBoxFor(), etc
//so the unobtrusive validation attributes are added to your input elements.
//You may consider using #Html.ValidationMessageFor() so error messages are displayed next to the inputs instead in the validation summary
//Example:
<div>
#Html.LabelFor(m => m.ScheduledDate)
</div>
<div>
#Html.TextBoxFor(m => m.ScheduledDate, new {id = "dtScheduledDate"})
#Html.ValidationMessageFor(m => m.ScheduledDate)
</div>
<input type="submit" value="Get Report" />
}
With this in place, you should be able to post the data in the initial page using ajax. Then based on the response received you will open another window\tab (as mentioned, depending on browser and user settings this may be opened in a new window or even be blocked) with the second page content (OrderListItems).
Here's a skeleton of what I think you are trying to do. Note that window.open is a popup though and most user will have popups blocked.
<form id="formCreateOrderListReport">
<input type="text" vaule="testing" name="id" id="id"/>
<input type="submit" value="submit" />
</form>
<script type="text/javascript">
$('#formCreateOrderListReport').on('submit', function (event) {
$.ajax({
type: "POST",
url: '/home/test',
data: { id: $('#id').val()},
cache: false
}).done(function () {
debugger;
alert("success");
var newUrl = '/home/contact';
window.open(newUrl, '_blank');
}).fail(function () {
debugger;
alert("error");
});
return false;
});
</script>
Scale down the app to get the UI flow that you want then work with data.

AngularJS: integrating with server-side validation

I have an angular app that contains a save button taken from the examples:
<button ng-click="save" ng-disabled="form.$invalid">SAVE</button>
This works great for client side validation because form.$invalid becomes false as user fixes problems, but I have an email field which is set invalid if another user is registered with same email.
As soon as I set my email field invalid, I cannot submit the form, and the user has no way to fix that validation error. So now I can no longer use form.$invalid to disable my submit button.
There must be a better way
This is another case where a custom directive is your friend. You'll want to create a directive and inject $http or $resource into it to make a call back to the server while you're validating.
Some pseudo code for the custom directive:
app.directive('uniqueEmail', function($http) {
var toId;
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
//when the scope changes, check the email.
scope.$watch(attr.ngModel, function(value) {
// if there was a previous attempt, stop it.
if(toId) clearTimeout(toId);
// start a new attempt with a delay to keep it from
// getting too "chatty".
toId = setTimeout(function(){
// call to some API that returns { isValid: true } or { isValid: false }
$http.get('/Is/My/EmailValid?email=' + value).success(function(data) {
//set the validity of the field
ctrl.$setValidity('uniqueEmail', data.isValid);
});
}, 200);
})
}
}
});
And here's how you'd use it in the mark up:
<input type="email" ng-model="userEmail" name="userEmail" required unique-email/>
<span ng-show="myFormName.userEmail.$error.uniqueEmail">Email is not unique.</span>
EDIT: a small explanation of what's happening above.
When you update the value in the input, it updates the $scope.userEmail
The directive has a $watch on $scope.userEmail it set up in it's linking function.
When the $watch is triggered it makes a call to the server via $http ajax call, passing the email
The server would check the email address and return a simple response like '{ isValid: true }
that response is used to $setValidity of the control.
There is a in the markup with ng-show set to only show when the uniqueEmail validity state is false.
... to the user that means:
Type the email.
slight pause.
"Email is not unique" message displays "real time" if the email isn't unique.
EDIT2: This is also allow you to use form.$invalid to disable your submit button.
I needed this in a few projects so I created a directive. Finally took a moment to put it up on GitHub for anyone who wants a drop-in solution.
https://github.com/webadvanced/ng-remote-validate
Features:
Drop in solution for Ajax validation of any text or password input
Works with Angulars build in validation and cab be accessed at formName.inputName.$error.ngRemoteValidate
Throttles server requests (default 400ms) and can be set with ng-remote-throttle="550"
Allows HTTP method definition (default POST) with ng-remote-method="GET"
Example usage for a change password form that requires the user to enter their current password as well as the new password.:
<h3>Change password</h3>
<form name="changePasswordForm">
<label for="currentPassword">Current</label>
<input type="password"
name="currentPassword"
placeholder="Current password"
ng-model="password.current"
ng-remote-validate="/customer/validpassword"
required>
<span ng-show="changePasswordForm.currentPassword.$error.required && changePasswordForm.confirmPassword.$dirty">
Required
</span>
<span ng-show="changePasswordForm.currentPassword.$error.ngRemoteValidate">
Incorrect current password. Please enter your current account password.
</span>
<label for="newPassword">New</label>
<input type="password"
name="newPassword"
placeholder="New password"
ng-model="password.new"
required>
<label for="confirmPassword">Confirm</label>
<input ng-disabled=""
type="password"
name="confirmPassword"
placeholder="Confirm password"
ng-model="password.confirm"
ng-match="password.new"
required>
<span ng-show="changePasswordForm.confirmPassword.$error.match">
New and confirm do not match
</span>
<div>
<button type="submit"
ng-disabled="changePasswordForm.$invalid"
ng-click="changePassword(password.new, changePasswordForm);reset();">
Change password
</button>
</div>
</form>
I have created plunker with solution that works perfect for me. It uses custom directive but on entire form and not on single field.
http://plnkr.co/edit/HnF90JOYaz47r8zaH5JY
I wouldn't recommend disabling submit button for server validation.
Ok. In case if someone needs working version, it is here:
From doc:
$apply() is used to enter Angular execution context from JavaScript
(Keep in mind that in most places (controllers, services)
$apply has already been called for you by the directive which is handling the event.)
This made me think that we do not need: $scope.$apply(function(s) { otherwise it will complain about $digest
app.directive('uniqueName', function($http) {
var toId;
return {
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
//when the scope changes, check the name.
scope.$watch(attr.ngModel, function(value) {
// if there was a previous attempt, stop it.
if(toId) clearTimeout(toId);
// start a new attempt with a delay to keep it from
// getting too "chatty".
toId = setTimeout(function(){
// call to some API that returns { isValid: true } or { isValid: false }
$http.get('/rest/isUerExist/' + value).success(function(data) {
//set the validity of the field
if (data == "true") {
ctrl.$setValidity('uniqueName', false);
} else if (data == "false") {
ctrl.$setValidity('uniqueName', true);
}
}).error(function(data, status, headers, config) {
console.log("something wrong")
});
}, 200);
})
}
}
});
HTML:
<div ng-controller="UniqueFormController">
<form name="uniqueNameForm" novalidate ng-submit="submitForm()">
<label name="name"></label>
<input type="text" ng-model="name" name="name" unique-name> <!-- 'unique-name' because of the name-convention -->
<span ng-show="uniqueNameForm.name.$error.uniqueName">Name is not unique.</span>
<input type="submit">
</form>
</div>
Controller might look like this:
app.controller("UniqueFormController", function($scope) {
$scope.name = "Bob"
})
Thanks to the answers from this page learned about https://github.com/webadvanced/ng-remote-validate
Option directives, which is slightly less than I do not really liked, as each field to write the directive.
Module is the same - a universal solution.
But in the modules I was missing something - check the field for several rules.
Then I just modified the module https://github.com/borodatych/ngRemoteValidate
Apologies for the Russian README, eventually will alter.
I hasten to share suddenly have someone with the same problem.
Yes, and we have gathered here for this...
Load:
<script type="text/javascript" src="../your/path/remoteValidate.js"></script>
Include:
var app = angular.module( 'myApp', [ 'remoteValidate' ] );
HTML
<input type="text" name="login"
ng-model="user.login"
remote-validate="( '/ajax/validation/login', ['not_empty',['min_length',2],['max_length',32],'domain','unique'] )"
required
/>
<br/>
<div class="form-input-valid" ng-show="form.login.$pristine || (form.login.$dirty && rv.login.$valid)">
From 2 to 16 characters (numbers, letters and hyphens)
</div>
<span class="form-input-valid error" ng-show="form.login.$error.remoteValidate">
<span ng:bind="form.login.$message"></span>
</span>
BackEnd [Kohana]
public function action_validation(){
$field = $this->request->param('field');
$value = Arr::get($_POST,'value');
$rules = Arr::get($_POST,'rules',[]);
$aValid[$field] = $value;
$validation = Validation::factory($aValid);
foreach( $rules AS $rule ){
if( in_array($rule,['unique']) ){
/// Clients - Users Models
$validation = $validation->rule($field,$rule,[':field',':value','Clients']);
}
elseif( is_array($rule) ){ /// min_length, max_length
$validation = $validation->rule($field,$rule[0],[':value',$rule[1]]);
}
else{
$validation = $validation->rule($field,$rule);
}
}
$c = false;
try{
$c = $validation->check();
}
catch( Exception $e ){
$err = $e->getMessage();
Response::jEcho($err);
}
if( $c ){
$response = [
'isValid' => TRUE,
'message' => 'GOOD'
];
}
else{
$e = $validation->errors('validation');
$response = [
'isValid' => FALSE,
'message' => $e[$field]
];
}
Response::jEcho($response);
}

JQuery Load using MVC3 #Url.Action does not pass parameters properly

I noticed that doing #Url.Action("myAction", new { param1 = 123, param2 = 456}) provides me with an invalid URL Home/myAction?param1=123&param2=456.
I am attempting to do
$("#myAjaxDiv").load(url);
But only param1 is getting populated in the action method.
When I remove the & and make it just & then it works, but doing a string replace is super hacky.
url = url.replace("&", "&");
Am I missing something here?
EDIT: Per request I'm including some of my sample app. (you can create a new MVC app and just add these quickly and see for yourself)
Controller:
public ActionResult AjaxTest(int? year, int? month)
{
ViewBag.Message = string.Format("Year: {0}, Month: {1}", year.HasValue ? year.ToString() : "no year", month.HasValue ? month.ToString() : "no month");
return PartialView("AjaxTest");
}
AjaxTest View:
#ViewBag.Message
Index View:
<script>
$(function () {
var url="";
$("#noParams").click(function () {
url = "Home/AjaxTest";
$("#ajaxy").load(url)
$("#url").text(url);
});
$("#yearParam").click(function () {
url = "Home/AjaxTest?year=2012";
$("#ajaxy").load(url)
$("#url").text(url);
});
$("#yearAndMonthParam").click(function () {
url = "Home/AjaxTest?year=2012&month=10";
$("#ajaxy").load(url)
$("#url").text(url);
});
$("#generated").click(function () {
url = "#(Url.Action("AjaxTest", new { year=2012, month=10}))";
$("#ajaxy").load(url);
$("#url").text(url);
});
});
</script>
<a id="noParams" href="#">No Params</a> <br />
<a id="yearParam" href="#">Year Param</a> <br />
<a id="yearAndMonthParam" href="#">Year and Month Param</a> <br />
<a id="generated" href="#">Generated</a> <br />
<div id="ajaxy">
</div>
<div>
URL: <span id="url"></span>
</div>
By default every content (which is not IHtmlString) emitted using a # block is automatically HTML encoded by Razor (see this Razor intro article Html Encoding section)
The Url.Action returns just a plain string so thats why the & gets encoded.
Use the Html.Raw if you don't want the encodeing:
url = "#(Html.Raw(Url.Action("AjaxTest", new { year=2012, month=10})))";
You can build the url in this way also.
var url = "#Url.Action("AjaxTest","YourControllerName")?year=2012&month=10";
$("#ajaxy").load(url);

Resources