diff --git a/bundle/Resources/public/admin/js/init.js b/bundle/Resources/public/admin/js/init.js index 43d5d65..a4a588d 100644 --- a/bundle/Resources/public/admin/js/init.js +++ b/bundle/Resources/public/admin/js/init.js @@ -1,41 +1,54 @@ -(function($) { - 'use strict'; - $('.multientry').multientry(); +const init = () => { + window.initaliseMultientries(); - const saveButton = document.getElementById('content_type_edit__sidebar_right__save-tab'); + const observer = new MutationObserver(() => { + window.initaliseMultientries(); + }); - const observer = new MutationObserver(e => { - $('.multientry').multientry(); + // enables observer when element is dropped + document.addEventListener('drop', () => { + document.querySelectorAll('.ibexa-collapse').forEach((element) => { + observer.observe(element, { childList: true, subtree: true }); }); + }); - // enables observer when elements is dropped - document.addEventListener("drop", e => { - document.querySelectorAll('.ibexa-collapse').forEach(el => { - observer.observe(el, { childList: true, subtree: true }); - }); + // disable observer while dragging to reduce function firing + document.addEventListener('drag', () => { + document.querySelectorAll('.ibexa-collapse').forEach(() => { + observer.disconnect(); }); + }); - // disable observer while dragging to reduce function firing - document.addEventListener("drag", e => { - document.querySelectorAll('.ibexa-collapse').forEach(el => { - observer.disconnect(); - }); - }); + // checks if any multientry inputs are empty and expands the field type + const saveButton = document.getElementById('content_type_edit__sidebar_right__save-tab'); + + saveButton && saveButton.addEventListener('click', (event) => { + document.querySelectorAll('.multientry input').forEach((input) => { + if (input.value.length > 0) { + return; + } + + event.preventDefault(); + + const collapseWrapper = input.closest('.ibexa-collapse'); + const collapseBody = collapseWrapper.querySelector('.ibexa-collapse__body'); + const collapseToggle = collapseWrapper.querySelector('.ibexa-collapse__toggle-btn'); - // checks if any multientry inputs are empty and expands the field type - saveButton && saveButton.addEventListener("click", e => { - document.querySelectorAll('.multientry input').forEach(el => { - if (el.value.length === 0) { - e.preventDefault(); - const fullElement = el.closest('.ibexa-collapse'); - const elementBody = fullElement.querySelector('.ibexa-collapse__body'); - const collapseToggle = fullElement.querySelector('.ibexa-collapse__toggle-btn'); - fullElement.classList.contains('multientry-error') ? null : fullElement.classList.add('multientry-error'); - fullElement.classList.contains('ibexa-collapse--collapsed') ? fullElement.classList.remove('ibexa-collapse--collapsed') : null; - elementBody.classList.contains('show') ? null : elementBody.classList.add('show'); - collapseToggle.classList.contains('collapsed') ? elementBody.classList.remove('collapsed') : null; - fullElement.dataset.collapsed ? fullElement.dataset.collapsed = false : null; - } - }) + collapseWrapper.classList.add('multientry-error'); + collapseWrapper.classList.remove('ibexa-collapse--collapsed'); + collapseWrapper.dataset.collapsed = false; + + collapseBody.classList.add('show'); + + if (collapseToggle.classList.contains('collapsed')) { + collapseBody.classList.remove('collapsed'); + } }); -})(jQuery); + }); +}; + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); +} else { + init(); +} diff --git a/bundle/Resources/public/admin/js/multientry.js b/bundle/Resources/public/admin/js/multientry.js index e05ff1a..32c707f 100644 --- a/bundle/Resources/public/admin/js/multientry.js +++ b/bundle/Resources/public/admin/js/multientry.js @@ -1,129 +1,199 @@ -(function($) { - 'use strict'; - - //initialize - var MultiEntry = function(element, options){ - this.opts = $.extend({ - insert_first: true, - last_item_can_be_removed: false, - limit: null, - show_errors: true, - }, options || {}); - this.id = 0; - this.$el = $(element); - $.extend(this.opts, this.$el.data() || {}); - this.$items_container = this.$el.find('.multientry-items'); - this.items_exist = this.$items_container.find('.multientry-item').length ? true : false; - this.$add_button = this.$el.find('.multientry_add'); - this.item_template = this.$el.data('prototype'); - this.close_element = ''; - this.error_message = this.opts.error_message || ('Max number of items: '+ this.opts.limit); - this.$error = $('
'+ this.error_message +'
'); - this.setup_dom(); - this.setup_events(); - setTimeout($.proxy(function(){ - this.opts.insert_first && !(this.items_exist) && this.add(); - }, this), 0); +const SELECTORS = { + root: '.multientry', + items_container: '.multientry-items', + item: '.multientry-item', + add_button: '.multientry_add', + remove_button: '.icon-close', +}; + +const EVENTS = { + PREFIX: 'multientry:', + add: { + on: 'add', + before: 'before:add', + }, + remove: { + on: 'remove', + before: 'before:remove', + }, + limit: { + reached: 'limit:reached', + valid: 'limit:valid', + }, +}; + +class MultiEntry { + constructor(element, options = {}) { + this.parser = new DOMParser(); + this.id = 0; + + this.$element = element; + this.$element.dataset.instanceId = options.instance_id; + + const externalOptions = { + ...options, + ...this.$element.dataset, }; - - MultiEntry.prototype.next_id = function(){ - var timestamp = +new Date(); - return timestamp+''+(this.id++); - }; - - //instance methods - MultiEntry.prototype.render_item_template = function() { - var $template = $(this.item_template.replace(/__name__/g, this.next_id() )); - $template.addClass('multientry-item new'); - $template.append(this.close_element); - return $template; - }; - - MultiEntry.prototype.setup_dom = function() { - this.$el.find('.multientry-item').append(this.close_element); - }; - - MultiEntry.prototype.setup_events = function() { - var self = this; - - this.$add_button.on('click', function(e){ - e.preventDefault(); - self.add(); - }); - - this.$items_container.on('click', '.icon-close', function(e){ - e.preventDefault(); - self.remove($(this).closest('.multientry-item')); - }); - }; - - MultiEntry.prototype.add = function() { - if(this.opts.limit && this.limit_reached()) {return;} - var $item = this.render_item_template(); - this.trigger('before:add', {item: $item}); - this.$items_container.append($item); - this.trigger('add', {item: $item}); - this.opts.limit && this.limit_check(); - }; - - MultiEntry.prototype.remove = function($item) { - if(this.items_count() === 1 && !this.opts.last_item_can_be_removed){return;} - this.trigger('before:remove', {item: $item}); - $item.remove(); - this.opts.limit && this.limit_check(); - this.trigger('remove', {item: $item}); - }; - - MultiEntry.prototype.items_count = function() { - return this.$items_container.find('.multientry-item').length; - }; - - MultiEntry.prototype.limit_check = function() { - this.limit_reached() ? this.on_limit_reached() : this.on_limit_valid(); - }; - - MultiEntry.prototype.limit_reached = function() { - return this.items_count() >= this.opts.limit; - }; - - MultiEntry.prototype.on_limit_valid = function() { - this.opts.show_errors && this.$error.remove(); - this.$add_button.removeClass('disabled'); - this.trigger('limit:valid'); + this.options = { + insert_first: true, + last_item_can_be_removed: false, + limit: null, + show_errors: true, + error_message: `Max number of items: ${externalOptions.limit}`, + ...externalOptions, }; - MultiEntry.prototype.on_limit_reached = function() { - this.opts.show_errors && this.$items_container.append(this.$error); - this.$add_button.addClass('disabled'); - this.trigger('limit:reached'); + this.$add_button = this.$element.querySelector(SELECTORS.add_button); + this.$items_container = this.$element.querySelector(SELECTORS.items_container); + this.items_exist = !!this.$items_container.querySelector(SELECTORS.item); + + this.item_template = this.$element.dataset.prototype; + this.remove_button_template = ''; + + this.$error = MultiEntry.create_element_from_string( + `
${this.options.error_message}
` + ); + + this.setup_dom(); + this.setup_events(); + setTimeout(() => { + if (this.options.insert_first && !this.items_exist) { + this.add(); + } + }, 0); + } + + next_id() { + const timestamp = new Date(); + + return `${timestamp}${this.id++}`; + } + + render_item_template() { + const $template = MultiEntry.create_element_from_string( + this.item_template.replace(/__name__/g, this.next_id()) + ); + $template.classList.add('multientry-item', 'new'); + $template.append(MultiEntry.create_element_from_string(this.remove_button_template)); + + $template.querySelector(SELECTORS.remove_button).addEventListener('click', () => { + this.remove($template); + }); + + return $template; + } + + setup_dom() { + this.$element.querySelectorAll(SELECTORS.item).forEach(($item) => { + $item.append(MultiEntry.create_element_from_string(this.remove_button_template)); + }); + } + + setup_events() { + this.$add_button.addEventListener('click', (event) => { + event.preventDefault(); + this.add(); + }); + } + + add() { + if (this.options.limit && this.limit_reached()) { + return; + } + + const $newItem = this.render_item_template(); + this.trigger(EVENTS.add.before, { item: $newItem }); + this.$items_container.append($newItem); + this.trigger(EVENTS.add.on, { item: $newItem }); + this.options.limit && this.limit_check(); + } + + remove($item) { + if (this.items_count() === 1 && !this.options.last_item_can_be_removed) { + return; + } + + this.trigger(EVENTS.remove.before, { item: $item }); + $item.remove(); + this.trigger(EVENTS.remove.on, { item: $item }); + this.options.limit && this.limit_check(); + } + + limit_check() { + if (this.limit_reached()) { + this.on_limit_reached(); + } else { + this.on_limit_valid(); + } + } + + limit_reached() { + return this.items_count() >= this.options.limit; + } + + on_limit_reached() { + if (this.options.show_errors) { + this.$items_container.append(this.$error); + } + + this.$add_button.classList.add('disabled'); + this.trigger(EVENTS.limit.reached); + } + + on_limit_valid() { + if (this.options.show_errors) { + this.$error.remove(); + } + + this.$add_button.classList.remove('disabled'); + this.trigger(EVENTS.limit.valid); + } + + trigger(suffix, data) { + const eventName = `${EVENTS.PREFIX}${suffix}`; + const detail = { + ...data, + instance: this, }; - - MultiEntry.prototype.trigger = function(event, data){ - var prefix = 'multientry:'; - data = $.extend({}, data, {instance: this}); - this.$el.trigger(prefix+event, data); - $(document.body).trigger(prefix+event, data); - }; - - //Expose as jquery plugin - $.fn.multientry = function(options){ - var method = typeof options === 'string' && options; - $(this).each(function(){ - var $this = $(this); - var instance = $this.data('multientry'); - if(instance){ - method && instance[method](); - return; - } - instance = new MultiEntry(this, options); - $this.data('multientry', instance); - }); - return this; - }; - - //Expose class - $.MultiEntry = MultiEntry; - -})(jQuery); - + const event = new CustomEvent(eventName, { detail }); + + this.$element.dispatchEvent(event); + document.body.dispatchEvent(event); + } + + items_count() { + return this.$items_container.querySelectorAll(SELECTORS.item).length; + } + + static create_element_from_string(elementString) { + const template = document.createElement('template'); + template.innerHTML = elementString.trim(); + + return template.content.firstChild; + } +} + +const instances = []; +window.initaliseMultientries = function (options = {}) { + document.querySelectorAll(SELECTORS.root).forEach(($multientry) => { + let instance = instances[$multientry.dataset.instanceId]; + if (instance) { + return; + } + + instance = new MultiEntry($multientry, { + ...options, + instance_id: instances.length, + }); + instances.push(instance); + }); + + return [...instances]; +}; + +window.runMethodOnAllMultientries = function (methodName) { + instances.forEach((instance) => { + instance[methodName] && instance[methodName](); + }); +};