PageRenderTime 58ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/files/fuelux/3.5.0/js/fuelux.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 1787 lines | 1323 code | 325 blank | 139 comment | 250 complexity | 8bdb8cb8e43d61a88e0dfa9eaf66d108 MD5 | raw file
  1. /*!
  2. * Fuel UX v3.5.0
  3. * Copyright 2012-2015 ExactTarget
  4. * Licensed under the BSD-3-Clause license ()
  5. */
  6. // For more information on UMD visit: https://github.com/umdjs/umd/
  7. ( function( factory ) {
  8. if ( typeof define === 'function' && define.amd ) {
  9. define( [ 'jquery', 'bootstrap' ], factory );
  10. } else {
  11. factory( jQuery );
  12. }
  13. }( function( jQuery ) {
  14. if ( typeof jQuery === 'undefined' ) {
  15. throw new Error( 'Fuel UX\'s JavaScript requires jQuery' )
  16. }
  17. if ( typeof jQuery.fn.dropdown === 'undefined' || typeof jQuery.fn.collapse === 'undefined' ) {
  18. throw new Error( 'Fuel UX\'s JavaScript requires Bootstrap' )
  19. }
  20. ( function( $ ) {
  21. /*
  22. * Fuel UX Checkbox
  23. * https://github.com/ExactTarget/fuelux
  24. *
  25. * Copyright (c) 2014 ExactTarget
  26. * Licensed under the BSD New license.
  27. */
  28. // -- BEGIN MODULE CODE HERE --
  29. var old = $.fn.checkbox;
  30. // CHECKBOX CONSTRUCTOR AND PROTOTYPE
  31. var Checkbox = function( element, options ) {
  32. this.options = $.extend( {}, $.fn.checkbox.defaults, options );
  33. // cache elements
  34. this.$element = $( element ).is( 'input[type="checkbox"]' ) ? $( element ) : $( element ).find( 'input[type="checkbox"]:first' );
  35. this.$label = this.$element.parent();
  36. this.$parent = this.$label.parent( '.checkbox' );
  37. this.$toggleContainer = this.$element.attr( 'data-toggle' );
  38. this.state = {
  39. disabled: false,
  40. checked: false
  41. };
  42. if ( this.$parent.length === 0 ) {
  43. this.$parent = null;
  44. }
  45. if ( Boolean( this.$toggleContainer ) ) {
  46. this.$toggleContainer = $( this.$toggleContainer );
  47. } else {
  48. this.$toggleContainer = null;
  49. }
  50. // handle events
  51. this.$element.on( 'change.fu.checkbox', $.proxy( this.itemchecked, this ) );
  52. this.$label.on( 'click', $.proxy( this.toggle, this ) ); //make repeated label clicks work
  53. // set default state
  54. this.setState();
  55. };
  56. Checkbox.prototype = {
  57. constructor: Checkbox,
  58. setState: function( $chk ) {
  59. $chk = $chk || this.$element;
  60. this.state.disabled = Boolean( $chk.prop( 'disabled' ) );
  61. this.state.checked = Boolean( $chk.is( ':checked' ) );
  62. this._resetClasses();
  63. // set state of checkbox
  64. this._toggleCheckedState();
  65. this._toggleDisabledState();
  66. //toggle container
  67. this.toggleContainer();
  68. },
  69. enable: function() {
  70. this.state.disabled = false;
  71. this.$element.attr( 'disabled', false );
  72. this._resetClasses();
  73. this.$element.trigger( 'enabled.fu.checkbox' );
  74. },
  75. disable: function() {
  76. this.state.disabled = true;
  77. this.$element.attr( 'disabled', true );
  78. this._setDisabledClass();
  79. this.$element.trigger( 'disabled.fu.checkbox' );
  80. },
  81. check: function() {
  82. this.state.checked = true;
  83. this.$element.prop( 'checked', true );
  84. this.$element.attr( 'checked', 'checked' );
  85. this._setCheckedClass();
  86. this.$element.trigger( 'checked.fu.checkbox' );
  87. },
  88. uncheck: function() {
  89. this.state.checked = false;
  90. this.$element.prop( 'checked', false );
  91. this.$element.removeAttr( 'checked' );
  92. this._resetClasses();
  93. this.$element.trigger( 'unchecked.fu.checkbox' );
  94. },
  95. isChecked: function() {
  96. return this.state.checked;
  97. },
  98. toggle: function( e ) {
  99. //keep event from firing twice in Chrome
  100. if ( !e || ( e.target === e.originalEvent.target ) ) {
  101. this.state.checked = !this.state.checked;
  102. this._toggleCheckedState();
  103. if ( Boolean( e ) ) {
  104. //stop bubbling, otherwise event fires twice in Firefox.
  105. e.preventDefault();
  106. //make change event still fire (prevented by preventDefault to avoid firefox bug, see preceeding line)
  107. this.$element.trigger( 'change', e );
  108. }
  109. }
  110. },
  111. toggleContainer: function() {
  112. if ( Boolean( this.$toggleContainer ) ) {
  113. if ( this.state.checked ) {
  114. this.$toggleContainer.removeClass( 'hide' );
  115. this.$toggleContainer.attr( 'aria-hidden', 'false' );
  116. } else {
  117. this.$toggleContainer.addClass( 'hide' );
  118. this.$toggleContainer.attr( 'aria-hidden', 'true' );
  119. }
  120. }
  121. },
  122. itemchecked: function( element ) {
  123. this.setState( $( element.target ) );
  124. },
  125. destroy: function() {
  126. this.$parent.remove();
  127. // remove any external bindings
  128. // [none]
  129. // empty elements to return to original markup
  130. // [none]
  131. return this.$parent[ 0 ].outerHTML;
  132. },
  133. _resetClasses: function() {
  134. var classesToRemove = [];
  135. if ( !this.state.checked ) {
  136. classesToRemove.push( 'checked' );
  137. }
  138. if ( !this.state.disabled ) {
  139. classesToRemove.push( 'disabled' );
  140. }
  141. classesToRemove = classesToRemove.join( ' ' );
  142. this.$label.removeClass( classesToRemove );
  143. if ( this.$parent ) {
  144. this.$parent.removeClass( classesToRemove );
  145. }
  146. },
  147. _toggleCheckedState: function() {
  148. if ( this.state.checked ) {
  149. this.check();
  150. } else {
  151. this.uncheck();
  152. }
  153. },
  154. _toggleDisabledState: function() {
  155. if ( this.state.disabled ) {
  156. this.disable();
  157. } else {
  158. this.enable();
  159. }
  160. },
  161. _setCheckedClass: function() {
  162. this.$label.addClass( 'checked' );
  163. if ( this.$parent ) {
  164. this.$parent.addClass( 'checked' );
  165. }
  166. },
  167. _setDisabledClass: function() {
  168. this.$label.addClass( 'disabled' );
  169. if ( this.$parent ) {
  170. this.$parent.addClass( 'disabled' );
  171. }
  172. }
  173. };
  174. // CHECKBOX PLUGIN DEFINITION
  175. $.fn.checkbox = function( option ) {
  176. var args = Array.prototype.slice.call( arguments, 1 );
  177. var methodReturn;
  178. var $set = this.each( function() {
  179. var $this = $( this );
  180. var data = $this.data( 'fu.checkbox' );
  181. var options = typeof option === 'object' && option;
  182. if ( !data ) {
  183. $this.data( 'fu.checkbox', ( data = new Checkbox( this, options ) ) );
  184. }
  185. if ( typeof option === 'string' ) {
  186. methodReturn = data[ option ].apply( data, args );
  187. }
  188. } );
  189. return ( methodReturn === undefined ) ? $set : methodReturn;
  190. };
  191. $.fn.checkbox.defaults = {};
  192. $.fn.checkbox.Constructor = Checkbox;
  193. $.fn.checkbox.noConflict = function() {
  194. $.fn.checkbox = old;
  195. return this;
  196. };
  197. // DATA-API
  198. $( document ).on( 'mouseover.fu.checkbox.data-api', '[data-initialize=checkbox]', function( e ) {
  199. var $control = $( e.target ).closest( '.checkbox' ).find( '[type=checkbox]' );
  200. if ( !$control.data( 'fu.checkbox' ) ) {
  201. $control.checkbox( $control.data() );
  202. }
  203. } );
  204. // Must be domReady for AMD compatibility
  205. $( function() {
  206. $( '[data-initialize=checkbox] [type=checkbox]' ).each( function() {
  207. var $this = $( this );
  208. if ( !$this.data( 'fu.checkbox' ) ) {
  209. $this.checkbox( $this.data() );
  210. }
  211. } );
  212. } );
  213. } )( jQuery );
  214. ( function( $ ) {
  215. /*
  216. * Fuel UX Combobox
  217. * https://github.com/ExactTarget/fuelux
  218. *
  219. * Copyright (c) 2014 ExactTarget
  220. * Licensed under the BSD New license.
  221. */
  222. // -- BEGIN MODULE CODE HERE --
  223. var old = $.fn.combobox;
  224. // COMBOBOX CONSTRUCTOR AND PROTOTYPE
  225. var Combobox = function( element, options ) {
  226. this.$element = $( element );
  227. this.options = $.extend( {}, $.fn.combobox.defaults, options );
  228. this.$dropMenu = this.$element.find( '.dropdown-menu' );
  229. this.$input = this.$element.find( 'input' );
  230. this.$button = this.$element.find( '.btn' );
  231. this.$element.on( 'click.fu.combobox', 'a', $.proxy( this.itemclicked, this ) );
  232. this.$element.on( 'change.fu.combobox', 'input', $.proxy( this.inputchanged, this ) );
  233. this.$element.on( 'shown.bs.dropdown', $.proxy( this.menuShown, this ) );
  234. // set default selection
  235. this.setDefaultSelection();
  236. };
  237. Combobox.prototype = {
  238. constructor: Combobox,
  239. destroy: function() {
  240. this.$element.remove();
  241. // remove any external bindings
  242. // [none]
  243. // set input value attrbute in markup
  244. this.$element.find( 'input' ).each( function() {
  245. $( this ).attr( 'value', $( this ).val() );
  246. } );
  247. // empty elements to return to original markup
  248. // [none]
  249. return this.$element[ 0 ].outerHTML;
  250. },
  251. doSelect: function( $item ) {
  252. if ( typeof $item[ 0 ] !== 'undefined' ) {
  253. this.$selectedItem = $item;
  254. this.$input.val( this.$selectedItem.text().trim() );
  255. } else {
  256. this.$selectedItem = null;
  257. }
  258. },
  259. menuShown: function() {
  260. if ( this.options.autoResizeMenu ) {
  261. this.resizeMenu();
  262. }
  263. },
  264. resizeMenu: function() {
  265. var width = this.$element.outerWidth();
  266. this.$dropMenu.outerWidth( width );
  267. },
  268. selectedItem: function() {
  269. var item = this.$selectedItem;
  270. var data = {};
  271. if ( item ) {
  272. var txt = this.$selectedItem.text().trim();
  273. data = $.extend( {
  274. text: txt
  275. }, this.$selectedItem.data() );
  276. } else {
  277. data = {
  278. text: this.$input.val()
  279. };
  280. }
  281. return data;
  282. },
  283. selectByText: function( text ) {
  284. var $item = $( [] );
  285. this.$element.find( 'li' ).each( function() {
  286. if ( ( this.textContent || this.innerText || $( this ).text() || '' ).toLowerCase() === ( text || '' ).toLowerCase() ) {
  287. $item = $( this );
  288. return false;
  289. }
  290. } );
  291. this.doSelect( $item );
  292. },
  293. selectByValue: function( value ) {
  294. var selector = 'li[data-value="' + value + '"]';
  295. this.selectBySelector( selector );
  296. },
  297. selectByIndex: function( index ) {
  298. // zero-based index
  299. var selector = 'li:eq(' + index + ')';
  300. this.selectBySelector( selector );
  301. },
  302. selectBySelector: function( selector ) {
  303. var $item = this.$element.find( selector );
  304. this.doSelect( $item );
  305. },
  306. setDefaultSelection: function() {
  307. var selector = 'li[data-selected=true]:first';
  308. var item = this.$element.find( selector );
  309. if ( item.length > 0 ) {
  310. // select by data-attribute
  311. this.selectBySelector( selector );
  312. item.removeData( 'selected' );
  313. item.removeAttr( 'data-selected' );
  314. }
  315. },
  316. enable: function() {
  317. this.$element.removeClass( 'disabled' );
  318. this.$input.removeAttr( 'disabled' );
  319. this.$button.removeClass( 'disabled' );
  320. },
  321. disable: function() {
  322. this.$element.addClass( 'disabled' );
  323. this.$input.attr( 'disabled', true );
  324. this.$button.addClass( 'disabled' );
  325. },
  326. itemclicked: function( e ) {
  327. this.$selectedItem = $( e.target ).parent();
  328. // set input text and trigger input change event marked as synthetic
  329. this.$input.val( this.$selectedItem.text().trim() ).trigger( 'change', {
  330. synthetic: true
  331. } );
  332. // pass object including text and any data-attributes
  333. // to onchange event
  334. var data = this.selectedItem();
  335. // trigger changed event
  336. this.$element.trigger( 'changed.fu.combobox', data );
  337. e.preventDefault();
  338. // return focus to control after selecting an option
  339. this.$element.find( '.dropdown-toggle' ).focus();
  340. },
  341. inputchanged: function( e, extra ) {
  342. // skip processing for internally-generated synthetic event
  343. // to avoid double processing
  344. if ( extra && extra.synthetic ) return;
  345. var val = $( e.target ).val();
  346. this.selectByText( val );
  347. // find match based on input
  348. // if no match, pass the input value
  349. var data = this.selectedItem();
  350. if ( data.text.length === 0 ) {
  351. data = {
  352. text: val
  353. };
  354. }
  355. // trigger changed event
  356. this.$element.trigger( 'changed.fu.combobox', data );
  357. }
  358. };
  359. // COMBOBOX PLUGIN DEFINITION
  360. $.fn.combobox = function( option ) {
  361. var args = Array.prototype.slice.call( arguments, 1 );
  362. var methodReturn;
  363. var $set = this.each( function() {
  364. var $this = $( this );
  365. var data = $this.data( 'fu.combobox' );
  366. var options = typeof option === 'object' && option;
  367. if ( !data ) $this.data( 'fu.combobox', ( data = new Combobox( this, options ) ) );
  368. if ( typeof option === 'string' ) methodReturn = data[ option ].apply( data, args );
  369. } );
  370. return ( methodReturn === undefined ) ? $set : methodReturn;
  371. };
  372. $.fn.combobox.defaults = {
  373. autoResizeMenu: true
  374. };
  375. $.fn.combobox.Constructor = Combobox;
  376. $.fn.combobox.noConflict = function() {
  377. $.fn.combobox = old;
  378. return this;
  379. };
  380. // DATA-API
  381. $( document ).on( 'mousedown.fu.combobox.data-api', '[data-initialize=combobox]', function( e ) {
  382. var $control = $( e.target ).closest( '.combobox' );
  383. if ( !$control.data( 'fu.combobox' ) ) {
  384. $control.combobox( $control.data() );
  385. }
  386. } );
  387. // Must be domReady for AMD compatibility
  388. $( function() {
  389. $( '[data-initialize=combobox]' ).each( function() {
  390. var $this = $( this );
  391. if ( !$this.data( 'fu.combobox' ) ) {
  392. $this.combobox( $this.data() );
  393. }
  394. } );
  395. } );
  396. } )( jQuery );
  397. ( function( $ ) {
  398. /*
  399. * Fuel UX Datepicker
  400. * https://github.com/ExactTarget/fuelux
  401. *
  402. * Copyright (c) 2014 ExactTarget
  403. * Licensed under the BSD New license.
  404. */
  405. // -- BEGIN MODULE CODE HERE --
  406. var INVALID_DATE = 'Invalid Date';
  407. var MOMENT_NOT_AVAILABLE = 'moment.js is not available so you cannot use this function';
  408. var datepickerStack = [];
  409. var moment = false;
  410. var old = $.fn.datepicker;
  411. var requestedMoment = false;
  412. var runStack = function() {
  413. var i, l;
  414. requestedMoment = true;
  415. for ( i = 0, l = datepickerStack.length; i < l; i++ ) {
  416. datepickerStack[ i ].init.call( datepickerStack[ i ].scope );
  417. }
  418. datepickerStack = [];
  419. };
  420. //only load moment if it's there. otherwise we'll look for it in window.moment
  421. if ( typeof define === 'function' && define.amd ) { //check if AMD is available
  422. require( [ 'moment' ], function( amdMoment ) {
  423. moment = amdMoment;
  424. runStack();
  425. }, function( err ) {
  426. var failedId = err.requireModules && err.requireModules[ 0 ];
  427. if ( failedId === 'moment' ) {
  428. runStack();
  429. }
  430. } );
  431. } else {
  432. runStack();
  433. }
  434. // DATEPICKER CONSTRUCTOR AND PROTOTYPE
  435. var Datepicker = function( element, options ) {
  436. this.$element = $( element );
  437. this.options = $.extend( true, {}, $.fn.datepicker.defaults, options );
  438. this.$calendar = this.$element.find( '.datepicker-calendar' );
  439. this.$days = this.$calendar.find( '.datepicker-calendar-days' );
  440. this.$header = this.$calendar.find( '.datepicker-calendar-header' );
  441. this.$headerTitle = this.$header.find( '.title' );
  442. this.$input = this.$element.find( 'input' );
  443. this.$wheels = this.$element.find( '.datepicker-wheels' );
  444. this.$wheelsMonth = this.$element.find( '.datepicker-wheels-month' );
  445. this.$wheelsYear = this.$element.find( '.datepicker-wheels-year' );
  446. this.artificialScrolling = false;
  447. this.formatDate = this.options.formatDate || this.formatDate;
  448. this.inputValue = null;
  449. this.moment = false;
  450. this.momentFormat = null;
  451. this.parseDate = this.options.parseDate || this.parseDate;
  452. this.preventBlurHide = false;
  453. this.restricted = this.options.restricted || [];
  454. this.restrictedParsed = [];
  455. this.restrictedText = this.options.restrictedText;
  456. this.sameYearOnly = this.options.sameYearOnly;
  457. this.selectedDate = null;
  458. this.yearRestriction = null;
  459. this.$calendar.find( '.datepicker-today' ).on( 'click.fu.datepicker', $.proxy( this.todayClicked, this ) );
  460. this.$days.on( 'click.fu.datepicker', 'tr td button', $.proxy( this.dateClicked, this ) );
  461. this.$element.find( '.dropdown-menu' ).on( 'mousedown.fu.datepicker', $.proxy( this.dropdownMousedown, this ) );
  462. this.$header.find( '.next' ).on( 'click.fu.datepicker', $.proxy( this.next, this ) );
  463. this.$header.find( '.prev' ).on( 'click.fu.datepicker', $.proxy( this.prev, this ) );
  464. this.$headerTitle.on( 'click.fu.datepicker', $.proxy( this.titleClicked, this ) );
  465. this.$input.on( 'blur.fu.datepicker', $.proxy( this.inputBlurred, this ) );
  466. this.$input.on( 'focus.fu.datepicker', $.proxy( this.inputFocused, this ) );
  467. this.$wheels.find( '.datepicker-wheels-back' ).on( 'click.fu.datepicker', $.proxy( this.backClicked, this ) );
  468. this.$wheels.find( '.datepicker-wheels-select' ).on( 'click.fu.datepicker', $.proxy( this.selectClicked, this ) );
  469. this.$wheelsMonth.on( 'click.fu.datepicker', 'ul button', $.proxy( this.monthClicked, this ) );
  470. this.$wheelsYear.on( 'click.fu.datepicker', 'ul button', $.proxy( this.yearClicked, this ) );
  471. this.$wheelsYear.find( 'ul' ).on( 'scroll.fu.datepicker', $.proxy( this.onYearScroll, this ) );
  472. var init = function() {
  473. if ( this.checkForMomentJS() ) {
  474. moment = moment || window.moment; // need to pull in the global moment if they didn't do it via require
  475. this.moment = true;
  476. this.momentFormat = this.options.momentConfig.format;
  477. this.setCulture( this.options.momentConfig.culture );
  478. // support moment with lang (< v2.8) or locale
  479. moment.locale = moment.locale || moment.lang;
  480. }
  481. this.setRestrictedDates( this.restricted );
  482. if ( !this.setDate( this.options.date ) ) {
  483. this.$input.val( '' );
  484. this.inputValue = this.$input.val();
  485. }
  486. if ( this.sameYearOnly ) {
  487. this.yearRestriction = ( this.selectedDate ) ? this.selectedDate.getFullYear() : new Date().getFullYear();
  488. }
  489. };
  490. if ( requestedMoment ) {
  491. init.call( this );
  492. } else {
  493. datepickerStack.push( {
  494. init: init,
  495. scope: this
  496. } );
  497. }
  498. };
  499. Datepicker.prototype = {
  500. constructor: Datepicker,
  501. backClicked: function() {
  502. this.changeView( 'calendar' );
  503. },
  504. changeView: function( view, date ) {
  505. if ( view === 'wheels' ) {
  506. this.$calendar.hide().attr( 'aria-hidden', 'true' );
  507. this.$wheels.show().removeAttr( 'aria-hidden', '' );
  508. if ( date ) {
  509. this.renderWheel( date );
  510. }
  511. } else {
  512. this.$wheels.hide().attr( 'aria-hidden', 'true' );
  513. this.$calendar.show().removeAttr( 'aria-hidden', '' );
  514. if ( date ) {
  515. this.renderMonth( date );
  516. }
  517. }
  518. },
  519. checkForMomentJS: function() {
  520. if (
  521. ( $.isFunction( window.moment ) || ( typeof moment !== 'undefined' && $.isFunction( moment ) ) ) &&
  522. $.isPlainObject( this.options.momentConfig ) &&
  523. this.options.momentConfig.culture && this.options.momentConfig.format
  524. ) {
  525. return true;
  526. } else {
  527. return false;
  528. }
  529. },
  530. dateClicked: function( e ) {
  531. var $td = $( e.currentTarget ).parents( 'td:first' );
  532. var date;
  533. if ( $td.hasClass( 'restricted' ) ) {
  534. return;
  535. }
  536. this.$days.find( 'td.selected' ).removeClass( 'selected' );
  537. $td.addClass( 'selected' );
  538. date = new Date( $td.attr( 'data-year' ), $td.attr( 'data-month' ), $td.attr( 'data-date' ) );
  539. this.selectedDate = date;
  540. this.$input.val( this.formatDate( date ) );
  541. this.inputValue = this.$input.val();
  542. this.$input.focus();
  543. this.$element.trigger( 'dateClicked.fu.datepicker', date );
  544. },
  545. destroy: function() {
  546. this.$element.remove();
  547. // any external bindings
  548. // [none]
  549. // empty elements to return to original markup
  550. this.$days.find( 'tbody' ).empty();
  551. this.$wheelsYear.find( 'ul' ).empty();
  552. return this.$element[ 0 ].outerHTML;
  553. },
  554. disable: function() {
  555. this.$element.addClass( 'disabled' );
  556. this.$element.find( 'input, button' ).attr( 'disabled', 'disabled' );
  557. this.$element.find( '.input-group-btn' ).removeClass( 'open' );
  558. },
  559. dropdownMousedown: function() {
  560. var self = this;
  561. this.preventBlurHide = true;
  562. setTimeout( function() {
  563. self.preventBlurHide = false;
  564. }, 0 );
  565. },
  566. enable: function() {
  567. this.$element.removeClass( 'disabled' );
  568. this.$element.find( 'input, button' ).removeAttr( 'disabled' );
  569. },
  570. formatDate: function( date ) {
  571. var padTwo = function( value ) {
  572. var s = '0' + value;
  573. return s.substr( s.length - 2 );
  574. };
  575. if ( this.moment ) {
  576. return moment( date ).format( this.momentFormat );
  577. } else {
  578. return padTwo( date.getMonth() + 1 ) + '/' + padTwo( date.getDate() ) + '/' + date.getFullYear();
  579. }
  580. },
  581. getCulture: function() {
  582. if ( this.moment ) {
  583. return moment.locale();
  584. } else {
  585. throw MOMENT_NOT_AVAILABLE;
  586. }
  587. },
  588. getDate: function() {
  589. return ( !this.selectedDate ) ? new Date( NaN ) : this.selectedDate;
  590. },
  591. getFormat: function() {
  592. if ( this.moment ) {
  593. return this.momentFormat;
  594. } else {
  595. throw MOMENT_NOT_AVAILABLE;
  596. }
  597. },
  598. getFormattedDate: function() {
  599. return ( !this.selectedDate ) ? INVALID_DATE : this.formatDate( this.selectedDate );
  600. },
  601. getRestrictedDates: function() {
  602. return this.restricted;
  603. },
  604. inputBlurred: function( e ) {
  605. var inputVal = this.$input.val();
  606. var date;
  607. if ( inputVal !== this.inputValue ) {
  608. date = this.setDate( inputVal );
  609. if ( date === null ) {
  610. this.$element.trigger( 'inputParsingFailed.fu.datepicker', inputVal );
  611. } else if ( date === false ) {
  612. this.$element.trigger( 'inputRestrictedDate.fu.datepicker', date );
  613. } else {
  614. this.$element.trigger( 'changed.fu.datepicker', date );
  615. }
  616. }
  617. if ( !this.preventBlurHide ) {
  618. this.$element.find( '.input-group-btn' ).removeClass( 'open' );
  619. }
  620. },
  621. inputFocused: function( e ) {
  622. this.$element.find( '.input-group-btn' ).addClass( 'open' );
  623. },
  624. isInvalidDate: function( date ) {
  625. var dateString = date.toString();
  626. if ( dateString === INVALID_DATE || dateString === 'NaN' ) {
  627. return true;
  628. }
  629. return false;
  630. },
  631. isRestricted: function( date, month, year ) {
  632. var restricted = this.restrictedParsed;
  633. var i, from, l, to;
  634. if ( this.sameYearOnly && this.yearRestriction !== null && year !== this.yearRestriction ) {
  635. return true;
  636. }
  637. for ( i = 0, l = restricted.length; i < l; i++ ) {
  638. from = restricted[ i ].from;
  639. to = restricted[ i ].to;
  640. if (
  641. ( year > from.year || ( year === from.year && month > from.month ) || ( year === from.year && month === from.month && date >= from.date ) ) &&
  642. ( year < to.year || ( year === to.year && month < to.month ) || ( year === to.year && month === to.month && date <= to.date ) )
  643. ) {
  644. return true;
  645. }
  646. }
  647. return false;
  648. },
  649. monthClicked: function( e ) {
  650. this.$wheelsMonth.find( '.selected' ).removeClass( 'selected' );
  651. $( e.currentTarget ).parent().addClass( 'selected' );
  652. },
  653. next: function() {
  654. var month = this.$headerTitle.attr( 'data-month' );
  655. var year = this.$headerTitle.attr( 'data-year' );
  656. month++;
  657. if ( month > 11 ) {
  658. if ( this.sameYearOnly ) {
  659. return;
  660. }
  661. month = 0;
  662. year++;
  663. }
  664. this.renderMonth( new Date( year, month, 1 ) );
  665. },
  666. onYearScroll: function( e ) {
  667. if ( this.artificialScrolling ) {
  668. return;
  669. }
  670. var $yearUl = $( e.currentTarget );
  671. var height = ( $yearUl.css( 'box-sizing' ) === 'border-box' ) ? $yearUl.outerHeight() : $yearUl.height();
  672. var scrollHeight = $yearUl.get( 0 ).scrollHeight;
  673. var scrollTop = $yearUl.scrollTop();
  674. var bottomPercentage = ( height / ( scrollHeight - scrollTop ) ) * 100;
  675. var topPercentage = ( scrollTop / scrollHeight ) * 100;
  676. var i, start;
  677. if ( topPercentage < 5 ) {
  678. start = parseInt( $yearUl.find( 'li:first' ).attr( 'data-year' ), 10 );
  679. for ( i = ( start - 1 ); i > ( start - 11 ); i-- ) {
  680. $yearUl.prepend( '<li data-year="' + i + '"><button type="button">' + i + '</button></li>' );
  681. }
  682. this.artificialScrolling = true;
  683. $yearUl.scrollTop( ( $yearUl.get( 0 ).scrollHeight - scrollHeight ) + scrollTop );
  684. this.artificialScrolling = false;
  685. } else if ( bottomPercentage > 90 ) {
  686. start = parseInt( $yearUl.find( 'li:last' ).attr( 'data-year' ), 10 );
  687. for ( i = ( start + 1 ); i < ( start + 11 ); i++ ) {
  688. $yearUl.append( '<li data-year="' + i + '"><button type="button">' + i + '</button></li>' );
  689. }
  690. }
  691. },
  692. //some code ripped from http://stackoverflow.com/questions/2182246/javascript-dates-in-ie-nan-firefox-chrome-ok
  693. parseDate: function( date ) {
  694. var self = this;
  695. var BAD_DATE = new Date( NaN );
  696. var dt, isoExp, momentParse, momentParseWithFormat, tryMomentParseAll, month, parts, use;
  697. if ( date ) {
  698. if ( this.moment ) { //if we have moment, use that to parse the dates
  699. momentParseWithFormat = function( d ) {
  700. var md = moment( d, self.momentFormat );
  701. return ( true === md.isValid() ) ? md.toDate() : BAD_DATE;
  702. };
  703. momentParse = function( d ) {
  704. var md = moment( new Date( d ) );
  705. return ( true === md.isValid() ) ? md.toDate() : BAD_DATE;
  706. };
  707. tryMomentParseAll = function( d, parseFunc1, parseFunc2 ) {
  708. var pd = parseFunc1( d );
  709. if ( !self.isInvalidDate( pd ) ) {
  710. return pd;
  711. }
  712. pd = parseFunc2( pd );
  713. if ( !self.isInvalidDate( pd ) ) {
  714. return pd;
  715. }
  716. return BAD_DATE;
  717. };
  718. if ( 'string' === typeof( date ) ) {
  719. // Attempts to parse date strings using this.momentFormat, falling back on newing a date
  720. return tryMomentParseAll( date, momentParseWithFormat, momentParse );
  721. } else {
  722. // Attempts to parse date by newing a date object directly, falling back on parsing using this.momentFormat
  723. return tryMomentParseAll( date, momentParse, momentParseWithFormat );
  724. }
  725. } else { //if moment isn't present, use previous date parsing strategy
  726. if ( typeof( date ) === 'string' ) {
  727. dt = new Date( Date.parse( date ) );
  728. if ( !this.isInvalidDate( dt ) ) {
  729. return dt;
  730. } else {
  731. date = date.split( 'T' )[ 0 ];
  732. isoExp = /^\s*(\d{4})-(\d\d)-(\d\d)\s*$/;
  733. parts = isoExp.exec( date );
  734. if ( parts ) {
  735. month = parseInt( parts[ 2 ], 10 );
  736. dt = new Date( parts[ 1 ], month - 1, parts[ 3 ] );
  737. if ( month === ( dt.getMonth() + 1 ) ) {
  738. return dt;
  739. }
  740. }
  741. }
  742. } else {
  743. dt = new Date( date );
  744. if ( !this.isInvalidDate( dt ) ) {
  745. return dt;
  746. }
  747. }
  748. }
  749. }
  750. return new Date( NaN );
  751. },
  752. prev: function() {
  753. var month = this.$headerTitle.attr( 'data-month' );
  754. var year = this.$headerTitle.attr( 'data-year' );
  755. month--;
  756. if ( month < 0 ) {
  757. if ( this.sameYearOnly ) {
  758. return;
  759. }
  760. month = 11;
  761. year--;
  762. }
  763. this.renderMonth( new Date( year, month, 1 ) );
  764. },
  765. renderMonth: function( date ) {
  766. date = date || new Date();
  767. var firstDay = new Date( date.getFullYear(), date.getMonth(), 1 ).getDay();
  768. var lastDate = new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
  769. var lastMonthDate = new Date( date.getFullYear(), date.getMonth(), 0 ).getDate();
  770. var $month = this.$headerTitle.find( '.month' );
  771. var month = date.getMonth();
  772. var now = new Date();
  773. var nowDate = now.getDate();
  774. var nowMonth = now.getMonth();
  775. var nowYear = now.getFullYear();
  776. var selected = this.selectedDate;
  777. var $tbody = this.$days.find( 'tbody' );
  778. var year = date.getFullYear();
  779. var curDate, curMonth, curYear, i, j, rows, stage, $td, $tr;
  780. if ( selected ) {
  781. selected = {
  782. date: selected.getDate(),
  783. month: selected.getMonth(),
  784. year: selected.getFullYear()
  785. };
  786. }
  787. $month.find( '.current' ).removeClass( 'current' );
  788. $month.find( 'span[data-month="' + month + '"]' ).addClass( 'current' );
  789. this.$headerTitle.find( '.year' ).text( year );
  790. this.$headerTitle.attr( {
  791. 'data-month': month,
  792. 'data-year': year
  793. } );
  794. $tbody.empty();
  795. if ( firstDay !== 0 ) {
  796. curDate = lastMonthDate - firstDay + 1;
  797. stage = -1;
  798. } else {
  799. curDate = 1;
  800. stage = 0;
  801. }
  802. rows = ( lastDate <= ( 35 - firstDay ) ) ? 5 : 6;
  803. for ( i = 0; i < rows; i++ ) {
  804. $tr = $( '<tr></tr>' );
  805. for ( j = 0; j < 7; j++ ) {
  806. $td = $( '<td></td>' );
  807. if ( stage === -1 ) {
  808. $td.addClass( 'last-month' );
  809. } else if ( stage === 1 ) {
  810. $td.addClass( 'next-month' );
  811. }
  812. curMonth = month + stage;
  813. curYear = year;
  814. if ( curMonth < 0 ) {
  815. curMonth = 11;
  816. curYear--;
  817. } else if ( curMonth > 11 ) {
  818. curMonth = 0;
  819. curYear++;
  820. }
  821. $td.attr( {
  822. 'data-date': curDate,
  823. 'data-month': curMonth,
  824. 'data-year': curYear
  825. } );
  826. if ( curYear === nowYear && curMonth === nowMonth && curDate === nowDate ) {
  827. $td.addClass( 'current-day' );
  828. } else if ( curYear < nowYear || ( curYear === nowYear && curMonth < nowMonth ) ||
  829. ( curYear === nowYear && curMonth === nowMonth && curDate < nowDate ) ) {
  830. $td.addClass( 'past' );
  831. if ( !this.options.allowPastDates ) {
  832. $td.addClass( 'restricted' ).attr( 'title', this.restrictedText );
  833. }
  834. }
  835. if ( this.isRestricted( curDate, curMonth, curYear ) ) {
  836. $td.addClass( 'restricted' ).attr( 'title', this.restrictedText );
  837. }
  838. if ( selected && curYear === selected.year && curMonth === selected.month && curDate === selected.date ) {
  839. $td.addClass( 'selected' );
  840. }
  841. if ( $td.hasClass( 'restricted' ) ) {
  842. $td.html( '<span><b class="datepicker-date">' + curDate + '</b></span>' );
  843. } else {
  844. $td.html( '<span><button type="button" class="datepicker-date">' + curDate + '</button></span>' );
  845. }
  846. curDate++;
  847. if ( stage === -1 && curDate > lastMonthDate ) {
  848. curDate = 1;
  849. stage = 0;
  850. } else if ( stage === 0 && curDate > lastDate ) {
  851. curDate = 1;
  852. stage = 1;
  853. }
  854. $tr.append( $td );
  855. }
  856. $tbody.append( $tr );
  857. }
  858. },
  859. renderWheel: function( date ) {
  860. var month = date.getMonth();
  861. var $monthUl = this.$wheelsMonth.find( 'ul' );
  862. var year = date.getFullYear();
  863. var $yearUl = this.$wheelsYear.find( 'ul' );
  864. var i, $monthSelected, $yearSelected;
  865. if ( this.sameYearOnly ) {
  866. this.$wheelsMonth.addClass( 'full' );
  867. this.$wheelsYear.addClass( 'hide' );
  868. } else {
  869. this.$wheelsMonth.removeClass( 'full' );
  870. this.$wheelsYear.removeClass( 'hide' );
  871. }
  872. $monthUl.find( '.selected' ).removeClass( 'selected' );
  873. $monthSelected = $monthUl.find( 'li[data-month="' + month + '"]' );
  874. $monthSelected.addClass( 'selected' );
  875. $monthUl.scrollTop( $monthUl.scrollTop() + ( $monthSelected.position().top - $monthUl.outerHeight() / 2 - $monthSelected.outerHeight( true ) / 2 ) );
  876. $yearUl.empty();
  877. for ( i = ( year - 10 ); i < ( year + 11 ); i++ ) {
  878. $yearUl.append( '<li data-year="' + i + '"><button type="button">' + i + '</button></li>' );
  879. }
  880. $yearSelected = $yearUl.find( 'li[data-year="' + year + '"]' );
  881. $yearSelected.addClass( 'selected' );
  882. this.artificialScrolling = true;
  883. $yearUl.scrollTop( $yearUl.scrollTop() + ( $yearSelected.position().top - $yearUl.outerHeight() / 2 - $yearSelected.outerHeight( true ) / 2 ) );
  884. this.artificialScrolling = false;
  885. $monthSelected.find( 'button' ).focus();
  886. },
  887. selectClicked: function() {
  888. var month = this.$wheelsMonth.find( '.selected' ).attr( 'data-month' );
  889. var year = this.$wheelsYear.find( '.selected' ).attr( 'data-year' );
  890. this.changeView( 'calendar', new Date( year, month, 1 ) );
  891. },
  892. setCulture: function( cultureCode ) {
  893. if ( !cultureCode ) {
  894. return false;
  895. }
  896. if ( this.moment ) {
  897. moment.locale( cultureCode );
  898. } else {
  899. throw MOMENT_NOT_AVAILABLE;
  900. }
  901. },
  902. setDate: function( date ) {
  903. var parsed = this.parseDate( date );
  904. if ( !this.isInvalidDate( parsed ) ) {
  905. if ( !this.isRestricted( parsed.getDate(), parsed.getMonth(), parsed.getFullYear() ) ) {
  906. this.selectedDate = parsed;
  907. this.renderMonth( parsed );
  908. this.$input.val( this.formatDate( parsed ) );
  909. } else {
  910. this.selectedDate = false;
  911. this.renderMonth();
  912. }
  913. } else {
  914. this.selectedDate = null;
  915. this.renderMonth();
  916. }
  917. this.inputValue = this.$input.val();
  918. return this.selectedDate;
  919. },
  920. setFormat: function( format ) {
  921. if ( !format ) {
  922. return false;
  923. }
  924. if ( this.moment ) {
  925. this.momentFormat = format;
  926. } else {
  927. throw MOMENT_NOT_AVAILABLE;
  928. }
  929. },
  930. setRestrictedDates: function( restricted ) {
  931. var parsed = [];
  932. var self = this;
  933. var i, l;
  934. var parseItem = function( val ) {
  935. if ( val === -Infinity ) {
  936. return {
  937. date: -Infinity,
  938. month: -Infinity,
  939. year: -Infinity
  940. };
  941. } else if ( val === Infinity ) {
  942. return {
  943. date: Infinity,
  944. month: Infinity,
  945. year: Infinity
  946. };
  947. } else {
  948. val = self.parseDate( val );
  949. return {
  950. date: val.getDate(),
  951. month: val.getMonth(),
  952. year: val.getFullYear()
  953. };
  954. }
  955. };
  956. this.restricted = restricted;
  957. for ( i = 0, l = restricted.length; i < l; i++ ) {
  958. parsed.push( {
  959. from: parseItem( restricted[ i ].from ),
  960. to: parseItem( restricted[ i ].to )
  961. } );
  962. }
  963. this.restrictedParsed = parsed;
  964. },
  965. titleClicked: function( e ) {
  966. this.changeView( 'wheels', new Date( this.$headerTitle.attr( 'data-year' ), this.$headerTitle.attr( 'data-month' ), 1 ) );
  967. },
  968. todayClicked: function( e ) {
  969. var date = new Date();
  970. if ( ( date.getMonth() + '' ) !== this.$headerTitle.attr( 'data-month' ) || ( date.getFullYear() + '' ) !== this.$headerTitle.attr( 'data-year' ) ) {
  971. this.renderMonth( date );
  972. }
  973. },
  974. yearClicked: function( e ) {
  975. this.$wheelsYear.find( '.selected' ).removeClass( 'selected' );
  976. $( e.currentTarget ).parent().addClass( 'selected' );
  977. }
  978. };
  979. // DATEPICKER PLUGIN DEFINITION
  980. $.fn.datepicker = function( option ) {
  981. var args = Array.prototype.slice.call( arguments, 1 );
  982. var methodReturn;
  983. var $set = this.each( function() {
  984. var $this = $( this );
  985. var data = $this.data( 'fu.datepicker' );
  986. var options = typeof option === 'object' && option;
  987. if ( !data ) $this.data( 'fu.datepicker', ( data = new Datepicker( this, options ) ) );
  988. if ( typeof option === 'string' ) methodReturn = data[ option ].apply( data, args );
  989. } );
  990. return ( methodReturn === undefined ) ? $set : methodReturn;
  991. };
  992. $.fn.datepicker.defaults = {
  993. allowPastDates: false,
  994. date: new Date(),
  995. formatDate: null,
  996. momentConfig: {
  997. culture: 'en',
  998. format: 'L' // more formats can be found here http://momentjs.com/docs/#/customization/long-date-formats/.
  999. },
  1000. parseDate: null,
  1001. 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') } ])
  1002. restrictedText: 'Restricted',
  1003. sameYearOnly: false
  1004. };
  1005. $.fn.datepicker.Constructor = Datepicker;
  1006. $.fn.datepicker.noConflict = function() {
  1007. $.fn.datepicker = old;
  1008. return this;
  1009. };
  1010. // DATA-API
  1011. $( document ).on( 'mousedown.fu.datepicker.data-api', '[data-initialize=datepicker]', function( e ) {
  1012. var $control = $( e.target ).closest( '.datepicker' );
  1013. if ( !$control.data( 'datepicker' ) ) {
  1014. $control.datepicker( $control.data() );
  1015. }
  1016. } );
  1017. //used to prevent the dropdown from closing when clicking within it's bounds
  1018. $( document ).on( 'click.fu.datepicker.data-api', '.datepicker .dropdown-menu', function( e ) {
  1019. var $target = $( e.target );
  1020. if ( !$target.is( '.datepicker-date' ) || $target.closest( '.restricted' ).length ) {
  1021. e.stopPropagation();
  1022. }
  1023. } );
  1024. //used to prevent the dropdown from closing when clicking on the input
  1025. $( document ).on( 'click.fu.datepicker.data-api', '.datepicker input', function( e ) {
  1026. e.stopPropagation();
  1027. } );
  1028. $( function() {
  1029. $( '[data-initialize=datepicker]' ).each( function() {
  1030. var $this = $( this );
  1031. if ( $this.data( 'datepicker' ) ) {
  1032. return;
  1033. }
  1034. $this.datepicker( $this.data() );
  1035. } );
  1036. } );
  1037. } )( jQuery );
  1038. ( function( $ ) {
  1039. /*
  1040. * Fuel UX Dropdown Auto Flip
  1041. * https://github.com/ExactTarget/fuelux
  1042. *
  1043. * Copyright (c) 2014 ExactTarget
  1044. * Licensed under the BSD New license.
  1045. */
  1046. // -- BEGIN MODULE CODE HERE --
  1047. $( document.body ).on( 'click.fu.dropdown-autoflip', '[data-toggle=dropdown][data-flip]', function( event ) {
  1048. if ( $( this ).data().flip === "auto" ) {
  1049. // have the drop down decide where to place itself
  1050. _autoFlip( $( this ).next( '.dropdown-menu' ) );
  1051. }
  1052. } );
  1053. // For pillbox suggestions dropdown
  1054. $( document.body ).on( 'suggested.fu.pillbox', function( event, element ) {
  1055. _autoFlip( $( element ) );
  1056. $( element ).parent().addClass( 'open' );
  1057. } );
  1058. function _autoFlip( menu ) {
  1059. // hide while the browser thinks
  1060. $( menu ).css( {
  1061. visibility: "hidden"
  1062. } );
  1063. // decide where to put menu
  1064. if ( dropUpCheck( menu ) ) {
  1065. menu.parent().addClass( "dropup" );
  1066. } else {
  1067. menu.parent().removeClass( "dropup" );
  1068. }
  1069. // show again
  1070. $( menu ).css( {
  1071. visibility: "visible"
  1072. } );
  1073. }
  1074. function dropUpCheck( element ) {
  1075. // caching container
  1076. var $container = _getContainer( element );
  1077. // building object with measurementsances for later use
  1078. var measurements = {};
  1079. measurements.parentHeight = element.parent().outerHeight();
  1080. measurements.parentOffsetTop = element.parent().offset().top;
  1081. measurements.dropdownHeight = element.outerHeight();
  1082. measurements.containerHeight = $container.overflowElement.outerHeight();
  1083. // this needs to be different if the window is the container or another element is
  1084. measurements.containerOffsetTop = ( !!$container.isWindow ) ? $container.overflowElement.scrollTop() : $container.overflowElement.offset().top;
  1085. // doing the calculations
  1086. measurements.fromTop = measurements.parentOffsetTop - measurements.containerOffsetTop;
  1087. measurements.fromBottom = measurements.containerHeight - measurements.parentHeight - ( measurements.parentOffsetTop - measurements.containerOffsetTop );
  1088. // actual determination of where to put menu
  1089. // false = drop down
  1090. // true = drop up
  1091. if ( measurements.dropdownHeight < measurements.fromBottom ) {
  1092. return false;
  1093. } else if ( measurements.dropdownHeight < measurements.fromTop ) {
  1094. return true;
  1095. } else if ( measurements.dropdownHeight >= measurements.fromTop && measurements.dropdownHeight >= measurements.fromBottom ) {
  1096. // decide which one is bigger and put it there
  1097. if ( measurements.fromTop >= measurements.fromBottom ) {
  1098. return true;
  1099. } else {
  1100. return false;
  1101. }
  1102. }
  1103. }
  1104. function _getContainer( element ) {
  1105. var containerElement, isWindow;
  1106. if ( element.attr( 'data-target' ) ) {
  1107. containerElement = element.attr( 'data-target' );
  1108. isWindow = false;
  1109. } else {
  1110. containerElement = window;
  1111. isWindow = true;
  1112. }
  1113. $.each( element.parents(), function( index, value ) {
  1114. if ( $( value ).css( 'overflow' ) !== 'visible' ) {
  1115. containerElement = value;
  1116. isWindow = false;
  1117. return false;
  1118. }
  1119. } );
  1120. return {
  1121. overflowElement: $( containerElement ),
  1122. isWindow: isWindow
  1123. };
  1124. }
  1125. // register empty plugin
  1126. $.fn.dropdownautoflip = function() { /* empty */ };
  1127. } )( jQuery );
  1128. ( function( $ ) {
  1129. /*
  1130. * Fuel UX Loader
  1131. * https://github.com/ExactTarget/fuelux
  1132. *
  1133. * Copyright (c) 2014 ExactTarget
  1134. * Licensed under the BSD New license.
  1135. */
  1136. // -- BEGIN MODULE CODE HERE --
  1137. var old = $.fn.loader;
  1138. // LOADER CONSTRUCTOR AND PROTOTYPE
  1139. var Loader = function( element, options ) {
  1140. this.$element = $( element );
  1141. this.options = $.extend( {}, $.fn.loader.defaults, options );
  1142. this.begin = ( this.$element.is( '[data-begin]' ) ) ? parseInt( this.$element.attr( 'data-begin' ), 10 ) : 1;
  1143. this.delay = ( this.$element.is( '[data-delay]' ) ) ? parseFloat( this.$element.attr( 'data-delay' ) ) : 150;
  1144. this.end = ( this.$element.is( '[data-end]' ) ) ? parseInt( this.$element.attr( 'data-end' ), 10 ) : 8;
  1145. this.frame = ( this.$element.is( '[data-frame]' ) ) ? parseInt( this.$element.attr( 'data-frame' ), 10 ) : this.begin;
  1146. this.isIElt9 = false;
  1147. this.timeout = {};
  1148. var ieVer = this.msieVersion();
  1149. if ( ieVer !== false && ieVer < 9 ) {
  1150. this.$element.addClass( 'iefix' );
  1151. this.isIElt9 = true;
  1152. }
  1153. this.$element.attr( 'data-frame', this.frame + '' );
  1154. this.play();
  1155. };
  1156. Loader.prototype = {
  1157. constructor: Loader,
  1158. destroy: function() {
  1159. this.$element.remove();
  1160. // any external bindings
  1161. // [none]
  1162. // empty elements to return to original markup
  1163. // [none]
  1164. // returns string of markup
  1165. return this.$element[ 0 ].outerHTML;
  1166. },
  1167. ieRepaint: function() {
  1168. if ( this.isIElt9 ) {
  1169. this.$element.addClass( 'iefix_repaint' ).removeClass( 'iefix_repaint' );
  1170. }
  1171. },
  1172. msieVersion: function() {
  1173. var ua = window.navigator.userAgent;
  1174. var msie = ua.indexOf( 'MSIE ' );
  1175. if ( msie > 0 ) {
  1176. return parseInt( ua.substring( msie + 5, ua.indexOf( ".", msie ) ), 10 );
  1177. } else {
  1178. return false;
  1179. }
  1180. },
  1181. next: function() {
  1182. this.frame++;
  1183. if ( this.frame > this.end ) {
  1184. this.frame = this.begin;
  1185. }
  1186. this.$element.attr( 'data-frame', this.frame + '' );
  1187. this.ieRepaint();
  1188. },
  1189. pause: function() {
  1190. clearTimeout( this.timeout );
  1191. },
  1192. play: function() {
  1193. var self = this;
  1194. clearTimeout( this.timeout );
  1195. this.timeout = setTimeout( function() {
  1196. self.next();
  1197. self.play();
  1198. }, this.delay );
  1199. },
  1200. previous: function() {
  1201. this.frame--;
  1202. if ( this.frame < this.begin ) {
  1203. this.frame = this.end;
  1204. }
  1205. this.$element.attr( 'data-frame', this.frame + '' );
  1206. this.ieRepaint();
  1207. },
  1208. reset: function() {
  1209. this.frame = this.begin;
  1210. this.$element.attr( 'data-frame', this.frame + '' );
  1211. this.ieRepaint();
  1212. }
  1213. };
  1214. // LOADER PLUGIN DEFINITION
  1215. $.fn.loader = function( option ) {
  1216. var args = Array.prototype.slice.call( arguments, 1 );
  1217. var methodReturn;
  1218. var $set = this.each( function() {
  1219. var $this = $( this );
  1220. var data = $this.data( 'fu.loader' );
  1221. var options = typeof option === 'object' && option;
  1222. if ( !data ) $this.data( 'fu.loader', ( data = new Loader( this, options ) ) );
  1223. if ( typeof option === 'string' ) methodReturn = data[ option ].apply( data, args );
  1224. } );
  1225. return ( methodReturn === undefined ) ? $set : methodReturn;
  1226. };
  1227. $.fn.loader.defaults = {};
  1228. $.fn.loader.Constructor = Loader;
  1229. $.fn.loader.noConflict = function() {
  1230. $.fn.loader = old;
  1231. return this;
  1232. };
  1233. // INIT LOADER ON DOMCONTENTLOADED
  1234. $( function() {
  1235. $( '[data-initialize=loader]' ).each( function() {
  1236. var $this = $( this );
  1237. if ( !$this.data( 'fu.loader' ) ) {
  1238. $this.loader( $this.data() );
  1239. }
  1240. } );
  1241. } );
  1242. } )( jQuery );
  1243. ( function( $ ) {
  1244. /*
  1245. * Fuel UX Placard
  1246. * https://github.com/ExactTarget/fuelux
  1247. *
  1248. * Copyright (c) 2014 ExactTarget
  1249. * Licensed under the BSD New license.
  1250. */
  1251. // -- BEGIN MODULE CODE HERE --
  1252. var old = $.fn.placard;
  1253. // PLACARD CONSTRUCTOR AND PROTOTYPE
  1254. var Placard = function( element, options ) {
  1255. var self = this;
  1256. this.$element = $( element );
  1257. this.options = $.extend( {}, $.fn.placard.defaults, options );
  1258. this.$accept = this.$element.find( '.placard-accept' );
  1259. this.$cancel = this.$element.find( '.placard-cancel' );
  1260. this.$field = this.$element.find( '.placard-field' );
  1261. this.$footer = this.$element.find( '.placard-footer' );
  1262. this.$header = this.$element.find( '.placard-header' );
  1263. this.$popup = this.$element.find( '.placard-popup' );
  1264. this.actualValue = null;
  1265. this.clickStamp = '_';
  1266. this.previousValue = '';
  1267. if ( this.options.revertOnCancel === -1 ) {
  1268. this.options.revertOnCancel = ( this.$accept.length > 0 ) ? true : false;
  1269. }
  1270. this.$field.on( 'focus.fu.placard', $.proxy( this.show, this ) );
  1271. this.$accept.on( 'click.fu.placard', $.proxy( this.complete, this, 'accept' ) );
  1272. this.$cancel.on( 'click.fu.placard', function( e ) {
  1273. e.preventDefault();
  1274. self.complete( 'cancel' );
  1275. } );
  1276. this.ellipsis();
  1277. };
  1278. Placard.prototype = {
  1279. constructor: Placard,
  1280. complete: function( action ) {
  1281. var func = this.options[ 'on' + action[ 0 ].toUpperCase() + action.substring( 1 ) ];
  1282. var obj = {
  1283. previousValue: this.previousValue,
  1284. value: this.$field.val()
  1285. };
  1286. if ( func ) {
  1287. func( obj );
  1288. this.$element.trigger( action, obj );
  1289. } else {
  1290. if ( action === 'cancel' && this.options.revertOnCancel ) {
  1291. this.$field.val( this.previousValue );
  1292. }
  1293. this.$element.trigger( action, obj );
  1294. this.hide();
  1295. }
  1296. },
  1297. destroy: function() {
  1298. this.$element.remove();
  1299. // remove any external bindings
  1300. $( document ).off( 'click.fu.placard.externalClick.' + this.clickStamp );
  1301. // set input value attrbute
  1302. this.$element.find( 'input' ).each( function() {
  1303. $( this ).attr( 'value', $( this ).val() );
  1304. } );
  1305. // empty elements to return to original markup
  1306. // [none]
  1307. // return string of markup
  1308. return this.$element[ 0 ].outerHTML;
  1309. },
  1310. disable: function() {
  1311. this.$element.addClass( 'disabled' );
  1312. this.$field.attr( 'disabled', 'disabled' );
  1313. this.hide();
  1314. },
  1315. ellipsis: function() {
  1316. var field, i, str;
  1317. if ( this.$element.attr( 'data-ellipsis' ) === 'true' ) {
  1318. field = this.$field.get( 0 );
  1319. if ( this.$field.is( 'input' ) ) {
  1320. field.scrollLeft = 0;
  1321. } else {
  1322. field.scrollTop = 0;
  1323. if ( field.clientHeight < field.scrollHeight ) {
  1324. this.actualValue = this.$field.val();
  1325. this.$field.val( '' );
  1326. str = '';
  1327. i = 0;
  1328. while ( field.clientHeight >= field.scrollHeight ) {
  1329. str += this.actualValue[ i ];
  1330. this.$field.val( str + '...' );
  1331. i++;
  1332. }
  1333. str = ( str.length > 0 ) ? str.substring( 0, str.length - 1 ) : '';
  1334. this.$field.val( str + '...' );
  1335. }
  1336. }
  1337. }
  1338. },
  1339. enable: function() {
  1340. this.$element.removeClass( 'disabled' );
  1341. this.$field.removeAttr( 'disabled' );
  1342. },
  1343. externalClickListener: function( e, force ) {
  1344. if ( force === true || this.isExternalClick( e ) ) {
  1345. this.complete( this.options.externalClickAction );
  1346. }
  1347. },
  1348. getValue: function() {
  1349. if ( this.actualValue !== null ) {
  1350. return this.actualValue;
  1351. } else {
  1352. return this.$field.val();
  1353. }
  1354. },
  1355. hide: function() {
  1356. if ( !this.$element.hasClass( 'showing' ) ) {
  1357. return;
  1358. }
  1359. this.$element.removeClass( 'showing' );
  1360. this.ellipsis();
  1361. $( document ).off( 'click.fu.placard.externalClick.' + this.clickStamp );
  1362. this.$element.trigger( 'hidden.fu.placard' );
  1363. },
  1364. isExternalClick: function( e ) {
  1365. var el = this.$element.get( 0 );
  1366. var exceptions = this.options.externalClickExceptions || [];
  1367. var $originEl = $( e.target );
  1368. var i, l;
  1369. if ( e.target === el || $originEl.parents( '.placard:first' ).get( 0 ) === el ) {
  1370. return false;
  1371. } else {
  1372. for ( i = 0, l = exceptions.length; i < l; i++ ) {
  1373. if ( $originEl.is( exceptions[ i ] ) || $originEl.parents( exceptions[ i ] ).length > 0 ) {
  1374. return false;
  1375. }
  1376. }
  1377. }
  1378. return true;
  1379. },
  1380. setValue: function( val ) {
  1381. this.$field.val( val );
  1382. if ( !this.$element.hasClass( 'showing' ) ) {
  1383. this.ellipsis();
  1384. }
  1385. },
  1386. show: function() {
  1387. var other;
  1388. if ( this.$element.hasClass( 'showing' ) ) {
  1389. return;
  1390. }
  1391. other = $( document ).find( '.placard.showing' );
  1392. if ( other.length > 0 ) {
  1393. if ( other.data( 'fu.placard' ) && other.data( 'fu.placard' ).options.explicit ) {
  1394. return;
  1395. }
  1396. other.placard( 'externalClickListener', {}, true );
  1397. }
  1398. this.previousValue = this.$field.val();
  1399. this.$element.addClass( 'showing' );
  1400. if ( this.actualValue !== null ) {
  1401. this.$field.val( this.actualValue );
  1402. this.actualValue = null;
  1403. }
  1404. if ( this.$header.length > 0 ) {
  1405. this.$popup.css( 'top', '-' + this.$header.outerHeight( true ) + 'px' );
  1406. }
  1407. if ( this.$footer.length > 0 ) {
  1408. this.$popup.css( 'bottom', '-' + this.$footer.outerHeight( true ) + 'px' );
  1409. }
  1410. this.$element.trigger( 'shown.fu.placard' );
  1411. this.clickStamp = new Date().getTime() + ( Math.floor( Math.random() * 100 ) + 1 );
  1412. if ( !this.options.explicit ) {
  1413. $( document ).on( 'click.fu.placard.externalClick.' + this.clickStamp, $.proxy( this.externalClickListener, this ) );
  1414. }
  1415. }
  1416. };
  1417. // PLACARD PLUGIN DEFINITION
  1418. $.fn.placard = function( option ) {
  1419. var args = Array.prototype.slice.call( arguments, 1 );
  1420. var methodReturn;
  1421. var $set = this.each( function() {
  1422. var $this = $( this );
  1423. var data = $this.data( 'fu.placard' );
  1424. var options = typeof option === 'object' && option;
  1425. if ( !data ) $this.data( 'fu.placard', ( data = new Placard( this, options ) ) );
  1426. if ( typeof option === 'string' ) methodReturn = data[ option ].apply( data, args );
  1427. } );
  1428. return ( methodReturn === undefined ) ? $set : methodReturn;
  1429. };
  1430. $.fn.placard.defaults = {
  1431. onAccept: undefined,
  1432. onCancel: undefined,
  1433. externalClickAction: 'cancel',
  1434. externalClickExceptions: [],
  1435. explicit: false,
  1436. revertOnCancel: -1 //negative 1 will check for an '.placard-accept' button. Also can be set to true or false
  1437. };
  1438. $.fn.placard.Constructor = Placard;
  1439. $.fn.placard.noConflict = function() {
  1440. $.fn.placard = old;
  1441. return this;
  1442. };
  1443. // DATA-API
  1444. $( document ).on( 'focus.fu.placard.data-api', '[data-initialize=placard]', function( e ) {
  1445. var $control = $( e.target ).closest( '.placard' );
  1446. if ( !$control.data( 'fu.placard' ) ) {
  1447. $control.placard( $control.data() );
  1448. }
  1449. } );
  1450. // Must be domReady for AMD compatibility
  1451. $( function() {
  1452. $( '[data-initialize=placard]' ).each( function() {
  1453. var $this = $( this );
  1454. if ( $this.data( 'fu.placard' ) ) return;
  1455. $this.placard( $this.data() );
  1456. } );
  1457. } );
  1458. } )( jQuery );
  1459. ( function( $ ) {
  1460. /*
  1461. * Fuel UX Radio
  1462. * https://github.com/ExactTarget/fuelux