I have the following code. I want to display here the timestamp of the deleted task in my index.ejs file.
<% for(var i = 0; i < complete.length; i++){ %>
<li><%= complete[i] %> </li>
<% } %>
in my index.js i installed npm date-format liabrary and i have this code. But i don't get the code running with the deleted time in my ejs file
var format = require('date-format');
//console.log(format(new Date(), 'yyyyMMdd\tHH:mm:ss'));
var date_time = new Date();
console.log(date_time);
var task = [];
var complete = [];
//post route for adding new task
app.post("/addtask", function(req, res) {
var newTask = req.body.newtask;
//add the new task from the post route
task.push(newTask);
res.redirect("/");
});
app.post("/removetask", function(req, res) {
var completeTask = req.body.check;
//check for the "typeof" the different completed task, then add into the complete task
if (typeof completeTask === "string") {
complete.push(completeTask);
//check if the completed task already exits in the task when checked, then remove it
task.splice(task.indexOf(completeTask), 1);
} else if (typeof completeTask === "object") {
for (var i = 0; i < completeTask.length; i++) {
complete.push(completeTask[i]);
task.splice(task.indexOf(completeTask[i]), 1);
}
}
res.redirect("/");
});
i tried to use the date object and to display it with concatination, but it didn't work
I solved my own question. I installed moment and added to index.js
var moment = require('moment');
app.locals.moment = require('moment');
and then i added to my index.ejs file the code.
<% for(var i = 0; i < complete.length; i++){ %>
<li><%= complete[i] %> erledigt am <%= moment().format('DD.MM.YYYY, HH:mm:ss') %></li>
<% } %>
Related
I am using framework7.In AJAX success function I am loading first 20 people and remaining to be loaded by infinite scroll.
This is the div element
<div class="page-content infinite-scroll" data-distance="50">
<div class="searchbar-backdrop"></div>
<div class="entry_content">
<div class="members_list list searchbar-found list-block">
<ul class="row22">
</ul>
</div>
<div class="block searchbar-not-found">
<div class="block-inner">Nothing found</div>
</div>
</div>
Also this is the script
$on('pageInit', () => {
app.request({
url: base_url+api_path+'/members_directory.php',
method: "POST",
timeout: 0,
dataType: "json",
beforeSend: function () {
app.preloader.show();
},
success: function(data) {
app.preloader.hide();
console.log(data);
for(var i=0;i<20;++i){
// for(var i=0;i<data.getEmployees.length;++i){
var user_id = data.getEmployees[i].user_id;
var username = data.getEmployees[i].username;
var profile_photo = data.getEmployees[i].profile_photo;
var employee_name = data.getEmployees[i].employee_name;
var employee_id = data.getEmployees[i].employee_id;
$('.page_members_dir .members_list ul').append(
'<li class="item-content member_item"><div class="item-title member_name">'+employee_name+'</div></li>'
);
};
/*infinite*/
var loading = false;
// Last loaded index
var lastIndex = $('.list-block li').length;
// Max items to load
var maxItems = 60;
// Append items per load
var itemsPerLoad = 20;
// Attach 'infinite' event handler
$('.infinite-scroll').on('infinite', function () {
// app.attachInfiniteScroll($('.infinite-scroll'));
console.log("inside");
// Exit, if loading in progress
if (loading) return;
// Set loading flag
loading = true;
// Emulate 1s loading
setTimeout(function () {
// Reset loading flag
loading = false;
if (lastIndex >= maxItems) {
// Nothing more to load, detach infinite scroll events to prevent unnecessary loadings
app.detachInfiniteScroll($('.infinite-scroll'));
// Remove preloader
$('.infinite-scroll-preloader').remove();
return;
}
// Generate new items HTML
var html = '';
for (var i = lastIndex + 1; i <= data.getEmployees.length; i++) {
var user_id = data.getEmployees[i].user_id;
var username = data.getEmployees[i].username;
var profile_photo = data.getEmployees[i].profile_photo;
var employee_name = data.getEmployees[i].employee_name;
var employee_id = data.getEmployees[i].employee_id;
html += '<li class="item-content member_item"><div class="item-title member_name">'+employee_name+'</div></li>';
}
console.log(html);
// Append new items
$('.list-block ul').append(html);
// Update last loaded index
lastIndex = $('.list-block li').length;
}, 1000);
});
},
error: function(data) {
//console.log('error');
console.log(data);
}
});
});//pageInit
return $render;
}
I am not getting any errors in console.
The list data is getting from ajax page.
If I console after $('.infinite-scroll').on('infinite', function () nothing is displayed.
Is this right way to infinite scroll use in AJAX success function.
Please help
My database return a field with raw html string. I'd like to modify element by adding ng-click='showImage()'. For example, the original html is:
<p>
<img src="http://mywebsite.com/img-1.jpg" />
</p>
My source code in my controller:
var parser = new DOMParser();
var doc = parser.parseFromString($scope.body, 'text/html');
var images = doc.getElementsByTagName('img')
for (var i = 0; i < images.length; i++ ) {
var img = images[i];
img.setAttribute('ng-click','showImage()');
$scope.addImage(img.getAttribute('src'));
}
$scope.body = doc.documentElement.innerHTML;
The result is correct:
<p>
<img src="http://mywebsite.com/img-1.jpg" ng-click="showImage()"/>
</p>
But I got the following error message when I click the image:
Uncaught TypeError: scope.$apply is not a function
Why? Can anybody help? Thanks.
Edit (complete source code for the controller):
(function() {
'use strict';
angular
.module('App')
.controller('DetailsController', DetailsController);
DetailsController.$inject = ['$scope','$stateParams','ParseSvc','$controller'];
function DetailsController($scope, $stateParams, ParseSvc, $controller) {
$controller('BaseController', { $scope: $scope });
$scope.article = JSON.parse($stateParams.article);
activate();
function activate() {
var parser = new DOMParser();
var doc = parser.parseFromString($scope.article.body, 'text/html');
var images = doc.getElementsByTagName('img')
for (var i = 0; i < images.length; i++ ) {
var img = images[i];
img.setAttribute('ng-click','showImages(' + i + ')');
$scope.addImage(img.getAttribute('src'));
}
$scope.article.body = doc.documentElement.innerHTML;
}
}
})();
Base controller:
(function() {
'use strict';
angular.module('App')
.controller('BaseController', ['$scope','$ionicModal','$ionicSlideBoxDelegate','$ionicScrollDelegate',
function($scope, $ionicModal, $ionicSlideBoxDelegate, $ionicScrollDelegate) {
$scope.allImages = [];
$scope.zoomMin = 1;
$scope.addImage = function(src) {
$scope.allImages.push({'src' : src});
}
$scope.showImages = function(index) {
$scope.activeSlide = index;
$scope.showModal('templates/gallery-zoomview.html');
}
$scope.showModal = function(templateUrl) {
$ionicModal.fromTemplateUrl(templateUrl, {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
$scope.modal.show();
});
};
// Close the modal
$scope.closeModal = function() {
$scope.modal.hide();
$scope.modal.remove();
};
$scope.updateSlideStatus = function(slide) {
var zoomFactor = $ionicScrollDelegate.$getByHandle('scrollHandle' + slide).getScrollPosition().zoom;
if (zoomFactor == $scope.zoomMin) {
$ionicSlideBoxDelegate.enableSlide(true);
} else {
$ionicSlideBoxDelegate.enableSlide(false);
}
}
}]);
})();
I found an compatibility issue using the two mentioned components on same list, my html and js code below:
HTML:
<ion-content ng-controller="homeCtrl">
<ion-refresher on-refresh="loadNewContent()" pulling-text="LoadMore..." spinner="android"></ion-refresher>
<div ng-repeat="item in items">
<img ng-src="{{pathUrl+item['path']}}" style="height: auto;width:100%;">
</div>
<ion-infinite-scroll ng-if="hasMore" on-infinite="loadMoreContent()" spinner="spiral" distance="5" immediate-check="false"></ion-infinite-scroll>
</ion-content>
JavaScript:
JiCtrls.controller('homeCtrl', ['$scope', '$timeout', 'DbService', 'JsonService',
function ($scope, $timeout, DbService, JsonService) {
$scope.items = [];
$scope.hasMore = true;
var run = false;
loadData(0);
//下拉更新
$scope.loadNewContent = function () {
loadData(2);
// Stop the ion-refresher from spinning
$scope.$broadcast("scroll.refreshComplete");
};
//上拉更新
$scope.loadMoreContent = function () {
loadData(1);
$scope.$broadcast('scroll.infiniteScrollComplete');
};
function loadData(stateType) {
if (!run) {
run = true;
if ($scope.sql == undefined) {
$scope.sql = "select top 5 * from information ";
}
DbService.getData($scope.sql, '').success(function (data, status, headers, config) {
var convertData = JsonService.convertData(data);
if (stateType == 1 || stateType == 0) {
// $scope.items = $scope.items.concat(convertData);
for (var i = 0; i < convertData.length; i++) {
$scope.items.push(convertData[i]);
}
}
else {
for (var i = 0; i < convertData.length; i++) {
$scope.items.unshift(convertData[i]);
}
}
if (convertData == null || convertData.length <= 0) {
$scope.hasmore = false;
;
}
$timeout(function () {
run = false;
}, 500);
}).error(function (errorData, errorStatus, errorHeaders, errorConfig) {
console.log(errorData);
});
}
}
}
]);
Everything is normal in Chrome browser and Iphone, but in a part of the Android phone there is a big problem.When ion-refresher trigger on-refresh function,the on-infinite="loadMoreContent()" function will run indefinitely. So,What is the problem?
Try to put $scope.$broadcast("scroll.refreshComplete"); and $scope.$broadcast('scroll.infiniteScrollComplete'); into DbService.getData(...).success() callback, not in functions triggered by on-infinite and on-refresh.
Explanation:
When the user scrolls to the end of the screen, $scope.loadMoreContent which is registered with on-infinite is triggered. The spinner shows, and ion-infinite-scroll pauses checking whether the user has reached the end of the screen, until $scope.$broadcast('scroll.infiniteScrollComplete'); is broadcast, when it hides the spinner and resumes the checking.
In your code, imagine there's a 3s delay in network, no new item is added to the list in that 3s. As a result, the inner height of ion-content is never updated, so the check that determines whether the user has reached the end of the screen will always return true. And you effectively prevent ion-infinite-scroll from pausing this checking by broadcasting scroll.infiniteScrollComplete the moment on-infinite is triggered. That's why it will update indefinitely.
To improve your code quality and prevent future problems, you may need to call $scope.$apply in your DbService.getData().success() callback (depending on the implementation of getData) and manually notify ion-content to resize in the callback.
P.S. 来自中国的Ionic开发者你好 :-) 两个中国人讲英语真累啊
Update
I've made a codepen that combines both ion-refresher and ion-infinite-scroll, I think it's working pretty fine.
http://codepen.io/KevinWang15/pen/xVQLPP
HTML
<html ng-app="ionicApp">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title>ion-refresher + ion-infinite-scroll 2</title>
<link href="//code.ionicframework.com/nightly/css/ionic.css" rel="stylesheet">
<script src="//code.ionicframework.com/nightly/js/ionic.bundle.js"></script>
</head>
<body ng-controller="MyCtrl">
<ion-header-bar class="bar-positive">
<h1 class="title">ion-refresher + ion-infinite-scroll 2</h1>
</ion-header-bar>
<ion-content delegate-handle="mainScroll">
<ion-refresher on-refresh="doRefresh()">
</ion-refresher>
<ion-list>
<ion-item ng-repeat="item in list">{{item}}</ion-item>
</ion-list>
<ion-infinite-scroll
ng-if="hasMore"
on-infinite="loadMore()"
distance="1%">
</ion-infinite-scroll>
</ion-content>
</body>
</html>
JS
angular.module('ionicApp', ['ionic'])
.controller('MyCtrl', function($scope, $timeout, $q, $ionicScrollDelegate) {
/*
list of items, used by ng-repeat
*/
$scope.list = [];
var itemOffset = 0,
itemsPerPage = 5;
/*
used by ng-if on ion-infinite-scroll
*/
$scope.hasMore = true;
/*
isRefreshing flag.
When set to true, on data arrive
it first empties the list
then appends new data to the list.
*/
var isRefreshing = false;
/*
introduce a custom dataFetcher instance
so that the old fetch process can be aborted
when the user refreshes the page.
*/
var dataFetcher=null;
/*
returns a "dataFetcher" object
with a promise and an abort() method
when abort() is called, the promise will be rejected.
*/
function fetchData(itemOffset, itemsPerPage) {
var list = [];
//isAborted flag
var isAborted = false;
var deferred = $q.defer();
//simulate async response
$timeout(function() {
if (!isAborted) {
//if not aborted
//assume there are 22 items in all
for (var i = itemOffset; i < itemOffset + itemsPerPage && i < 22; i++) {
list.push("Item " + (i + 1) + "/22");
}
deferred.resolve(list);
} else {
//when aborted, reject, and don't append the out-dated new data to the list
deferred.reject();
}
}, 1000);
return {
promise: deferred.promise,
abort: function() {
//set isAborted flag to true so that the promise will be rejected, and no out-dated data will be appended to the list
isAborted = true;
}
};
}
$scope.doRefresh = function() {
//resets the flags and counters.
$scope.hasMore = true;
itemOffset = 0;
isRefreshing = true;
//aborts previous data fetcher
if(!!dataFetcher) dataFetcher.abort();
//triggers loadMore()
$scope.loadMore();
}
$scope.loadMore = function() {
//aborts previous data fetcher
if(!!dataFetcher) dataFetcher.abort();
//fetch new data
dataFetcher=fetchData(itemOffset, itemsPerPage);
dataFetcher.promise.then(function(list) {
if (isRefreshing) {
//clear isRefreshing flag
isRefreshing = false;
//empty the list (delete old data) before appending new data to the end of the list.
$scope.list.splice(0);
//hide the spin
$scope.$broadcast('scroll.refreshComplete');
}
//Check whether it has reached the end
if (list.length < itemsPerPage) $scope.hasMore = false;
//append new data to the list
$scope.list = $scope.list.concat(list);
//hides the spin
$scope.$broadcast('scroll.infiniteScrollComplete');
//notify ion-content to resize after inner height has changed.
//so that it will trigger infinite scroll again if needed.
$timeout(function(){
$ionicScrollDelegate.$getByHandle('mainScroll').resize();
});
});
//update itemOffset
itemOffset += itemsPerPage;
};
});
The correct Javscript is as follows:
JiCtrls.controller('homeCtrl', ['$scope', '$timeout', '$ionicScrollDelegate', 'DbService', 'JsonService',
function ($scope, $timeout, $ionicScrollDelegate, DbService, JsonService) {
$scope.items = [];
$scope.hasMore = true;
loadData(0);
//下拉更新
$scope.loadNewContent = function () {
loadData(2);
};
//上拉更新
$scope.loadMoreContent = function () {
loadData(1);
};
function loadData(stateType) {
if ($scope.sql == undefined) {
$scope.sql = "select top 5 * from information”;
}
DbService.getData($scope.sql, '').success(function (data, status, headers, config) {
var convertData = JsonService.convertData(data);
if (stateType == 0) {
for (var i = 0; i < convertData.length; i++) {
$scope.items.push(convertData[i]);
}
}
else if (stateType == 1) {
// $scope.items = $scope.items.concat(convertData);
for (var i = 0; i < convertData.length; i++) {
$scope.items.push(convertData[i]);
}
$timeout(function () {
$scope.$broadcast('scroll.infiniteScrollComplete');
}, 500);
}
else {
for (var i = 0; i < convertData.length; i++) {
$scope.items.unshift(convertData[i]);
}
// Stop the ion-refresher from spinning
$timeout(function () {
$scope.$broadcast("scroll.refreshComplete");
}, 500);
}
$ionicScrollDelegate.resize();
if (convertData == null || convertData.length <= 0) {
$scope.hasmore = false;
}
}).error(function (errorData, errorStatus, errorHeaders, errorConfig) {
console.log(errorData);
});
}
}
]);
<%: Html.ActionLink("Cancel", "Edit", "Users", new {id = " + userID + " }, null) %>
In the code above userId is a variable. This syntax is not right, what should it be?
You cannot use an HTML helper which is run on the server to use a Javascript variable which is known on the client. So you need to generate your URL with the information you dispose on the server. So all you can do on the server is this:
<%: Html.ActionLink("Cancel", "Edit", "Users", null, new { id = "mylink" }) %>
Then I suppose that on the client you are doing some javascript (ideally with jquery) and a moment comes when you want to query the server using this url and the userID you've calculated on the client. So for example you could modify the link action dynamically by appending some id:
$(function() {
$('#mylink').click(function() {
var userId = ...
this.href = this.href + '?' + userId;
});
});
or if you wanted to AJAXify this link:
$(function() {
$('#mylink').click(function() {
var userId = ...
$.ajax({
url: this.href,
data: { id: userId },
success: function(result) {
...
}
});
return false;
});
});
I am using jquery.validate in MVC 2 with MicrosoftMvcJQueryValidation. I have data annotations on my model which is then being translated into jquery validators. I am using a modification to MicrosoftMvcJQueryValidation as outlined by Soe Tun to allow my error messages to appear in a validation summary instead of beside the controls.
When the page loads, everything works as expected. The problem is that I am using ajax forms with replace mode to rewrite the form. When I do this, I lose all of my client side validation.
Validation still happens server side, and the fields that have errors are correctly being given the css classes to change their style. However, only the last error message is being shown in my validation summary.
The controller isn't anything special. If the model is valid, do work, otherwise return the same model back into the view.
Here's a sample of my ajax form
<% using (Ajax.BeginForm("AddCreditCard", "Dashboard",
new { },
new AjaxOptions() {
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "newPaymentSetup",
LoadingElementId = "loading-pane"
}, new { id="new-credit-card-form" })) { %>
Here is the modified javascript.
jQuery.validator.addMethod("regex", function(value, element, params) {
if (this.optional(element)) {
return true;
}
var match = new RegExp(params).exec(value);
return (match && (match.index == 0) && (match[0].length == value.length));
});
// glue
function __MVC_ApplyValidator_Range(object, min, max) {
object["range"] = [min, max];
}
function __MVC_ApplyValidator_RegularExpression(object, pattern) {
object["regex"] = pattern;
}
function __MVC_ApplyValidator_Required(object) {
object["required"] = true;
}
function __MVC_ApplyValidator_StringLength(object, maxLength) {
object["maxlength"] = maxLength;
}
function __MVC_ApplyValidator_Unknown(object, validationType, validationParameters) {
object[validationType] = validationParameters;
}
function __MVC_CreateFieldToValidationMessageMapping(validationFields) {
var mapping = {};
for (var i = 0; i < validationFields.length; i++) {
var thisField = validationFields[i];
mapping[thisField.FieldName] = "#" + thisField.ValidationMessageId;
}
return mapping;
}
function __MVC_CreateErrorMessagesObject(validationFields) {
var messagesObj = {};
for (var i = 0; i < validationFields.length; i++) {
var thisField = validationFields[i];
var thisFieldMessages = {};
messagesObj[thisField.FieldName] = thisFieldMessages;
var validationRules = thisField.ValidationRules;
for (var j = 0; j < validationRules.length; j++) {
var thisRule = validationRules[j];
if (thisRule.ErrorMessage) {
var jQueryValidationType = thisRule.ValidationType;
switch (thisRule.ValidationType) {
case "regularExpression":
jQueryValidationType = "regex";
break;
case "stringLength":
jQueryValidationType = "maxlength";
break;
}
thisFieldMessages[jQueryValidationType] = thisRule.ErrorMessage;
}
}
}
return messagesObj;
}
function __MVC_CreateRulesForField(validationField) {
var validationRules = validationField.ValidationRules;
// hook each rule into jquery
var rulesObj = {};
for (var i = 0; i < validationRules.length; i++) {
var thisRule = validationRules[i];
switch (thisRule.ValidationType) {
case "range":
__MVC_ApplyValidator_Range(rulesObj,
thisRule.ValidationParameters["minimum"], thisRule.ValidationParameters["maximum"]);
break;
case "regularExpression":
__MVC_ApplyValidator_RegularExpression(rulesObj,
thisRule.ValidationParameters["pattern"]);
break;
case "required":
var fieldName = validationField.FieldName.replace(".", "_");
if ($("#" + fieldName).get(0).type !== 'checkbox') {
// only apply required if the input control is NOT a checkbox.
__MVC_ApplyValidator_Required(rulesObj);
}
break;
case "stringLength":
__MVC_ApplyValidator_StringLength(rulesObj,
thisRule.ValidationParameters["maximumLength"]);
break;
default:
__MVC_ApplyValidator_Unknown(rulesObj,
thisRule.ValidationType, thisRule.ValidationParameters);
break;
}
}
return rulesObj;
}
function __MVC_CreateValidationOptions(validationFields) {
var rulesObj = {};
for (var i = 0; i < validationFields.length; i++) {
var validationField = validationFields[i];
var fieldName = validationField.FieldName;
rulesObj[fieldName] = __MVC_CreateRulesForField(validationField);
}
return rulesObj;
}
function __MVC_EnableClientValidation(validationContext) {
// this represents the form containing elements to be validated
var theForm = $("#" + validationContext.FormId);
var fields = validationContext.Fields;
var rulesObj = __MVC_CreateValidationOptions(fields);
var fieldToMessageMappings = __MVC_CreateFieldToValidationMessageMapping(fields);
var errorMessagesObj = __MVC_CreateErrorMessagesObject(fields);
var options = {
errorClass: "input-validation-error",
errorElement: "span",
errorPlacement: function(error, element) {
var messageSpan = fieldToMessageMappings[element.attr("name")];
$(messageSpan).empty();
$(messageSpan).removeClass("field-validation-valid");
$(messageSpan).addClass("field-validation-error");
error.removeClass("input-validation-error");
error.attr("_for_validation_message", messageSpan);
error.appendTo(messageSpan);
},
messages: errorMessagesObj,
rules: rulesObj,
success: function(label) {
var messageSpan = $(label.attr("_for_validation_message"));
$(messageSpan).empty();
$(messageSpan).addClass("field-validation-valid");
$(messageSpan).removeClass("field-validation-error");
}
};
var validationSummaryId = validationContext.ValidationSummaryId;
if (validationSummaryId) {
// insert an empty <ul> into the validation summary <div> tag (as necessary)
$("<ul />").appendTo($("#" + validationSummaryId + ":not(:has(ul:first))"));
options = {
errorContainer: "#" + validationSummaryId,
errorLabelContainer: "#" + validationSummaryId + " ul:first",
wrapper: "li",
showErrors: function(errorMap, errorList) {
var errContainer = $(this.settings.errorContainer);
var errLabelContainer = $("ul:first", errContainer);
// Add error CSS class to user-input controls with errors
for (var i = 0; this.errorList[i]; i++) {
var element = this.errorList[i].element;
var messageSpan = $(fieldToMessageMappings[element.name]);
var msgSpanHtml = messageSpan.html();
if (!msgSpanHtml || msgSpanHtml.length == 0) {
// Don't override the existing Validation Message.
// Only if it is empty, set it to an asterisk.
//messageSpan.html("*");
}
messageSpan.removeClass("field-validation-valid").addClass("field-validation-error");
$("#" + element.id).addClass("input-validation-error");
}
for (var i = 0; this.successList[i]; i++) {
// Remove error CSS class from user-input controls with zero validation errors
var element = this.successList[i];
var messageSpan = fieldToMessageMappings[element.name];
$(messageSpan).addClass("field-validation-valid").removeClass("field-validation-error");
$("#" + element.id).removeClass("input-validation-error");
}
if (this.numberOfInvalids() > 0) {
errContainer.removeClass("validation-summary-valid").addClass("validation-summary-errors");
}
this.defaultShowErrors();
// when server-side errors still exist in the Validation Summary, don't hide it
var totalErrorCount = errLabelContainer.children("li:not(:has(label))").length + this.numberOfInvalids();
if (totalErrorCount > 0) {
$(this.settings.errorContainer).css("display", "block").addClass("validation-summary-errors").removeClass("validation-summary-valid");
$(this.settings.errorLabelContainer).css("display", "block");
}
},
messages: errorMessagesObj,
rules: rulesObj
};
}
// register callbacks with our AJAX system
var formElement = document.getElementById(validationContext.FormId);
var registeredValidatorCallbacks = formElement.validationCallbacks;
if (!registeredValidatorCallbacks) {
registeredValidatorCallbacks = [];
formElement.validationCallbacks = registeredValidatorCallbacks;
}
registeredValidatorCallbacks.push(function() {
theForm.validate();
return theForm.valid();
});
theForm.validate(options);
}
// need to wait for the document to signal that it is ready
$(document).ready(function() {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
});
I've tried moving the calls at the bottom in the document ready into my OnSuccess method, but that didn't do it.
So, how do I get client side validation to reinitialize when I do my ajax replace, and how do I get all my errors to show in the validation summary? I'm hoping that if I fix one issue, it will correct the other.
EDIT:
Here's a little more info about what I am doing
Here's the wrapper
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<QuickPayModel>" %>
<div id="quickpay-wrapper">
<% if (Model.NewPaymentMethod) { %>
<% Html.RenderAction<DashboardController>(x => x.QuickPayNewMethod()); %>
<% } else { %>
<% Html.RenderPartial("QuickPayMakePayment", Model); %>
<% } %>
</div>
Here is the make a payment panel.
<%= Html.ClientValidationSummary(new { id = "valSumContainer" })%>
<% Html.EnableClientValidation(); %>
<% using (Ajax.BeginForm("QuickPay", "Dashboard",
new { },
new AjaxOptions() {
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "updatePaymentHistory",
LoadingElementId = "loading-pane"
}, new { }))
{ %>
<div class="horizontalline"><%= Html.Spacer() %></div>
<% ViewContext.FormContext.ValidationSummaryId = "valSumContainer"; %>
<p>
<%: Html.LabelFor(x => x.PaymentMethods)%>
<% if (Model.HasOnePaymentMethod) { %>
<%: Html.DisplayFor(x => x.SelectedPaymentMethodName) %>
<%: Html.HiddenFor(x => x.SelectedPaymentMethodId) %>
<% } else { %>
<%: Html.DropDownListFor(x => x.SelectedPaymentMethodId, Model.PaymentMethodsSelectList, "Select a Payment Method", new { })%>
<%: Html.HiddenFor(x => x.SelectedPaymentMethodName)%>
<script type="text/javascript">
$(function () {
$("#PaymentMethods").change(function () {
$("#SelectedPaymentMethodId").val($(this).val());
$("#SelectedPaymentMethodName").val($('option:selected', this).text());
});
});
</script>
<% } %>
<%: Html.Spacer(12, 1) %><%: Ajax.ActionLink("New Payment Method", "QuickPayNewMethod",
new AjaxOptions() { InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "newPaymentSetup",
LoadingElementId = "loading-pane"
})%>
<%: Html.ValidationMessageFor(x => x.SelectedPaymentMethodId)%>
</p>
<p>
<%: Html.LabelFor(x => x.Amount)%>
<%: Html.TextBoxFor(x => x.Amount, new { disabled = Model.UseInvoicing ? "disabled" : String.Empty,
title = Model.UseInvoicing ? "the total payment amount of all selected invoices" : String.Empty,
#class = "small" })%>
<%: Html.ValidationMessageFor(x => x.Amount)%>
</p>
<p>
<%: Html.LabelFor(x => x.PayDate)%>
<%: Html.TextBox("PayDate", Model.PayDate.ToShortDateString(), new { #class = "medium" })%>
<%: Html.ValidationMessageFor(x => x.PayDate)%>
</p>
<script type="text/javascript">
$(function () {
quickPaySetup();
});
</script>
<div class="horizontalline"><%= Html.Spacer() %></div>
<%= FTNI.Controls.Submit("Submit Payment") %>
<%: Html.AntiForgeryToken() %>
<%: Html.ValidationMessage("Payment-Result")%>
<% } %>
And now my new payment method panel
<script type="text/javascript">
$(function () {
newPaymentSetup();
});
</script>
<h4>New Payment Method</h4>
<% if(Model.HasPaymentMethods) { %>
<span style="float:right;">
<%: Ajax.ActionLink("Cancel", "QuickPay",
new AjaxOptions() {
HttpMethod = "Get",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "quickPaySetup",
LoadingElementId = "loading-pane"
})%>
</span>
<% } %>
<div>Enter the information below to create a new payment method.</div><br />
<%= Html.ClientValidationSummary(new { id = "valSumContainer" })%>
<% Html.EnableClientValidation(); %>
<div id="new-payment-method-tabs">
<ul>
<li>Credit Card</li>
<li>E-Check</li>
</ul>
<div id="new-credit-card">
<% Html.RenderPartial("NewCreditCard", Model.CreditCardModel); %>
</div>
<div id="new-ach">
<% Html.RenderPartial("NewACH", Model.ACHModel); %>
</div>
</div>
Each form starts off with something like this
<% using (Ajax.BeginForm("AddCreditCard", "Dashboard",
new { },
new AjaxOptions() {
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "newPaymentSetup",
LoadingElementId = "loading-pane"
}, new { id="new-credit-card-form" })) { %>
<% ViewContext.FormContext.ValidationSummaryId = "valSumContainer"; %>
Initial load works. Any ajax replaces cause the form context to be lost and not reinitialize no matter what I do. The form posts back, validation occurs server side. All invalid fields are changed (css error classes added), but only the last error is shown in the summary.
I am going to tell you how I did just a few days ago. Please have a look to this question for details.
In my case I was showing the content of the form using an ajax call inside a jquery dialog. When the call complete I just replace the dialog content with the content sent back from the controller.
I have modified the code inside the Microsoft script as described in my question and then called the init method on document ready. This should work for your case too...
For the second error (how do I get all my errors to show in the validation summary?) I have simply modified the code as described in the same source post you are referring to and did not experienced any problem.
Hope it helps!
I ended up reworking my solution so that I was no longer writing the forms to the page through callbacks. While this was not my intended approach, it does work. I chose to use jquery modals to display the data rather than changing the content of one area of the screen.
Ideally, I would not have to render all of the forms to the page and could call them up on demand, but it seems jquery client side validation will not wire up unless the form is present on the page load. I am unsure of the form elements are required present on form load, but it may be a limitation I just have to deal with.
Another workaround would have been to render them all to the page and just show/hide each form through jquery. It's not much different than using modals, but at least the validation would work. I'd still like to see a solution where forms using validation summaries and client side jquery validation through data annotations can be written to the page through callbacks and still wired up and function correctly.