I want a validation method which will check that two fields are equal then hide one of them. At the moment I use equalTo to check they are equal. So I planned to add a validation method which delegates to equalTo and hides the field depending on the result.
The problem I have is that I cannot work out how to call equalTo on the correct instance of the validator. When I call it, I always see an error in equalTo, "this.settings is not defined". This is equalTo
// http://docs.jquery.com/Plugins/Validation/Methods/equalTo
equalTo: function( value, element, param ) {
// bind to the blur event of the target in order to revalidate whenever the target field is updated
// TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
var target = $(param);
//error here
if ( this.settings.onfocusout ) {
target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
$(element).valid();
});
}
return value === target.val();
},
Here is my code.
// validate form
$.validator.addMethod("equalToAndHide", function(value, element, param) {
//this does not work
var result = $.validator.methods.equalTo.call(value, element, param);
if(result === true){
$(element).hide();
}
return result;
}, $.validator.format("Values must be equal_???"));
$('form').validate({
rules : {
confirmEmail : {
equalToAndHide : utils.byId("alternateEmail")
}
},
submitHandler : function(form) {
accordionAjax.post($(form));
}
});
I would prefer to leave equalTo as it is rather than override it and amend the call to this.settings, if possible.
You don't need to call the original equalTo from within your custom method... just write a whole new function using the original as your basis.
This is the default equalTo function...
function( value, element, param ) {
var target = $(param);
if ( this.settings.onfocusout ) {
target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
$(element).valid();
});
}
return value === target.val();
}
Put it inside your custom method and tweak it to suite your needs...
$.validator.addMethod("equalToAndHide", function(value, element, param) {
var target = $(param);
if ( this.settings.onfocusout ) {
target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
$(element).valid();
});
}
return value === target.val();
$('#whatever').hide();
}, $.validator.format("Values must be equal_???"));
Related
My Ajax method is calling action method in controller, and succeeding in the call return. However the object I am passing is always null.
I have read many, maybe not all as there are quite a few, similar questions. I have tried different things, such as different variations of removing dataType and contentType from the ajax function. I have set break points in the action and set alerts in scripts to verify the object is not null before sending to the JsonResult Action. I have verified that data from the Action method is reaching the succeeded section of the ajax function.
So Here is the scenario: I have an MVC Core 2.2 index page. I added a search textbox. everything works correctly If I block JS in the browser, So I know the HTML is correct. But I wanted to give an Ajax option for a "more pleasant" user experience. I actually did get the ajax to work on simple hard coded strings. But now for some reason the passed in object is null.
Lets start with the view's script:
//This is the Object I want passed through Ajax
//class pageValues {
// constructor(){
// this.sortColumn = $("#inpSortColumn").val();
// this.sortOrder = $("#inpSortOrder").val();
// this.filter = $("#Filter").val();
// this.message = "";
// this.currentPage = $("#inpCurrentPage").val();
// this.recordsPerPage = $("#inpPageSize").val();
// this.recordCount = 0;
// }
//}
// I also tried as a simple variable without a constructor and added
// default values incase undefined values were causing issues
var pageValues = {
sortColumn: ($("#inpSortColumn").val() == undefined ) ? "LastName" : $("#inpSortColumn").val(),
sortOrder: ($("#inpSortOrder").val() == undefined ) ? "ASC" : $("#inpSortOrder").val(),
filter: ($("#Filter").val() == undefined ) ? "" : $("#Filter").val(),
message: ($("#inpMessage").val() == undefined ) ? "" : $("#inpMessage").val(),
currentPage: ($("#inpCurrentPage").val() == undefined) ? 1: $("#inpCurrentPage").val(),
recordsPerPage: ($("#inpPageSize").val() == undefined) ? 5 : $("#inpPageSize").val(),
totalRecords: ($("#inpTotalRecords").val() == undefined ) ? 0 : $("#inpTotalRecords").val()
};
$(document).ready(function () {
// If we are here, the browser allows JS
// So, replace the submit buttons with Ajax functions
ReplaceHtml();
});
function ReplaceHtml() {
// Search Button
var divSearch = $("#divSearchBtn");
divSearch.hide();
divSearch.empty();
divSearch.append('<button id="btnAjaxSearch" type="button" ' +
'class="" onclick="RequestRecords();">Search</button>');
divSearch.show();
}
// Here we call the Ajax function passing the data object and the callback function
function RequestRecords() {
alert($("#Filter").val()); // This is just to Verify value is present
AjaxCallForRecords(pageValues, ReturnedData);
}
// This is the callback function
function ReturnedData(data) {
// This verifies we hit the callback
alert("inside ajax callback");
// The Verification that the Object returned is valid.
// The problem appeared here,
// The firstname was always the same no matter the Search Filter.
// Telling me the object on the server side receiving the 'pageValues'
// had been recreated due to being null.
alert(data.users[0].firstName);
}
// Of course, here is the ajax function
// I have played around with data and content settings
// When I changed those I got 'Response Errors' but could never get the ResponseText
function AjaxCallForRecords(dataToSend, callback) {
console.log(dataToSend); // This prove Data is here
$.ajax({
type: "GET",
url: '#Url.Action("Index_Ajax","ApplicationUsers")',
data: JSON.stringify(dataToSend),
dataType: "json",
contentType: "application/json",
success: function (data) { callback(data); },
error: function (data) { alert("Error. ResponseText: " + data.responseText); }
});
}
</script>
Ok, Now to the Controller:
public JsonResult Index_Ajax([FromBody] UsersCodeAndClasses.PageValues pageValues)
{
// A break point here reveals 'pageValues' is always null - this is the problem.....
// In the GetFilteredData function I do create a new 'pageValues' object if null
// So my Search 'Filter' will always be empty, and I will always get all the records.
// Get Records
List<InputUser> users = _usersCode.GetFilteredData(pageValues);
// The next block of code assembles the data to return to the view
// Again the 'pageValues' is null because that is what gets passed in, or rather, never assigned
//Build Return Data
UsersCodeAndClasses.AjaxReturnData data = new UsersCodeAndClasses.AjaxReturnData()
{
pageValues = pageValues,
users = users
};
return Json(data);
}
And Finally, The Server side 'pageValues' declaration:
public class PageValues
{
// Class used to pass page and sorting information to Ajax Call
public string sortColumn { get; set; } = "LastName";
public string sortOrder { get; set; } = "ASC";
public string filter { get; set; } = "";
public string message { get; set; } = "";
public int currentPage { get; set; } = 1;
public int recordsPerPage { get; set; } = 5;
public int recordCount { get; set; }
}
public class AjaxReturnData
{
// Class is used to pass multiple data to the Ajax Call
public PageValues pageValues { get; set; }
public List<InputUser> users { get; set; }
}
So, I am expecting data to be passed, I just do not know why the server is not assigning the data. I am new at this and could use an experienced eye.
Thanks
Simply change your type from GET to POST in Ajax call.
I spent some more time researching everything about ajax return values and classes.
Ultimately, my class was malformed, once I changed that it started working. I also changed the type to POST, I did not want to use POST just to read records. But I am sending a lot of data keeping up with search, pagination and sorting.
The below code works though I feel like it is very verbose and some parts may be unnecessary. Hope it helps someone, and please feel free to comment and help me out on things that could help others.
<script>
// Class to use for ajax data
class pageValues {
constructor(){
this.sortColumn = ($("#inpSortColumn").val() == undefined) ? "LastName" : $("#inpSortColumn").val();
this.sortOrder = ($("#inpSortOrder").val() == undefined) ? "ASC" : $("#inpSortOrder").val();
this.filter = ($("#Filter").val() == undefined) ? "" : $("#Filter").val();
this.message = ($("#inpMessage").val() == undefined) ? "" : $("#inpMessage").val();
this.currentPage = ($("#inpCurrentPage").val() == undefined) ? 1 : $("#inpCurrentPage").val();
this.recordsPerPage = ($("#inpPageSize").val() == undefined) ? 5 : $("#inpPageSize").val();
this.totalRecords= ($("#inpTotalRecords").val() == undefined) ? 0 : $("#inpTotalRecords").val();
}
get SortColumn() { return this.sortColumn; }
set SortColumn(value) { this.sortColumn = value; }
get SortOrder() { return this.sortOrder; }
set SortOrder(value) { this.sortOrder = value;}
get Filter() { return this.filter; }
set Filter(value) { this.filter = value; }
get Message() { return this.message; }
set Message(value) { this.message = value; }
get CurrentPage() { return this.currentPage; }
set CurrentPage(value) { this.currentPage = value; }
get RecordsPerPage() { return this.recordsPerPage; }
set RecordsPerPage(value) { this.recordsPerPage = value; }
get TotalRecords() { return this.totalRecords; }
set TotalRecords(value) { this.totalRecords = value; }
}
$(document).ready(function () {
// If we are here, the browser allows JS
// So, replace the submit buttons with Ajax functions
ReplaceHtml();
});
function ReplaceHtml() {
// Search Button
var divSearch = $("#divSearchBtn");
divSearch.hide();
divSearch.empty();
divSearch.append('<button id="btnAjaxSearch" type="button" ' +
'class="" onclick="RequestRecords();">Search</button>');
divSearch.show();
}
// Here we call the Ajax function passing the data object and the callback function
function RequestRecords() {
alert($("#Filter").val()); // This is just to Verify value is present
AjaxCallForRecords(new pageValues(), ReturnedData);
}
// This is the callback funtion
function ReturnedData(data) {
// The verification we hit the callback
alert("inside ajax callback");
alert(data.users[0].firstName);
}
// Ajax function
function AjaxCallForRecords(dataToSend, callback) {
console.log(dataToSend);
$.ajax({
type: "POST",
url: '#Url.Action("Index_Ajax","ApplicationUsers")',
data: JSON.stringify(dataToSend),
dataType: "json",
contentType: "application/json",
success: function (data) { callback(data); },
error: function (data) { alert("Error. ResponseText: " + data.responseText); }
});
}
</script>
I've been working on this app for a while. I have several other modules that all work fine. I've been having a ton of trouble with this particular module and it's super frustrating. This problem looks super simple. Maybe I'm over thinking it. Hopefully someone will say that I am. :)
In this module, I decided to use methods from my model. This particular one is non-instanced. Here is my model:
/*
* Account.js
*/
module.exports = {
connection: 'islMongo',
attributes: {
name: {
type: 'string',
required: true,
},
},
numberToName: function(accountNumber) {
Account.findOne(accountNumber).exec(function(err, a){
if (err) {
return 'err';
} else {
return 'ok';
}
});
return 'broke';
},
};
I call it from one of my controllers like this:
var accountName = Account.numberToName(params.id);
At this point accountName's value is "broke". I don't understand why it wouldn't either return "err" or "ok". I simplified my actual function here for testing.
Edit:
I have other calls that work properly. For instance:
updateBalance: function(account, amount, callback) {
/* Accepts account id or account object */
(function _lookupAccount(afterLookup) {
if (typeof account === 'object') return afterLookup(null, account);
Account.findOne(account)
.exec(afterLookup);
})(function (err, a) {
if (err) return callback(err);
if (!a) {
err = new Error();
err.message = "Couldn't find account.";
err.status = 400;
return callback(err);
}
a.balance = parseInt(a.balance) + parseInt(amount);
a.save(callback);
});
},
Is called like this:
Account.updateBalance(params.account, -2000);
The definition has a callback, but I don't actually use one because it isn't needed. The method works fine.
Sails.js documentation provides example methods that don't use callbacks. They simply return the requested data.
// Attribute methods
getFullName: function (){
return this.firstName + ' ' + this.lastName;
},
isMarried: function () {
return !!this.spouse;
},
isEligibleForSocialSecurity: function (){
return this.age >= 65;
},
encryptPassword: function () {
}
And called like this:
if ( rick.isMarried() ) {
// ...
}
Which is what I am trying to do with my method at the top of this post. It seems like the exec() portion of Account.findOne() isn't even being called.
Sails.js & Node.js are asynchronous. So in simple words they don't wait for response from database, but when they got date they call a callback. So you need to read about Queries and callbacks and what is callback hell (you should never do that).
And now get back to your problem.
/*
Account.js
*/
//...
numberToName: function(accountNumber, callback) {
// if you want some additional logic you can create function here and call callback in it
Account.findOne(accountNumber).exec(callback);
}
//...
Tip: callbacks first param is always error.
// AccountController
method: function(req, res){
var id = req.param('id'); // if its int you should parseInt()
var callback = function(error, account){
if(error)
res.send('error');
else
res.send(account.name);
};
Account.numberToName(id, callback);
}
I'm using Kendo MVVM and I have a kendo numerictextbox bound to a kendo observable.
All I want is: when the user changes value, a confirm should pop saying something like 'are you sure?' if yes -> no problem, go on.
if no -> NOTHING should happen!
In theory it sounds simple as that... but I found 3 major issues:
1) numerictextbox only got 2 events: spin and change... so any idea of using keypress/focus/or any other event is discarded.
2) So tried using the change event... but I can't preventDefault! Another try was to save previous value and restore it back in case of 'no answer' but this brings me to trigger event change TWICE!
3) Any other model field who is 'observing' the numerictextbox will change before I even answer the confirm box... And I absolutely don't want this!
P.S. I also got a dropdownlist and a datepicker that must work in the same way!
Help please!
Provided a fast example: http://dojo.telerik.com/EyItE
Here you can see how the numericbox2 (who is observing numericbox1 and is computed) changes itself before the user answer yes/no (problem 3)
and keypress/focus/preventDefault doesn't work.
here is an answer about binding events not supported by default:
Kendo MVVM and binding or extending custom events
For preventDefault (or "reverting" the value). I tried to store the previous value as you suggested and it is does not fire twice:
var viewModel = kendo.observable({
myItem: {
// fields, etc
myNumericBox: 10,
myNumericBox2: function () {
return viewModel.get("myItem.myNumericBox")*2;
},
tmp: 10
},
onChange: function (e) {
if ( confirm("are you sure?")) {
viewModel.set("myItem.tmp", viewModel.get("myItem.myNumericBox"));
}
else {
viewModel.set("myItem.myNumericBox", viewModel.get("myItem.tmp"));
}
},
tryf: function () {
alert("hello!"); // doesn't trigger
},
tryk: function() {
alert("hello2!"); // doesn't trigger
}
});
I solved with a custom binding that ask you a confirm between html widget change -> model update.
kendo.data.binders.widget.valueConfirm = kendo.data.Binder.extend({
init: function (widget, bindings, options) { // start
kendo.data.Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
this._change = $.proxy(this.change, this);
this.widget.bind("change", this._change); // observe
},
refresh: function () { // when model change
if (!this._initChange) {
var widget = this.widget;
var value = this.bindings.valueConfirm.get(); // value of the model
if (widget.ns == ".kendoDropDownList") { // for the dropdown i have to use select
widget.select(function (d) {
return d.id == value.id;
});
}
else widget.value(value); // update widget
}
},
change: function () { // when html item change
var widget = this.widget;
if (widget.ns == ".kendoDropDownList") var value = widget.dataItem(); // for dropdown i need dataitem
else var value = widget.value();
var old = this.bindings.valueConfirm.get();
this._initChange = true;
// I want to bypass the confirm if the value is not valid (for example after 1st load with blank values).
if (old == null || old == 'undefined' || old == 'NaN') this.bindings.valueConfirm.set(value); // Update the View-Model
else {
if (confirm("Are you sure?")) {
this.bindings.valueConfirm.set(value); // Update the View-Model
}
else {
this._initChange = false;
this.refresh(); // Reset old value
}
}
this._initChange = false;
},
destroy: function () { // dunno if this is useful
this.widget.unbind("change", this._change);
}
});
I'm trying to build a front-end for a metrics tool with Ember. The code that I've written so far has been very much influenced by Eviltrout's emberreddit application
https://github.com/eviltrout/emberreddit
The goal is to have two classes that depend on each other: metrics and filters.
1) Once the application initializes, the filters, which are instances of the Filter-class, are loaded from the server. Once the filters have loaded, they are displayed as checkboxes on the screen. After that, the metrics objects should take the filters as parameters and query the server for data.
2) Once the user changes the checkboxes and thus updates the filter objects, the application should take the filters as parameters again and fetch new metrics data from the server.
My problem is that I don't know how to handle the dependencies between these two sets of objects with asynchronous ajax calls. At it's current state, my application doesn't finish loading the filters when it already starts loading the metrics. Therefore, the filters don't get passed as parameters for the metrics ajax-call.
My question is: What's the best way to do this ember? There surely has to be a way to handle the order of ajax calls. My intuition is that manually adding observers isn't the way to go.
Here are the models of my application:
//FILTER MODELS
var defaultFilters = ['dates', 'devices'];
//set Filter class. The Filter object will be multiplied for each filter.
App.Filter = Ember.Object.extend({
//capitalize first letter to get title
filterTitle: function() {
return this.get('id').charAt(0).toUpperCase() + this.get('id').slice(1);
}.property('id'),
//set attribute to see if filter has loaded
loadedFilter: false,
//create method to load filter values from server
loadValues: function() {
var filter = this;
return Ember.Deferred.promise(function (p) {
if (filter.get('loadedFilter')) {
p.resolve(filter.get('values'));
} else {
p.resolve($.getJSON("http://127.0.0.1:13373/options/" + filter.get('id')).then(function(response) {
var values = Ember.A();
response[filter.get('id')].forEach(function(value) {
values.push(value);
});
filter.setProperties({values: values, loadedFilter: true});
return values;
}))
}})}
}
);
//reopen class to create "all" method which returns all instances of Filter class
App.Filter.reopenClass({
all: function() {
if (this._all) {return this._all; }
var all = Ember.A();
defaultFilters.forEach(function(id) {
all.pushObject(App.Filter.create({id: id}));
});
this._all = all;
return all;
}});
//Create a Filters array to store all the filters.
App.Filters = App.Filter.all();
//METRIC MODELS
App.Metric = Ember.Object.extend({
metricTitle: function() {
return this.get('id').charAt(0).toUpperCase() + this.get('id').slice(1);
}.property('id'),
loadedMetric: false,
filtersBinding: 'App.Filters',
loadValues: function() {
var metric = this;
var filters = metric.get('filters');
if (filters.get('loadedFilters'))
console.log('loading metrics');
return Ember.Deferred.promise(function (p) {
if (metric.get('loadedMetric')) {
p.resolve(metric.get('values'));
} else {
p.resolve(
console.log('sending ajax'),
$.ajax({
url: "http://127.0.0.1:13373/" + metric.get('id') + "/",
data: JSON.stringify(metric.get('filters')),
}).then(function(response) {
var values = Ember.A();
response[metric.get('id')].forEach(function(value) {
values.push(value);
});
metric.setProperties({"values": values, "loadedMetric": true});
return values;
}))
}})}
});
App.Metric.reopenClass({
findByView: function(searchView) {
if (this._metrics) {return this._metrics; }
var metrics = Ember.A();
defaultMetricsSettings.forEach(function(metric) {
if (metric.view == searchView)
metrics.pushObject(App.Metric.create({id: metric.id},{view: metric.view}, {calculation: metric.calculation}, {format: metric.format}, {width: metric.width}));
});
this._metrics = metrics;
return metrics;
}
});
And here are the routes:
App.ApplicationRoute = Ember.Route.extend({
//set application routes model to all filters
model: function() {
return App.Filter.all();
},
//after filter has loaded, let's load its values
afterModel: function(model) {
return model.forEach(function(item) {
item.loadValues();
});
},
//create a controller called ApplicationController and pass the filter as its model
setupController: function(controller, filter) {
controller.set('model', filter);
}
});
App.DashboardRoute = Ember.Route.extend({
model: function() {
return App.Metric.findByView('Dashboard');
},
afterModel: function(model) {
return model.forEach(function(item) {
item.loadValues();
});
},
setupController: function(controller, metric) {
controller.set('model', metric);
}
});
Controllers:
App.ApplicationController = Ember.ArrayController.extend({
//ApplicationController controls all the filters. Let's create a controller to handle each instance of a filter
itemController: 'filter'
});
App.FilterController = Ember.ObjectController.extend({
//this sets the titleId property that is used only for binding html attributes in template. Stupid way to do this.
titleId: function() {
return "#" + this.get('filterTitle');}.property('filterTitle')
});
Your afterModel hook could do this in a sequence of dependent promises. The current implementation is returning immediately, instead you chain the promise and finally return the last promise as the result of the hook. The router will wait for the whole set of calls to complete before continuing to setupController.
afterModel: function(model) {
var promise;
model.forEach(function(item)) {
if (promise) {
promise = promise.then(function() {
item.loadValues();
});
} else {
promise = item.loadValues();
}
}
return promise;
}
I'm not sure how many of the calls you have, but you may want to batch some of these together to reduce the number of HTTP requests.
I'm using Bootstrap Typeahead to suggest som search results. The results are returned from a ajax ressource, and since this resource creates a delay, I'm experiencing a unfortunate effect.
Example:
If typing a 4 letter word, the suggestions will appear after 2 letters, I can then go through the results with the keys up/down, but suddenly the suggestions will reload because the last request has finished.
Is there any way to "cancel" any remaining, if user is currently using the keys up/down to go through the suggestions?
('#query').typeahead({
items: 4,
source: function (query,process) {
map = {};
$.getJSON('/app_dev.php/ajax/autosuggest/'+query, function (data) {
vehicles = [];
$.each(data, function(i,vehicle){
map[vehicle.full] = vehicle;
vehicles.push(vehicle.full);
});
process(vehicles);
});
},
updater: function (item) {
// do something here when item is selected
},
highlighter: function (item) {
return item;
},
matcher: function (item) {
return true;
}
});
I think the following will satisfy your needs (its hard to reproduce exactly) :
There is no easy way to abort a delayed response, but you could extend typeahead as I figured out here (without modifying bootstrap.js)
The concept is to catch keydown, detect if the event is KEY_UP or KEY_DOWN, set a flag is_browsing, and then abort process if is_browsing is true (that is, if the user has hitted KEY_UP or KEY_DOWN and no other keys afterwards).
Extending typeahead :
// save the original function object
var _superTypeahead = $.fn.typeahead;
// add is_browsing as a new flag
$.extend( _superTypeahead.defaults, {
is_browsing: false
});
// create a new constructor
var Typeahead = function(element, options) {
_superTypeahead.Constructor.apply( this, arguments )
}
// extend prototype and add a _super function
Typeahead.prototype = $.extend({}, _superTypeahead.Constructor.prototype, {
constructor: Typeahead
, _super: function() {
var args = $.makeArray(arguments)
// call bootstrap core
_superTypeahead.Constructor.prototype[args.shift()].apply(this, args)
}
//override typeahead original keydown
, keydown: function (e) {
this._super('keydown', e)
this.options.is_browsing = ($.inArray(e.keyCode, [40,38])>-1)
}
//override process, abort if user is browsing
, process: function (items) {
if (this.options.is_browsing) return
this._super('process', items)
}
});
// override the old initialization with the new constructor
$.fn.typeahead = $.extend(function(option) {
var args = $.makeArray(arguments),
option = args.shift()
// this is executed everytime element.modal() is called
return this.each(function() {
var $this = $(this)
var data = $this.data('typeahead'),
options = $.extend({}, _superTypeahead.defaults, $this.data(), typeof option == 'object' && option)
if (!data) {
$this.data('typeahead', (data = new Typeahead(this, options)))
}
if (typeof option == 'string') {
data[option].apply( data, args )
}
});
}, $.fn.typeahead);
This typeahead-extension could be placed anywhere, eg in a <script type="text/javascript"> -section
Testing the extension :
<input type="text" id="test" name="test" placeholder="type some text" data-provide="typeahead">
<script type="text/javascript">
$(document).ready(function() {
var url='typeahead.php';
$("#test").typeahead({
items : 10,
source: function (query, process) {
return $.get(url, { query: query }, function (data) {
return process(data.options);
});
}
});
});
</script>
A "serverside" PHP script that returns a lot of randomized options with forced delay, typeahead.php :
<?
header('Content-type: application/json');
$JSON='';
sleep(3); //delay execution in 3 secs
for ($count=0;$count<30000;$count++) {
if ($JSON!='') $JSON.=',';
//create random strings
$s=str_shuffle("abcdefghijklmnopq");
$JSON.='"'.$s.'"';
}
$JSON='{ "options": ['.$JSON.'] }';
echo $JSON;
?>
It really seems to work for me. But I cannot be sure that it will work in your case. Let me now if you have success or not.