var WPViews = WPViews || {}; var wpv_stop_rollover = {}; window.wpvPaginationAjaxLoaded = {}; window.wpvPaginationAnimationFinished = {}; window.wpvPaginationQueue = {}; // ------------------------------------ // Clone // ------------------------------------ // Textarea and select clone() bug workaround | Spencer Tipping // Licensed under the terms of the MIT source code license // Motivation. // jQuery's clone() method works in most cases, but it fails to copy the value of textareas and select elements. This patch replaces jQuery's clone() method with a wrapper that fills in the // values after the fact. // An interesting error case submitted by Piotr Przybyl: If two box itself rather than relying on jQuery's value-based val(). ;( function() { jQuery.fn.wpv_clone = function() { var result = jQuery.fn.clone.apply( this, arguments ), my_textareas = this.find( 'textarea' ).add( this.filter( 'textarea' ) ), result_textareas = result.find( 'textarea' ).add( result.filter( 'textarea' ) ), my_selects = this.find( 'select' ).add( this.filter( 'select' ) ), result_selects = result.find( 'select' ).add( result.filter( 'select' ) ); for ( var i = 0, l = my_textareas.length; i < l; ++i ) { jQuery( result_textareas[i] ).val( jQuery( my_textareas[i] ).val() ); } for ( var i = 0, l = my_selects.length; i < l; ++i ) { for ( var j = 0, m = my_selects[i].options.length; j < m; ++j ) { if ( my_selects[i].options[j].selected === true ) { result_selects[i].options[j].selected = true; } else { result_selects[i].options[j].selected = false; } } } return result; }; })(); WPViews.ViewFrontendUtils = function( $ ) { // ------------------------------------ // Constants and variables // ------------------------------------ var self = this; self.datepicker_style_id = 'js-toolset-datepicker-style'; self.is_datepicker_style_loaded = false; // ------------------------------------ // Methods // ------------------------------------ self.just_return = function() { return; }; /** * extract_url_query_parameters * * Extracts parameters from a query string, managing arrays, and returns an array of pairs key => value * * @param string query_string * * @return array * * @note ##URLARRAYVALHACK## is a hacky constant * * @uses decodeURIComponent * * @since 1.9 */ self.extract_url_query_parameters = function( query_string ) { var query_string_pairs = {}; if ( query_string == "" ) { return query_string_pairs; } var query_string_split = query_string.split( '&' ), query_string_split_length = query_string_split.length; for ( var i = 0; i < query_string_split_length; ++i ) { var qs_part = query_string_split[i].split( '=' ); if ( qs_part.length != 2 ) { continue; }; var thiz_key = qs_part[0], thiz_val = decodeURIComponent( qs_part[1].replace( /\+/g, " " ) ); // Adjust thiz_key to work with POSTed arrays thiz_key = thiz_key.replace( /(\[)\d?(\])/, "" ); thiz_key = thiz_key.replace( "[]", "" );// Just in case thiz_key = thiz_key.replace( /(%5B)\d?(%5D)/, "" ); thiz_key = thiz_key.replace( "%5B%5D", "" );// Just in case thiz_key = thiz_key.replace( /(%255B)\d?(%255D)/, "" ); thiz_key = thiz_key.replace( "%255B%255D", "" );// Just in case if ( query_string_pairs.hasOwnProperty( thiz_key ) ) { if ( query_string_pairs[thiz_key] != thiz_val ) { // @hack alert!! We can not avoid using this :-( query_string_pairs[thiz_key] += '##URLARRAYVALHACK##' + thiz_val; } else { query_string_pairs[thiz_key] = thiz_val; } } else { query_string_pairs[thiz_key] = thiz_val; } } return query_string_pairs; }; /** * get_extra_url_query_parameters * * Gets the current URL query parameters, but only those that do not belong to the current form already. * * @note Arrays are returned on a single key with values on a single string, separated by the ##URLARRAYVALHACK## placeholder. We might review that later * @note Since 2.2, force include URL parameters that belong to form controls when the form does not use dependency nor AJAX results. * As the form should be posted with page reload in this case, and we are not performing a dependency call, URL parameters win over optios in the form * and have been not recorded yet in the AJAX method manager. * * @return array * * @uses self.extract_url_query_parameters * * @since 2.1 */ self.get_extra_url_query_parameters_by_form = function( form ) { var query_string = self.extract_url_query_parameters( window.location.search.substr( 1 ) ), data = {}, force_from_form = ! ( form.hasClass( 'js-wpv-dps-enabled' ) || form.hasClass( 'js-wpv-ajax-results-enabled' ) || form.hasClass( 'js-wpv-ajax-results-submit-enabled' ) ); for ( var prop in query_string ) { var $matchExisting = form.find( '[name="' + prop + '"], [name="' + prop + '\\[\\]"]' ); if ( query_string.hasOwnProperty( prop ) && ! data.hasOwnProperty( prop ) && ( force_from_form || $matchExisting.length === 0 || $matchExisting .filter( '.js-wpv-extra-url-param' ) .length > 0 ) ) { data[ prop ] = query_string[ prop ]; } } return data; }; /** * set_extra_url_query_parameters * * Forces the current URL query parameters in a form, but only those that do not belong to the form already. * * @uses self.extract_url_query_parameters * * @since 2.1 */ self.set_extra_url_query_parameters_by_form = function( form ) { var extra = self.get_extra_url_query_parameters_by_form( form ); $.each( extra, function( key, value ) { if ( form.find( '[name="' + key + '"], [name="' + key + '\\[\\]"]' ).length === 0 ) { // @hack alert!! WE can not avoid this :-( var pieces = value.split( '##URLARRAYVALHACK##' ), pieces_length = pieces.length; if ( pieces_length < 2 ) { $( '' ).attr({ type: 'hidden', name: key, value: value, class: 'js-wpv-extra-url-param' }) .appendTo( form ); } else { for ( var iter = 0; iter < pieces_length; iter++ ) { $( '' ).attr({ type: 'hidden', name: key + "[]", value: pieces[iter], class: 'js-wpv-extra-url-param' }) .appendTo( form ); } } } }); }; /** * Dynamically load the Toolset datepicker style, only when needed. * * @note we should do the same with mediaelement and wp-mediaelement * @note the handle for this used to be wptoolset-field-datepicker * * @since 2.3.0 */ self.maybe_load_datepicker_style = function() { if ( ! self.is_datepicker_style_loaded ) { if ( document.getElementById( self.datepicker_style_id ) ) { self.is_datepicker_style_loaded = true; } else { var head = document.getElementsByTagName( 'head' )[0], link = document.createElement( 'link' ); link.id = self.datepicker_style_id; link.rel = 'stylesheet'; link.type = 'text/css'; link.href = wpv_pagination_local.datepicker_style_url; link.media = 'all'; head.appendChild( link ); self.is_datepicker_style_loaded = true; } } }; /** * Destroy all initialized datepickers in the page, before doing changes and initializing them again. * * @since 2.3.0 */ self.destroy_frontend_datepicker = function() { $( ".js-wpv-frontend-datepicker.js-wpv-frontend-datepicker-inited" ) .removeClass( 'js-wpv-frontend-datepicker-inited' ) .datepicker( "destroy" ); }; /** * render_frontend_datepicker * * Adds a datepicker to a selector but only if it has not been added before. * * Fired on document.ready, after AJAX pagination and after AJAX parametric search events. * * @since 1.9 */ self.render_frontend_datepicker = function() { $( '.js-wpv-frontend-datepicker:not(.js-wpv-frontend-datepicker-inited)' ).each( function() { self.maybe_load_datepicker_style(); var thiz = $( this ); thiz .addClass( 'js-wpv-frontend-datepicker-inited' ) .datepicker({ onSelect: function( dateText, inst ) { var url_param = thiz.data( 'param' ), data = 'date=' + dateText, form = thiz.closest( 'form' ); data += '&date-format=' + $( '.js-wpv-date-param-' + url_param + '-format' ).val(); data += '&action=wpv_format_date'; $.post( wpv_pagination_local.front_ajaxurl, data, function( response ) { response = JSON.parse( response ); form.find('.js-wpv-date-param-' + url_param ).html( response['display'] ); form.find('.js-wpv-date-front-end-clear-' + url_param ).show(); form.find('.js-wpv-date-param-' + url_param + '-value' ).val( response['timestamp'] ).trigger( 'change' ); }); }, dateFormat: 'ddmmyy', minDate: wpv_pagination_local.datepicker_min_date, maxDate: wpv_pagination_local.datepicker_max_date, showOn: "button", buttonImage: wpv_pagination_local.calendar_image, buttonText: wpv_pagination_local.calendar_text, buttonImageOnly: true, changeMonth: true, changeYear: true, yearRange: wpv_pagination_local.datepicker_min_year + ':' + wpv_pagination_local.datepicker_max_year, }); }); }; /** * clone_form * * Clones a form using the fixed clone() method that covers select and textarea elements * * @param object fil * @param array targets * * @since 1.9 */ self.clone_form = function( fil, targets ) { var cloned = fil.wpv_clone(); targets.each( function() { $( this ).replaceWith( cloned ); }); }; /** * render_frontend_media_shortcodes * * Render the WordPress media players for items inside a container. * * @param object container * * @since 1.9 */ self.render_frontend_media_shortcodes = function( container ) { // Audio shortcodes container.find( '.wp-audio-shortcode.mejs-container' ).each( function() { var $oldStructure = $( this ), $audioTag = $oldStructure.find( 'audio.wp-audio-shortcode' ); $oldStructure.replaceWith( $audioTag ); }); container.find( '.wp-audio-shortcode' ).each( function() { var thiz = $( this ); thiz.mediaelementplayer(); }); // Video shortcodes container.find( '.wp-video-shortcode.mejs-container' ).each( function() { var $oldStructure = $( this ), $videoTag = $oldStructure.find( 'video.wp-video-shortcode[id$=_from_mejs]' ); $oldStructure.replaceWith( $videoTag ); }); container.find( '.wp-video-shortcode' ).each( function() { var thiz = $( this ); thiz.mediaelementplayer(); }); // Playlists // Note that existing playlists will not work properlyon // on existing content after the infinite scrolling pagination effect: // we can not restart them container.find( '.wp-playlist' ).each( function() { var thiz = $( this ); return new WPPlaylistView({ el: this }); }); }; /** * get_form_element_type * * Get a form element type, be it text, radio, checkbox, textarea or select * * @param selector A jQuery selector object * * @return string 'text'|'radio'|'checkbox'|'textarea'|'select'|empty if selector is empty * * @since 2.2 */ self.get_form_element_type = function( selector ) { if ( selector.length > 0 ) { return selector[0].tagName == "INPUT" ? selector[0].type.toLowerCase() : selector[0].tagName.toLowerCase(); } else { return ''; } } self.get_hidden_item_width = function( item ) { var item_clone = item .clone() .css({ 'display': 'block', 'visibility': 'visible', 'position': 'absolute', 'z-index': '-99999', 'left': '99999999px', 'top': '0px', 'white-space': 'nowrap' }) .appendTo( 'body' ), width = item_clone.outerWidth(); item_clone.remove(); return width; }; // ------------------------------------ // Get updated results // ------------------------------------ /** * get_updated_query_results * * Shared method for paginating Views and WordPress Archives, and also for parametric search on both. * Returns a promise so you can operate on the response at will. * * Note that it applis the current form regardless it being submitted or not. * @todo we might want to avoid adding that data when the form contains unsubmitted changes. * * @param view_number The object hash * @param page The page that we want to get * @param form The form to track changes against * @param expect 'form'|'full'|'both' Whether to return the full View, just the form, or both * * @since 2.1 */ self.get_updated_query_results = function( view_number, page, form, expect ) { var data = {}, sort = {}, environment = {}, search = {}, extra = {}. attributes = {}, lang = wpv_pagination_local.wpmlLang, parametric_data = form.data( 'parametric' ); sort = WPViews.view_sorting.get_sort_data( view_number, form ); if ( parametric_data['environment'].current_post_id > 0 ) { environment['wpv_aux_current_post_id'] = parametric_data['environment'].current_post_id; } if ( parametric_data['environment'].parent_post_id > 0 ) { environment['wpv_aux_parent_post_id'] = parametric_data['environment'].parent_post_id; } if ( parametric_data['environment'].parent_term_id > 0 ) { environment['wpv_aux_parent_term_id'] = parametric_data['environment'].parent_term_id; } if ( parametric_data['environment'].parent_user_id > 0 ) { environment['wpv_aux_parent_user_id'] = parametric_data['environment'].parent_user_id; } environment['archive'] = parametric_data['environment'].archive; if ( form.find( '.js-wpv-post-relationship-update' ).length ) { search['dps_pr'] = form.find( '.js-wpv-post-relationship-update' ).serializeArray(); } if ( form.hasClass( 'js-wpv-dps-enabled' ) || form.hasClass( 'js-wpv-ajax-results-enabled' ) || form.hasClass( 'js-wpv-ajax-results-submit-enabled' ) ) { search['dps_general'] = form.find( '.js-wpv-filter-trigger, .js-wpv-filter-trigger-delayed' ).serializeArray(); } attributes = parametric_data['attributes']; extra = self.get_extra_url_query_parameters_by_form( form, true ); data = { 'view_number': view_number, page: page, sort: sort, attributes: attributes, environment: environment, search: search, extra: extra, expect: expect }; if ( lang ) { data['lang'] = lang; } switch ( parametric_data.query ) { case 'archive': data['action'] = 'wpv_get_archive_query_results'; data['loop'] = parametric_data.loop; break; default: data['action'] = 'wpv_get_view_query_results'; data['id'] = parametric_data.id; if ( form.attr( 'data-targetid' ) ) { data['target_id'] = form.data( 'targetid' ); } else if ( $( '.js-wpv-form-only.js-wpv-filter-form-' + view_number ).length > 0 ) { data['target_id'] = $( '.js-wpv-form-only.js-wpv-filter-form-' + view_number ).data( 'targetid' ); } data['wpv_view_widget_id'] = parametric_data['widget_id']; break; } return $.ajax({ type: "POST", dataType: "json", url: wpv_pagination_local.front_ajaxurl, data: data }).done( function( response ) { // If any of the loaded pages contains a playlist shortcode, // append the required templates in case they are not there yet. if ( response.success && _.has( response.data, 'playlist_templates' ) && $( '#tmpl-wp-playlist-current-item' ).length === 0 ) { $( 'body' ).append( response.data.playlist_templates ); } }); }; /** * Add, update or remove query string argument. * * @param key {string} Argument name * @param value {string} Argument value. Not supplying a value will remove the parameter, supplying one will * add/update the argument. * @param url {string} The URL. If no URL is supplied, it will be grabbed from window.location * @return string The updated URL. * @link http://stackoverflow.com/a/11654596/3191395 * * @since 2.3 */ self.updateUrlQuery = function(key, value, url) { if(!url) { url = window.location.href; } var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"); var hash; if (re.test(url)) { if (typeof value !== 'undefined' && value !== null) { return url.replace(re, '$1' + key + "=" + value + '$2$3'); } else { hash = url.split('#'); url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); if(typeof hash[1] !== 'undefined' && hash[1] !== null) { url += '#' + hash[1]; } return url; } } else { if (typeof value !== 'undefined' && value !== null) { var separator = url.indexOf('?') !== -1 ? '&' : '?'; hash = url.split('#'); url = hash[0] + separator + key + '=' + value; if (typeof hash[1] !== 'undefined' && hash[1] !== null) { url += '#' + hash[1]; } return url; } else { return url; } } }; // ------------------------------------ // Events // ------------------------------------ /** * Window resize event * * Make Views layouts responsive * * @since 1.9 * @since 1.11 added debounce */ $( window ).on( 'resize', _.debounce( function() { $( '.js-wpv-layout-responsive' ).each( function() { $( this ).css( 'width', '' ); }) .promise() .done( function() { $( document ).trigger( 'js_event_wpv_layout_responsive_resize_completed' ); }); }, wpv_pagination_local.resize_debounce_tolerance )); // ------------------------------------ // Init // ------------------------------------ self.init = function() { self.render_frontend_datepicker(); }; self.init(); }; WPViews.ViewSorting = function( $ ) { var self = this; /** * Get the sort data for a given View. * * @note If the current orderby option does not have an orderby_as option and does not match the stored option, * we remove (we empty) that value so it gets sorted as a native, string value. * * @since 2.3.0 */ self.get_sort_data = function( view_number, form ) { var sort = {}, parametric_data = form.data( 'parametric' ); sort['wpv_sort_orderby'] = parametric_data['sort']['orderby']; sort['wpv_sort_order'] = parametric_data['sort']['order']; sort['wpv_sort_orderby_as'] = parametric_data['sort']['orderby_as']; sort['wpv_sort_orderby_second'] = parametric_data['sort']['orderby_second']; sort['wpv_sort_order_second'] = parametric_data['sort']['order_second']; if ( form.find( '.js-wpv-sort-control-orderby' ).length > 0 ) { var orderby_type = WPViews.view_frontend_utils.get_form_element_type( form.find( '.js-wpv-sort-control-orderby' ) ); switch ( orderby_type ) { case 'select': sort['wpv_sort_orderby'] = form.find( '.js-wpv-sort-control-orderby' ).val(); if ( form.find( '.js-wpv-sort-control-orderby option:selected' ).data( 'orderbyas' ) ) { sort['wpv_sort_orderby_as'] = form.find( '.js-wpv-sort-control-orderby option:selected' ).data( 'orderbyas' ); } else if ( sort['wpv_sort_orderby'] != parametric_data['sort']['orderby'] ) { sort['wpv_sort_orderby_as'] = ''; } break; case 'radio': sort['wpv_sort_orderby'] = form.find( '.js-wpv-sort-control-orderby:checked' ).val(); if ( form.find( '.js-wpv-sort-control-orderby:checked' ).data( 'orderbyas' ) ) { sort['wpv_sort_orderby_as'] = form.find( '.js-wpv-sort-control-orderby:checked' ).data( 'orderbyas' ); } else if ( sort['wpv_sort_orderby'] != parametric_data['sort']['orderby'] ) { sort['wpv_sort_orderby_as'] = ''; } break; case 'text': case 'hidden': sort['wpv_sort_orderby'] = form.find( '.js-wpv-sort-control-orderby' ).val(); if ( form.find( '.js-wpv-sort-control-orderby' ).data( 'orderbyas' ) ) { sort['wpv_sort_orderby_as'] = form.find( '.js-wpv-sort-control-orderby' ).data( 'orderbyas' ); } else if ( sort['wpv_sort_orderby'] != parametric_data['sort']['orderby'] ) { sort['wpv_sort_orderby_as'] = ''; } break; } } if ( form.find( '.js-wpv-sort-control-order' ).length > 0 ) { var order_type = WPViews.view_frontend_utils.get_form_element_type( form.find( '.js-wpv-sort-control-order' ) ); switch ( order_type ) { case 'select': sort['wpv_sort_order'] = form.find( '.js-wpv-sort-control-order' ).val(); break; case 'radio': sort['wpv_sort_order'] = form.find( '.js-wpv-sort-control-order:checked' ).val(); break; case 'text': case 'hidden': sort['wpv_sort_order'] = form.find( '.js-wpv-sort-control-order' ).val(); break; } } if ( '' === sort['wpv_sort_order'] && form.find( '.js-wpv-sort-control-orderby' ).length > 0 ) { var order_type = WPViews.view_frontend_utils.get_form_element_type( form.find( '.js-wpv-sort-control-orderby' ) ); switch ( order_type ) { case 'select': sort['wpv_sort_order'] = form.find( '.js-wpv-sort-control-orderby option:selected' ).data( 'forceorder' ); break; case 'radio': sort['wpv_sort_order'] = form.find( '.js-wpv-sort-control-orderby:checked' ).data( 'forceorder' ); break; case 'text': case 'hidden': sort['wpv_sort_order'] = form.find( '.js-wpv-sort-control-orderby' ).data( 'forceorder' ); break; } } return sort; } /** * Sync the sorting controls used inside and outside of the View form. * * @since 2.3.0 * @since 2.3.1 Split into set_sort_data_inside_form and set_sort_data_outside_form. */ self.set_sort_data = function( form, sort ) { self.set_sort_data_inside_form( form, sort ) .set_sort_data_outside_form( form, sort ); return self; }; /** * Sync the sorting controls used inside and outside of the View form for the case of paginating while the View is * already sorted. * * @since 2.5.2 */ self.set_sort_data_for_pagination = function ( form ) { var sort = {}, parametric_data = form.data( 'parametric' );; if ( form.find( '.js-wpv-sort-control-orderby' ).length > 0 ) { var orderby_type = WPViews.view_frontend_utils.get_form_element_type( form.find( '.js-wpv-sort-control-orderby' ) ); switch ( orderby_type ) { case 'select': sort['orderby'] = form.find( '.js-wpv-sort-control-orderby' ).val(); if ( form.find( '.js-wpv-sort-control-orderby option:selected' ).data( 'orderbyas' ) ) { sort['orderby_as'] = form.find( '.js-wpv-sort-control-orderby option:selected' ).data( 'orderbyas' ); } else if ( sort['orderby'] != parametric_data['sort']['orderby'] ) { sort['orderby_as'] = ''; } if ( form.find( '.js-wpv-sort-control-orderby option:selected' ).data( 'forceorder' ) ) { sort['order'] = form.find( '.js-wpv-sort-control-orderby option:selected' ).data( 'forceorder' ); } break; case 'radio': sort['orderby'] = form.find( '.js-wpv-sort-control-orderby:checked' ).val(); if ( form.find( '.js-wpv-sort-control-orderby:checked' ).data( 'orderbyas' ) ) { sort['orderby_as'] = form.find( '.js-wpv-sort-control-orderby:checked' ).data( 'orderbyas' ); } else if ( sort['orderby'] != parametric_data['sort']['orderby'] ) { sort['orderby_as'] = ''; } if ( form.find( '.js-wpv-sort-control-orderby:checked' ).data( 'forceorder' ) ) { sort['order'] = form.find( '.js-wpv-sort-control-orderby:checked' ).data( 'forceorder' ); } break; case 'text': case 'hidden': sort['orderby'] = form.find( '.js-wpv-sort-control-orderby' ).val(); if ( form.find( '.js-wpv-sort-control-orderby' ).data( 'orderbyas' ) ) { sort['orderby_as'] = form.find( '.js-wpv-sort-control-orderby' ).data( 'orderbyas' ); } else if ( sort['orderby'] != parametric_data['sort']['orderby'] ) { sort['orderby_as'] = ''; } if ( form.find( '.js-wpv-sort-control-orderby' ).data( 'forceorder' ) ) { sort['order'] = form.find( '.js-wpv-sort-control-orderby' ).data( 'forceorder' ); } break; } } self.set_sort_data( form, sort ); }; /** * Set the sort data for orderby controls as select dropdowns. * * @since 2.3.1 */ self.set_sort_data_orderby_select = function( orderby_control, sort ) { if ( orderby_control.find( 'option[value="' + sort['orderby'] + '"]' ).length == 0 ) { orderby_control.append( $('