/** * ## Initialisierung * * ### Initialisierung über HTML * * ```html * * ``` * * ### Initialisierung über Javascript * * ```html * * ``` * * ```javascript * $('#element').labels({ * referenceTable: 'wiedervorlage', * referenceId: 123 * }); * ``` * * ### Initialisierung gemischt * * ```html * * ``` * * ```javascript * $('#element').labels({ * referenceId: 123 * }); * ``` * * ## Initialisierungsoptionen * * * referenceTable: (Pflichtangabe) * * referenceId: (Pflichtangabe) * * trigger: CSS-ID-Selektor zum Öffnen des LabelManager-Overlays (Optional; Default: `null`) * * autoload: Labels bei Initialisierung direkt vom Server laden? (Optional; Default: `true`) * * compact: Labels kompakt darstellen? (Optional; Default: `false`) * * ## API * * ### API-Objekt holen * * ```javascript * var api = $('#element').labelsApi(); * ``` * * ### API-Methoden * * * `api.showOverlay()` Label-Overlay einblenden * * `api.hideOverlay()` Label-Overlay ausblenden */ /** * Modul zum Rendern von Labeln * * @type {{render: *}} */ var LabelRenderer = (function ($) { 'use strict'; var me = { /** * @param {Object} collection */ render: function (collection) { Object.keys(collection).forEach(function (key) { var labels = collection[key]; if (!Array.isArray(labels)) { console.error('Can not render label data. Wrong data type.', labels); return; } // Alle Label-Container mit der gleichen Referenz finden // Bsp. key = 'labels-wiedervorlage-123' var targetSelector = '.label-container.' + key; var $target = $(targetSelector); // Container-Element für Labels vorhanden? if ($target.length === 0) { console.error('Can not find label target for "' + key + '".'); return; } $target.html(''); // Label-Elemente erzeugen und in Label-Container stecken labels.forEach(function (item) { if (!me.hasTypeLabel($target, item.type)) { var $label = me.createLabelElement(item.type, item.title, item.bgcolor); $label.appendTo($target); } }); $target.addClass(key); }); }, /** * @param {string} baseColor Hex color value * * @return {object} */ calculateLabelColors: function (baseColor) { var altColor, colors; var brightness = me.calculateColorBrightness(baseColor); if (brightness === 'dark') { altColor = me.calculateColorVariant(baseColor, 35); colors = { 'background-color': altColor, 'border-color': baseColor, 'color': baseColor }; } else { altColor = me.calculateColorVariant(baseColor, -35); colors = { 'background-color': baseColor, 'border-color': altColor, 'color': altColor }; } return colors; }, /** * @param {string} type * @param {string} title * @param {string} color Hex-Color (Format: #336699) * * @return {jQuery} */ createLabelElement: function (type, title, color) { var altColor, bgColor, borderColor, textColor; var brightness = me.calculateColorBrightness(color); if (brightness === 'dark') { altColor = me.calculateColorVariant(color, 35); bgColor = altColor; textColor = color; borderColor = color; } else { altColor = me.calculateColorVariant(color, -35); bgColor = color; textColor = altColor; borderColor = altColor; } var $label = $(''); $label.addClass('label-manager-type-' + type); $label.css('background-color', bgColor); $label.css('border-color', borderColor); $label.css('color', textColor); $label.data('labelType', type); $label.attr('title', title); var $labelTextNormal = $('').addClass('label-text-normal'); $labelTextNormal.html(title).appendTo($label); var $labelTextCompact = $('').addClass('label-text-compact'); $labelTextCompact.html(' ').appendTo($label); return $label; }, /** * @param {jQuery} $element * @param {string} type * * @return {boolean} */ hasTypeLabel: function ($element, type) { var $result = $element.find('.label-manager-type-' + type); return $result.length > 0; }, /** * @param {string} hexColor * * @return {string} [light|dark] */ calculateColorBrightness: function(hexColor) { var rgbArr = me.convertHexColorToRgb(hexColor); var r = parseInt(rgbArr[0], 10); var g = parseInt(rgbArr[1], 10); var b = parseInt(rgbArr[2], 10); var yiq = (r * 299 + g * 587 + b * 114) / 1000; return (yiq >= 128) ? 'light' : 'dark'; }, /** * @param {string} hexColor * @param {number} percent * * @return {string} */ calculateColorVariant: function(hexColor, percent) { var rgbArr = me.convertHexColorToRgb(hexColor); var difference = parseInt(percent * 2.55, 10); var r = parseInt(rgbArr[0] + difference, 10); var g = parseInt(rgbArr[1] + difference, 10); var b = parseInt(rgbArr[2] + difference, 10); // Sicherstellen dass RGB-Werte zwischen 0 und 255 liegen r = Math.min(Math.max(r, 0), 255); g = Math.min(Math.max(g, 0), 255); b = Math.min(Math.max(b, 0), 255); var rHex = r.toString(16); if (rHex.length === 1) { rHex = '0' + rHex; } var gHex = g.toString(16); if (gHex.length === 1) { gHex = '0' + gHex; } var bHex = b.toString(16); if (bHex.length === 1) { bHex = '0' + bHex; } return '#' + rHex + gHex + bHex; }, /** * @param {string} hexColor * * @return {Array|null} */ convertHexColorToRgb: function(hexColor) { var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hexColor = hexColor.replace(shorthandRegex, function(m, r, g, b) { return '#' + r + r + g + g + b + b; }); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor); return result ? [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16) ] : null; } }; return { render: me.render, calculateLabelColors: me.calculateLabelColors }; })(jQuery); /** * LabelManager-Modul * * Der LabelManager erzeugt das Overlay zum Hinzufügen/Löschen von Labels. * * @type {{init: *, showOverlay: *, hideOverlay: *, destroy: *}} */ var LabelManager = (function ($, LabelRendererModule) { 'use strict'; var me = { selector: { overlay: '#label-manager-overlay' }, storage: { $overlay: null }, init: function () { me.registerEvents(); }, destroy: function () { me.unregisterEvents(); }, registerEvents: function () { $(document).off('click.labelManager', '.label-manager'); $(document).on('click.labelManager', '.label-manager', function (event) { event.preventDefault(); var $trigger = $(this); me.showOverlayForElement($trigger); }); $(document).off('change.labelManager', 'input.label-manager-type-checkbox'); $(document).on('change.labelManager', 'input.label-manager-type-checkbox', function (event) { event.preventDefault(); var $checkbox = $(this); var referenceTable = $checkbox.data('labelReferenceTable'); var referenceId = $checkbox.data('labelReferenceId'); var labelType = $checkbox.data('labelType'); if ($checkbox.prop('checked')) { me.assignLabel(labelType, referenceTable, referenceId) } else { me.unassignLabel(labelType, referenceTable, referenceId); } }); }, unregisterEvents: function () { $(document).off('click.labelManager', '.label-manager'); $(document).off('change.labelManager', 'input.label-manager-type-checkbox'); }, /** * @param {string} labelType * @param {string} referenceTable * @param {number} referenceId */ assignLabel: function (labelType, referenceTable, referenceId) { if (typeof labelType !== 'string' || labelType === '') { throw 'LabelManager: InvalidArgument "labelType".'; } if (typeof referenceTable !== 'string' || referenceTable === '') { throw 'LabelManager: InvalidArgument "referenceTable".'; } if (typeof referenceId !== 'number' || referenceId === '' || referenceId === 0) { throw 'LabelManager: InvalidArgument "referenceId".'; } $.ajax({ url: 'index.php?module=ajax&action=labels&cmd=assign', method: 'post', dataType: 'json', data: { type: labelType, reference_id: referenceId, reference_table: referenceTable }, success: function (result) { LabelRendererModule.render(result.data); }, error: function (jqXhr) { alert('Fehler: ' + jqXhr.responseJSON.error); } }); }, /** * @param {string} labelType * @param {string} referenceTable * @param {number} referenceId */ unassignLabel: function (labelType, referenceTable, referenceId) { if (typeof labelType !== 'string' || labelType === '') { throw 'LabelManager: InvalidArgument "labelType".'; } if (typeof referenceTable !== 'string' || referenceTable === '') { throw 'LabelManager: InvalidArgument "referenceTable".'; } if (typeof referenceId !== 'number' || referenceId === '' || referenceId === 0) { throw 'LabelManager: InvalidArgument "referenceId".'; } $.ajax({ url: 'index.php?module=ajax&action=labels&cmd=unassign', method: 'post', dataType: 'json', data: { type: labelType, reference_id: referenceId, reference_table: referenceTable }, success: function (result) { LabelRendererModule.render(result.data); }, error: function (jqXhr) { alert('Fehler: ' + jqXhr.responseJSON.error); } }); }, /** * @return {jQuery} */ getOverlay: function () { if (typeof me.storage.$overlay === 'undefined' || me.storage.$overlay === null) { me.storage.$overlay = me.createOverlay(); } return me.storage.$overlay; }, /** * Label-Manager-Overlay einblenden * * @param {jQuery} $trigger */ showOverlayForElement: function ($trigger) { if (!$trigger instanceof jQuery) { throw 'LabelManager: InvalidArgument "$trigger".'; } var $overlay = me.getOverlay(); var referenceTable = $trigger.data('labelReferenceTable'); var referenceId = parseInt($trigger.data('labelReferenceId')); // Label-Typen laden und rendern me.fetchLabelTypesList(referenceTable, referenceId).then(me.renderLabelTypesList); // Position des Overlays setzen + Overlay einblenden var offset = me.calculateOverlayPosition($trigger, $overlay); $overlay.css({ position: 'fixed', top: offset.top, left: offset.left, right: null, bottom: null }); $overlay.show(); }, /** * @param {string} referenceTable * @param {number} referenceId * @param {number} offsetTop * @param {number} offsetLeft */ showOverlay: function (referenceTable, referenceId, offsetTop, offsetLeft) { var $overlay = me.getOverlay(); offsetTop += 0; offsetLeft += 0; // Label-Typen laden und rendern me.fetchLabelTypesList(referenceTable, referenceId).then(me.renderLabelTypesList); // Position des Overlays setzen + Overlay einblenden $overlay.css({ position: 'fixed', top: offsetTop, left: offsetLeft, right: null, bottom: null }); $overlay.show(); }, /** * Label-Manager-Overlay ausblenden */ hideOverlay: function () { if (me.storage.$overlay === null) { $(me.selector.overlay).hide(); return; } me.getOverlay().hide(); }, /** * Label-Manager-Overlay erzeugen * * @return {jQuery} */ createOverlay: function () { var overlaySelector = me.selector.overlay; if ($(overlaySelector).length > 0) { return $(overlaySelector); } var overlayTemplate = '

Labels

' + '
' + ''; var overlayIdAttr = overlaySelector.substr(1); var $overlay = $('
').attr('id', overlayIdAttr).html(overlayTemplate); $overlay.off('click.labelManager', '#label-manager-close-icon'); $overlay.on('click.labelManager', '#label-manager-close-icon', function (event) { event.preventDefault(); $overlay.hide(); }); $overlay.hide(); $overlay.appendTo('body'); $overlay.draggable({ handle: 'header' }); return $overlay; }, /** * Position für Label-Manager-Overlay berechnen * * @param {jQuery} $trigger * @param {jQuery} $overlay * * @return {{top: number, left: number}} */ calculateOverlayPosition: function ($trigger, $overlay) { var triggerOffset = $trigger.offset(); var overlayWidth = $overlay.width(); var overlayHeight = $overlay.height(); var windowWidth = $(window).width(); var windowHeight = $(window).height(); var windowScrollLeft = window.pageXOffset || 0; var windowScrollTop = window.pageYOffset || 0; var triggerTop = triggerOffset.top - windowScrollTop; var triggerLeft = triggerOffset.left - windowScrollLeft; // Overlay relativ zum "Öffner"-Link platzieren var overlayLeft = triggerLeft + 20; var overlayTop = triggerTop; // Korrektur, falls Overlay rechts abgeschnitten werden würde var overlayRightBoundary = overlayLeft + overlayWidth + 20; if (overlayRightBoundary > windowWidth) { overlayLeft = windowWidth - (overlayWidth + 20); overlayTop += 20; } // Korrektur, falls Overlay unten abgeschnitten werden würde var overlayBottomBoundary = overlayTop + overlayHeight; if (overlayBottomBoundary > windowHeight) { overlayTop = windowHeight - (overlayHeight + 20); } return {top: overlayTop, left: overlayLeft}; }, /** * Liste mit Label-Typen abrufen * * @param {string} referenceTable * @param {number} referenceId * * @return {jqXHR} */ fetchLabelTypesList: function (referenceTable, referenceId) { if (typeof referenceTable !== 'string' || referenceTable === '') { throw 'LabelManager: InvalidArgument "referenceTable".'; } if (typeof referenceId !== 'number' || referenceId === '' || referenceId === 0) { throw 'LabelManager: InvalidArgument "referenceId".'; } return $.ajax({ url: 'index.php?module=ajax&action=labels&cmd=list', method: 'post', dataType: 'json', data: { reference_id: referenceId, reference_table: referenceTable } }); }, /** * @param {Array|object[]} labelList */ renderLabelTypesList: function (labelList) { var $overlay = me.getOverlay(); var $contentList = $overlay.find('section.content-list').html('').show(); if (labelList.length === 0) { $contentList.html('Keine Label gefunden'); return; } var itemHtml = ''; var itemTemplate = '
' + ' ' + '' + '
'; labelList.forEach(function (labelItem) { var checkedAttr = labelItem.selected === true ? 'checked="checked"' : ''; itemHtml = itemTemplate; itemHtml = itemHtml.replace('{{checkedAttr}}', checkedAttr); itemHtml = itemHtml.replace('{{type}}', labelItem.type); itemHtml = itemHtml.replace('{{type}}', labelItem.type); itemHtml = itemHtml.replace('{{title}}', labelItem.title); var $line = $(itemHtml); var labelColors = LabelRendererModule.calculateLabelColors(labelItem.bgcolor); $line.find('.label-title').css(labelColors); var $checkbox = $line.find('.label-manager-type-checkbox'); $checkbox.data('labelType', labelItem.type); $checkbox.data('labelTitle', labelItem.title); $checkbox.data('labelColor', labelItem.bgcolor); $checkbox.data('labelReferenceTable', labelItem.referenceTable); $checkbox.data('labelReferenceId', labelItem.referenceId); $contentList.append($line); }); // Höhe zurücksetzen (wird durch Draggable gesetzt) $overlay.css('height', 'auto'); } }; return { init: me.init, destroy: me.destroy, showOverlay: me.showOverlay, hideOverlay: me.hideOverlay }; })(jQuery, LabelRenderer); /** * Modul zum Laden von Labels * * @type {{loadElement: *, loadAll: *}} */ var LabelLoader = (function ($, LabelRendererModule) { 'use strict'; var me = { /** * Lädt alle (noch) nicht geladenenen Labels */ loadAll: function () { // Referenzen für benötigte Labels sammeln var collection = me.collectLabels(); var collectionKeys = Object.keys(collection); if (collectionKeys.length === 0) { return; // Keine Daten vorhanden } // Label-Daten für gesammelte Labels abrufen und rendern me.fetchCollection(collection).then(me.renderCollection, function (jqXhr) { alert('LabelLoader-Fehler: ' + jqXhr.responseJSON.error); }); }, /** * Lädt die Labels für ein einzelnes Element * * autoload-Einstellung wird ignoriert * * @param {jQuery} $element */ loadElement: function ($element) { if (!$element instanceof jQuery) { return; } if ($element.length !== 1) { return; } var referenceTable = $element.data('labelReferenceTable'); var referenceId = $element.data('labelReferenceId'); var compactData = $element.data('labelCompact'); var compact = typeof compactData !== 'undefined' && typeof compactData === 'boolean' ? compactData : false; // Pflicht-Optionen vorhanden if (typeof referenceTable === 'undefined' || typeof referenceId === 'undefined') { return; } referenceId = parseInt(referenceId); if (isNaN(referenceId)) { return; } $element.addClass('labels-' + referenceTable + '-' + referenceId); $element.addClass('label-container'); $element.removeClass('label-loader'); if (compact) { $element.addClass('label-compact'); } me.fetchLabels(referenceTable, referenceId); }, /** * @param {string} referenceTable * @param {number} referenceId */ fetchLabels: function (referenceTable, referenceId) { if (typeof referenceTable !== 'string' || referenceTable === '') { throw 'LabelManager: InvalidArgument "referenceTable".'; } if (typeof referenceId !== 'number' || referenceId === '' || referenceId === 0) { throw 'LabelManager: InvalidArgument "referenceId".'; } var collection = {}; collection[referenceTable] = [referenceId]; $.ajax({ url: 'index.php?module=ajax&action=labels&cmd=collect', method: 'post', dataType: 'json', data: { collection: collection }, success: function (result) { LabelRendererModule.render(result.data); }, error: function (jqXhr) { alert('Fehler: ' + jqXhr.responseJSON.error); } }); }, /** * Sammelt alle Labels die noch nicht geladen wurden * * @return {Object} Collection von Labels die geladen werden müssen */ collectLabels: function () { var result = {}; var $loaders = $('.label-loader'); $loaders.each(function () { var $elem = $(this); var referenceTable = $elem.data('labelReferenceTable'); var referenceId = $elem.data('labelReferenceId'); if (typeof referenceTable === 'undefined' || typeof referenceId === 'undefined') { return; } var autoloadData = $elem.data('labelAutoload'); var autoload = typeof autoloadData !== 'undefined' && typeof autoloadData === 'boolean' ? autoloadData : true; if (!autoload) { return; } // Label-Container erstellen mit eindeutiger CSS-Klasse (aber nicht Unique) // CSS-Klasse kann mehrmals vorkommen, in unterschiedlichen DataTables // mit identischen Referenz-ID und -Tabelle. $elem.addClass('labels-' + referenceTable + '-' + referenceId); $elem.addClass('label-container'); $elem.removeClass('label-loader'); var compactData = $elem.data('labelCompact'); var compactStyle = typeof compactData !== 'undefined' && typeof compactData === 'boolean' ? compactData : false; if (compactStyle) { $elem.addClass('label-compact'); } // References sammeln if (!result.hasOwnProperty(referenceTable)) { result[referenceTable] = []; } if (!me.inArray(referenceId, result[referenceTable])) { result[referenceTable].push(referenceId); } }); return result; }, /** * @param {object} collection * * @return {jqXHR|null} */ fetchCollection: function (collection) { return $.ajax({ url: 'index.php?module=ajax&action=labels&cmd=collect', method: 'post', dataType: 'json', data: {collection: collection} }); }, /** * @param {object} collectionResult Das Ergebnis von me.fetchCollection() */ renderCollection: function (collectionResult) { if (!collectionResult.hasOwnProperty('data')) { return; } LabelRendererModule.render(collectionResult.data); }, /** * Prüfen ob Wert in einem Array vorkommt * * @param {number|string} value * @param {Array} array * * @return {boolean} */ inArray: function (value, array) { return array.indexOf(value) > -1; } }; return { loadAll: me.loadAll, loadElement: me.loadElement }; })(jQuery, LabelRenderer); /** * Labels jQuery-Plugin + LabelsApi * * Dokumentation: Siehe Dateianfang */ (function ($, LabelLoaderModule, LabelManagerModule) { 'use strict'; var LabelsApi = function ($element, options) { var me = { /** @property {Object} me.defaults Default configuration */ defaults: { referenceTable: null, // Pflicht-Option referenceId: null, // Pflicht-Option autoload: true, compact: false, trigger: null }, storage: { $trigger: null, $element: null, options: {} }, /** * @param {jQuery} $element * @param {Object} options */ init: function ($element, options) { if (typeof $element === 'undefined') { throw 'LabelsApi konnte nicht initialisiert werden. Element wurde nicht übergeben.'; } if ($element.length !== 1) { throw 'LabelsApi konnte nicht initialisiert werden. Trigger-Element wurde nicht gefunden.'; } // Optionen mit Defaults mergen // Reihenfolge: Javascript-Optionen überschreiben Data-Attribute var dataAttributesAll = $element.data(); var dataAttributesLabel = me.filterDataAttributesByPrefix(dataAttributesAll, 'label'); var mergedOptions = $.extend({}, me.defaults, dataAttributesLabel, options); // Benötigte Optionen prüfen if (mergedOptions.referenceTable === null) { throw 'LabelsApi konnte nicht initialisiert werden. Benötigte Option \'referenceTable\' fehlt.'; } if (mergedOptions.referenceId === null) { throw 'LabelsApi konnte nicht initialisiert werden. Benötigte Option \'referenceId\' fehlt.'; } mergedOptions.referenceId = parseInt(mergedOptions.referenceId); if (isNaN(mergedOptions.referenceId)) { throw 'LabelsApi konnte nicht initialisiert werden. Benötigte Option \'referenceId\' ist ungültig.'; } // Sicherstellen dass alle Data-Attribute gesetzt sind; // bei Initialisierung über jQuery-Plugin mit Options-Array. if (mergedOptions.hasOwnProperty('referenceTable')) { $element.data('labelReferenceTable', mergedOptions.referenceTable); } if (mergedOptions.hasOwnProperty('referenceId')) { $element.data('labelReferenceId', mergedOptions.referenceId); } if (mergedOptions.hasOwnProperty('autoload')) { $element.data('labelAutoload', mergedOptions.autoload); } if (mergedOptions.hasOwnProperty('compact')) { $element.data('labelCompact', mergedOptions.compact); } if (mergedOptions.hasOwnProperty('trigger')) { $element.data('labelTrigger', mergedOptions.trigger); } LabelLoaderModule.loadElement($element); if (typeof mergedOptions.trigger !== 'undefined' && mergedOptions.trigger !== null) { me.initTriggerElement(mergedOptions.trigger, mergedOptions.referenceTable, mergedOptions.referenceId); } }, /** * @param {string} triggerSelector * @param {string} referenceTable * @param {number} referenceId */ initTriggerElement: function (triggerSelector, referenceTable, referenceId) { var $trigger = $(triggerSelector); if ($trigger.length !== 1) { console.warn('LabelsApi: Trigger-Element "' + triggerSelector + '" wurde nicht gefunden'); return; } $trigger .addClass('label-manager') .data('labelReferenceTable', referenceTable) .data('labelReferenceId', referenceId); me.storage.$trigger = $trigger; }, /** * Label-Manager-Overlay einblenden */ showOverlay: function () { if (me.storage.$trigger === null) { console.warn('LabelsApi: Kein Trigger definiert'); } me.storage.$trigger.trigger('click.labelManager'); }, /** * Label-Manager-Overlay ausblenden */ hideOverlay: function () { LabelManagerModule.hideOverlay(); }, /** * @param {object} attributes * @param {string} prefix * * @return {object} */ filterDataAttributesByPrefix: function (attributes, prefix) { if (typeof attributes !== 'object') { throw 'LabelManagerApi: InvalidArgument "attributes".'; } if (typeof prefix !== 'string' || prefix === '') { throw 'LabelManagerApi: InvalidArgument "prefix".'; } var filteredAttributes = {}; Object.keys(attributes).forEach(function (key) { if (key.indexOf(prefix) === 0) { var value = attributes[key]; var keyWithoutPrefix = key.substr(prefix.length); var keyFirstCharLowered = keyWithoutPrefix.charAt(0).toLowerCase() + keyWithoutPrefix.slice(1); filteredAttributes[keyFirstCharLowered] = value; } }); return filteredAttributes; } }; me.init($element, options); return { showOverlay: me.showOverlay, hideOverlay: me.hideOverlay } }; // jQuery-Plugin registrieren $.fn.labels = function (options) { return this.each(function () { var $elem = $(this); // Neue API-Instanz erzeugen // Dokumentation: Siehe Dateianfang var api = new LabelsApi($elem, options); /**@return {{showOverlay: *, hideOverlay: *}} */ $elem.init.prototype.labelsApi = function () { return api; }; $elem.data('labelsApi', api); }); }; }(jQuery, LabelLoader, LabelManager)); $(function () { LabelManager.init(); LabelLoader.loadAll(); // LabelManager-Overlay ausblenden beim Wechseln von Tabs $('#tabs').on('tabsactivate', function() { LabelManager.hideOverlay(); }); });