fuelux
Version:
Base Fuel UX styles and controls
1,783 lines (1,423 loc) • 268 kB
JavaScript
/*!
* Fuel UX v3.16.4
* Copyright 2012-2018 ExactTarget
* Licensed under the BSD-3-Clause license (https://github.com/ExactTarget/fuelux/blob/master/LICENSE)
*/
// For more information on UMD visit: https://github.com/umdjs/umd/
( function( factory ) {
if ( typeof define === 'function' && define.amd ) {
define( [ 'jquery', 'bootstrap' ], factory );
} else {
factory( jQuery );
}
}( function( jQuery ) {
if ( typeof jQuery === 'undefined' ) {
throw new Error( 'Fuel UX\'s JavaScript requires jQuery' )
}
if ( typeof jQuery.fn.dropdown === 'undefined' || typeof jQuery.fn.collapse === 'undefined' ) {
throw new Error( 'Fuel UX\'s JavaScript requires Bootstrap' )
}
( function( $ ) {
/* global jQuery:true */
/*
* Fuel UX Checkbox
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN MODULE CODE HERE --
var old = $.fn.checkbox;
// CHECKBOX CONSTRUCTOR AND PROTOTYPE
var logError = function logError( error ) {
if ( window && window.console && window.console.error ) {
window.console.error( error );
}
};
var Checkbox = function Checkbox( element, options ) {
this.options = $.extend( {}, $.fn.checkbox.defaults, options );
var $element = $( element );
if ( element.tagName.toLowerCase() !== 'label' ) {
logError( 'Checkbox must be initialized on the `label` that wraps the `input` element. See https://github.com/ExactTarget/fuelux/blob/master/reference/markup/checkbox.html for example of proper markup. Call `.checkbox()` on the `<label>` not the `<input>`' );
return;
}
// cache elements
this.$label = $element;
this.$chk = this.$label.find( 'input[type="checkbox"]' );
this.$container = $element.parent( '.checkbox' ); // the container div
if ( !this.options.ignoreVisibilityCheck && this.$chk.css( 'visibility' ).match( /hidden|collapse/ ) ) {
logError( 'For accessibility reasons, in order for tab and space to function on checkbox, checkbox `<input />`\'s `visibility` must not be set to `hidden` or `collapse`. See https://github.com/ExactTarget/fuelux/pull/1996 for more details.' );
}
// determine if a toggle container is specified
var containerSelector = this.$chk.attr( 'data-toggle' );
this.$toggleContainer = $( containerSelector );
// handle internal events
this.$chk.on( 'change', $.proxy( this.itemchecked, this ) );
// set default state
this.setInitialState();
};
Checkbox.prototype = {
constructor: Checkbox,
setInitialState: function setInitialState() {
var $chk = this.$chk;
// get current state of input
var checked = $chk.prop( 'checked' );
var disabled = $chk.prop( 'disabled' );
// sync label class with input state
this.setCheckedState( $chk, checked );
this.setDisabledState( $chk, disabled );
},
setCheckedState: function setCheckedState( element, checked ) {
var $chk = element;
var $lbl = this.$label;
var $containerToggle = this.$toggleContainer;
if ( checked ) {
$chk.prop( 'checked', true );
$lbl.addClass( 'checked' );
$containerToggle.removeClass( 'hide hidden' );
$lbl.trigger( 'checked.fu.checkbox' );
} else {
$chk.prop( 'checked', false );
$lbl.removeClass( 'checked' );
$containerToggle.addClass( 'hidden' );
$lbl.trigger( 'unchecked.fu.checkbox' );
}
$lbl.trigger( 'changed.fu.checkbox', checked );
},
setDisabledState: function setDisabledState( element, disabled ) {
var $chk = $( element );
var $lbl = this.$label;
if ( disabled ) {
$chk.prop( 'disabled', true );
$lbl.addClass( 'disabled' );
$lbl.trigger( 'disabled.fu.checkbox' );
} else {
$chk.prop( 'disabled', false );
$lbl.removeClass( 'disabled' );
$lbl.trigger( 'enabled.fu.checkbox' );
}
return $chk;
},
itemchecked: function itemchecked( evt ) {
var $chk = $( evt.target );
var checked = $chk.prop( 'checked' );
this.setCheckedState( $chk, checked );
},
toggle: function toggle() {
var checked = this.isChecked();
if ( checked ) {
this.uncheck();
} else {
this.check();
}
},
check: function check() {
this.setCheckedState( this.$chk, true );
},
uncheck: function uncheck() {
this.setCheckedState( this.$chk, false );
},
isChecked: function isChecked() {
var checked = this.$chk.prop( 'checked' );
return checked;
},
enable: function enable() {
this.setDisabledState( this.$chk, false );
},
disable: function disable() {
this.setDisabledState( this.$chk, true );
},
destroy: function destroy() {
this.$label.remove();
return this.$label[ 0 ].outerHTML;
}
};
Checkbox.prototype.getValue = Checkbox.prototype.isChecked;
// CHECKBOX PLUGIN DEFINITION
$.fn.checkbox = function checkbox( option ) {
var args = Array.prototype.slice.call( arguments, 1 );
var methodReturn;
var $set = this.each( function applyData() {
var $this = $( this );
var data = $this.data( 'fu.checkbox' );
var options = typeof option === 'object' && option;
if ( !data ) {
$this.data( 'fu.checkbox', ( data = new Checkbox( this, options ) ) );
}
if ( typeof option === 'string' ) {
methodReturn = data[ option ].apply( data, args );
}
} );
return ( methodReturn === undefined ) ? $set : methodReturn;
};
$.fn.checkbox.defaults = {
ignoreVisibilityCheck: false
};
$.fn.checkbox.Constructor = Checkbox;
$.fn.checkbox.noConflict = function noConflict() {
$.fn.checkbox = old;
return this;
};
// DATA-API
$( document ).on( 'mouseover.fu.checkbox.data-api', '[data-initialize=checkbox]', function initializeCheckboxes( e ) {
var $control = $( e.target );
if ( !$control.data( 'fu.checkbox' ) ) {
$control.checkbox( $control.data() );
}
} );
// Must be domReady for AMD compatibility
$( function onReadyInitializeCheckboxes() {
$( '[data-initialize=checkbox]' ).each( function initializeCheckbox() {
var $this = $( this );
if ( !$this.data( 'fu.checkbox' ) ) {
$this.checkbox( $this.data() );
}
} );
} );
} )( jQuery );
( function( $ ) {
/* global jQuery:true */
/*
* Fuel UX Combobox
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN MODULE CODE HERE --
var old = $.fn.combobox;
// COMBOBOX CONSTRUCTOR AND PROTOTYPE
var Combobox = function( element, options ) {
this.$element = $( element );
this.options = $.extend( {}, $.fn.combobox.defaults, options );
this.$dropMenu = this.$element.find( '.dropdown-menu' );
this.$input = this.$element.find( 'input' );
this.$button = this.$element.find( '.btn' );
this.$inputGroupBtn = this.$element.find( '.input-group-btn' );
this.$element.on( 'click.fu.combobox', 'a', $.proxy( this.itemclicked, this ) );
this.$element.on( 'change.fu.combobox', 'input', $.proxy( this.inputchanged, this ) );
this.$element.on( 'shown.bs.dropdown', $.proxy( this.menuShown, this ) );
this.$input.on( 'keyup.fu.combobox', $.proxy( this.keypress, this ) );
// set default selection
this.setDefaultSelection();
// if dropdown is empty, disable it
var items = this.$dropMenu.children( 'li' );
if ( items.length === 0 ) {
this.$button.addClass( 'disabled' );
}
// filter on load in case the first thing they do is press navigational key to pop open the menu
if ( this.options.filterOnKeypress ) {
this.options.filter( this.$dropMenu.find( 'li' ), this.$input.val(), this );
}
};
Combobox.prototype = {
constructor: Combobox,
destroy: function() {
this.$element.remove();
// remove any external bindings
// [none]
// set input value attrbute in markup
this.$element.find( 'input' ).each( function() {
$( this ).attr( 'value', $( this ).val() );
} );
// empty elements to return to original markup
// [none]
return this.$element[ 0 ].outerHTML;
},
doSelect: function( $item ) {
if ( typeof $item[ 0 ] !== 'undefined' ) {
// remove selection from old item, may result in remove and
// re-addition of class if item is the same
this.$element.find( 'li.selected:first' ).removeClass( 'selected' );
// add selection to new item
this.$selectedItem = $item;
this.$selectedItem.addClass( 'selected' );
// update input
this.$input.val( this.$selectedItem.text().trim() );
} else {
// this is a custom input, not in the menu
this.$selectedItem = null;
this.$element.find( 'li.selected:first' ).removeClass( 'selected' );
}
},
clearSelection: function() {
this.$selectedItem = null;
this.$input.val( '' );
this.$dropMenu.find( 'li' ).removeClass( 'selected' );
},
menuShown: function() {
if ( this.options.autoResizeMenu ) {
this.resizeMenu();
}
},
resizeMenu: function() {
var width = this.$element.outerWidth();
this.$dropMenu.outerWidth( width );
},
selectedItem: function() {
var item = this.$selectedItem;
var data = {};
if ( item ) {
var txt = this.$selectedItem.text().trim();
data = $.extend( {
text: txt
}, this.$selectedItem.data() );
} else {
data = {
text: this.$input.val().trim(),
notFound: true
};
}
return data;
},
selectByText: function( text ) {
var $item = $( [] );
this.$element.find( 'li' ).each( function() {
if ( ( this.textContent || this.innerText || $( this ).text() || '' ).trim().toLowerCase() === ( text || '' ).trim().toLowerCase() ) {
$item = $( this );
return false;
}
} );
this.doSelect( $item );
},
selectByValue: function( value ) {
var selector = 'li[data-value="' + value + '"]';
this.selectBySelector( selector );
},
selectByIndex: function( index ) {
// zero-based index
var selector = 'li:eq(' + index + ')';
this.selectBySelector( selector );
},
selectBySelector: function( selector ) {
var $item = this.$element.find( selector );
this.doSelect( $item );
},
setDefaultSelection: function() {
var selector = 'li[data-selected=true]:first';
var item = this.$element.find( selector );
if ( item.length > 0 ) {
// select by data-attribute
this.selectBySelector( selector );
item.removeData( 'selected' );
item.removeAttr( 'data-selected' );
}
},
enable: function() {
this.$element.removeClass( 'disabled' );
this.$input.removeAttr( 'disabled' );
this.$button.removeClass( 'disabled' );
},
disable: function() {
this.$element.addClass( 'disabled' );
this.$input.attr( 'disabled', true );
this.$button.addClass( 'disabled' );
},
itemclicked: function( e ) {
this.$selectedItem = $( e.target ).parent();
// set input text and trigger input change event marked as synthetic
this.$input.val( this.$selectedItem.text().trim() ).trigger( 'change', {
synthetic: true
} );
// pass object including text and any data-attributes
// to onchange event
var data = this.selectedItem();
// trigger changed event
this.$element.trigger( 'changed.fu.combobox', data );
e.preventDefault();
// return focus to control after selecting an option
this.$element.find( '.dropdown-toggle' ).focus();
},
keypress: function( e ) {
var ENTER = 13;
//var TAB = 9;
var ESC = 27;
var LEFT = 37;
var UP = 38;
var RIGHT = 39;
var DOWN = 40;
var IS_NAVIGATIONAL = (
e.which === UP ||
e.which === DOWN ||
e.which === LEFT ||
e.which === RIGHT
);
if ( this.options.showOptionsOnKeypress && !this.$inputGroupBtn.hasClass( 'open' ) ) {
this.$button.dropdown( 'toggle' );
this.$input.focus();
}
if ( e.which === ENTER ) {
e.preventDefault();
var selected = this.$dropMenu.find( 'li.selected' ).text().trim();
if ( selected.length > 0 ) {
this.selectByText( selected );
} else {
this.selectByText( this.$input.val() );
}
this.$inputGroupBtn.removeClass( 'open' );
} else if ( e.which === ESC ) {
e.preventDefault();
this.clearSelection();
this.$inputGroupBtn.removeClass( 'open' );
} else if ( this.options.showOptionsOnKeypress ) {
if ( e.which === DOWN || e.which === UP ) {
e.preventDefault();
var $selected = this.$dropMenu.find( 'li.selected' );
if ( $selected.length > 0 ) {
if ( e.which === DOWN ) {
$selected = $selected.next( ':not(.hidden)' );
} else {
$selected = $selected.prev( ':not(.hidden)' );
}
}
if ( $selected.length === 0 ) {
if ( e.which === DOWN ) {
$selected = this.$dropMenu.find( 'li:not(.hidden):first' );
} else {
$selected = this.$dropMenu.find( 'li:not(.hidden):last' );
}
}
this.doSelect( $selected );
}
}
// Avoid filtering on navigation key presses
if ( this.options.filterOnKeypress && !IS_NAVIGATIONAL ) {
this.options.filter( this.$dropMenu.find( 'li' ), this.$input.val(), this );
}
this.previousKeyPress = e.which;
},
inputchanged: function( e, extra ) {
var val = $( e.target ).val();
// skip processing for internally-generated synthetic event
// to avoid double processing
if ( extra && extra.synthetic ) {
this.selectByText( val );
return;
}
this.selectByText( val );
// find match based on input
// if no match, pass the input value
var data = this.selectedItem();
if ( data.text.length === 0 ) {
data = {
text: val
};
}
// trigger changed event
this.$element.trigger( 'changed.fu.combobox', data );
}
};
Combobox.prototype.getValue = Combobox.prototype.selectedItem;
// COMBOBOX PLUGIN DEFINITION
$.fn.combobox = function( option ) {
var args = Array.prototype.slice.call( arguments, 1 );
var methodReturn;
var $set = this.each( function() {
var $this = $( this );
var data = $this.data( 'fu.combobox' );
var options = typeof option === 'object' && option;
if ( !data ) {
$this.data( 'fu.combobox', ( data = new Combobox( this, options ) ) );
}
if ( typeof option === 'string' ) {
methodReturn = data[ option ].apply( data, args );
}
} );
return ( methodReturn === undefined ) ? $set : methodReturn;
};
$.fn.combobox.defaults = {
autoResizeMenu: true,
filterOnKeypress: false,
showOptionsOnKeypress: false,
filter: function filter( list, predicate, self ) {
var visible = 0;
self.$dropMenu.find( '.empty-indicator' ).remove();
list.each( function( i ) {
var $li = $( this );
var text = $( this ).text().trim();
$li.removeClass();
if ( text === predicate ) {
$li.addClass( 'text-success' );
visible++;
} else if ( text.substr( 0, predicate.length ) === predicate ) {
$li.addClass( 'text-info' );
visible++;
} else {
$li.addClass( 'hidden' );
}
} );
if ( visible === 0 ) {
self.$dropMenu.append( '<li class="empty-indicator text-muted"><em>No Matches</em></li>' );
}
}
};
$.fn.combobox.Constructor = Combobox;
$.fn.combobox.noConflict = function() {
$.fn.combobox = old;
return this;
};
// DATA-API
$( document ).on( 'mousedown.fu.combobox.data-api', '[data-initialize=combobox]', function( e ) {
var $control = $( e.target ).closest( '.combobox' );
if ( !$control.data( 'fu.combobox' ) ) {
$control.combobox( $control.data() );
}
} );
// Must be domReady for AMD compatibility
$( function() {
$( '[data-initialize=combobox]' ).each( function() {
var $this = $( this );
if ( !$this.data( 'fu.combobox' ) ) {
$this.combobox( $this.data() );
}
} );
} );
} )( jQuery );
( function( $ ) {
/* global jQuery:true */
/*
* Fuel UX Datepicker
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN MODULE CODE HERE --
var INVALID_DATE = 'Invalid Date';
var MOMENT_NOT_AVAILABLE = 'moment.js is not available so you cannot use this function';
var datepickerStack = [];
var moment = false;
var old = $.fn.datepicker;
var requestedMoment = false;
var runStack = function() {
var i, l;
requestedMoment = true;
for ( i = 0, l = datepickerStack.length; i < l; i++ ) {
datepickerStack[ i ].init.call( datepickerStack[ i ].scope );
}
datepickerStack = [];
};
//only load moment if it's there. otherwise we'll look for it in window.moment
if ( typeof define === 'function' && define.amd ) { //check if AMD is available
require( [ 'moment' ], function( amdMoment ) {
moment = amdMoment;
runStack();
}, function( err ) {
var failedId = err.requireModules && err.requireModules[ 0 ];
if ( failedId === 'moment' ) {
runStack();
}
} );
} else {
runStack();
}
// DATEPICKER CONSTRUCTOR AND PROTOTYPE
var Datepicker = function( element, options ) {
this.$element = $( element );
this.options = $.extend( true, {}, $.fn.datepicker.defaults, options );
this.$calendar = this.$element.find( '.datepicker-calendar' );
this.$days = this.$calendar.find( '.datepicker-calendar-days' );
this.$header = this.$calendar.find( '.datepicker-calendar-header' );
this.$headerTitle = this.$header.find( '.title' );
this.$input = this.$element.find( 'input' );
this.$inputGroupBtn = this.$element.find( '.input-group-btn' );
this.$wheels = this.$element.find( '.datepicker-wheels' );
this.$wheelsMonth = this.$element.find( '.datepicker-wheels-month' );
this.$wheelsYear = this.$element.find( '.datepicker-wheels-year' );
this.artificialScrolling = false;
this.formatDate = this.options.formatDate || this.formatDate;
this.inputValue = null;
this.moment = false;
this.momentFormat = null;
this.parseDate = this.options.parseDate || this.parseDate;
this.preventBlurHide = false;
this.restricted = this.options.restricted || [];
this.restrictedParsed = [];
this.restrictedText = this.options.restrictedText;
this.sameYearOnly = this.options.sameYearOnly;
this.selectedDate = null;
this.yearRestriction = null;
this.$calendar.find( '.datepicker-today' ).on( 'click.fu.datepicker', $.proxy( this.todayClicked, this ) );
this.$days.on( 'click.fu.datepicker', 'tr td button', $.proxy( this.dateClicked, this ) );
this.$header.find( '.next' ).on( 'click.fu.datepicker', $.proxy( this.next, this ) );
this.$header.find( '.prev' ).on( 'click.fu.datepicker', $.proxy( this.prev, this ) );
this.$headerTitle.on( 'click.fu.datepicker', $.proxy( this.titleClicked, this ) );
this.$input.on( 'change.fu.datepicker', $.proxy( this.inputChanged, this ) );
this.$input.on( 'mousedown.fu.datepicker', $.proxy( this.showDropdown, this ) );
this.$inputGroupBtn.on( 'hidden.bs.dropdown', $.proxy( this.hide, this ) );
this.$inputGroupBtn.on( 'shown.bs.dropdown', $.proxy( this.show, this ) );
this.$wheels.find( '.datepicker-wheels-back' ).on( 'click.fu.datepicker', $.proxy( this.backClicked, this ) );
this.$wheels.find( '.datepicker-wheels-select' ).on( 'click.fu.datepicker', $.proxy( this.selectClicked, this ) );
this.$wheelsMonth.on( 'click.fu.datepicker', 'ul button', $.proxy( this.monthClicked, this ) );
this.$wheelsYear.on( 'click.fu.datepicker', 'ul button', $.proxy( this.yearClicked, this ) );
this.$wheelsYear.find( 'ul' ).on( 'scroll.fu.datepicker', $.proxy( this.onYearScroll, this ) );
var init = function() {
if ( this.checkForMomentJS() ) {
moment = moment || window.moment; // need to pull in the global moment if they didn't do it via require
this.moment = true;
this.momentFormat = this.options.momentConfig.format;
this.setCulture( this.options.momentConfig.culture );
// support moment with lang (< v2.8) or locale
moment.locale = moment.locale || moment.lang;
}
this.setRestrictedDates( this.restricted );
if ( !this.setDate( this.options.date ) ) {
this.$input.val( '' );
this.inputValue = this.$input.val();
}
if ( this.sameYearOnly ) {
this.yearRestriction = ( this.selectedDate ) ? this.selectedDate.getFullYear() : new Date().getFullYear();
}
};
if ( requestedMoment ) {
init.call( this );
} else {
datepickerStack.push( {
init: init,
scope: this
} );
}
};
Datepicker.prototype = {
constructor: Datepicker,
backClicked: function() {
this.changeView( 'calendar' );
},
changeView: function( view, date ) {
if ( view === 'wheels' ) {
this.$calendar.hide().attr( 'aria-hidden', 'true' );
this.$wheels.show().removeAttr( 'aria-hidden', '' );
if ( date ) {
this.renderWheel( date );
}
} else {
this.$wheels.hide().attr( 'aria-hidden', 'true' );
this.$calendar.show().removeAttr( 'aria-hidden', '' );
if ( date ) {
this.renderMonth( date );
}
}
},
checkForMomentJS: function() {
if (
( $.isFunction( window.moment ) || ( typeof moment !== 'undefined' && $.isFunction( moment ) ) ) &&
$.isPlainObject( this.options.momentConfig ) &&
( typeof this.options.momentConfig.culture === 'string' && typeof this.options.momentConfig.format === 'string' )
) {
return true;
} else {
return false;
}
},
dateClicked: function( e ) {
var $td = $( e.currentTarget ).parents( 'td:first' );
var date;
if ( $td.hasClass( 'restricted' ) ) {
return;
}
this.$days.find( 'td.selected' ).removeClass( 'selected' );
$td.addClass( 'selected' );
date = new Date( $td.attr( 'data-year' ), $td.attr( 'data-month' ), $td.attr( 'data-date' ) );
this.selectedDate = date;
this.$input.val( this.formatDate( date ) );
this.inputValue = this.$input.val();
this.hide();
this.$input.focus();
this.$element.trigger( 'dateClicked.fu.datepicker', date );
},
destroy: function() {
this.$element.remove();
// any external bindings
// [none]
// empty elements to return to original markup
this.$days.find( 'tbody' ).empty();
this.$wheelsYear.find( 'ul' ).empty();
return this.$element[ 0 ].outerHTML;
},
disable: function() {
this.$element.addClass( 'disabled' );
this.$element.find( 'input, button' ).attr( 'disabled', 'disabled' );
this.$inputGroupBtn.removeClass( 'open' );
},
enable: function() {
this.$element.removeClass( 'disabled' );
this.$element.find( 'input, button' ).removeAttr( 'disabled' );
},
formatDate: function( date ) {
var padTwo = function( value ) {
var s = '0' + value;
return s.substr( s.length - 2 );
};
if ( this.moment ) {
return moment( date ).format( this.momentFormat );
} else {
return padTwo( date.getMonth() + 1 ) + '/' + padTwo( date.getDate() ) + '/' + date.getFullYear();
}
},
getCulture: function() {
if ( this.moment ) {
return moment.locale();
} else {
throw MOMENT_NOT_AVAILABLE;
}
},
getDate: function() {
return ( !this.selectedDate ) ? new Date( NaN ) : this.selectedDate;
},
getFormat: function() {
if ( this.moment ) {
return this.momentFormat;
} else {
throw MOMENT_NOT_AVAILABLE;
}
},
getFormattedDate: function() {
return ( !this.selectedDate ) ? INVALID_DATE : this.formatDate( this.selectedDate );
},
getRestrictedDates: function() {
return this.restricted;
},
inputChanged: function() {
var inputVal = this.$input.val();
var date;
if ( inputVal !== this.inputValue ) {
date = this.setDate( inputVal );
if ( date === null ) {
this.$element.trigger( 'inputParsingFailed.fu.datepicker', inputVal );
} else if ( date === false ) {
this.$element.trigger( 'inputRestrictedDate.fu.datepicker', date );
} else {
this.$element.trigger( 'changed.fu.datepicker', date );
}
}
},
show: function() {
var date = ( this.selectedDate ) ? this.selectedDate : new Date();
this.changeView( 'calendar', date );
this.$inputGroupBtn.addClass( 'open' );
this.$element.trigger( 'shown.fu.datepicker' );
},
showDropdown: function( e ) { //input mousedown handler, name retained for legacy support of showDropdown
if ( !this.$input.is( ':focus' ) && !this.$inputGroupBtn.hasClass( 'open' ) ) {
this.show();
}
},
hide: function() {
this.$inputGroupBtn.removeClass( 'open' );
this.$element.trigger( 'hidden.fu.datepicker' );
},
hideDropdown: function() { //for legacy support of hideDropdown
this.hide();
},
isInvalidDate: function( date ) {
var dateString = date.toString();
if ( dateString === INVALID_DATE || dateString === 'NaN' ) {
return true;
}
return false;
},
isRestricted: function( date, month, year ) {
var restricted = this.restrictedParsed;
var i, from, l, to;
if ( this.sameYearOnly && this.yearRestriction !== null && year !== this.yearRestriction ) {
return true;
}
for ( i = 0, l = restricted.length; i < l; i++ ) {
from = restricted[ i ].from;
to = restricted[ i ].to;
if (
( year > from.year || ( year === from.year && month > from.month ) || ( year === from.year && month === from.month && date >= from.date ) ) &&
( year < to.year || ( year === to.year && month < to.month ) || ( year === to.year && month === to.month && date <= to.date ) )
) {
return true;
}
}
return false;
},
monthClicked: function( e ) {
this.$wheelsMonth.find( '.selected' ).removeClass( 'selected' );
$( e.currentTarget ).parent().addClass( 'selected' );
},
next: function() {
var month = this.$headerTitle.attr( 'data-month' );
var year = this.$headerTitle.attr( 'data-year' );
month++;
if ( month > 11 ) {
if ( this.sameYearOnly ) {
return;
}
month = 0;
year++;
}
this.renderMonth( new Date( year, month, 1 ) );
},
onYearScroll: function( e ) {
if ( this.artificialScrolling ) {
return;
}
var $yearUl = $( e.currentTarget );
var height = ( $yearUl.css( 'box-sizing' ) === 'border-box' ) ? $yearUl.outerHeight() : $yearUl.height();
var scrollHeight = $yearUl.get( 0 ).scrollHeight;
var scrollTop = $yearUl.scrollTop();
var bottomPercentage = ( height / ( scrollHeight - scrollTop ) ) * 100;
var topPercentage = ( scrollTop / scrollHeight ) * 100;
var i, start;
if ( topPercentage < 5 ) {
start = parseInt( $yearUl.find( 'li:first' ).attr( 'data-year' ), 10 );
for ( i = ( start - 1 ); i > ( start - 11 ); i-- ) {
$yearUl.prepend( '<li data-year="' + i + '"><button type="button">' + i + '</button></li>' );
}
this.artificialScrolling = true;
$yearUl.scrollTop( ( $yearUl.get( 0 ).scrollHeight - scrollHeight ) + scrollTop );
this.artificialScrolling = false;
} else if ( bottomPercentage > 90 ) {
start = parseInt( $yearUl.find( 'li:last' ).attr( 'data-year' ), 10 );
for ( i = ( start + 1 ); i < ( start + 11 ); i++ ) {
$yearUl.append( '<li data-year="' + i + '"><button type="button">' + i + '</button></li>' );
}
}
},
//some code ripped from http://stackoverflow.com/questions/2182246/javascript-dates-in-ie-nan-firefox-chrome-ok
parseDate: function( date ) {
var self = this;
var BAD_DATE = new Date( NaN );
var dt, isoExp, momentParse, momentParseWithFormat, tryMomentParseAll, month, parts, use;
if ( date ) {
if ( this.moment ) { //if we have moment, use that to parse the dates
momentParseWithFormat = function( d ) {
var md = moment( d, self.momentFormat );
return ( true === md.isValid() ) ? md.toDate() : BAD_DATE;
};
momentParse = function( d ) {
var md = moment( new Date( d ) );
return ( true === md.isValid() ) ? md.toDate() : BAD_DATE;
};
tryMomentParseAll = function( rawDateString, parseFunc1, parseFunc2 ) {
var pd = parseFunc1( rawDateString );
if ( !self.isInvalidDate( pd ) ) {
return pd;
}
pd = parseFunc2( rawDateString );
if ( !self.isInvalidDate( pd ) ) {
return pd;
}
return BAD_DATE;
};
if ( 'string' === typeof( date ) ) {
// Attempts to parse date strings using this.momentFormat, falling back on newing a date
return tryMomentParseAll( date, momentParseWithFormat, momentParse );
} else {
// Attempts to parse date by newing a date object directly, falling back on parsing using this.momentFormat
return tryMomentParseAll( date, momentParse, momentParseWithFormat );
}
} else { //if moment isn't present, use previous date parsing strategy
if ( typeof( date ) === 'string' ) {
dt = new Date( Date.parse( date ) );
if ( !this.isInvalidDate( dt ) ) {
return dt;
} else {
date = date.split( 'T' )[ 0 ];
isoExp = /^\s*(\d{4})-(\d\d)-(\d\d)\s*$/;
parts = isoExp.exec( date );
if ( parts ) {
month = parseInt( parts[ 2 ], 10 );
dt = new Date( parts[ 1 ], month - 1, parts[ 3 ] );
if ( month === ( dt.getMonth() + 1 ) ) {
return dt;
}
}
}
} else {
dt = new Date( date );
if ( !this.isInvalidDate( dt ) ) {
return dt;
}
}
}
}
return new Date( NaN );
},
prev: function() {
var month = this.$headerTitle.attr( 'data-month' );
var year = this.$headerTitle.attr( 'data-year' );
month--;
if ( month < 0 ) {
if ( this.sameYearOnly ) {
return;
}
month = 11;
year--;
}
this.renderMonth( new Date( year, month, 1 ) );
},
renderMonth: function( date ) {
date = date || new Date();
var firstDay = new Date( date.getFullYear(), date.getMonth(), 1 ).getDay();
var lastDate = new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
var lastMonthDate = new Date( date.getFullYear(), date.getMonth(), 0 ).getDate();
var $month = this.$headerTitle.find( '.month' );
var month = date.getMonth();
var now = new Date();
var nowDate = now.getDate();
var nowMonth = now.getMonth();
var nowYear = now.getFullYear();
var selected = this.selectedDate;
var $tbody = this.$days.find( 'tbody' );
var year = date.getFullYear();
var curDate, curMonth, curYear, i, j, rows, stage, previousStage, lastStage, $td, $tr;
if ( selected ) {
selected = {
date: selected.getDate(),
month: selected.getMonth(),
year: selected.getFullYear()
};
}
$month.find( '.current' ).removeClass( 'current' );
$month.find( 'span[data-month="' + month + '"]' ).addClass( 'current' );
this.$headerTitle.find( '.year' ).text( year );
this.$headerTitle.attr( {
'data-month': month,
'data-year': year
} );
$tbody.empty();
if ( firstDay !== 0 ) {
curDate = lastMonthDate - firstDay + 1;
stage = -1;
} else {
curDate = 1;
stage = 0;
}
rows = ( lastDate <= ( 35 - firstDay ) ) ? 5 : 6;
for ( i = 0; i < rows; i++ ) {
$tr = $( '<tr></tr>' );
for ( j = 0; j < 7; j++ ) {
$td = $( '<td></td>' );
if ( stage === -1 ) {
$td.addClass( 'last-month' );
if ( previousStage !== stage ) {
$td.addClass( 'first' );
}
} else if ( stage === 1 ) {
$td.addClass( 'next-month' );
if ( previousStage !== stage ) {
$td.addClass( 'first' );
}
}
curMonth = month + stage;
curYear = year;
if ( curMonth < 0 ) {
curMonth = 11;
curYear--;
} else if ( curMonth > 11 ) {
curMonth = 0;
curYear++;
}
$td.attr( {
'data-date': curDate,
'data-month': curMonth,
'data-year': curYear
} );
if ( curYear === nowYear && curMonth === nowMonth && curDate === nowDate ) {
$td.addClass( 'current-day' );
} else if ( curYear < nowYear || ( curYear === nowYear && curMonth < nowMonth ) ||
( curYear === nowYear && curMonth === nowMonth && curDate < nowDate ) ) {
$td.addClass( 'past' );
if ( !this.options.allowPastDates ) {
$td.addClass( 'restricted' ).attr( 'title', this.restrictedText );
}
}
if ( this.isRestricted( curDate, curMonth, curYear ) ) {
$td.addClass( 'restricted' ).attr( 'title', this.restrictedText );
}
if ( selected && curYear === selected.year && curMonth === selected.month && curDate === selected.date ) {
$td.addClass( 'selected' );
}
if ( $td.hasClass( 'restricted' ) ) {
$td.html( '<span><b class="datepicker-date">' + curDate + '</b></span>' );
} else {
$td.html( '<span><button type="button" class="datepicker-date">' + curDate + '</button></span>' );
}
curDate++;
lastStage = previousStage;
previousStage = stage;
if ( stage === -1 && curDate > lastMonthDate ) {
curDate = 1;
stage = 0;
if ( lastStage !== stage ) {
$td.addClass( 'last' );
}
} else if ( stage === 0 && curDate > lastDate ) {
curDate = 1;
stage = 1;
if ( lastStage !== stage ) {
$td.addClass( 'last' );
}
}
if ( i === ( rows - 1 ) && j === 6 ) {
$td.addClass( 'last' );
}
$tr.append( $td );
}
$tbody.append( $tr );
}
},
renderWheel: function( date ) {
var month = date.getMonth();
var $monthUl = this.$wheelsMonth.find( 'ul' );
var year = date.getFullYear();
var $yearUl = this.$wheelsYear.find( 'ul' );
var i, $monthSelected, $yearSelected;
if ( this.sameYearOnly ) {
this.$wheelsMonth.addClass( 'full' );
this.$wheelsYear.addClass( 'hidden' );
} else {
this.$wheelsMonth.removeClass( 'full' );
this.$wheelsYear.removeClass( 'hide hidden' ); // .hide is deprecated
}
$monthUl.find( '.selected' ).removeClass( 'selected' );
$monthSelected = $monthUl.find( 'li[data-month="' + month + '"]' );
$monthSelected.addClass( 'selected' );
$monthUl.scrollTop( $monthUl.scrollTop() + ( $monthSelected.position().top - $monthUl.outerHeight() / 2 - $monthSelected.outerHeight( true ) / 2 ) );
$yearUl.empty();
for ( i = ( year - 10 ); i < ( year + 11 ); i++ ) {
$yearUl.append( '<li data-year="' + i + '"><button type="button">' + i + '</button></li>' );
}
$yearSelected = $yearUl.find( 'li[data-year="' + year + '"]' );
$yearSelected.addClass( 'selected' );
this.artificialScrolling = true;
$yearUl.scrollTop( $yearUl.scrollTop() + ( $yearSelected.position().top - $yearUl.outerHeight() / 2 - $yearSelected.outerHeight( true ) / 2 ) );
this.artificialScrolling = false;
$monthSelected.find( 'button' ).focus();
},
selectClicked: function() {
var month = this.$wheelsMonth.find( '.selected' ).attr( 'data-month' );
var year = this.$wheelsYear.find( '.selected' ).attr( 'data-year' );
this.changeView( 'calendar', new Date( year, month, 1 ) );
},
setCulture: function( cultureCode ) {
if ( !cultureCode ) {
return false;
}
if ( this.moment ) {
moment.locale( cultureCode );
} else {
throw MOMENT_NOT_AVAILABLE;
}
},
setDate: function( date ) {
var parsed = this.parseDate( date );
if ( !this.isInvalidDate( parsed ) ) {
if ( !this.isRestricted( parsed.getDate(), parsed.getMonth(), parsed.getFullYear() ) ) {
this.selectedDate = parsed;
this.renderMonth( parsed );
this.$input.val( this.formatDate( parsed ) );
} else {
this.selectedDate = false;
this.renderMonth();
}
} else {
this.selectedDate = null;
this.renderMonth();
}
this.inputValue = this.$input.val();
return this.selectedDate;
},
setFormat: function( format ) {
if ( !format ) {
return false;
}
if ( this.moment ) {
this.momentFormat = format;
} else {
throw MOMENT_NOT_AVAILABLE;
}
},
setRestrictedDates: function( restricted ) {
var parsed = [];
var self = this;
var i, l;
var parseItem = function( val ) {
if ( val === -Infinity ) {
return {
date: -Infinity,
month: -Infinity,
year: -Infinity
};
} else if ( val === Infinity ) {
return {
date: Infinity,
month: Infinity,
year: Infinity
};
} else {
val = self.parseDate( val );
return {
date: val.getDate(),
month: val.getMonth(),
year: val.getFullYear()
};
}
};
this.restricted = restricted;
for ( i = 0, l = restricted.length; i < l; i++ ) {
parsed.push( {
from: parseItem( restricted[ i ].from ),
to: parseItem( restricted[ i ].to )
} );
}
this.restrictedParsed = parsed;
},
titleClicked: function( e ) {
this.changeView( 'wheels', new Date( this.$headerTitle.attr( 'data-year' ), this.$headerTitle.attr( 'data-month' ), 1 ) );
},
todayClicked: function( e ) {
var date = new Date();
if ( ( date.getMonth() + '' ) !== this.$headerTitle.attr( 'data-month' ) || ( date.getFullYear() + '' ) !== this.$headerTitle.attr( 'data-year' ) ) {
this.renderMonth( date );
}
},
yearClicked: function( e ) {
this.$wheelsYear.find( '.selected' ).removeClass( 'selected' );
$( e.currentTarget ).parent().addClass( 'selected' );
}
};
//for control library consistency
Datepicker.prototype.getValue = Datepicker.prototype.getDate;
// DATEPICKER PLUGIN DEFINITION
$.fn.datepicker = function( option ) {
var args = Array.prototype.slice.call( arguments, 1 );
var methodReturn;
var $set = this.each( function() {
var $this = $( this );
var data = $this.data( 'fu.datepicker' );
var options = typeof option === 'object' && option;
if ( !data ) {
$this.data( 'fu.datepicker', ( data = new Datepicker( this, options ) ) );
}
if ( typeof option === 'string' ) {
methodReturn = data[ option ].apply( data, args );
}
} );
return ( methodReturn === undefined ) ? $set : methodReturn;
};
$.fn.datepicker.defaults = {
allowPastDates: false,
date: new Date(),
formatDate: null,
momentConfig: {
culture: 'en',
format: 'L' // more formats can be found here http://momentjs.com/docs/#/customization/long-date-formats/.
},
parseDate: null,
restricted: [], //accepts an array of objects formatted as so: { from: {{date}}, to: {{date}} } (ex: [ { from: new Date('12/11/2014'), to: new Date('03/31/2015') } ])
restrictedText: 'Restricted',
sameYearOnly: false
};
$.fn.datepicker.Constructor = Datepicker;
$.fn.datepicker.noConflict = function() {
$.fn.datepicker = old;
return this;
};
// DATA-API
$( document ).on( 'mousedown.fu.datepicker.data-api', '[data-initialize=datepicker]', function( e ) {
var $control = $( e.target ).closest( '.datepicker' );
if ( !$control.data( 'datepicker' ) ) {
$control.datepicker( $control.data() );
}
} );
//used to prevent the dropdown from closing when clicking within it's bounds
$( document ).on( 'click.fu.datepicker.data-api', '.datepicker .dropdown-menu', function( e ) {
var $target = $( e.target );
if ( !$target.is( '.datepicker-date' ) || $target.closest( '.restricted' ).length ) {
e.stopPropagation();
}
} );
//used to prevent the dropdown from closing when clicking on the input
$( document ).on( 'click.fu.datepicker.data-api', '.datepicker input', function( e ) {
e.stopPropagation();
} );
$( function() {
$( '[data-initialize=datepicker]' ).each( function() {
var $this = $( this );
if ( $this.data( 'datepicker' ) ) {
return;
}
$this.datepicker( $this.data() );
} );
} );
} )( jQuery );
( function( $ ) {
/* global jQuery:true */
/*
* Fuel UX Dropdown Auto Flip
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN MODULE CODE HERE --
$( document ).on( 'click.fu.dropdown-autoflip', '[data-toggle=dropdown][data-flip]', function( event ) {
if ( $( this ).data().flip === "auto" ) {
// have the drop down decide where to place itself
_autoFlip( $( this ).next( '.dropdown-menu' ) );
}
} );
// For pillbox suggestions dropdown
$( document ).on( 'suggested.fu.pillbox', function( event, element ) {
_autoFlip( $( element ) );
$( element ).parent().addClass( 'open' );
} );
function _autoFlip( menu ) {
// hide while the browser thinks
$( menu ).css( {
visibility: "hidden"
} );
// decide where to put menu
if ( dropUpCheck( menu ) ) {
menu.parent().addClass( "dropup" );
} else {
menu.parent().removeClass( "dropup" );
}
// show again
$( menu ).css( {
visibility: "visible"
} );
}
function dropUpCheck( element ) {
// caching container
var $container = _getContainer( element );
// building object with measurementsances for later use
var measurements = {};
measurements.parentHeight = element.parent().outerHeight();
measurements.parentOffsetTop = element.parent().offset().top;
measurements.dropdownHeight = element.outerHeight();
measurements.containerHeight = $container.overflowElement.outerHeight();
// this needs to be different if the window is the container or another element is
measurements.containerOffsetTop = ( !!$container.isWindow ) ? $container.overflowElement.scrollTop() : $container.overflowElement.offset().top;
// doing the calculations
measurements.fromTop = measurements.parentOffsetTop - measurements.containerOffsetTop;
measurements.fromBottom = measurements.containerHeight - measurements.parentHeight - ( measurements.parentOffsetTop - measurements.containerOffsetTop );
// actual determination of where to put menu
// false = drop down
// true = drop up
if ( measurements.dropdownHeight < measurements.fromBottom ) {
return false;
} else if ( measurements.dropdownHeight < measurements.fromTop ) {
return true;
} else if ( measurements.dropdownHeight >= measurements.fromTop && measurements.dropdownHeight >= measurements.fromBottom ) {
// decide which one is bigger and put it there
if ( measurements.fromTop >= measurements.fromBottom ) {
return true;
} else {
return false;
}
}
}
function _getContainer( element ) {
var targetSelector = element.attr( 'data-target' );
var isWindow = true;
var containerElement;
if ( !targetSelector ) {
// no selection so find the relevant ancestor
$.each( element.parents(), function( index, parentElement ) {
if ( $( parentElement ).css( 'overflow' ) !== 'visible' ) {
containerElement = parentElement;
isWindow = false;
return false;
}
} );
} else if ( targetSelector !== 'window' ) {
containerElement = $( targetSelector );
isWindow = false;
}
// fallback to window
if ( isWindow ) {
containerElement = window;
}
return {
overflowElement: $( containerElement ),
isWindow: isWindow
};
}
// register empty plugin
$.fn.dropdownautoflip = function() {
/* empty */
};
} )( jQuery );
( function( $ ) {
/* global jQuery:true */
/*
* Fuel UX Loader
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN MODULE CODE HERE --
var old = $.fn.loader;
// LOADER CONSTRUCTOR AND PROTOTYPE
var Loader = function( element, options ) {
this.$element = $( element );
this.options = $.extend( {}, $.fn.loader.defaults, options );
};
Loader.prototype = {
constructor: Loader,
destroy: function() {
this.$element.remove();
// any external bindings
// [none]
// empty elements to return to original markup
// [none]
// returns string of markup
return this.$element[ 0 ].outerHTML;
},
ieRepaint: function() {},
msieVersion: function() {},
next: function() {},
pause: function() {},
play: function() {},
previous: function() {},
reset: function() {}
};
// LOADER PLUGIN DEFINITION
$.fn.loader = function( option ) {
var args = Array.prototype.slice.call( arguments, 1 );
var methodReturn;
var $set = this.each( function() {
var $this = $( this );
var data = $this.data( 'fu.loader' );
var options = typeof option === 'object' && option;
if ( !data ) {
$this.data( 'fu.loader', ( data = new Loader( this, options ) ) );
}
if ( typeof option === 'string' ) {
methodReturn = data[ option ].apply( data, args );
}
} );
return ( methodReturn === undefined ) ? $set : methodReturn;
};
$.fn.loader.defaults = {};
$.fn.loader.Constructor = Loader;
$.fn.loader.noConflict = function() {
$.fn.loader = old;
return this;
};
// INIT LOADER ON DOMCONTENTLOADED
$( function() {
$( '[data-initialize=loader]' ).each( function() {
var $this = $( this );
if ( !$this.data( 'fu.loader' ) ) {
$this.loader( $this.data() );
}
} );
} );
} )( jQuery );
( function( $ ) {
/* global jQuery:true */
/*
* Fuel UX Placard
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN MODULE CODE HERE --
var old = $.fn.placard;
var EVENT_CALLBACK_MAP = {
'accepted': 'onAccept',
'cancelled': 'onCancel'
};
// PLACARD CONSTRUCTOR AND PROTOTYPE
var Placard = function Placard( element, options ) {
var self = this;
this.$element = $( element );
this.options = $.extend( {}, $.fn.placard.defaults, options );
if ( this.$element.attr( 'data-ellipsis' ) === 'true' ) {
this.options.applyEllipsis = true;
}
this.$accept = this.$element.find( '.placard-accept' );
this.$cancel = this.$element.find( '.placard-cancel' );
this.$field = this.$element.find( '.placard-field' );
this.$footer = this.$element.find( '.placard-footer' );
this.$header = this.$element.find( '.placard-header' );
this.$popup = this.$element.find( '.placard-popup' );
this.actualValue = null;
this.clickStamp = '_';
this.previousValue = '';
if ( this.options.revertOnCancel === -1 ) {
this.options.revertOnCancel = ( this.$accept.length > 0 );
}
// Placard supports inputs, textareas, or contenteditable divs. These checks determine which is being used
this.isContentEditableDiv = this.$field.is( 'div' );
this.isInput = this.$field.is( 'input' );
this.divInTextareaMode = ( this.isContentEditableDiv && this.$field.attr( 'data-textarea' ) === 'true' );
this.$field.on( 'focus.fu.placard', $.proxy( this.show, this ) );
this.$field.on( 'keydown.fu.placard', $.proxy( this.keyComplete, this ) );
this.$element.on( 'close.fu.placard', $.proxy( this.hide, this ) );
this.$accept.on( 'click.fu.placard', $.proxy( this.complete, this, 'accepted' ) );
this.$cancel.on( 'click.fu.placard', function( e ) {
e.preventDefault();
self.complete( 'cancelled' );
} );
this.applyEllipsis();
};
var _isShown = function _isShown( placard ) {
return placard.$element.hasClass( 'showing' );
};
var _closeOtherPlacards = function _closeOtherPlacards() {
var otherPlacards;
otherPlacards = $( document ).find( '.placard.showing' );
if ( otherPlacards.length > 0 ) {
if ( otherPlacards.data( 'fu.placard' ) && otherPlacards.data( 'fu.placard' ).options.explicit ) {
return false; //failed
}
otherPlacards.placard( 'externalClickListener', {}, true );
}
return true; //succeeded
};
Placard.prototype = {
constructor: Placard,
complete: function complete( action ) {
var func = this.options[ EVENT_CALLBACK_MAP[ action ] ];
var obj = {
previousValue: this.previousValue,
value: this.getValue()
};
if ( func ) {
func( obj );
this.$element.trigger( action + '.fu.placard', obj );
} else {
if ( action === 'cancelled' && this.options.revertOnCancel ) {
this.setValue( this.previousValue, true );
}
this.$element.trigger( action + '.fu.placard', obj );
this.hide();
}
},
keyComplete: function keyComplete( e ) {
if ( ( ( this.isContentEditableDiv && !this.divInTextareaMode ) || this.isInput ) && e.keyCode === 13 ) {
this.complete( 'accepted' );
this.$field.blur();
} else if ( e.keyCode === 27 ) {
this.complete( 'cancelled' );
this.$field.blur();
}
},
destroy: function destroy() {
this.$element.remove();
// remove any external bindings
$( document ).off( 'click.fu.placard.externalClick.' + this.clickStamp );
// set input value attribute
this.$element.find( 'input' ).each( function() {
$( this ).attr( 'value', $( this ).val() );
} );
// empty elements to return to original markup
// [none]
// return string of markup
return this.$element[ 0 ].outerHTML;
},
disable: function disable() {
this.$element.addClass( 'disabled' );
this.$field.attr( 'disabled', 'disabled' );
if ( this.isContentEditableDiv ) {
this.$field.removeAttr( 'contenteditab