I'm using the vex dialog library basically as a replacement for standard alert, confirm, prompt etc boxes and I have been using them like this for example:
$('.js-edit-cancel').on('click', function(e) {
e.preventDefault();
vex.dialog.buttons.YES.text = t('Yes');
vex.dialog.buttons.NO.text = t('No');
vex.dialog.confirm({
message: t('Are you sure you want to cancel?'),
callback: function(value) {
if (value) {
// Some code here...
}
}
});
});
Now whilst this works, it starts to become a little repetitive when you are using the dialog boxes for several things.
Ideally I could simply add HTML5 data-* attributes to any element I wanted one on, something like data-confirm-box or data-prompt-box; however problem with this method is I also need to be able to set custom messages rather than always having it set to Are you sure you want to cancel? as well as being able to supply a callback function to run; I guess technically I could supply this data as extra data-* attributes, but it just seems a bit messy to me.
Then I thought I could keep doing click events for each element I wanted it on and passing in the custom message, but I guess I would also have to pass in a callback this way and then this would end up with having two code blocks each time you want to use one; the click event handler and then the callback function.
Is there any cleaner way to do what I want to do - be able to set a custom message as well as an optional callback / custom code each time I want a confirmation box?
The library itself is vanilla JavaScript but I'm also using jQuery as you can see so I'm happy using either.
You may declare and use a unified function(let's say showDialog(type, options)) passing in the type of dialog and a custom options object:
function showDialog(type, options) {
if (typeof type !== 'string'
|| ['alert','prompt','confirm','open'].indexOf(type) === -1) {
throw new Error('Wrong dialog type!');
}
// You can specify your own buttons if you want more options than just OK or Cancel.
// If you simply want to change the labels, you can override the default options
// You can also specify a static message text for a certain dialog type
vex.dialog[type](options);
}
// Approximate usage:
...
$('.js-edit-cancel').on('click', function(e) {
e.preventDefault();
showDialog('confirm', {
message: t('Are you sure you want to cancel?')
});
});
...
$('.some_element').on('click', function(e) {
e.preventDefault();
showDialog('alert', {
message: t("You don't have permission on this action!")
});
});
...
$('.some_other_element').on('click', function(e) {
e.preventDefault();
showDialog('open', {
message: t('Select date and time:'),
callback: function(data) {
if (data) {
// Some code here...
}
}
});
});
Related
I have a script with the following (only showing the applicable lines)
var setUploadDoneIndicator = function(form)
{
if ($(form).find('[id$=_upload_done_indicator]').is(':checked') == false)
{
console.log("Setting indicator");
$(form).find('[id$=_upload_done_indicator]').trigger('click');
}
}
var unsetUploadDoneIndicator = function(form)
{
if ($(form).find('[id$=_upload_done_indicator]').is(':checked') == true)
{
console.log("UnSetting indicator");
$(form).find('[id$=_upload_done_indicator]').trigger('click');
}
}
$('[id$=_upload_form]').each(function (event)
{
current_form = this;
$(this).fileupload(
{
done: function (e, data)
{
setUploadDoneIndicator(current_form);
}
});
}
This ticks a checkbox correctly, the idea is to listen on the checked state of the checkbox on another JS File. If I call unsetUploadDoneIndicator() right after setUploadDoneIndicator() in this script, it unticks the tickbox.
Then in another JS file I have
$('#pricing_ab_upload_done_indicator').change(function()
{
if ($(this).is(':checked'))
{
console.log("got checked");
unsetUploadDoneIndicator($('#pricing_ab_upload_form'));
}
});
Which calls the function in the first script, but does not untick the tickbox. I am unsure whether the pointer is passed over correctly, when printing out the received object in unsetUploadDoneIndicator() it does print out a JQuery object which seems correct, the
if ($(form).find('[id$=_upload_done_indicator]').is(':checked')
returns true, but the trigger does not happen, so i'm not sure if the element is actually found.
If I change the second script with the following, the trigger to uncheck the box does not happen either
$('#pricing_ab_upload_done_indicator').change(function()
{
if ($(this).is(':checked'))
{
console.log("got checked");
$('#pricing_ab_upload_done_indicator').trigger('click');
}
});
Why would the trigger not happen?
It's easier to just remove your functions and use the following:
$('[id$=_upload_form]').each(function (event) {
current_form = this;
$(this).fileupload( {
done: function (e, data) {
var checkbox = $(current_form).find('[id$=_upload_done_indicator]');
// Set the opposite value
checkbox.prop("checked", !checkbox.prop("checked"));
}
});
}
Avoid binding multiple events on the same action. I see you have a .change event on the checkbox as well, which will result in the "click" event triggering twice. This will make it look like nothing happened.
On a sidenote, if you are using the same selector often (like $(current_form).find('[id$=_upload_done_indicator]') in your code example), it's better to cache it in a variable to increase performance. It's also easier to work with to write a short variable name instead of repeating the entire selector.
Following #Dark Ashelin's advice, I added a custom event. This seems like a much simpler and more logical way of implementing this type of functionality.
In the first script when the upload is done I have
$(current_form).trigger('event_upload_completed');
In the second script I have
$('#pricing_ab_upload_form').on('event_upload_completed', function()
{
console.log("Upload completed");
});
This way does not require a callback to the first script from the second script to reset the state of the checkbox (it does not require a dummy html element at all)
I tested this in the latest Chrome and FireFox and in IE11. If this method has compatibility issues with older IE's please comment on this answer
I am using uitest.js (built-on Jasmine.js) to test our Kendo UI Mobile application. I am displaying an actionsheet and programmatically selecting on of the options. This works ok in the app but throws an error that fails the test.
I am using an action sheet like this:
<ul data-role="actionsheet" id="marketplace-price-actions" >
<li class="km-actionsheet-title">Select Price</li>
<li>$$$</li>
<li>$$</li>
<li>$</li>
</ul>
and In my spec I am selecting one of the options like this:
$("#marketplace-price-actions li a").eq(2).mousedown().mouseup();
and this works, yet throws the following error:
TypeError: "undefined is not a function"
I have created a jsfiddle that displays this at http://jsfiddle.net/zkent/DD6vj/2/. Be sure to open the console.
EDIT Based on the selected answer, the error was from passing values to the functions. I chose to create separate callbacks. See http://jsfiddle.net/zkent/DD6vj/4/.
It looks like you're not supposed to pass parameters to your action. I'm not sure why it's implemented this way, but this is causing your error (which also happens if you simply click on it, so it's not related to Jasmine).
As far as I can see, you have three options:
Use a separate callback for each item
Modify the ActionSheet source code to supply the clicked element to your action
Use a closure over the value you pass and return a handler
Option 3 seems to be the best solution if you only need to pass one value but want to avoid code repetition due to multiple handlers.
Please note that I haven't tested the following solutions at all, so use at your own risk.
For option #2, something like this might work:
kendo.mobile.ui.ActionSheet.fn._click = (function (click) {
return function (e) {
if (e.isDefaultPrevented()) {
return;
}
var action = $(e.currentTarget).data("action");
if (action) {
kendo.getter(action)(window)({
target: this.target,
context: this.context,
element: e.currentTarget // pass in the element that was clicked on
});
}
e.preventDefault();
this.close();
}
})(kendo.mobile.ui.ActionSheet.fn._click);
That way you'd at least know which element was clicked on and you could add data attributes to pass data, if you wanted, e.g.:
<li>$$</li>
which you could then read in your handler:
function alertme(e) {
console.log(e);
console.log($(e.element).attr("data-value"));
}
(demo)
For option #3, you would simply define your action as:
function alertme(val) {
return function(e) {
console.log(e);
console.log(val);
};
}
and your element would be as it was:
<li>$$
(demo)
This always gets me. After initializing all lovely UI elements on a web page, I load some content in (either into a modal or tabs for example) and the newly loaded content does not have the UI elements initialized. eg:
$('a.button').button(); // jquery ui button as an example
$('select').chosen(); // chosen ui as another example
$('#content').load('/uri'); // content is not styled :(
My current approach is to create a registry of elements that need binding:
var uiRegistry = {
registry: [],
push: function (func) { this.registry.push(func) },
apply: function (scope) {
$.each(uiRegistry.registry, function (i, func) {
func(scope);
});
}
};
uiRegistry.push(function (scope) {
$('a.button', scope).button();
$('select', scope).chosen();
});
uiRegistry.apply('body'); // content gets styled as per usual
$('#content').load('/uri', function () {
uiRegistry.apply($(this)); // content gets styled :)
});
I can't be the only person with this problem, so are there any better patterns for doing this?
My answer is basically the same as the one you outline, but I use jquery events to trigger the setup code. I call it the "moddom" event.
When I load the new content, I trigger my event on the parent:
parent.append(newcode).trigger('moddom');
In the widget, I look for that event:
$.on('moddom', function(ev) {
$(ev.target).find('.myselector')
})
This is oversimplified to illustrate the event method.
In reality, I wrap it in a function domInit, which takes a selector and a callback argument. It calls the callback whenever a new element that matches the selector is found - with a jquery element as the first argument.
So in my widget code, I can do this:
domInit('.myselector', function(myelement) {
myelement.css('color', 'blue');
})
domInit sets data on the element in question "domInit" which is a registry of the functions that have already been applied.
My full domInit function:
window.domInit = function(select, once, callback) {
var apply, done;
done = false;
apply = function() {
var applied, el;
el = $(this);
if (once && !done) {
done = true;
}
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
if (done) {
return;
}
$(ev.target).find(select).each(apply);
});
};
Now we just have to remember to trigger the 'moddom' event whenever we make dom changes.
You could simplify this if you don't need the "once" functionality, which is a pretty rare edge case. It calls the callback only once. For example if you are going to do something global when any element that matches is found - but it only needs to happen once. Simplified without done parameter:
window.domInit = function(select, callback) {
var apply;
apply = function() {
var applied, el;
el = $(this);
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
$(ev.target).find(select).each(apply);
});
};
It seems to me browsers should have a way to receive a callback when the dom changes, but I have never heard of such a thing.
best approach will be to wrap all the ui code in a function -even better a separate file -
and on ajax load just specify that function as a call back ..
here is a small example
let's say you have code that bind the text fields with class someclass-for-date to a date picker then your code would look like this ..
$('.someclass-for-date').datepicker();
here is what i think is best
function datepickerUi(){
$('.someclass-for-date').datepicker();
}
and here is what the load should look like
$('#content').load('/uri', function(){
datepickerUi();
})
or you can load it at the end of your html in script tag .. (but i dont like that , cuz it's harder to debug)
here is some tips
keep your code and css styles as clean as possible .. meaning that for text fields that should be date pickers give them one class all over your website ..
at this rate all of your code will be clean and easy to maintain ..
read more on OOCss this will clear what i mean.
mostly with jquery it's all about organization ... give it some thought and you will get what you want done with one line of code ..
edit
here is a js fiddle with something similar to your but i guess it's a bit cleaner click here
I am a rookie in JS, have a problem understanding JQUERY semantics.
I have a function written for checking the content of a cell.
Problem: the function just starts when the cell loses focus, if I click Submit, the error shows first, then it will run the function.
I want the function to run even when I am inside the cell. How to do it?
Initiated by this:
$(".user_id").blur(function(){ validateUserId($('.user_id')); });
The function:
function validateUserId(reference) {
if ( 5 == $(reference).val().length ) {
$.get('index.php?user=' + $(reference).val(), function(data) {
if ( "error" == data ) {
$(reference).parent().parent().addClass('error');
alert('error');
} else {
$(reference).parent().parent().removeClass('error');
$(reference).addClass('valid');
$(reference).parent().parent().addClass('success');
}
});
} else {
alert('short');
$(reference).parent().parent().addClass('error');
}
}
$(".user_id").on('keyup', function(){
validateUserId($(this));
});
i would use the keyup event.
So everytime somebody types a key in your input cell, the function will be executed.
$(".user_id").on("keyup", function(){ validateUserId($(this)); });
I changed the $(".user_id"), you take the value from ,to $(this). Since you want the value of the field you did the keyup event on. (And not an other field, if there would be 2 fields with the same .user_id class.)
Try to bind function on focus event
$("input[submit]").click(function(e){
e.preventDefault();
var valid = validateUserId($('.user_id')); // maybe the function could return a boolean value too instead of just adding classes to the HTML part
if (valid) {
$("#your_form_id").submit(); // only if the input is valid, submit it
}
});
sidenote on your problem: if you click the submit button it will first trigger the submit action which may give you an error and then execute the blur() block of code
Here is a working demo http://jsfiddle.net/pomeh/GwswM/. I've rewritten a little the code to:
cache DOM selections into variables,
use jQuery methods chaining,
improve if conditions with tripe equal signs,
separated AJAX calls and application logic,
cache identical AJAX calls,
use jQuery deferred to hide the asynchronous aspect of the verification (linked to AJAX)
Hope that'll help :)
Firstly, is it possible? Been struggling with this one for hours; I think the reason my events aren't firing is because one event is unbinding/overwriting the other. I want to bind two change events to the same element. How can I do that?
As per request, here's the function I'm struggling with:
(function($) {
$.fn.cascade = function(name, trigger, url) {
var cache = {};
var queue = {};
this.each(function() {
var $input = $(this);
var $trigger = $input.closest('tr').prev('tr').find(trigger);
//$input.hide();
var addOptions = function($select, options) {
$select.append('<option value="">- Select -</option>');
for(var i in options) {
$select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
}
$select.val($input.val()).trigger('change');
}
var $select = $('<select>')
// copy classes
.attr('class', $input.attr('class'))
// update hidden input
.bind('change', function() {
$input.val($(this).val());
})
// save data for chaining
.data('name', name)
.data('trigger', $trigger);
$input.after($select);
$trigger.bind('change', function() {
var value = $(this).val();
$select.empty();
if(value == '' || value == null) {
$select.trigger('change');
return;
}
// TODO: cache should be a jagged multi-dimensional array for nested triggers
if(value in cache) {
addOptions($select, cache[value]);
} else if(value in queue) {
$select.addClass('loading');
queue[value].push($select);
} else {
var getDict = {}
getDict[name] = value;
// TODO: use recursion to chain up more than one level of triggers
if($(this).data('trigger')) {
getDict[$(this).data('name')] = $(this).data('trigger').val();
}
$select.addClass('loading');
queue[value] = [$select];
$.getJSON(url, getDict, function(options) {
cache[value] = options;
while(queue[value].length > 0) {
var $select = queue[value].pop();
$select.removeClass('loading');
addOptions($select, options);
}
});
}
}).trigger('change');
});
return this;
}
})(jQuery);
The relevant chunk of HTML is even longer... but essentially it's a select box with a bunch of years, and then an <input> that gets (visibly) replaced with a <select> showing the vehicle makes for that year, and then another <input> that gets replaced with the models for that make/year.
Actually, it seems to be running pretty well now except for on page load. The initial values are getting wiped.
Solved the issue by pulling out that $select.bind() bit and making it live:
$('select.province').live('change', function() {
$(this).siblings('input.province').val($(this).val());
});
$('select.make').live('change', function() {
$(this).siblings('input.make').val($(this).val());
});
$('select.model').live('change', function() {
$(this).siblings('input.model').val($(this).val());
});
Sucks that it's hard-coded in there for my individual cases though. Ideally, I'd like to encapsulate all the logic in that function. So that I can just have
$('input.province').cascade('country', 'select.country', '/get-provinces.json');
$('input.make').cascade('year', 'select.year', '/get-makes.json');
$('input.model').cascade('make', 'select.make', '/get-models.json');
Yes that is possible.
$(…).change(function () { /* fn1 */ })
.change(function () { /* fn2 */ });
jQuery event binding is additive, calling .change a second time does not remove the original event handler.
Ryan is correct in jQuery being additive, although if you find there are problems because you are chaining the same event, beautiful jQuery allows another approach, and that is calling the second function within the first after completion of the first as shown below.
$('input:checkbox').change(function() {
// Do thing #1.; <-- don't forget your semi-colon here
(function() {
// Do thing #2.
});
});
I use this technique frequently with form validation, one function for checking and replacing disallowed characters input, and the second for running a regex on the results of the parent function.
Update to Post:
OK... You all are quick to beat on me with your negative scores, without understanding the difference in how we each view Mark's request. I will proceed to explain by example why my approach is the better one, as it allows for the greatest flexibility and control. I have thrown up a quick example at the link below. A picture's worth a 1000 words.
Nested Functions on One Event Trigger
This example shows how you can tie in three functions to just one change event, and also how the second and third functions can be controlled independently, even though they are still triggered by the parent change event. This also shows how programmatically the second and third functions can BOTH be tied into the same parent function trigger, yet respond either with or independently (see this by UNCHECKING the checkbox) of the parent function it is nested within.
$('#thecheckbox').change(function() {
$("#doOne").fadeIn();
if ($('#thecheckbox').attr('checked')) { doFunc2() }
else { doFunc3() };
function doFunc2() { $("#doTwo").fadeIn(); return true; }
function doFunc3() { $("#doTwo").fadeOut(); return true; }
$("#doThree").fadeIn();
});
I've included the third 'Do thing #3 in the example, to show how yet another event can follow the two nested functions as described earlier.
Forgive the earlier bad pseudocode originally posted first, as I always use ID's with my jQuery because of their ability to give everything an individual status to address with jQuery. I never use the 'input:checkbox' method in my own coding, as this relies on the 'type' attribute of an input statement, and therefore would require extra processing to isolate any desired checkbox if there is more than one checkbox in the document. Hopefully, the example will succeed at articulating what my comments here have not.
I am actually not sure exactly if you can bind two different change events. But, why not use logic to complete both events? For example...
$('input:checkbox').change(function() {
// Do thing #1.
// Do thing #2.
});
That way, you get the same benefit. Now, if there are two different things you need to do, you may need to use logic so that only one or the other thing happens, but I think you would have to do that anyway, even if you can bind two change events to the same element.