How to detect the dismissal of a bootstrap modal dialog in javascript? - javascript

I am trying to work with the bootstrap modal dialog. The events I am using are hidden and show. I have no problem using the show event. I don't understand how to use the hidden event on the other hand. I am displaying a form in the modal dialog and on the submit event of the form, I am hiding the modal dialog with $('.modal').modal('hide'). This hide event is also fired when the modal is dismissed either by using the close icon, by clicking an abort button which has this markup <button type="button" class="btn btn-default" data-dismiss="modal">Abort</button>, by pressing the escape key or by clicking somewhere on the .modal-backdrop. How can I distinguish a successful form submission from a dismissal of the dialog?

Since Bootstrap 3:
$('#yourModal').on('hidden.bs.modal', function () {
// code here
});

I have solved this issue in a slightly hacky way: When the form is submitted, i change the value of a custom data attribute of an element that is not part of the form being submitted. When the hidden event fires, I compare the values of my custom data attribute with the current value of the input element in the form that was displayed in the modal. If the two values differ, the modal has been dismissed, otherwise it was submitted.
$( '#modalWithForm' ).on( 'submit', 'form', function ( e ) {
e.preventDefault();
$.ajax( {
url: $( this ).attr( 'action' ),
method: 'POST',
data: {
param: parseInt( $( '#input' ).val(), 10),
}
} ).done( function ( ) {
$( 'label[data-custom]' ).data( 'custom', $( '#input' ).val() );
$( '#modalWithForm' ).modal( 'hide' );
} );
} );
$( '#modalWithForm' ).on( 'hidden.bs.modal', function () {
var modalDismissed = parseInt( $( '#input' ).val(), 10 ) !== parseInt( $( 'label[data-custom]' ).data( 'custom' ) );
$.ajax( {
url: '/Update',
method: 'POST',
dataType: "text",
data: {
param: parseInt( modalDismissed ? $( 'label[data-custom]' ).data( 'custom' ) : $( '#input' ).val(), 10 )
}
} ).done( function ( updatedForm ) {
$('form').empty().html(updatedForm);
} );
} );

Related

JQuery-UI autocomplete search() not displaying DropDownList

Given the documentation for the search method for the Autocomplete Widget, I would expect a button that calls this method would display a box containing a list of available selections. Nothing happens.
I have the following code that creates the autocomplete widget on a text box:
$("#StateListCoolBox").autocomplete({
source: StateListCoolBoxTags,
focus: function( event, ui ) {
$("#StateListCoolBox").val(ui.item.label);
return false;
},
select: function( event, ui ) {
$("#StateListCoolBox").val(ui.item.label);
$("#StateListCoolBox-id").val(ui.item.value);
return false;
}
});
It works fine.
I have the following code attached to the button I want to display the list. It gets called but nothing happens:
function StateListCoolBox_dropDownClick() {
$("#StateListCoolBox").autocomplete("search", "" );
};
I have tested this with text in the corresponding textbox and with the textbox blank.
How do I get a button to behave like the button on a DropDown Combo, so that when clicked, the list of available selections is displayed?
If you look at the "View Source" for this: http://jqueryui.com/autocomplete/#combobox
You will see:
_createShowAllButton: function() {
var input = this.input,
wasOpen = false;
$( "<a>" )
.attr( "tabIndex", -1 )
.attr( "title", "Show All Items" )
.tooltip()
.appendTo( this.wrapper )
.button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
})
.removeClass( "ui-corner-all" )
.addClass( "custom-combobox-toggle ui-corner-right" )
.on( "mousedown", function() {
wasOpen = input.autocomplete( "widget" ).is( ":visible" );
})
.on( "click", function() {
input.trigger( "focus" );
// Close if already visible
if ( wasOpen ) {
return;
}
// Pass empty string as value to search for, displaying all results
input.autocomplete( "search", "" );
});
}
So this shows all the results by triggering focus event on the text field.
How do I get a button to behave like the button on a DropDown Combo, so that when clicked, the list of available selections is displayed?
I think this fits what you wanted to accomplish. So Try the following with minLength: 0:
function StateListCoolBox_dropDownClick() {
$("#StateListCoolBox").trigger("focus").autocomplete( "search", "" );
};
That said, there should be nothing wrong with your method:
Triggers a search event and invokes the data source if the event is not canceled. Can be used by a selectbox-like button to open the suggestions when clicked. When invoked with no parameters, the current input's value is used. Can be called with an empty string and minLength: 0 to display all items.
Your current code is simply missing: minLength: 0. Try both if you like.

WooCommerce event not triggering when using Select2 instead of actual select

I've built a custom website using Wordpress and WooCommerce and have installed Select2 to generate custom selects which is working fine. The issue I am having is with some of the selects on the WooCommerce pages, specifically those that trigger an event on change.
The custom selects successfully change the option selected, but the issue arises with selects that are meant to trigger an event. For example, the colour variation dropdown on the product page or the 'Sort By' select on the store page.
I've looked through the WooCommerce JS files and discovered some WooCommerce specific events that are triggered when a selection is made using the actual select box but I'm not sure how to implement this when using Select2 instead.
Here is a copy of the WooCommerce JS in relation to the event I'm talking about (in this case the change to the select for product variations):
.on( 'change', '.variations select', function() {
$form.find( 'input[name="variation_id"], input.variation_id' ).val( '' ).change();
$form.find( '.wc-no-matching-variations' ).remove();
if ( $use_ajax ) {
if ( $xhr ) {
$xhr.abort();
}
var all_attributes_chosen = true;
var some_attributes_chosen = false;
var data = {};
$form.find( '.variations select' ).each( function() {
var attribute_name = $( this ).data( 'attribute_name' ) || $( this ).attr( 'name' );
if ( $( this ).val().length === 0 ) {
all_attributes_chosen = false;
} else {
some_attributes_chosen = true;
}
data[ attribute_name ] = $( this ).val();
});
if ( all_attributes_chosen ) {
// Get a matchihng variation via ajax
data.product_id = $product_id;
$xhr = $.ajax( {
url: wc_cart_fragments_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_variation' ),
type: 'POST',
data: data,
success: function( variation ) {
if ( variation ) {
$form.find( 'input[name="variation_id"], input.variation_id' )
.val( variation.variation_id )
.change();
$form.trigger( 'found_variation', [ variation ] );
} else {
$form.trigger( 'reset_data' );
$form.find( '.single_variation_wrap' ).after( '<p class="wc-no-matching-variations woocommerce-info">' + wc_add_to_cart_variation_params.i18n_no_matching_variations_text + '</p>' );
$form.find( '.wc-no-matching-variations' ).slideDown( 200 );
}
}
} );
} else {
$form.trigger( 'reset_data' );
}
if ( some_attributes_chosen ) {
if ( $reset_variations.css( 'visibility' ) === 'hidden' ) {
$reset_variations.css( 'visibility', 'visible' ).hide().fadeIn();
}
} else {
$reset_variations.css( 'visibility', 'hidden' );
}
} else {
$form.trigger( 'woocommerce_variation_select_change' );
$form.trigger( 'check_variations', [ '', false ] );
$( this ).blur();
}
// Custom event for when variation selection has been changed
$form.trigger( 'woocommerce_variation_has_changed' );
} )
And then my own attempt to utilise this event:
$('#pa_colour').select2();
$('#pa_colour').on('change', function(){
var $form = $(this).parents('form');
$form.trigger( 'woocommerce_variation_select_change' );
$form.trigger( 'woocommerce_variation_has_changed' );
});
Unfortunately the site isn't live yet so I can't provide a link but hopefully you get the idea.
If someone can help me here I'd be so appreciative, I'm not exactly sure how Wordpress hooks (if this is what this is) work and I may be just missing something obvious.
Thanks,
Kathryn
This isn't a solution exactly, but I ended up replacing the Select2 plugin with the Selectric plugin and that works perfectly. Oh well! Thanks guys. http://lcdsantos.github.io/jQuery-Selectric/
I came across the same issue and found a solution in the last comment in this thread Select2 not showing selected value
The comment by Matt inspired by Kevin suggested wrapping the select2 call in $(window).bind("load", function() {...}); which worked for me.
Kudos to those guys.

Woocommerce "Add to cart" ajax

On the product category page, when someone clicks "Add to cart", woocommerce adds "View cart" below this button through Ajax. I found that the script which handle this is /assets/js/frontend/add-to-cart.js
Now, I want to add also "Procceed to checkout", so someone can go to checkout immediately.
This is the output of the script:
jQuery( function( $ ) {
// wc_add_to_cart_params is required to continue, ensure the object exists
if ( typeof wc_add_to_cart_params === 'undefined' )
return false;
// Ajax add to cart
$( document ).on( 'click', '.add_to_cart_button', function(e) {
// AJAX add to cart request
var $thisbutton = $( this );
if ( $thisbutton.is( '.product_type_simple' ) ) {
if ( ! $thisbutton.attr( 'data-product_id' ) )
return true;
$thisbutton.removeClass( 'added' );
$thisbutton.addClass( 'loading' );
var data = {
action: 'woocommerce_add_to_cart',
};
$.each( $thisbutton.data(), function( key, value ) {
data[key] = value;
});
// Trigger event
$( 'body' ).trigger( 'adding_to_cart', [ $thisbutton, data ] );
// Ajax action
$.post( wc_add_to_cart_params.ajax_url, data, function( response ) {
if ( ! response )
return;
var this_page = window.location.toString();
this_page = this_page.replace( 'add-to-cart', 'added-to-cart' );
if ( response.error && response.product_url ) {
window.location = response.product_url;
return;
}
// Redirect to cart option
if ( wc_add_to_cart_params.cart_redirect_after_add === 'yes' ) {
window.location = wc_add_to_cart_params.cart_url;
return;
} else {
$thisbutton.removeClass( 'loading' );
fragments = response.fragments;
cart_hash = response.cart_hash;
// Block fragments class
if ( fragments ) {
$.each( fragments, function( key, value ) {
$( key ).addClass( 'updating' );
});
}
// Block widgets and fragments
$( '.shop_table.cart, .updating, .cart_totals' ).fadeTo( '400', '0.6' ).block({
message: null,
overlayCSS: {
opacity: 0.6
}
});
// Changes button classes
$thisbutton.addClass( 'added' );
// View cart text
if ( ! wc_add_to_cart_params.is_cart && $thisbutton.parent().find( '.added_to_cart' ).size() === 0 ) {
$thisbutton.after( ' <a href="' + wc_add_to_cart_params.cart_url + '" class="added_to_cart wc-forward" title="' +
wc_add_to_cart_params.i18n_view_cart + '">' + wc_add_to_cart_params.i18n_view_cart + '</a>' );
}
// Replace fragments
if ( fragments ) {
$.each( fragments, function( key, value ) {
$( key ).replaceWith( value );
});
}
// Unblock
$( '.widget_shopping_cart, .updating' ).stop( true ).css( 'opacity', '1' ).unblock();
// Cart page elements
$( '.shop_table.cart' ).load( this_page + ' .shop_table.cart:eq(0) > *', function() {
$( '.shop_table.cart' ).stop( true ).css( 'opacity', '1' ).unblock();
$( 'body' ).trigger( 'cart_page_refreshed' );
});
$( '.cart_totals' ).load( this_page + ' .cart_totals:eq(0) > *', function() {
$( '.cart_totals' ).stop( true ).css( 'opacity', '1' ).unblock();
});
// Trigger event so themes can refresh other areas
$( 'body' ).trigger( 'added_to_cart', [ fragments, cart_hash, $thisbutton ] );
}
});
return false;
}
return true;
});
Is there anybody who has done something similar?
If you look here from the Woocommerce repo, you can see that add-to-cart.js is localized from that class.
Unfortunately, there isn't a filter to just add your own link. What you could try is copying add-to-cart.js to your theme and set the new src of the registered add-to-cart.js to your new local copy, by using this method.
From there you can alter the this conditional found in Woocommerce repo.
So, technically yes, you could could this, but there are caveats:
You would need to repeat this process for variation products
If translation is a concern, you need to address that as well
Any time the plugin updates, you now have to comb through these files for any differences that could break functionality or cause a security issue.

Jquery Mobile Listview Autocomplete: how to trigger function when user presses enter?

I’m following the jquery mobile remote autocomplete demo:
http://jquerymobile.com/demos/1.3.0-beta.1/docs/demos/listviews/listview-filter-autocomplete.html
My list is being dynamically populated from my datasource fine and I can do things when the user clicks on a result from the list.
However I also need it to trigger a function when the user hits enter (or clicks “Go” on the phone)... How can I do this? Here's my current code:
$( document ).on( "pageinit", "#myPage", function() {
$( "#autocomplete" ).on( "click","li",function() {
// do stuff when user clicks on item in list
alert('Doing stuff!');
});
$( "#autocomplete" ).on( "listviewbeforefilter", function ( e, data ) {
var $ul = $( this ),
$input = $( data.input ),
value = $input.val(),
html = "";
$ul.html( "" );
if ( value && value.length > 2 ) {
$ul.html( "<li><div class='ui-loader'><span class='ui-icon ui-icon-loading'></span></div></li>" );
$ul.listview( "refresh" );
$.ajax({
url: "http://mywebservice/"+$input.val(),
dataType: "json",
crossDomain: false
})
.then( function ( response ) {
$.each( response, function ( i, val ) {
html += "<li><a href='#'>" + val.display_name + "</a></li>";
});
$ul.html( html );
$ul.listview( "refresh" );
$ul.trigger( "updatelayout");
});
}
});
});
I've been searching alot for help, but most results have been talking about the jquery autocomplete and not the jquery mobile listview autocomplete...
Any help would be much appreciated -thanks!
Hey I used a local autocomplete jQM widget but this will work the same for ya -
HTML -
<div data-role="page" id="carPage">
<div data-role="content">
<ul id="autocomplete" data-role="listview" data-inset="true" data-filter="true" data-filter-placeholder="Find a car..." data-filter-theme="d">
<li>Acura</li>
<li>Audi</li>
<li>BMW</li>
<li>Cadillac</li>
<li>Ferrari</li>
<li>Honda</li>
</ul>
</div>
</div>
JS -
$(function () {
$('#carPage input[data-type="search"]').on('keydown', function(e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 13) { //Enter keycode
alert('enter key was pressed');
}
});
});
jsFiddle Demo
/Update
Regarding the go button - Because the autocomplete widget wraps a form element around your content the go button will trigger a submit on the form. This means you can listen to the enter key press and go button press with this simple event handler like this below -
$("#carPage form").submit(function() {
// this will handle both the enter key and go button on device
});
I updated the jsFiddle demo with both approaches above. I like the second approach best because it handles both scenarios the easiest.

How to test a function which has a setTimeout with jasmine?

I need to write a test for a function that has a setTimeout() call inside, but i can't find how i should do.
This is the function
// Disables all submit buttons after a submit button is pressed.
var block_all_submit_and_ajax = function( el ) {
// Clone the clicked button, we need to know what button has been clicked so that we can react accordingly
var $clone = $( el ).clone();
// Change the type to hidden
$clone.attr( 'type', 'hidden' );
// Put the hidden button in the DOM
$( el ).after( $clone );
// Disable all submit button. I use setTimeout otherwise this doesn't work in chrome.
setTimeout(function() {
$( '#facebook input[type=submit]' ).prop( 'disabled', true );
}, 10);
// unbind all click handler from ajax
$( '#facebook a.btn' ).unbind( "click" );
// Disable all AJAX buttons.
$( '#facebook a.btn' ).click( function( e ) {
e.preventDefault();
e.stopImmediatePropagation();
} );
};
And this is my test
it( "Disable all submit buttons", function() {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
console.log( 'f' );
expect( el ).toHaveProp( 'disabled', true );
} );
} );
I've tried using jasmine.Clock.useMock(); and jasmine.Clock.tick(11); but i couldn't get things to work, the test never pass
The overall approach varies based on your Jasmine version.
Jasmine 1.3
You can use waitsFor:
it( "Disable all submit buttons", function() {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
// Wait 100ms for all elements to be disabled.
waitsFor('button to be disabled', function(){
var found = true;
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
if (!el.prop('disabled')) found = false;
});
return found;
}, 100);
});
You could also use waits if you know exactly how long it will take:
it( "Disable all submit buttons", function() {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
// Wait 20ms before running 'runs' section.
waits(20);
runs(function(){
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
expect( el ).toHaveProp( 'disabled', true );
});
});
});
There is also a third way of doing this, without the need for waits, waitsFor, and runs.
it( "Disable all submit buttons", function() {
jasmine.Clock.useMock();
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
jasmine.Clock.tick(10);
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
expect( el ).toHaveProp( 'disabled', true );
});
});
Jasmine 2.0
You can use done, the test callback:
it( "Disable all submit buttons", function(done) {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
utility_functions.block_all_submit_and_ajax( $button.get(0) );
setTimeout(function(){
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
expect( el ).toHaveProp( 'disabled', true );
});
// Let Jasmine know the test is done.
done();
}, 20);
});
you can mock out the timer behavior:
it( "Disable all submit buttons", function() {
jasmine.clock().install();
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
jasmine.clock().tick(10);
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
expect( el ).toHaveProp( 'disabled', true );
});
jasmine.clock().uninstall()
});
For anyone googling this, a better answer can be found timer testing
import { fakeAsync, tick, discardPeriodicTasks } from '#angular/core/testing';
it('polls statusStore.refreshStatus on an interval', fakeAsync(() => {
spyOn(mockStatusStore, 'refreshStatus').and.callThrough();
component.ngOnInit();
expect(mockStatusStore.refreshStatus).not.toHaveBeenCalled();
tick(3001);
expect(mockStatusStore.refreshStatus).toHaveBeenCalled();
tick(3001);
expect(mockStatusStore.refreshStatus).toHaveBeenCalledTimes(2);
discardPeriodicTasks();
}));
Since Jasmine 2 the syntax has changed: http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support
You now can simply pass a done callback to beforeEach, it, and afterEach:
it('tests something async', function(done) {
setTimeout(function() {
expect(somethingSlow).toBe(true);
done();
}, 400);
});
Update: Since writing this it's now also possible to use async/await which would be my preferred approach.
I've never done any testing with jasmine, but I think I understand your problem. I would restructure the code a little to allow for you to wrap the function being called in a proxy function like this:
Modify your code that is being test to extract the setTimeout code into another function:
Original Code:
// Disables all submit buttons after a submit button is pressed.
var block_all_submit_and_ajax = function( el ) {
// Clone the clicked button, we need to know what button has been clicked so that we can react accordingly
var $clone = $( el ).clone();
// Change the type to hidden
$clone.attr( 'type', 'hidden' );
// Put the hidden button in the DOM
$( el ).after( $clone );
// Disable all submit button. I use setTimeout otherwise this doesn't work in chrome.
setTimeout(function() {
$( '#facebook input[type=submit]' ).prop( 'disabled', true );
}, 10);
// unbind all click handler from ajax
$( '#facebook a.btn' ).unbind( "click" );
// Disable all AJAX buttons.
$( '#facebook a.btn' ).click( function( e ) {
e.preventDefault();
e.stopImmediatePropagation();
} );
};
Modified Code:
// Disables all submit buttons after a submit button is pressed.
var block_all_submit_and_ajax = function( el ) {
// Clone the clicked button, we need to know what button has been clicked so that we can react accordingly
var $clone = $( el ).clone();
// Change the type to hidden
$clone.attr( 'type', 'hidden' );
// Put the hidden button in the DOM
$( el ).after( $clone );
// Disable all submit button. I use setTimeout otherwise this doesn't work in chrome.
setTimeout(disableSubmitButtons, 10);
// unbind all click handler from ajax
$( '#facebook a.btn' ).unbind( "click" );
// Disable all AJAX buttons.
$( '#facebook a.btn' ).click( function( e ) {
e.preventDefault();
e.stopImmediatePropagation();
} );
};
var utilityFunctions =
{
disableSubmitButtons : function()
{
$( '#facebook input[type=submit]' ).prop( 'disabled', true );
}
}
Next I would modify the testing code like this:
it( "Disable all submit buttons", function() {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
var originalFunction = utilityFunctions.disableSubmitButtons;
utilityFunctions.disableSubmitButtons = function()
{
// call the original code, and follow it up with the test
originalFunction();
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
console.log( 'f' );
expect( el ).toHaveProp( 'disabled', true );
});
// set things back the way they were
utilityFunctions.disableSubmitButtons = originalFunction;
}
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
});

Categories

Resources