$.fn.romoDropdown = function() {
return $.map(this, function(element) {
return new RomoDropdown(element);
});
}
var RomoDropdown = function(element) {
this.elem = $(element);
this.doInitPopup();
this.romoInvoke = this.elem.romoInvoke()[0];
this.romoInvoke.doUnBindInvoke(); // disable auto invoke on click
if (this.elem.data('romo-dropdown-disable-click-invoke') !== true) {
this.elem.unbind('click');
this.elem.on('click', $.proxy(this.onToggleClick, this));
}
this.elem.on('dropdown:triggerToggle', $.proxy(this.onToggleClick, this));
this.elem.on('dropdown:triggerPopupOpen', $.proxy(this.onPopupOpen, this));
this.elem.on('dropdown:triggerPopupClose', $.proxy(this.onPopupClose, this));
this.elem.on('invoke:loadStart', $.proxy(function(e, invoke) {
this.doLoadBodyStart();
return false;
}, this));
this.elem.on('invoke:loadSuccess', $.proxy(function(e, data, invoke) {
this.doLoadBodySuccess(data);
return false;
}, this));
this.elem.on('invoke:loadError', $.proxy(function(e, xhr, invoke) {
this.doLoadBodyError(xhr);
return false;
}, this));
this.doBindElemKeyUp();
this.doInit();
this.doInitBody();
this.elem.trigger('dropdown:ready', [this]);
}
RomoDropdown.prototype.doInit = function() {
// override as needed
}
RomoDropdown.prototype.doInitPopup = function() {
this.popupElem = $('
');
this.popupElem.appendTo(this.elem.closest(this.elem.data('romo-dropdown-append-to-closest') || 'body'));
this.popupElem.on('modal:popupOpen', $.proxy(function(e) {
this.doUnBindWindowBodyClick();
this.doUnBindWindowBodyKeyUp();
this.doUnBindElemKeyUp();
}, this));
this.popupElem.on('modal:popupClose', $.proxy(function(e) {
this.doBindWindowBodyClick();
this.doBindWindowBodyKeyUp();
this.doBindElemKeyUp();
}, this));
this.bodyElem = this.popupElem.find('> .romo-dropdown-body');
if (this.elem.data('romo-dropdown-style-class') !== undefined) {
this.bodyElem.addClass(this.elem.data('romo-dropdown-style-class'));
}
this.contentElem = $();
var positionData = this._parsePositionData(this.elem.data('romo-dropdown-position'));
this.popupPosition = positionData.position || 'bottom';
this.popupAlignment = positionData.alignment || 'left';
this.popupElem.attr('data-romo-dropdown-position', this.popupPosition);
this.popupElem.attr('data-romo-dropdown-alignment', this.popupAlignment);
this.popupElem.attr('data-romo-dropdown-fixed', this.elem.data('romo-dropdown-fixed'));
this.doSetPopupZIndex(this.elem);
// don't propagate click events on the popup elem. this prevents the popup
// from closing when clicked (see body click event bind on popup open)
this.popupElem.on('click', function(e) {
if (e !== undefined) {
e.stopPropagation();
}
})
// the popup should be treated like a child elem. add it to Romo's
// parent-child elems so it will be removed when the elem is removed.
// delay adding it b/c other components may `append` generated dropdowns
// meaning the dropdown is removed and then re-added. if added immediately
// the "remove" part will incorrectly remove the popup.
setTimeout($.proxy(function() {
Romo.parentChildElems.add(this.elem, [this.popupElem]);
}, this), 1);
}
RomoDropdown.prototype.doInitBody = function() {
this.doResetBody();
this.contentElem = this.bodyElem.find('.romo-dropdown-content').last();
if (this.contentElem.size() === 0) {
this.contentElem = this.bodyElem;
}
this.closeElem = this.popupElem.find('[data-romo-dropdown-close="true"]');
this.closeElem.unbind('click');
this.closeElem.on('click', $.proxy(this.onPopupClose, this));
this.contentElem.css({
'min-height': this.elem.data('romo-dropdown-min-height'),
'height': this.elem.data('romo-dropdown-height'),
'overflow-x': this.elem.data('romo-dropdown-overflow-x') || 'auto',
'overflow-y': this.elem.data('romo-dropdown-overflow-y') || 'auto'
});
if (this.elem.data('romo-dropdown-max-height') === undefined) {
this.elem.attr('data-romo-dropdown-max-height', 'detect');
}
if (this.elem.data('romo-dropdown-max-height') !== 'detect') {
this.contentElem.css({
'max-height': this.elem.data('romo-dropdown-max-height')
});
}
if (this.elem.data('romo-dropdown-width') === 'elem') {
this.popupElem.css({
'width': this.elem.css('width')
});
} else {
this.contentElem.css({
'min-width': this.elem.data('romo-dropdown-min-width'),
'max-width': this.elem.data('romo-dropdown-max-width'),
'width': this.elem.data('romo-dropdown-width')
});
}
}
RomoDropdown.prototype.doResetBody = function() {
this.contentElem.css({
'min-width': '',
'max-width': '',
'width': '',
'min-height': '',
'max-height': '',
'height': '',
'overflow-x': '',
'overflow-y': ''
});
}
RomoDropdown.prototype.doLoadBodyStart = function() {
this.bodyElem.html('');
this.doInitBody();
this.doPlacePopupElem();
this.elem.trigger('dropdown:loadBodyStart', [this]);
}
RomoDropdown.prototype.doLoadBodySuccess = function(data) {
Romo.initHtml(this.bodyElem, data);
this.doInitBody();
this.doPlacePopupElem();
this.elem.trigger('dropdown:loadBodySuccess', [data, this]);
}
RomoDropdown.prototype.doLoadBodyError = function(xhr) {
this.elem.trigger('dropdown:loadBodyError', [xhr, this]);
}
RomoDropdown.prototype.onToggleClick = function(e) {
if (e !== undefined) {
e.preventDefault();
}
if (this.elem.hasClass('disabled') === false &&
this.elem.data('romo-dropdown-disable-toggle') !== true) {
this.doToggle();
return true;
}
return false;
}
RomoDropdown.prototype.doToggle = function() {
if (this.popupElem.hasClass('romo-dropdown-open')) {
setTimeout($.proxy(function() {
this.doPopupClose();
}, this), 100);
} else {
setTimeout($.proxy(function() {
this.doPopupOpen();
}, this), 100);
}
this.elem.trigger('dropdown:toggle', [this]);
}
RomoDropdown.prototype.onPopupOpen = function(e) {
if (e !== undefined) {
e.preventDefault();
}
if (this.elem.hasClass('disabled') === false &&
this.popupElem.hasClass('romo-dropdown-open') === false) {
setTimeout($.proxy(function() {
this.doPopupOpen();
}, this), 100);
}
}
RomoDropdown.prototype.doPopupOpen = function() {
if (this.elem.data('romo-dropdown-content-elem') !== undefined) {
this.doLoadBodySuccess($(this.elem.data('romo-dropdown-content-elem')).html())
} else {
this.romoInvoke.doInvoke();
}
this.popupElem.addClass('romo-dropdown-open');
this.doPlacePopupElem();
// bind an event to close the popup when clicking away from the
// popup. Bind on a timeout to allow time for any toggle
// click event to propagate. If no timeout, we'll bind this
// event, then the toggle click will propagate which will call
// this event and immediately close the popup.
setTimeout($.proxy(function() {
this.doBindWindowBodyClick();
}, this), 100);
// bind "esc" keystroke to toggle close
this.doBindWindowBodyKeyUp();
// bind window resizes reposition dropdown
$(window).on('resize', $.proxy(this.onResizeWindow, this));
this.elem.trigger('dropdown:popupOpen', [this]);
}
RomoDropdown.prototype.onPopupClose = function(e) {
if (e !== undefined) {
e.preventDefault();
}
if (this.elem.hasClass('disabled') === false &&
this.popupElem.hasClass('romo-dropdown-open') === true) {
setTimeout($.proxy(function() {
this.doPopupClose();
}, this), 100);
}
}
RomoDropdown.prototype.doPopupClose = function() {
this.popupElem.removeClass('romo-dropdown-open');
// unbind any event to close the popup when clicking away from it
this.doUnBindWindowBodyClick();
// unbind "esc" keystroke to toggle close
this.doUnBindWindowBodyKeyUp();
// unbind window resizes reposition dropdown
$(window).off('resize', $.proxy(this.onResizeWindow, this));
// clear the content elem markup if configured to
if (this.elem.data('romo-dropdown-clear-content') === true) {
this.contentElem.html('');
}
this.elem.trigger('dropdown:popupClose', [this]);
}
RomoDropdown.prototype.doBindElemKeyUp = function() {
this.elem.on('keyup', $.proxy(this.onElemKeyUp, this));
this.popupElem.on('keyup', $.proxy(this.onElemKeyUp, this));
}
RomoDropdown.prototype.doUnBindElemKeyUp = function() {
this.elem.off('keyup', $.proxy(this.onElemKeyUp, this));
this.popupElem.off('keyup', $.proxy(this.onElemKeyUp, this));
}
RomoDropdown.prototype.onElemKeyUp = function(e) {
if (this.elem.hasClass('disabled') === false) {
if (this.popupElem.hasClass('romo-dropdown-open')) {
if(e.keyCode === 27 /* Esc */ ) {
this.doPopupClose();
return false;
} else {
return true;
}
} else {
return true;
}
}
return true;
}
RomoDropdown.prototype.doBindWindowBodyClick = function() {
$('body').on('click', $.proxy(this.onWindowBodyClick, this));
$('body').on('modal:mousemove', $.proxy(this.onWindowBodyClick, this));
}
RomoDropdown.prototype.doUnBindWindowBodyClick = function() {
$('body').off('click', $.proxy(this.onWindowBodyClick, this));
$('body').off('modal:mousemove', $.proxy(this.onWindowBodyClick, this));
}
RomoDropdown.prototype.onWindowBodyClick = function(e) {
// if not clicked on the popup elem or the elem
var target = $(e.target);
if (e !== undefined &&
target.parents('.romo-dropdown-popup').size() === 0 &&
target.closest(this.elem).size() === 0) {
this.doPopupClose();
}
return true;
}
RomoDropdown.prototype.doBindWindowBodyKeyUp = function() {
$('body').on('keyup', $.proxy(this.onWindowBodyKeyUp, this));
}
RomoDropdown.prototype.doUnBindWindowBodyKeyUp = function() {
$('body').off('keyup', $.proxy(this.onWindowBodyKeyUp, this));
}
RomoDropdown.prototype.onWindowBodyKeyUp = function(e) {
if (e.keyCode === 27 /* Esc */) {
this.doPopupClose();
}
return true;
}
RomoDropdown.prototype.onResizeWindow = function(e) {
this.doPlacePopupElem();
return true;
}
RomoDropdown.prototype.doPlacePopupElem = function() {
if (this.elem.parents('.romo-modal-popup').size() !== 0) {
this.popupElem.css({'position': 'fixed'});
this.popupElem.offset(this.elem.offset());
}
var pos = $.extend({}, this.elem[0].getBoundingClientRect(), this.elem.offset());
var w = this.popupElem[0].offsetWidth;
var h = this.popupElem[0].offsetHeight;
var offset = {};
var configHeight = this.elem.data('romo-dropdown-height') || this.elem.data('romo-dropdown-max-height');
var configPosition = this.popupPosition;
if (configHeight === 'detect') {
var popupHeight = this.popupElem.height();
var topAvailHeight = this._getPopupMaxAvailableHeight('top');
var bottomAvailHeight = this._getPopupMaxAvailableHeight('bottom');
if (popupHeight < topAvailHeight && popupHeight < bottomAvailHeight) {
// if it fits both ways, use the config position way
configHeight = this._getPopupMaxAvailableHeight(configPosition);
} else if (topAvailHeight > bottomAvailHeight) {
configPosition = 'top';
configHeight = topAvailHeight;
} else {
configPosition = 'bottom';
configHeight = bottomAvailHeight;
}
this.contentElem.css({'max-height': configHeight.toString() + 'px'});
}
if(h > configHeight) {
h = configHeight;
}
switch (configPosition) {
case 'top':
var pad = 2;
$.extend(offset, { top: pos.top - h - pad });
break;
case 'bottom':
var pad = 2;
$.extend(offset, { top: pos.top + pos.height + pad });
break;
}
switch (this.popupAlignment) {
case 'left':
$.extend(offset, { left: pos.left });
break;
case 'right':
$.extend(offset, { left: pos.left + pos.width - w });
break;
}
this.popupElem.offset(offset);
}
RomoDropdown.prototype.doSetPopupZIndex = function(relativeElem) {
var relativeZIndex = Romo.parseZIndex(relativeElem);
this.popupElem.css({'z-index': relativeZIndex + 1200}); // see z-index.css
}
RomoDropdown.prototype._parsePositionData = function(posString) {
var posData = (posString || '').split(',');
return { position: posData[0], alignment: posData[1] };
}
RomoDropdown.prototype._getPopupMaxAvailableHeight = function(position) {
var maxHeight = undefined;
switch (position) {
case 'top':
var elemTop = this.elem[0].getBoundingClientRect().top;
maxHeight = elemTop - this._getPopupMaxHeightDetectPad(position);
break;
case 'bottom':
var elemBottom = this.elem[0].getBoundingClientRect().bottom;
maxHeight = $(window).height() - elemBottom - this._getPopupMaxHeightDetectPad(position);
break;
}
return maxHeight;
}
RomoDropdown.prototype._getPopupMaxHeightDetectPad = function(position) {
return this.elem.data('romo-dropdown-max-height-detect-pad-'+position) || this.elem.data('romo-dropdown-max-height-detect-pad') || 10;
}
Romo.onInitUI(function(e) {
Romo.initUIElems(e, '[data-romo-dropdown-auto="true"]').romoDropdown();
});