jQuery unobtrusive validation in .NET MVC 3 - showing success checkmark - asp.net-mvc-3

Using jQuery unobtrusive validation within a .NET MVC project and that seems to be working fine. I'm now trying to show a green checkmark when the field validates correctly (client-side and/or remote).
Here's a sample field declaration:
<div class="clearfix">
#Html.LabelFor(model => model.Address1, "Street")
<div class="input">
#Html.TextBoxFor(model => model.Address1, new { #class = "xlarge", #maxlength = "100", #placeholder = "e.g. 123 Main St" })
<span class="help-message">
#Html.ValidationMessageFor(model => model.Address1)
<span class="isaok">Looks great.</span>
</span>
<span class="help-block">Enter the street.</span>
</div>
</div>
What I'd like to do is add a class 'active' to the "span.isaok" which in turn has a checkmark for a background image.
I tried using highlight/unhighlight:
$.validator.setDefaults({
onkeyup: false,
highlight: function (element, errorClass, validClass) {
$(element).addClass(errorClass).removeClass(validClass);
$(element.form).find("label[for=" + element.id + "]").addClass("error");
$(element).parent().find("span.isaok").removeClass("active");
},
unhighlight: function (element, errorClass, validClass) {
$(element).removeClass(errorClass).addClass(validClass);
$(element.form).find("label[for=" + element.id + "]").removeClass("error");
if ($(element).val().length > 0) {
$(element).parent().find("span.isaok").addClass("active");
}
}
});
but that shows a green checkmark for all fields even if they're empty! (hence obviously wrong)
I then tried using the 'success' option but that never seems to be fired.
What am I missing?
Edit: So I found this blog post and was able to tap into the success function i.e.
$(function () {
var settings = $.data($('form')[0], 'validator').settings;
settings.onkeyup = false;
settings.onfocusout = function (element) { $(element).valid(); };
var oldErrorFunction = settings.errorPlacement;
var oldSuccessFunction = settings.success;
settings.errorPlacement = function (error, inputElement) {
inputElement.parent().find("span.isaok").removeClass("active");
oldErrorFunction(error, inputElement);
};
settings.success = function (label) {
var elementId = '#' + label.attr("for");
$(elementId).parent().find("span.isaok").addClass("active");
oldSuccessFunction(label);
};
});
but now if the form isn't valid it shows both the error message and the valid mark...
and the latter disappears as soon as I click anywhere on the page.

This appears to be an issue with the jquery.validate.unobtrusive interfering with the settings added later in $.validator.setDefault. The trick is to load the unobtrusive script after the custom settings. See here and vote to fix it here.

In case any one has a similar problem, I finally got this working by using the un-minified version of jquery.validate.unobtrusive.js and adding my js to the onError and onSuccess methods. Existing code was left as it. Use the re-minified version during deployment.
Thanks.

This is not a direct answer to your question. I am going to offer an alternative approach to this: TwitterBootstrapMVC.
With this library all you'd have to write for each input is:
#Html.Bootstrap().ControlGroup().TextBoxFor(m => m.Address1)
And that's it. You will have label, input, and validation message - all taken care of, without javascript. It generates proper html mark up for you. You just need to make sure that you have proper standard css for classes like .field-validation-error, .field-validation-valid...

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.

Conditional v-if is working only for the first time?

I have this in my view:
<div class="already_voted" v-if="already_voted" >
<p>You already voted or your are not allowed to vote</p>
</div>
This is my method :
upvote: function(com_id) {
var comment_id = {
comment_id :com_id
}
this.$http.post('/blog/article/comment/upvote', comment_id).then(function(response){
upvote_total= response.data.upvote_value;
this.already_voted = response.data.already_voted;
this.$dispatch('child-msg', this.already_voted);
$('.upvote_class_' + com_id ).text(upvote_total);
$('.isDisabledUpvote_' + com_id).addClass('disabled');
$('.isDisabledDownvote_' + com_id).removeClass('disabled');
},function(response){
});
},
Im getting value on click and if its true it need to show this div.
Problem is that this div is showed only for first time when already_voted is true and thats it. Next time when its true nothing happend. Any suggestion?
It looks like you are mixing jQuery and Vue, which should be avoided unless you have a specific reason to do so. Instead you should bind attributes to data. As a basic version of what you are doing you could bind both the disabled attribute and the message to a voted flag:
Markup
<div id="app">
<div v-if="voted">
You have already voted!
</div>
<button v-bind:disabled="voted" #click="vote()">
Vote
</button>
<button v-bind:disabled="!voted" #click="removeVote()">
Un-Vote
</button>
</div>
View Model
new Vue({
el: '#app',
methods: {
vote(){
this.voted = true;
},
removeVote(){
this.voted = false;
}
},
data: {
voted: false
}
});
Here I'm simply binding the disabled attribute using v-bind to the voted flag to disabled the buttons and am using v-if to show a message if the voted flag is true.
Here's the JSFiddle: https://jsfiddle.net/05sbjqLL/
Also be aware that this inside an anonymous function refers to the anonymous function itself, so either assign this to something (var self = this) outside the function or use an arrow function if using ES6.
EDIT
I've updated the JSFiddle to show you how you might handle your situation based on you comments:
https://jsfiddle.net/umkvps5g/
Firstly, I've created a directive that will allow you to initiate your variable from your cookie:
Vue.directive('init', {
bind: function(el, binding, vnode) {
vnode.context[binding.arg] = binding.value;
}
})
This can now be used as:
<div v-init:voted="{{ $request->cookie('voted') }}"></div>
I simply disabled the button to show you how to bind attributes to data, there's loads more that can be done, for example showing the message after a user clicks the button, I've just added a click counter and bound thev-if to that instead, so the message doesn't show until a user clicks the button:
<div v-if="vote_attempts">
You have already voted!
</div>
Then in vote() method:
vote() {
this.voted = true;
this.vote_attempts++;
},
Then data:
data: {
voted: false,
vote_attempts: 0
}

toastr js is not showing toasts

I am using toastr.js for notification, my drop down code is
<%= Html.DropDownListFor(m => m.MakeText, Model.MakeSelect, string.Empty, new { data_bind = "optionsText: Make,value: Make", Id = "ddMake", style = "width: 200px;font-size: 20px" })%>
and in site typescript is
$(document).ready(function () {
viewModel.Make.subscribe(function (item) {
item && toastr.success('selected make');
});
)}
but problem is i can not see toasts, once i implement only
toastr.success('selected make')
its work good, Can you please tell me what is the issue in subscribe code.
Thanks
item is falsy. That would mean item && toastr.success('selected make'); gets short-circuited and toastr.success('selected make'); is never evaluated.

MVC3 C# TextArea/CkEditor validation issue

I have an MVC3 C# .Net Web App. We are using the ckEditor library to enhance the TextAreas in our app. When using a standard TextArea, the Validation operates correctly. However, in the enhanced TextAreas (implementing the ckEditor), when submitting the page, the Validation fires an error. "Description is Required" even when though there is data present in the TextArea. Upon a second click of the Submit, the form submits fine.
Domain class property:
[Display(Name = "Description")]
[Required(ErrorMessage = "Description is required.")]
public virtual string Description { get; set; }
HTML:
<td style="border: 0;text-align:left " >
#Html.TextAreaFor(model => model.Description,
new
{
rows = 5,
cols = 100,
#class = "celltext2 save-alert"
})
#Html.ValidationMessageFor(model => model.Description)
</td>
I think that applying the ckEditor attributes is messing it up somehow. Ideas?`
Let me clarify more. We have a .js file that queries the controls and then does ckEditor initialzation. when I remove the $(this).ckeditor({}) it works fine.
JS File:
$('textarea').each(function () {
$(this).ckeditor({});
});
Something like this might work:
$('textarea').each(function () {
var textarea = $(this);
$(this).ckeditor({}).on('blur', function() {
textarea.html( $(this).html() );
});
});
EDIT(I've never used the jQuery adapter, after a small lesson I found this to work, the above the blur never fires and $(this).html() is not defined):
$('textarea').each(function () {
var textarea = $(this);
textarea.ckeditor(function () {
textarea.ckeditorGet().on('blur', function () {
textarea.html( this.getData() );
});
});
});
I think it's a little simpler than that. CKE creates an iframe that it is used instead of the actual textarea and you do correctly need to update the contents of the textarea with the data inside CKEditor before submitting. I would suggest, however, that you do this on submit instead of on blur. I would recommend setting an id to the relevant DOM elements but you get the idea.
// Replace textarea(s)
$('textarea').each(function () {
$(this).ckeditor({});
});
// Bind on submit event to update the text
$('form').submit(function() {
// Upate textarea value with the ckeditor data
$('textarea').val(CKEDITOR.instances.ValueCKE.getData());
});

How to properly disable an Html Helper Textbox or TextBoxFor?

I have a razor display that is being used for entry. In one case, I would like the user to be able to populate the text box, in the other case, I would like to prevent the user from populating it. I am using code like:
#Html.TextBoxFor(model => model.Goop, new { #class = "text-box", maxlength = 2, onfocus = "javascript:this.select();" })
if (Model.Review.ReviewType.Equals("M"))
{
<script type="text/javascript">
$(function () {
$("#Goop").prop("disabled", true);
});
</script>
}
I have tried to do this several ways, jQuery (above), CSS attribs, javascript, ASP.NET... but all have the same issue: When the form is submitted, if the Goop textbox is disabled, the value for Goop in the model is Null. Ideas?
Thanks in advance!
Maybe it's not as cool without jQuery, but when I do this in my apps I do something along the lines of
if (Model.Review.ReviewType.Equals("M"))
{
#Html.DisplayFor(model => model.Goop)
#Html.HiddenFor(model => model.Goop)
}
else
{
#Html.TextBoxFor(model => model.Goop)
}
If a form element is disabled, it does not post a value. That's how it's supposed to work.
To work around this, you will need to do one of several things. You can enable the fields just before posting by intercepting the submit method. You can use a hidden field to store the data in addition to the disabled control. Or you can just assume the values on the controller side.
by the way, it should be .prop("disabled", "disabled"), which renders as disabled="disabled", that's standards compliant.

Resources