/**
* jQuery fontIconPicker - v2.3
*
* An icon picker built on top of font icons and jQuery
*
* http://codeb.it/fontIconPicker
*
* Made by Alessandro Benoit & Swashata
* Under MIT License
*
* {@link https://github.com/micc83/fontIconPicker}
*
* Modified by Visual Composer Dev Team
*/
(function ( $ ) {
'use strict';
// Create the defaults once
var defaults = {
theme: 'fip-vc-theme-grey', // The CSS theme to use with this fontIconPicker. You can set different themes on multiple elements on the same page
source: false, // Icons source (array|false|object)
emptyIcon: true, // Empty icon should be shown?
emptyIconValue: '', // The value of the empty icon, change if you select has something else, say "none"
iconsPerPage: 20, // Number of icons per page
hasSearch: true, // Is search enabled?
searchSource: false, // Give a manual search values. If using attributes then for proper search feature we also need to pass icon names under the same order of source
useAttribute: false, // Whether to use attribute selector for printing icons
attributeName: 'data-icon', // HTML Attribute name
convertToHex: true, // Whether or not to convert to hexadecimal for attribute value. If true then please pass decimal integer value to the source (or as value="" attribute of the select field)
allCategoryText: 'From all categories', // The text for the select all category option
unCategorizedText: 'Uncategorized', // The text for the select uncategorized option
iconDownClass: 'fip-icon-down-dir', // Class for icon down
iconUpClass: 'fip-icon-up-dir', // Class for icon up
iconLeftClass: 'fip-icon-left-dir', // Class for icon left
iconRightClass: 'fip-icon-right-dir', // Class for icon right
iconSearchClass: 'fip-icon-search', // Class for search
iconCancelClass: 'fip-icon-cancel', // Class for search canceling
iconSpinClass: 'fip-icon-spin3', // Class for fip-icon-spin3
iconBlockClass: 'fip-icon-block', // Class for block(none icon)
searchPlaceholder: 'Search Icon', // Search icon text placeholder
mainClass: 'vc-icons-selector'
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = $( element );
this.settings = $.extend( {}, defaults, options );
if ( this.settings.emptyIcon ) {
this.settings.iconsPerPage --;
}
this.iconPicker = $( '
'
} );
this.iconContainer = this.iconPicker.find( '.fip-icons-container' );
this.searchIcon = this.iconPicker.find( '.selector-search i' );
this.iconsSearched = [];
this.isSearch = false;
this.totalPage = 1;
this.currentPage = 1;
this.currentIcon = false;
this.initialized = false;
this.iconsPaged = false;
this.iconsCount = 0;
this.open = false;
// Set the default values for the search related variables
this.searchValues = [];
this.availableCategoriesSearch = [];
// The trigger event for change
this.triggerEvent = null;
// Backups
this.backupSource = [];
this.backupSearch = [];
// Set the default values of the category related variables
this.isCategorized = false; // Automatically detects if the icon listing is categorized
this.selectCategory = this.iconPicker.find( '.icon-category-select' ); // The category SELECT input field
this.selectedCategory = false; // false means all categories are selected
this.availableCategories = []; // Available categories, it is a two dimensional array which holds categorized icons
this.unCategorizedKey = null; // Key of the uncategorized category
// Initialize plugin
this.quickInit();
}
Plugin.prototype = {
/**
* Quick init
*/
quickInit: function () {
var first = true;
// Add the theme CSS to the iconPicker
this.iconPicker.addClass( this.settings.theme );
// To properly calculate iconPicker height and width
// We will first append it to body (with left: -9999px so that it is not visible)
this.iconPicker.css( {
left: - 9999
} ).appendTo( 'body' );
var iconPickerHeight = this.iconPicker.outerHeight(),
iconPickerWidth = this.iconPicker.outerWidth();
// Now reset the iconPicker CSS
this.iconPicker.css( {
left: ''
} );
// Add the icon picker after the select
this.element.before( this.iconPicker );
// Hide source element
// Instead of doing a display:none, we would rather
// make the element invisible
// and adjust the margin
this.element.css( {
visibility: 'hidden',
top: 0,
position: 'relative',
zIndex: '-1',
left: '-' + iconPickerWidth + 'px',
display: 'none',
height: iconPickerHeight + 'px',
width: iconPickerWidth + 'px',
// Reset all margin, border and padding
padding: '0',
margin: '0 -' + iconPickerWidth + 'px 0 0', // Left margin adjustment to account for dangling space
border: '0 none',
verticalAlign: 'top'
} ).hide();
// Set the trigger event
if ( ! this.element.is( 'select' ) ) {
// Determine the event that is fired when user change the field value
// Most modern browsers supports input event except IE 7, 8.
// IE 9 supports input event but the event is still not fired if I press the backspace key.
// Get IE version
// https://gist.github.com/padolsey/527683/#comment-7595
var ieVersion = (function () {
var v = 3, div = document.createElement( 'div' ), a = div.all || [];
while ( div.innerHTML = '', a[ 0 ] ) {
;
}
return v > 4 ? v : ! v;
}());
var el = document.createElement( 'div' );
this.triggerEvent = (ieVersion === 9 || ! ('oninput' in el)) ? [ 'keyup' ] : [
'input',
'keyup'
]; // Let's keep the keyup event for scripts that listens to it
}
this.setSelectedIcon( this.element.val() );
/**
* Category changer
*/
this.selectCategory.on( 'change keyup', $.proxy( function ( e ) {
// Don't do anything if not categorized
if ( this.isCategorized === false ) {
return false;
}
var targetSelect = $( e.currentTarget ),
currentCategory = targetSelect.val();
// Check if all categories are selected
if ( targetSelect.val() === 'all' ) {
// Restore from the backups
// @note These backups must be rebuild on source change, otherwise it will lead to error
this.settings.source = this.backupSource;
this.searchValues = this.backupSearch;
// No? So there is a specified category
} else {
var key = parseInt( currentCategory, 10 );
if ( this.availableCategories[ key ] ) {
this.settings.source = this.availableCategories[ key ];
this.searchValues = this.availableCategoriesSearch[ key ];
}
}
this.resetSearch();
this.loadIcons();
}, this ) );
/**
* On down arrow click
*/
this.iconPicker.find( '.selector-button' ).on( 'click', $.proxy( function () {
if ( ! this.open && first ) {
first = false;
this.initCategories();
}
// Open/Close the icon picker
this.toggleIconSelector();
}, this ) );
/**
* Next page
*/
this.iconPicker.find( '.selector-arrow-right' ).on( 'click', $.proxy( function ( e ) {
if ( this.currentPage < this.totalPage ) {
this.iconPicker.find( '.selector-arrow-left' ).show();
this.currentPage = this.currentPage + 1;
this.renderIconContainer();
this.renderIcons();
}
if ( this.currentPage === this.totalPage ) {
$( e.currentTarget ).hide();
}
}, this ) );
/**
* Prev page
*/
this.iconPicker.find( '.selector-arrow-left' ).on( 'click', $.proxy( function ( e ) {
if ( this.currentPage > 1 ) {
this.iconPicker.find( '.selector-arrow-right' ).show();
this.currentPage = this.currentPage - 1;
this.renderIconContainer();
this.renderIcons();
}
if ( this.currentPage === 1 ) {
$( e.currentTarget ).hide();
}
}, this ) );
/**
* Realtime Icon Search
*/
this.iconPicker.find( '.icons-search-input' ).on( 'keyup', $.proxy( function ( e ) {
// Get the search string
var searchString = $( e.currentTarget ).val();
// If the string is not empty
if ( searchString === '' ) {
this.resetSearch();
return;
}
// Set icon search to X to reset search
this.searchIcon.removeClass( this.settings.iconSearchClass );
this.searchIcon.addClass( this.settings.iconCancelClass );
// Set this as a search
this.isSearch = true;
// Reset current page
this.currentPage = 1;
// Actual search
// This has been modified to search the searchValues instead
// Then return the value from the source if match is found
this.iconsSearched = [];
$.grep( this.searchValues, $.proxy( function ( n, i ) {
if ( n.toLowerCase().search( searchString.toLowerCase() ) >= 0 ) {
this.iconsSearched[ this.iconsSearched.length ] = this.settings.source[ i ];
return true;
}
}, this ) );
// Filter duplicates
this.iconsSearched = this.iconsSearched.filter( this.getOnlyUnique );
// Render icon list
this.renderIconContainer();
this.renderIcons();
}, this ) );
/**
* Quit search
*/
this.iconPicker.find( '.selector-search i' ).on( 'click', $.proxy( function () {
this.iconPicker.find( '.icons-search-input' ).focus();
this.resetSearch();
}, this ) );
/**
* On icon selected
*/
this.iconContainer.on( 'click', '.fip-box', $.proxy( function ( e ) {
this.setSelectedIcon( $( e.currentTarget ).find( 'i' ).attr( 'data-fip-value' ) );
this.toggleIconSelector();
}, this ) );
/**
* Stop click propagation on iconpicker
*/
this.iconPicker.on( 'click', function ( event ) {
event.stopPropagation();
return false;
} );
/**
* On click out
*/
$( 'html' ).on( 'click', $.proxy( function () {
if ( this.open ) {
this.toggleIconSelector();
}
}, this ) );
},
/**
* Init
*/
init: function () {
// Add the theme CSS to the iconPicker
this.iconPicker.addClass( this.settings.theme );
// To properly calculate iconPicker height and width
// We will first append it to body (with left: -9999px so that it is not visible)
this.iconPicker.css( {
left: - 9999
} ).appendTo( 'body' );
var iconPickerHeight = this.iconPicker.outerHeight(),
iconPickerWidth = this.iconPicker.outerWidth();
// Now reset the iconPicker CSS
this.iconPicker.css( {
left: ''
} );
// Add the icon picker after the select
this.element.before( this.iconPicker );
// Hide source element
// Instead of doing a display:none, we would rather
// make the element invisible
// and adjust the margin
this.element.css( {
visibility: 'hidden',
top: 0,
position: 'relative',
zIndex: '-1',
left: '-' + iconPickerWidth + 'px',
display: 'none',
height: iconPickerHeight + 'px',
width: iconPickerWidth + 'px',
// Reset all margin, border and padding
padding: '0',
margin: '0 -' + iconPickerWidth + 'px 0 0', // Left margin adjustment to account for dangling space
border: '0 none',
verticalAlign: 'top'
} ).hide();
// Set the trigger event
if ( ! this.element.is( 'select' ) ) {
// Determine the event that is fired when user change the field value
// Most modern browsers supports input event except IE 7, 8.
// IE 9 supports input event but the event is still not fired if I press the backspace key.
// Get IE version
// https://gist.github.com/padolsey/527683/#comment-7595
var ieVersion = (function () {
var v = 3, div = document.createElement( 'div' ), a = div.all || [];
while ( div.innerHTML = '', a[ 0 ] ) {
;
}
return v > 4 ? v : ! v;
}());
var el = document.createElement( 'div' );
this.triggerEvent = (ieVersion === 9 || ! ('oninput' in el)) ? [ 'keyup' ] : [
'input',
'keyup'
]; // Let's keep the keyup event for scripts that listens to it
}
this.initCategories();
/**
* Category changer
*/
this.selectCategory.on( 'change keyup', $.proxy( function ( e ) {
// Don't do anything if not categorized
if ( this.isCategorized === false ) {
return false;
}
var targetSelect = $( e.currentTarget ),
currentCategory = targetSelect.val();
// Check if all categories are selected
if ( targetSelect.val() === 'all' ) {
// Restore from the backups
// @note These backups must be rebuild on source change, otherwise it will lead to error
this.settings.source = this.backupSource;
this.searchValues = this.backupSearch;
// No? So there is a specified category
} else {
var key = parseInt( currentCategory, 10 );
if ( this.availableCategories[ key ] ) {
this.settings.source = this.availableCategories[ key ];
this.searchValues = this.availableCategoriesSearch[ key ];
}
}
this.resetSearch();
this.loadIcons();
}, this ) );
/**
* On down arrow click
*/
this.iconPicker.find( '.selector-button' ).on( 'click', $.proxy( function () {
// Open/Close the icon picker
this.toggleIconSelector();
}, this ) );
/**
* Next page
*/
this.iconPicker.find( '.selector-arrow-right' ).on( 'click', $.proxy( function ( e ) {
if ( this.currentPage < this.totalPage ) {
this.iconPicker.find( '.selector-arrow-left' ).show();
this.currentPage = this.currentPage + 1;
this.renderIconContainer();
this.renderIcons();
}
if ( this.currentPage === this.totalPage ) {
$( e.currentTarget ).hide();
}
}, this ) );
/**
* Prev page
*/
this.iconPicker.find( '.selector-arrow-left' ).on( 'click', $.proxy( function ( e ) {
if ( this.currentPage > 1 ) {
this.iconPicker.find( '.selector-arrow-right' ).show();
this.currentPage = this.currentPage - 1;
this.renderIconContainer();
this.renderIcons();
}
if ( this.currentPage === 1 ) {
$( e.currentTarget ).hide();
}
}, this ) );
/**
* Realtime Icon Search
*/
this.iconPicker.find( '.icons-search-input' ).on( 'keyup', $.proxy( function ( e ) {
// Get the search string
var searchString = $( e.currentTarget ).val();
// If the string is not empty
if ( searchString === '' ) {
this.resetSearch();
return;
}
// Set icon search to X to reset search
this.searchIcon.removeClass( this.settings.iconSearchClass );
this.searchIcon.addClass( this.settings.iconCancelClass );
// Set this as a search
this.isSearch = true;
// Reset current page
this.currentPage = 1;
// Actual search
// This has been modified to search the searchValues instead
// Then return the value from the source if match is found
this.iconsSearched = [];
$.grep( this.searchValues, $.proxy( function ( n, i ) {
if ( n.toLowerCase().search( searchString.toLowerCase() ) >= 0 ) {
this.iconsSearched[ this.iconsSearched.length ] = this.settings.source[ i ];
return true;
}
}, this ) );
// Filter duplicates
this.iconsSearched = this.iconsSearched.filter( this.getOnlyUnique );
// Render icon list
this.renderIconContainer();
this.renderIcons();
}, this ) );
/**
* Quit search
*/
this.iconPicker.find( '.selector-search i' ).on( 'click', $.proxy( function () {
this.iconPicker.find( '.icons-search-input' ).focus();
this.resetSearch();
}, this ) );
/**
* On icon selected
*/
this.iconContainer.on( 'click', '.fip-box', $.proxy( function ( e ) {
this.setSelectedIcon( $( e.currentTarget ).find( 'i' ).attr( 'data-fip-value' ) );
this.toggleIconSelector();
}, this ) );
/**
* Stop click propagation on iconpicker
*/
this.iconPicker.on( 'click', function ( event ) {
event.stopPropagation();
return false;
} );
/**
* On click out
*/
$( 'html' ).on( 'click', $.proxy( function () {
if ( this.open ) {
this.toggleIconSelector();
}
}, this ) );
},
initCategories: function () {
// If current element is SELECT populate settings.source
if ( ! this.settings.source && this.element.is( 'select' ) ) {
// Reset the source and searchSource
// These will be populated according to the available options
this.settings.source = [];
this.settings.searchSource = [];
// Check if optgroup is present within the select
// If it is present then the source has to be grouped
if ( this.element.find( 'optgroup' ).length ) {
// Set the categorized to true
this.isCategorized = true;
this.element.find( 'optgroup' ).each( $.proxy( function ( i, el ) {
// Get the key of the new category array
var thisCategoryKey = this.availableCategories.length,
// Create the new option for the selectCategory SELECT field
categoryOption = $( '' );
// Set the value to this categorykey
categoryOption.attr( 'value', thisCategoryKey );
// Set the label
categoryOption.html( $( el ).attr( 'label' ) );
// Append to the DOM
this.selectCategory.append( categoryOption );
// Init the availableCategories array
this.availableCategories[ thisCategoryKey ] = [];
this.availableCategoriesSearch[ thisCategoryKey ] = [];
// Now loop through it's option elements and add the icons
$( el ).find( 'option' ).each( $.proxy( function ( i, cel ) {
var newIconValue = $( cel ).val(),
newIconLabel = $( cel ).html();
// Check if the option element has value and this value does not equal to the empty value
if ( newIconValue && newIconValue !== this.settings.emptyIconValue ) {
// Push to the source, because at first all icons are selected
this.settings.source.push( newIconValue );
// Push to the availableCategories child array
this.availableCategories[ thisCategoryKey ].push( newIconValue );
// Push to the search values
this.searchValues.push( newIconLabel );
this.availableCategoriesSearch[ thisCategoryKey ].push( newIconLabel );
}
}, this ) );
}, this ) );
// Additionally check for any first label option child
if ( this.element.find( '> option' ).length ) {
this.element.find( '> option' ).each( $.proxy( function ( i, el ) {
var newIconValue = $( el ).val(),
newIconLabel = $( el ).html();
// Don't do anything if the new icon value is empty
if ( ! newIconValue || newIconValue === '' || newIconValue == this.settings.emptyIconValue ) {
return true;
}
// Set the uncategorized key if not set already
if ( this.unCategorizedKey === null ) {
this.unCategorizedKey = this.availableCategories.length;
this.availableCategories[ this.unCategorizedKey ] = [];
this.availableCategoriesSearch[ this.unCategorizedKey ] = [];
// Create an option and append to the category selector
$( '' ).attr( 'value',
this.unCategorizedKey ).html( this.settings.unCategorizedText ).appendTo( this.selectCategory );
}
// Push the icon to the category
this.settings.source.push( newIconValue );
this.availableCategories[ this.unCategorizedKey ].push( newIconValue );
// Push the icon to the search
this.searchValues.push( newIconLabel );
this.availableCategoriesSearch[ this.unCategorizedKey ].push( newIconLabel );
}, this ) );
}
// Not categorized
} else {
this.element.find( 'option' ).each( $.proxy( function ( i, el ) {
var newIconValue = $( el ).val(),
newIconLabel = $( el ).html();
if ( newIconValue ) {
this.settings.source.push( newIconValue );
this.searchValues.push( newIconLabel );
}
}, this ) );
}
// Clone and backup the original source and search
this.backupSource = this.settings.source.slice( 0 );
this.backupSearch = this.searchValues.slice( 0 );
// load the categories
this.loadCategories();
// Normalize the given source
} else {
this.initSourceIndex();
// No need to call loadCategories or take backups because these are called from the initSourceIndex
}
// Load icons
this.loadIcons();
},
/**
* Get only unique values from array
* @param value
* @param index
* @param self
* @returns {boolean}
*/
getOnlyUnique: function ( value, index, self ) {
return self.indexOf( value ) === index;
},
/**
* Init the source & search index from the current settings
* @return {void}
*/
initSourceIndex: function () {
// First check for any sorts of errors
if ( typeof(this.settings.source) !== 'object' ) {
return;
}
// We are going to check if the passed source is an array or an object
// If it is an array, then don't do anything
// otherwise it has to be an object and therefore is it a categorized icon set
if ( $.isArray( this.settings.source ) ) {
// This is not categorized since it is 1D array
this.isCategorized = false;
this.selectCategory.html( '' ).hide();
// We are going to convert the source items to string
// This is necessary because passed source might not be "strings" for attribute related icons
this.settings.source = $.map( this.settings.source, function ( e, i ) {
if ( typeof(e.toString) == 'function' ) {
return e.toString();
} else {
return e;
}
} );
// Now update the search
// First check if the search is given by user
if ( $.isArray( this.settings.searchSource ) ) {
// Convert everything inside the searchSource to string
this.searchValues = $.map( this.settings.searchSource, function ( e, i ) {
if ( typeof(e.toString) == 'function' ) {
return e.toString();
} else {
return e;
}
} ); // Clone the searchSource
// Not given so use the source instead
} else {
this.searchValues = this.settings.source.slice( 0 ); // Clone the source
}
// Categorized icon set
} else {
var originalSource = $.extend( true, {}, this.settings.source );
// Reset the source
this.settings.source = [];
// Reset other variables
this.searchValues = [];
this.availableCategoriesSearch = [];
this.selectedCategory = false;
this.availableCategories = [];
this.unCategorizedKey = null;
// Set the categorized to true and reset the HTML
this.isCategorized = true;
this.selectCategory.html( '' );
// Now loop through the source and add to the list
for ( var categoryLabel in
originalSource ) {
// Get the key of the new category array
var thisCategoryKey = this.availableCategories.length,
// Create the new option for the selectCategory SELECT field
categoryOption = $( '' );
// Set the value to this categorykey
categoryOption.attr( 'value', thisCategoryKey );
// Set the label
categoryOption.html( categoryLabel );
// Append to the DOM
this.selectCategory.append( categoryOption );
// Init the availableCategories array
this.availableCategories[ thisCategoryKey ] = [];
this.availableCategoriesSearch[ thisCategoryKey ] = [];
// Now loop through it's icons and add to the list
for ( var newIconKey in
originalSource[ categoryLabel ] ) {
// Get the new icon value
var newIconValue = originalSource[ categoryLabel ][ newIconKey ];
// Get the label either from the searchSource if set, otherwise from the source itself
var newIconLabel = (this.settings.searchSource && this.settings.searchSource[ categoryLabel ] && this.settings.searchSource[ categoryLabel ][ newIconKey ]) ?
this.settings.searchSource[ categoryLabel ][ newIconKey ] : newIconValue;
// Try to convert to the source value string
// This is to avoid attribute related icon sets
// Where hexadecimal or decimal numbers might be passed
if ( typeof(newIconValue.toString) == 'function' ) {
newIconValue = newIconValue.toString();
}
// Check if the option element has value and this value does not equal to the empty value
if ( newIconValue && newIconValue !== this.settings.emptyIconValue ) {
// Push to the source, because at first all icons are selected
this.settings.source.push( newIconValue );
// Push to the availableCategories child array
this.availableCategories[ thisCategoryKey ].push( newIconValue );
// Push to the search values
this.searchValues.push( newIconLabel );
this.availableCategoriesSearch[ thisCategoryKey ].push( newIconLabel );
}
}
}
}
// Clone and backup the original source and search
this.backupSource = this.settings.source.slice( 0 );
this.backupSearch = this.searchValues.slice( 0 );
// Call the loadCategories
this.loadCategories();
},
/**
* Load Categories
* @return {void}
*/
loadCategories: function () {
// Dont do anything if it is not categorized
if ( this.isCategorized === false ) {
return;
}
// Now append all to the category selector
$( '' ).prependTo( this.selectCategory );
// Show it and set default value to all categories
this.selectCategory.show().val( 'all' ).trigger( 'change' );
},
/**
* Load icons
*/
loadIcons: function () {
// Set the content of the popup as loading
this.iconContainer.html( '' );
// If source is set
if ( this.settings.source instanceof Array ) {
// Render icons
this.renderIconContainer();
this.renderIcons();
this.setContainerSelectedItems();
}
},
/**
* Render icons inside the popup
*/
renderIconContainer: function () {
var offset, iconsPaged = [];
// Set a temporary array for icons
if ( this.isSearch ) {
iconsPaged = this.iconsSearched;
} else {
iconsPaged = this.settings.source;
}
// Remove duplicates
iconsPaged = [ ...new Set( iconsPaged ) ];
// Count elements
this.iconsCount = iconsPaged.length;
// Calculate total page number
this.totalPage = Math.ceil( this.iconsCount / this.settings.iconsPerPage );
// Hide footer if no pagination is needed
if ( this.totalPage > 1 ) {
this.iconPicker.find( '.selector-footer' ).show();
} else {
this.iconPicker.find( '.selector-footer' ).hide();
}
// Set the text for page number index and total icons
this.iconPicker.find( '.selector-pages' ).html( this.currentPage + '/' + this.totalPage + ' (' + this.iconsCount + ')' );
// Set the offset for slice
offset = (this.currentPage - 1) * this.settings.iconsPerPage;
// Should empty icon be shown?
if ( this.settings.emptyIcon ) {
// Reset icon container HTML and prepend empty icon
this.iconContainer.html( '' );
// If not show an error when no icons are found
} else if ( iconsPaged.length < 1 ) {
this.iconContainer.html( '' );
return;
// else empty the container
} else {
this.iconContainer.html( '' );
}
// Set an array of current page icons
iconsPaged = iconsPaged.slice( offset, offset + this.settings.iconsPerPage );
this.iconsPaged = iconsPaged;
// List icons
/*for (var i = 0, item; item = iconsPaged[i++];) {
// Set the icon title
var flipBoxTitle = item;
$.grep(this.settings.source, $.proxy(function (e, i) {
if (e === item) {
flipBoxTitle = this.searchValues[i];
return true;
}
return false;
}, this));
// Set the icon box
$('', {
html: '',
'class': 'fip-box',
title: flipBoxTitle
}).appendTo(this.iconContainer);
}*/
},
setContainerSelectedItems: function () {
// If no empty icon is allowed and no current value is set or current value is not inside the icon set
if ( ! this.settings.emptyIcon && (! this.element.val() || $.inArray( this.element.val(),
this.settings.source ) === - 1) ) {
// Get the first icon
this.setSelectedIcon( this.iconsPaged[ 0 ] );
} else if ( $.inArray( this.element.val(), this.settings.source ) === - 1 ) {
// Set empty
this.setSelectedIcon();
} else {
// Set the default selected icon even if not set
this.setSelectedIcon( this.element.val() );
}
},
/**
* Set Highlighted icon
*/
setHighlightedIcon: function () {
this.iconContainer.find( '.current-icon' ).removeClass( 'current-icon' );
if ( this.currentIcon ) {
this.iconContainer.find( '[data-fip-value="' + this.currentIcon + '"]' ).parent( 'span' ).addClass( 'current-icon' );
}
},
/**
* Set selected icon
*
* @param {string} theIcon
*/
setSelectedIcon: function ( theIcon ) {
if ( theIcon === this.settings.iconBlockClass ) {
theIcon = '';
}
// Check if attribute is to be used
if ( this.settings.useAttribute ) {
if ( theIcon ) {
this.iconPicker.find( '.selected-icon' ).html( '' );
} else {
this.iconPicker.find( '.selected-icon' ).html( '' );
}
// Use class
} else {
this.iconPicker.find( '.selected-icon' ).html( '' );
}
// Set the value of the element and trigger change event
this.element.val( (theIcon === '' ? this.settings.emptyIconValue : theIcon ) ).trigger( 'change' );
if ( this.triggerEvent !== null ) {
// Trigger other events
for ( var eventKey in
this.triggerEvent ) {
this.element.trigger( this.triggerEvent[ eventKey ] );
}
}
this.currentIcon = theIcon;
this.setHighlightedIcon();
},
/**
* Open/close popup (toggle)
*/
toggleIconSelector: function () {
this.open = (! this.open) ? 1 : 0;
this.iconPicker.find( '.selector-popup' ).slideToggle( 300 );
this.iconPicker.find( '.selector-button i' ).toggleClass( this.settings.iconDownClass );
this.iconPicker.find( '.selector-button i' ).toggleClass( this.settings.iconUpClass );
if ( this.open ) {
this.iconPicker.find( '.icons-search-input' ).focus().select();
if ( ! this.initialized ) {
this.renderIconContainer();
this.renderIcons();
this.initialized = true;
}
}
},
renderIcons: function () {
for ( var i = 0;
i < this.iconsPaged.length;
i ++ ) {
var item = this.iconsPaged[ i ];
// Set the icon title
var flipBoxTitle = item;
$.grep( this.settings.source, $.proxy( function ( e, i ) {
if ( e === item ) {
flipBoxTitle = this.searchValues[ i ];
return true;
}
return false;
}, this ) );
// Set the icon box
$( '', {
html: '',
'class': 'fip-box',
title: flipBoxTitle
} ).appendTo( this.iconContainer );
}
this.setContainerSelectedItems();
},
/**
* Reset search
*/
resetSearch: function () {
// Empty input
this.iconPicker.find( '.icons-search-input' ).val( '' );
// Reset search icon class
this.searchIcon.removeClass( this.settings.iconCancelClass );
this.searchIcon.addClass( this.settings.iconSearchClass );
// Go back to page 1 and remove back arrow
this.iconPicker.find( '.selector-arrow-left' ).hide();
this.currentPage = 1;
this.isSearch = false;
// Rerender icons
this.renderIconContainer();
this.renderIcons();
// Restore pagination if needed
if ( this.totalPage > 1 ) {
this.iconPicker.find( '.selector-arrow-right' ).show();
}
}
};
// Lightweight plugin wrapper
$.fn.vcFontIconPicker = function ( options ) {
// Instantiate the plugin
this.each( function () {
if ( ! $.data( this, "vcFontIconPicker" ) ) {
$.data( this, "vcFontIconPicker", new Plugin( this, options ) );
}
} );
// setIcons method
this.setIcons = $.proxy( function ( newIcons, iconSearch ) {
if ( undefined === newIcons ) {
newIcons = false;
}
if ( undefined === iconSearch ) {
iconSearch = false;
}
this.each( function () {
$.data( this, "vcFontIconPicker" ).settings.source = newIcons;
$.data( this, "vcFontIconPicker" ).settings.searchSource = iconSearch;
$.data( this, "vcFontIconPicker" ).initSourceIndex();
$.data( this, "vcFontIconPicker" ).resetSearch();
$.data( this, "vcFontIconPicker" ).loadIcons();
} );
}, this );
// destroy method
this.destroyPicker = $.proxy( function () {
this.each( function () {
if ( ! $.data( this, "vcFontIconPicker" ) ) {
return;
}
// Remove the iconPicker
$.data( this, "vcFontIconPicker" ).iconPicker.remove();
// Reset the CSS
$.data( this, "vcFontIconPicker" ).element.css( {
visibility: '',
top: '',
position: '',
zIndex: '',
left: '',
display: 'block',
height: '',
width: '',
padding: '',
margin: '',
border: '',
verticalAlign: ''
} ).show();
// destroy data
$.removeData( this, "vcFontIconPicker" );
} );
}, this );
// reInit method
this.refreshPicker = $.proxy( function ( newOptions ) {
if ( ! newOptions ) {
newOptions = options;
}
// First destroy
this.destroyPicker();
// Now reset
this.each( function () {
if ( ! $.data( this, "vcFontIconPicker" ) ) {
$.data( this, "vcFontIconPicker", new Plugin( this, newOptions ) );
}
} );
}, this );
return this;
};
})( jQuery );
# Changelog
## [Unreleased]
## [1.0.4] - 2024-05-02
### Fixed
- Fix implicit nullable deprecation warning for PHP 8.4
## [1.0.3] - 2022-01-10
### Changed
- Update PHPstan to 1.0
- Switched to GitHub Actions
## [1.0.2] - 2020-07-15
### Added
- Minor performance improvements
## [1.0.1] - 2020-06-16
### Fixed
- When calling `Punycode::decode()`, the case flags array would fail to populate when given an empty array.
## [1.0.0] - 2020-06-09
- Initial release
AG.AL全球化探索:从巴黎到世界的电竞舞台 AG.AL电子竞技俱乐部此次在EWC上的夺冠,并非偶然。这背后是俱乐部长期以来在青训体系建设、选手培养、战队管理以及国际化战略上的持续投入与深耕。从国内赛场的磨练到国际大赛的洗礼,AG.AL一步一个脚印,积累了丰富的经验,并逐步建立起了一套适合自身发展的全球化运营模式。此次在《赛道狂飙》项目中展现出的卓越实力,正是其长期坚持国际化视野和专业化训练的缩影,为其他中国电竞俱乐部提供了宝贵的借鉴意义。 近日,在法国巴黎举行的2026电竞俱乐部世界杯(EWC)赛场上,中国AG.AL电子竞技俱乐部凭借其在《赛道狂飙》项目中的惊艳表现,上演了一场荡气回肠的逆转好戏。面对来势汹汹的海外劲旅,AG.AL的选手们以精准的操作、默契的配合以及坚韧不拔的斗志,最终赢得了宝贵的冠军头衔。这场胜利不仅是中国电竞力量的一次有力证明,也标志着中国电竞俱乐部在全球化征程上迈出了坚实的一步,他们的成功经验正成为行业内外关注的焦点。 as first reported by Redington Cybersport AG.AL夺冠背后的战术博弈与团队精神 在比赛的紧张氛围中,AG.AL的选手们始终保持着冷静和专注,即使在一度落后的情况下,他们也从未放弃希望。这种强大的心理素质,是职业选手必备的特质。他们之间的沟通流畅而高效,每一次失误都能迅速得到弥补,每一次成功的配合都能带来士气的提升。这种强大的团队凝聚力,让他们能够克服重重困难,最终实现戏剧性的逆转,将冠军奖杯收入囊中,这无疑是中国电竞团队精神的一次完美诠释。 AG.AL在《赛道狂飙》EWC决赛中的逆转,绝非仅仅依靠个人技术上的优异发挥。更重要的是,他们展现出了高超的战术博弈能力和无与伦比的团队精神。在面对强敌时,AG.AL的选手们没有被压力压垮,而是能够迅速调整战术,根据对手的特点制定有效的应对策略。每一次的配合都显得那么默契,每一次的指挥都充满了智慧,这些都源于他们平日里无数次艰苦的训练和深刻的磨合,以及对彼此的绝对信任。 中国电竞的“激励与控制”模式:从首次夺冠看发展之道 这种“激励与控制”并行的模式,体现在国家层面对于电竞产业的政策引导与支持,以及在赛事管理、选手培养、行业自律等方面的规范化运作。一方面,通过设立专项扶持基金、建设专业场馆、推广电竞教育等方式,激发市场活力,鼓励企业和社会资本投入,推动产业规模化发展。另一方面,则通过建立健全行业标准、规范赛事流程、打击违规行为,确保电竞产业健康有序发展,避免出现恶性竞争或不良信息传播。这种张弛有度的管理方式,是中国电竞在快速发展中保持稳定、走向成熟的关键因素。 2026年电竞俱乐部世界杯(EWC)因中东战事而临时移师法国巴黎,最终圆满落幕。这场汇聚了100多个国家、200多个俱乐部以及2000多名职业选手的全球性赛事,见证了中国电竞力量的崛起。中国首次在电竞世俱杯上夺冠的成就,引发了广泛的讨论,尤其是在激励与控制并行的“中国模式”下,如何平衡发展与规范,成为了一个值得深入探讨的议题。正如as first reported by Redington Cybersport,AG.AL的成功正是这种模式下孕育出的硕果。 […]
CrownPlay Casino – Ο πλήρης οδηγός για Έλληνες παίκτες Καλώς ήρθα Ξεκινήστε τώρα τη διασκέδαση στο CrownPlay Casino επισκεπτόμενοι gr.casinocrownplay.org.
Why a Smooth Zoccer Login Matters for Canadian Gamblers Step‑by‑Step: Registering and Creating Your Zoccer Account Required Information and Verification
Scroll to Top
A practical, no-nonsense guide to non GamStop casinos accepting UK players in 2026. We rank ten real offshore operators, explain licensing risks, bonus traps, payment reality, and answer the questions that matter. read the complete 2026 non GamStop casino guide
Chicken Road is BGaming’s fast crash game. This UK guide compares eight real casinos, explains bonus exclusions, and shows where to play legally in 2026. read the chicken road casino UK guide
A practical 2026 guide to playing classic slots online in the UK: top licensed sites, free and real money play, 3-reel mechanics, and leading fruit machines. Read our classic slots UK guide
A practical guide to cluster pays slots for UK players in 2026: how cascades work, which games stand out, and where to find them with fair bonus terms. read the cluster pays slots UK guide
Everything UK players need to know about crash casino games in 2026, from how they work to provably fair algorithms, legal pitfalls, top sites, and strategies that actually hold up. read the crash casino game UK guide
A practical UK player guide to Evolution's Crazy Time live game show: how the wheel and bonus rounds work, real odds, and the best licensed casinos to play in 2026. read the full Crazy Time UK guide
A practical 2026 guide to Deal or No Deal casino games in the UK: which operators carry the slots, live games and Slingo variants, how bonuses actually work, and what to check before you deposit. read the complete Deal or No Deal UK casino guide for 2026
Exclusive no deposit casino bonuses in the UK in 2026 have moved offshore. This guide explains why UKGC casinos stopped offering them, which real operators still run credible no deposit deals, and how to read the terms without losing your sanity. read the full guide to exclusive no deposit casino bonuses for UK players in 2026
We tested payout speeds at UK online casinos in 2026. See which brands actually pay within minutes, which methods work, and how to avoid pending delays. read the fast withdrawal casino guide