fuelux
Version:
Base Fuel UX styles and controls
296 lines (236 loc) • 7.67 kB
JavaScript
/* global jQuery:true */
/*
* Fuel UX Picker
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN UMD WRAPPER PREFACE --
// For more information on UMD visit:
// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
(function umdFactory (factory) {
if (typeof define === 'function' && define.amd) {
// if AMD loader is available, register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
module.exports = factory(require('jquery'));
} else {
// OR use browser globals if AMD is not present
factory(jQuery);
}
}(function PickerWrapper ($) {
// -- END UMD WRAPPER PREFACE --
// -- BEGIN MODULE CODE HERE --
var old = $.fn.picker;
// PLACARD CONSTRUCTOR AND PROTOTYPE
var Picker = function Picker(element, options) {
var self = this;
this.$element = $(element);
this.options = $.extend({}, $.fn.picker.defaults, options);
this.$accept = this.$element.find('.picker-accept');
this.$cancel = this.$element.find('.picker-cancel');
this.$trigger = this.$element.find('.picker-trigger');
this.$footer = this.$element.find('.picker-footer');
this.$header = this.$element.find('.picker-header');
this.$popup = this.$element.find('.picker-popup');
this.$body = this.$element.find('.picker-body');
this.clickStamp = '_';
this.isInput = this.$trigger.is('input');
this.$trigger.on('keydown.fu.picker', $.proxy(this.keyComplete, this));
this.$trigger.on('focus.fu.picker', $.proxy(function inputFocus(e){
if(typeof e === "undefined" || $(e.target).is('input[type=text]')){
$.proxy(this.show(), this);
}
}, this));
this.$trigger.on('click.fu.picker', $.proxy(function triggerClick(e){
if(!$(e.target).is('input[type=text]')){
$.proxy(this.toggle(), this);
}else{
$.proxy(this.show(), this);
}
}, this));
this.$accept.on('click.fu.picker', $.proxy(this.complete, this, 'accepted'));
this.$cancel.on('click.fu.picker', function (e) {
e.preventDefault(); self.complete('cancelled');
});
};
var _isOffscreen = function _isOffscreen(picker) {
var windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
var scrollTop = $(document).scrollTop();
var popupTop = picker.$popup.offset();
var popupBottom = popupTop.top + picker.$popup.outerHeight(true);
//if the bottom of the popup goes off the page, but the top does not, dropup.
if (popupBottom > windowHeight + scrollTop || popupTop.top < scrollTop){
return true;
}else{//otherwise, prefer showing the top of the popup only vs the bottom
return false;
}
};
var _display = function _display(picker) {
picker.$popup.css('visibility', 'hidden');
_showBelow(picker);
//if part of the popup is offscreen try to show it above
if(_isOffscreen(picker)){
_showAbove(picker);
//if part of the popup is still offscreen, prefer cutting off the bottom
if(_isOffscreen(picker)){
_showBelow(picker);
}
}
picker.$popup.css('visibility', 'visible');
};
var _showAbove = function _showAbove(picker) {
picker.$popup.css('top', - picker.$popup.outerHeight(true) + 'px');
};
var _showBelow = function _showBelow(picker) {
picker.$popup.css('top', picker.$trigger.outerHeight(true) + 'px');
};
Picker.prototype = {
constructor: Picker,
complete: function complete(action) {
var EVENT_CALLBACK_MAP = {
'accepted': 'onAccept',
'cancelled': 'onCancel',
'exited': 'onExit'
};
var func = this.options[ EVENT_CALLBACK_MAP[action] ];
var obj = {
contents: this.$body
};
if (func) {
func(obj);
this.$element.trigger(action + '.fu.picker', obj);
} else {
this.$element.trigger(action + '.fu.picker', obj);
this.hide();
}
},
keyComplete: function keyComplete(e) {
if (this.isInput && e.keyCode === 13) {
this.complete('accepted');
this.$trigger.blur();
} else if (e.keyCode === 27) {
this.complete('exited');
this.$trigger.blur();
}
},
destroy: function destroy() {
this.$element.remove();
// remove any external bindings
$(document).off('click.fu.picker.externalClick.' + this.clickStamp);
// 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.$trigger.attr('disabled', 'disabled');
},
enable: function enable() {
this.$element.removeClass('disabled');
this.$trigger.removeAttr('disabled');
},
toggle: function toggle() {
if (this.$element.hasClass('showing')) {
this.hide();
}else{
this.show();
}
},
hide: function hide() {
if (!this.$element.hasClass('showing')) {
return;
}
this.$element.removeClass('showing');
$(document).off('click.fu.picker.externalClick.' + this.clickStamp);
this.$element.trigger('hidden.fu.picker');
},
externalClickListener: function externalClickListener(e, force) {
if (force === true || this.isExternalClick(e)) {
this.complete('exited');
}
},
isExternalClick: function isExternalClick(e) {
var el = this.$element.get(0);
var exceptions = this.options.externalClickExceptions || [];
var $originEl = $(e.target);
var i, l;
if (e.target === el || $originEl.parents('.picker:first').get(0) === el) {
return false;
} else {
for (i = 0, l = exceptions.length; i < l; i++) {
if ($originEl.is(exceptions[i]) || $originEl.parents(exceptions[i]).length > 0) {
return false;
}
}
}
return true;
},
show: function show() {
var other;
other = $(document).find('.picker.showing');
if (other.length > 0) {
if (other.data('fu.picker') && other.data('fu.picker').options.explicit) {
return;
}
other.picker('externalClickListener', {}, true);
}
this.$element.addClass('showing');
_display(this);
this.$element.trigger('shown.fu.picker');
this.clickStamp = new Date().getTime() + (Math.floor(Math.random() * 100) + 1);
if (!this.options.explicit) {
$(document).on('click.fu.picker.externalClick.' + this.clickStamp, $.proxy(this.externalClickListener, this));
}
}
};
// PLACARD PLUGIN DEFINITION
$.fn.picker = function picker(option) {
var args = Array.prototype.slice.call(arguments, 1);
var methodReturn;
var $set = this.each(function () {
var $this = $(this);
var data = $this.data('fu.picker');
var options = typeof option === 'object' && option;
if (!data) {
$this.data('fu.picker', (data = new Picker(this, options)));
}
if (typeof option === 'string') {
methodReturn = data[option].apply(data, args);
}
});
return (methodReturn === undefined) ? $set : methodReturn;
};
$.fn.picker.defaults = {
onAccept: undefined,
onCancel: undefined,
onExit: undefined,
externalClickExceptions: [],
explicit: false
};
$.fn.picker.Constructor = Picker;
$.fn.picker.noConflict = function noConflict() {
$.fn.picker = old;
return this;
};
// DATA-API
$(document).on('focus.fu.picker.data-api', '[data-initialize=picker]', function (e) {
var $control = $(e.target).closest('.picker');
if (!$control.data('fu.picker')) {
$control.picker($control.data());
}
});
// Must be domReady for AMD compatibility
$(function () {
$('[data-initialize=picker]').each(function () {
var $this = $(this);
if ($this.data('fu.picker')) return;
$this.picker($this.data());
});
});
// -- BEGIN UMD WRAPPER AFTERWORD --
}));
// -- END UMD WRAPPER AFTERWORD --