Files
fuelux/js/placard.js
2017-06-20 16:14:33 -04:00

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 --