mirror of
https://github.com/ExactTarget/fuelux.git
synced 2026-01-10 15:08:02 -05:00
358 lines
9.8 KiB
JavaScript
358 lines
9.8 KiB
JavaScript
/* global jQuery:true */
|
|
|
|
/*
|
|
* Fuel UX Placard
|
|
* 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 PlacardWrapper ($) {
|
|
// -- END UMD WRAPPER PREFACE --
|
|
|
|
// -- 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('contenteditable');
|
|
}
|
|
this.hide();
|
|
},
|
|
|
|
applyEllipsis: function applyEllipsis() {
|
|
var field, i, str;
|
|
if (this.options.applyEllipsis) {
|
|
field = this.$field.get(0);
|
|
if ((this.isContentEditableDiv && !this.divInTextareaMode) || this.isInput) {
|
|
field.scrollLeft = 0;
|
|
} else {
|
|
field.scrollTop = 0;
|
|
if (field.clientHeight < field.scrollHeight) {
|
|
this.actualValue = this.getValue();
|
|
this.setValue('', true);
|
|
str = '';
|
|
i = 0;
|
|
while (field.clientHeight >= field.scrollHeight) {
|
|
str += this.actualValue[i];
|
|
this.setValue(str + '...', true);
|
|
i++;
|
|
}
|
|
str = (str.length > 0) ? str.substring(0, str.length - 1) : '';
|
|
this.setValue(str + '...', true);
|
|
}
|
|
}
|
|
|
|
}
|
|
},
|
|
|
|
enable: function enable() {
|
|
this.$element.removeClass('disabled');
|
|
this.$field.removeAttr('disabled');
|
|
if (this.isContentEditableDiv) {
|
|
this.$field.attr('contenteditable', 'true');
|
|
}
|
|
},
|
|
|
|
externalClickListener: function externalClickListener(e, force) {
|
|
if (force === true || this.isExternalClick(e)) {
|
|
this.complete(this.options.externalClickAction);
|
|
}
|
|
},
|
|
|
|
getValue: function getValue() {
|
|
if (this.actualValue !== null) {
|
|
return this.actualValue;
|
|
} else if (this.isContentEditableDiv) {
|
|
return this.$field.html();
|
|
} else {
|
|
return this.$field.val();
|
|
}
|
|
},
|
|
|
|
hide: function hide() {
|
|
if (!this.$element.hasClass('showing')) {
|
|
return;
|
|
}
|
|
|
|
this.$element.removeClass('showing');
|
|
this.applyEllipsis();
|
|
$(document).off('click.fu.placard.externalClick.' + this.clickStamp);
|
|
this.$element.trigger('hidden.fu.placard');
|
|
},
|
|
|
|
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('.placard: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;
|
|
},
|
|
|
|
/**
|
|
* setValue() sets the Placard triggering DOM element's display value
|
|
*
|
|
* @param {String} the value to be displayed
|
|
* @param {Boolean} If you want to explicitly suppress the application
|
|
* of ellipsis, pass `true`. This would typically only be
|
|
* done from internal functions (like `applyEllipsis`)
|
|
* that want to avoid circular logic. Otherwise, the
|
|
* value of the option applyEllipsis will be used.
|
|
* @return {Object} jQuery object representing the DOM element whose
|
|
* value was set
|
|
*/
|
|
setValue: function setValue(val, suppressEllipsis) {
|
|
//if suppressEllipsis is undefined, check placards init settings
|
|
if (typeof suppressEllipsis === 'undefined') {
|
|
suppressEllipsis = !this.options.applyEllipsis;
|
|
}
|
|
|
|
if (this.isContentEditableDiv) {
|
|
this.$field.empty().append(val);
|
|
} else {
|
|
this.$field.val(val);
|
|
}
|
|
|
|
if (!suppressEllipsis && !_isShown(this)) {
|
|
this.applyEllipsis();
|
|
}
|
|
|
|
return this.$field;
|
|
},
|
|
|
|
show: function show() {
|
|
if (_isShown(this)) { return; }
|
|
if (!_closeOtherPlacards()) { return; }
|
|
|
|
this.previousValue = (this.isContentEditableDiv) ? this.$field.html() : this.$field.val();
|
|
|
|
if (this.actualValue !== null) {
|
|
this.setValue(this.actualValue, true);
|
|
this.actualValue = null;
|
|
}
|
|
|
|
this.showPlacard();
|
|
},
|
|
|
|
showPlacard: function showPlacard() {
|
|
this.$element.addClass('showing');
|
|
|
|
if (this.$header.length > 0) {
|
|
this.$popup.css('top', '-' + this.$header.outerHeight(true) + 'px');
|
|
}
|
|
|
|
if (this.$footer.length > 0) {
|
|
this.$popup.css('bottom', '-' + this.$footer.outerHeight(true) + 'px');
|
|
}
|
|
|
|
this.$element.trigger('shown.fu.placard');
|
|
this.clickStamp = new Date().getTime() + (Math.floor(Math.random() * 100) + 1);
|
|
if (!this.options.explicit) {
|
|
$(document).on('click.fu.placard.externalClick.' + this.clickStamp, $.proxy(this.externalClickListener, this));
|
|
}
|
|
}
|
|
};
|
|
|
|
// PLACARD PLUGIN DEFINITION
|
|
|
|
$.fn.placard = 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.placard');
|
|
var options = typeof option === 'object' && option;
|
|
|
|
if (!data) {
|
|
$this.data('fu.placard', (data = new Placard(this, options)));
|
|
}
|
|
|
|
if (typeof option === 'string') {
|
|
methodReturn = data[option].apply(data, args);
|
|
}
|
|
});
|
|
|
|
return (methodReturn === undefined) ? $set : methodReturn;
|
|
};
|
|
|
|
$.fn.placard.defaults = {
|
|
onAccept: undefined,
|
|
onCancel: undefined,
|
|
externalClickAction: 'cancelled',
|
|
externalClickExceptions: [],
|
|
explicit: false,
|
|
revertOnCancel: -1,//negative 1 will check for an '.placard-accept' button. Also can be set to true or false
|
|
applyEllipsis: false
|
|
};
|
|
|
|
$.fn.placard.Constructor = Placard;
|
|
|
|
$.fn.placard.noConflict = function () {
|
|
$.fn.placard = old;
|
|
return this;
|
|
};
|
|
|
|
// DATA-API
|
|
|
|
$(document).on('focus.fu.placard.data-api', '[data-initialize=placard]', function (e) {
|
|
var $control = $(e.target).closest('.placard');
|
|
if (!$control.data('fu.placard')) {
|
|
$control.placard($control.data());
|
|
}
|
|
});
|
|
|
|
// Must be domReady for AMD compatibility
|
|
$(function () {
|
|
$('[data-initialize=placard]').each(function () {
|
|
var $this = $(this);
|
|
if ($this.data('fu.placard')) return;
|
|
$this.placard($this.data());
|
|
});
|
|
});
|
|
|
|
// -- BEGIN UMD WRAPPER AFTERWORD --
|
|
}));
|
|
// -- END UMD WRAPPER AFTERWORD --
|