diff --git a/bullet_train-fields/app/javascript/controllers/fields/color_picker_controller.js b/bullet_train-fields/app/javascript/controllers/fields/color_picker_controller.js index 0b22bd9e5..b841ea427 100644 --- a/bullet_train-fields/app/javascript/controllers/fields/color_picker_controller.js +++ b/bullet_train-fields/app/javascript/controllers/fields/color_picker_controller.js @@ -21,27 +21,22 @@ export default class extends Controller { connect() { this.initPluginInstance() - this.colorOptions = $(this.colorOptionsTarget) - .find('button') - .map(function (_, button) { - return $(button).attr('data-color').toLowerCase() - }) - .get() } disconnect() { this.teardownPluginInstance() } + + pickColor(event) { event.preventDefault() - const targetEl = event.target - const color = targetEl.dataset.color + const color = event.target.dataset.color - $(this.colorInputTarget).val(color) - $(this.colorPickerValueTarget).val(color) - $(this.userSelectedColorTarget).data('color', color) + this.colorInputTarget.value = color + this.colorPickerValueTarget.value = color + this.userSelectedColorTarget.dataset.color = color this.pickr.setColor(color) } @@ -61,21 +56,20 @@ export default class extends Controller { } showUserSelectedColor(color) { - $(this.colorInputTarget).val(color) - $(this.colorPickerValueTarget).val(color) - - $(this.userSelectedColorTarget) - .css('background-color', color) - .css('--tw-ring-color', color) - .attr('data-color', color) - .show() + this.colorInputTarget.value = color + this.colorPickerValueTarget.value = color + + this.userSelectedColorTarget.style.backgroundColor = color + this.userSelectedColorTarget.style.setProperty('--tw-ring-color', color) + this.userSelectedColorTarget.setAttribute('data-color', color) + this.userSelectedColorTarget.classList.remove('hidden') } unpickColor(event) { event.preventDefault() - $(this.colorPickerValueTarget).val('') - $(this.colorInputTarget).val('') - $(this.userSelectedColorTarget).hide() + this.colorPickerValueTarget.value = '' + this.colorInputTarget.value = '' + this.userSelectedColorTarget.classList.add('hidden') this.dispatchChangeEvent() } @@ -146,7 +140,12 @@ export default class extends Controller { this.pickr.destroy() } + get colorOptions () { + const colorButtons = this.colorOptionsTarget.querySelectorAll('button[data-color]') + return Array.from(colorButtons).map(button => button.getAttribute('data-color').toLowerCase()); + } + get selectedColor() { - return $(this.colorInputTarget).val() + return this.colorInputTarget.value } } diff --git a/bullet_train-fields/app/javascript/controllers/fields/date_controller.js b/bullet_train-fields/app/javascript/controllers/fields/date_controller.js index e8def6860..696defc3c 100644 --- a/bullet_train-fields/app/javascript/controllers/fields/date_controller.js +++ b/bullet_train-fields/app/javascript/controllers/fields/date_controller.js @@ -2,8 +2,10 @@ import { Controller } from "@hotwired/stimulus" require("daterangepicker/daterangepicker.css"); // requires jQuery, moment, might want to consider a vanilla JS alternative +import jquery from "jquery"; import 'daterangepicker'; import moment from 'moment-timezone' +import select2 from "select2"; export default class extends Controller { static targets = [ "field", "displayField", "clearButton", "currentTimeZoneWrapper", "timeZoneButtons", "timeZoneSelectWrapper", "timeZoneField", "timeZoneSelect" ] @@ -17,6 +19,19 @@ export default class extends Controller { pickerLocale: { type: Object, default: {} } } + initialize() { + if (window.jQuery === undefined) { + window.jQuery = jquery // required for select2 used for time zone select, but we also use global jQuery throughout below + } + if (!this.isSelect2LoadedOnWindowJquery) { + select2() + } + } + + get isSelect2LoadedOnWindowJquery() { + return window?.jQuery?.fn?.select2 !== undefined + } + connect() { this.initPluginInstance() } @@ -29,13 +44,13 @@ export default class extends Controller { // don't submit the form, unless it originated from the cancel/clear button event.preventDefault() - $(this.fieldTarget).val('') - $(this.displayFieldTarget).val('') + this.fieldTarget.value = '' + this.displayFieldTarget.value = '' } currentTimeZone(){ return ( - ( this.hasTimeZoneSelectWrapperTarget && $(this.timeZoneSelectWrapperTarget).is(":visible") && this.timeZoneSelectTarget.value ) || + ( this.hasTimeZoneSelectWrapperTarget && jQuery(this.timeZoneSelectWrapperTarget).is(":visible") && this.timeZoneSelectTarget.value ) || ( this.hasTimeZoneFieldTarget && this.timeZoneFieldTarget.value ) || this.currentTimeZoneValue ) @@ -51,8 +66,8 @@ export default class extends Controller { ) const displayVal = momentVal.format(format) const dataVal = this.includeTimeValue ? momentVal.toISOString(true) : momentVal.format('YYYY-MM-DD') - $(this.displayFieldTarget).val(displayVal) - $(this.fieldTarget).val(dataVal) + this.displayFieldTarget.value = displayVal + this.fieldTarget.value = dataVal // bubble up a change event when the input is updated for other listeners if(picker){ this.displayFieldTarget.dispatchEvent(new CustomEvent('change', { detail: { picker: picker }})) @@ -63,8 +78,8 @@ export default class extends Controller { // don't follow the anchor event.preventDefault() - $(this.currentTimeZoneWrapperTarget).toggleClass('hidden') - $(this.timeZoneButtonsTarget).toggleClass('hidden') + this.currentTimeZoneWrapperTarget.classList.toggle('hidden') + this.timeZoneButtonsTarget.classList.toggle('hidden') } // triggered on other click from the timezone buttons @@ -72,23 +87,23 @@ export default class extends Controller { // don't follow the anchor event.preventDefault() - $(this.timeZoneButtonsTarget).toggleClass('hidden') + this.timeZoneButtonsTarget.classList.toggle('hidden') if (this.hasTimeZoneSelectWrapperTarget) { - $(this.timeZoneSelectWrapperTarget).toggleClass('hidden') + this.timeZoneSelectWrapperTarget.classList.toggle('hidden') } if(!["", null].includes(this.fieldTarget.value)){ - $(this.displayFieldTarget).trigger("apply.daterangepicker"); + jQuery(this.displayFieldTarget).trigger("apply.daterangepicker"); } } resetTimeZoneUI(e) { e && e.preventDefault() - $(this.currentTimeZoneWrapperTarget).removeClass('hidden') - $(this.timeZoneButtonsTarget).addClass('hidden') + this.currentTimeZoneWrapperTarget.classList.remove('hidden') + this.timeZoneButtonsTarget.classList.add('hidden') if (this.hasTimeZoneSelectWrapperTarget) { - $(this.timeZoneSelectWrapperTarget).addClass('hidden') + this.timeZoneSelectWrapperTarget.classList.add('hidden') } } @@ -97,20 +112,26 @@ export default class extends Controller { // don't follow the anchor event.preventDefault() const currentTimeZoneEl = this.currentTimeZoneWrapperTarget.querySelector('a') - $(this.timeZoneFieldTarget).val(event.target.dataset.value) - $(currentTimeZoneEl).text(event.target.dataset.label) - $('.time-zone-button').removeClass('button').addClass('button-alternative') - $(event.target).removeClass('button-alternative').addClass('button') + if (this.hasTimeZoneFieldTarget) { + this.timeZoneFieldTarget.value = event.target.dataset.value + } + currentTimeZoneEl.textContent = event.target.dataset.label + this.element.querySelectorAll('.time-zone-button').forEach(el => { + el.classList.remove('button'); + el.classList.add('button-alternative'); + }); + event.target.classList.remove('button-alternative') + event.target.classList.add('button') this.resetTimeZoneUI() if(!["", null].includes(this.fieldTarget.value)){ - $(this.displayFieldTarget).trigger("apply.daterangepicker"); + jQuery(this.displayFieldTarget).trigger("apply.daterangepicker"); } } // triggered on selecting a new timezone from the timezone picker selectTimeZoneChange(event) { if(!["", null].includes(this.fieldTarget.value)){ - $(this.displayFieldTarget).trigger("apply.daterangepicker"); + jQuery(this.displayFieldTarget).trigger("apply.daterangepicker"); } } @@ -119,7 +140,7 @@ export default class extends Controller { event.preventDefault() this.resetTimeZoneUI() if(!["", null].includes(this.fieldTarget.value)){ - $(this.displayFieldTarget).trigger("apply.daterangepicker") + jQuery(this.displayFieldTarget).trigger("apply.daterangepicker") } } @@ -130,10 +151,10 @@ export default class extends Controller { if(momentParsed.isValid()){ const momentVal = moment.tz(momentParsed.format("YYYY-MM-DDTHH:mm"), newTimeZone) const dataVal = this.includeTimeValue ? momentVal.toISOString(true) : momentVal.format('YYYY-MM-DD') - $(this.fieldTarget).val(dataVal) + this.fieldTarget.value = dataVal } else { // nullify field value when the display format is wrong - $(this.fieldTarget).val("") + this.fieldTarget.value = '' } } @@ -142,7 +163,7 @@ export default class extends Controller { const isAmPm = this.isAmPmValue localeValues['format'] = this.includeTimeValue ? this.timeFormatValue : this.dateFormatValue - $(this.displayFieldTarget).daterangepicker({ + jQuery(this.displayFieldTarget).daterangepicker({ singleDatePicker: true, timePicker: this.includeTimeValue, timePickerIncrement: 5, @@ -151,41 +172,59 @@ export default class extends Controller { timePicker24Hour: !isAmPm, }) - $(this.displayFieldTarget).on('apply.daterangepicker', this.applyDateToField.bind(this)) - $(this.displayFieldTarget).on('cancel.daterangepicker', this.clearDate.bind(this)) - $(this.displayFieldTarget).on('input', this,this.displayFieldChange.bind(this)); + jQuery(this.displayFieldTarget).on('apply.daterangepicker', this.applyDateToField.bind(this)) + jQuery(this.displayFieldTarget).on('cancel.daterangepicker', this.clearDate.bind(this)) + jQuery(this.displayFieldTarget).on('input', this,this.displayFieldChange.bind(this)); this.pluginMainEl = this.displayFieldTarget - this.plugin = $(this.pluginMainEl).data('daterangepicker') // weird + this.plugin = jQuery(this.pluginMainEl).data('daterangepicker') // weird // Init time zone select if (this.includeTimeValue && this.hasTimeZoneSelectWrapperTarget) { this.timeZoneSelect = this.timeZoneSelectWrapperTarget.querySelector('select.select2') - $(this.timeZoneSelect).select2({ + jQuery(this.timeZoneSelect).select2({ width: 'style' }) const self = this - $(this.timeZoneSelect).on('change.select2', function(event) { + jQuery(this.timeZoneSelect).on('change.select2', function(event) { const currentTimeZoneEl = self.currentTimeZoneWrapperTarget.querySelector('a') const {value} = event.target - $(self.timeZoneFieldTarget).val(value) - $(currentTimeZoneEl).text(value) - const selectedOptionTimeZoneButton = $('.selected-option-time-zone-button') + const selectedTimeZoneOption = event.target.options[event.target.options.selectedIndex] - if (self.defaultTimeZonesValue.includes(value)) { - $('.time-zone-button').removeClass('button').addClass('button-alternative') - selectedOptionTimeZoneButton.addClass('hidden').attr('hidden', true) - $(`a[data-value="${value}"`).removeClass('button-alternative').addClass('button') + if (self.hasTimeZoneFieldTarget) { + self.timeZoneFieldTarget.value = value + } + currentTimeZoneEl.textContent = selectedTimeZoneOption.textContent + + const selectedOptionTimeZoneButton = self.element.querySelector('.selected-option-time-zone-button') + + if (self.defaultTimeZonesValue.includes(selectedTimeZoneOption.textContent)) { + self.element.querySelectorAll('.time-zone-button').forEach(el => { + el.classList.remove('button'); + el.classList.add('button-alternative'); + }) + selectedOptionTimeZoneButton.classList.add('hidden') + selectedOptionTimeZoneButton.hidden = true + self.element.querySelectorAll(`a[data-value="${value}"`).forEach(el => { + el.classList.remove('button-alternative') + el.classList.add('button') + }) } else { // deselect any selected button - $('.time-zone-button').removeClass('button').addClass('button-alternative') - selectedOptionTimeZoneButton.text(value) - selectedOptionTimeZoneButton.attr('data-value', value).removeAttr('hidden') - selectedOptionTimeZoneButton.removeClass(['hidden', 'button-alternative']).addClass('button') + self.element.querySelectorAll('.time-zone-button').forEach(el => { + el.classList.remove('button'); + el.classList.add('button-alternative'); + }) + selectedOptionTimeZoneButton.textContent = selectedTimeZoneOption.textContent + selectedOptionTimeZoneButton.setAttribute('data-value', value) + selectedOptionTimeZoneButton.hidden = false + selectedOptionTimeZoneButton.classList.remove('hidden') + selectedOptionTimeZoneButton.classList.remove('button-alternative') + selectedOptionTimeZoneButton.classList.add('button') } self.resetTimeZoneUI() @@ -195,13 +234,13 @@ export default class extends Controller { teardownPluginInstance() { if (this.plugin === undefined) { return } - $(this.pluginMainEl).off('apply.daterangepicker') - $(this.pluginMainEl).off('cancel.daterangepicker') + jQuery(this.pluginMainEl).off('apply.daterangepicker') + jQuery(this.pluginMainEl).off('cancel.daterangepicker') // revert to original markup, remove any event listeners this.plugin.remove() - if (this.includeTimeValue) { - $(this.timeZoneSelect).select2('destroy'); + if (this.includeTimeValue && this.hasTimeZoneSelectWrapperTarget) { + jQuery(this.timeZoneSelectTarget).select2('destroy'); } } } diff --git a/bullet_train-fields/app/javascript/controllers/fields/super_select_controller.js b/bullet_train-fields/app/javascript/controllers/fields/super_select_controller.js index 11613188d..d15db9a7f 100644 --- a/bullet_train-fields/app/javascript/controllers/fields/super_select_controller.js +++ b/bullet_train-fields/app/javascript/controllers/fields/super_select_controller.js @@ -1,6 +1,7 @@ import { Controller } from "@hotwired/stimulus" require("select2/dist/css/select2.min.css"); import select2 from "select2"; +import jquery from "jquery"; const select2SelectedPreviewSelector = ".select2-selection--single" const select2SearchInputFieldSelector = ".select2-search__field" @@ -20,13 +21,16 @@ export default class extends Controller { initialize() { this.dispatchNativeEvent = this.dispatchNativeEvent.bind(this) + if (window.jQuery === undefined) { + window.jQuery = jquery + } if (!this.isSelect2LoadedOnWindowJquery) { select2() } } get isSelect2LoadedOnWindowJquery() { - return window?.$?.fn?.select2 !== undefined + return window?.jQuery?.fn?.select2 !== undefined } get optionsOverride() { @@ -41,16 +45,18 @@ export default class extends Controller { } disconnect() { - this.teardownPluginInstance() + if (this.isSelect2LoadedOnWindowJquery) { + this.teardownPluginInstance() + } } cleanupBeforeInit() { - $(this.element).find('.select2-container--default').remove() + this.element.querySelectorAll('.select2-container--default').forEach(el => el.remove()); } initPluginInstance() { let options = { - dropdownParent: $(this.element) + dropdownParent: jQuery(this.element) }; if (!this.enableSearchValue) { @@ -84,7 +90,7 @@ export default class extends Controller { this.cleanupBeforeInit() // in case improperly torn down this.pluginMainEl = this.selectTarget // required because this.selectTarget is unavailable on disconnect() - $(this.pluginMainEl).select2(options); + jQuery(this.pluginMainEl).select2(options); this.initReissuePluginEventsAsNativeEvents() } @@ -96,11 +102,11 @@ export default class extends Controller { this.teardownPluginEventsAsNativeEvents() // revert to original markup, remove any event listeners - $(this.pluginMainEl).select2('destroy'); + jQuery(this.pluginMainEl).select2('destroy'); } open() { - $(this.pluginMainEl).select2('open') + jQuery(this.pluginMainEl).select2('open') } focusOnTextField(event) { @@ -127,13 +133,13 @@ export default class extends Controller { initReissuePluginEventsAsNativeEvents() { this.constructor.jQueryEventsToReissue.forEach((eventName) => { - $(this.pluginMainEl).on(eventName, this.dispatchNativeEvent) + jQuery(this.pluginMainEl).on(eventName, this.dispatchNativeEvent) }) } teardownPluginEventsAsNativeEvents() { this.constructor.jQueryEventsToReissue.forEach((eventName) => { - $(this.pluginMainEl).off(eventName) + jQuery(this.pluginMainEl).off(eventName) }) } @@ -144,12 +150,12 @@ export default class extends Controller { // https://stackoverflow.com/questions/29290389/select2-add-image-icon-to-option-dynamically formatState(opt) { - var imageUrl = $(opt.element).attr('data-image'); + var imageUrl = opt.element?.dataset.image var imageHtml = ""; if (imageUrl) { imageHtml = ' '; } - return $('' + imageHtml + sanitizeHTML(opt.text) + ''); + return jQuery('' + imageHtml + sanitizeHTML(opt.text) + ''); } } @@ -158,4 +164,4 @@ function sanitizeHTML(str) { return str.replace(/[^\w. ]/gi, function (c) { return '&#' + c.charCodeAt(0) + ';'; }); -}; +}; \ No newline at end of file diff --git a/bullet_train-fields/package.json b/bullet_train-fields/package.json index 09ff2ce73..13cc4af35 100644 --- a/bullet_train-fields/package.json +++ b/bullet_train-fields/package.json @@ -60,6 +60,7 @@ "emoji-mart": "^5.1.0", "i18n-js": "^3.8.0", "intl-tel-input": "^17.0.8", + "jquery": "^3.7.1", "select2": "^4.0.13", "tributejs": "^5.1.3", "trix": "^2.0.1", diff --git a/bullet_train-fields/yarn.lock b/bullet_train-fields/yarn.lock index ce20a8109..0241861f2 100644 --- a/bullet_train-fields/yarn.lock +++ b/bullet_train-fields/yarn.lock @@ -2983,6 +2983,11 @@ jquery@>=1.10: resolved "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz" integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== +jquery@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" + integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" diff --git a/bullet_train-sortable/app/javascript/concerns/index.js b/bullet_train-sortable/app/javascript/concerns/index.js deleted file mode 100644 index 8a92d6d17..000000000 --- a/bullet_train-sortable/app/javascript/concerns/index.js +++ /dev/null @@ -1 +0,0 @@ -import "./reassignable" diff --git a/bullet_train-sortable/app/javascript/concerns/reassignable.js b/bullet_train-sortable/app/javascript/concerns/reassignable.js deleted file mode 100644 index ed1af24ba..000000000 --- a/bullet_train-sortable/app/javascript/concerns/reassignable.js +++ /dev/null @@ -1,63 +0,0 @@ -require("dragula/dist/dragula.min.css") - -import dragula from 'dragula'; - -function saveAssignments($container) { - var parents = $container.find('[data-reassignment-parent]').map(function(index,element) { return parseInt($(element).attr('data-id')); }).toArray(); - var assignmentsById = {} - $.each(parents, function(_, parentId) { - assignmentsById[parentId] = $container.find("[data-reassignment-parent]").filter("[data-id='" + parentId + "']").find("[data-reassignable]").map(function(index,element) { return parseInt($(element).attr('data-id')); }).toArray(); - }); - $.post($container.attr('data-reassign'), {assignments_by_id: assignmentsById, dispatched_at: new Date().getTime()}); -} - -function enableReassignable($scope) { - setTimeout(function() { - var selector = '[data-reassign]'; - var $reassignable = $scope.find(selector).addBack(selector); - - $reassignable.each(function (_, container) { - - console.log("🍩 Reassignable 💬 Found the following container:") - console.log(container); - - var $container = $(container); - var dragulaObj = dragula($container.find('[data-reassignment-parent]').toArray(), { - moves: function(el, container, handle) { - if ($(handle).hasClass('undraggable') || ($(handle).closest('.undraggable').length > 0)) { - return false; - } - return !!$(handle).closest('[data-reassignable]').length; - }, - accepts: function (el, target, source, sibling) { - if ($(sibling).hasClass('undraggable') && $(sibling).prev().hasClass('undraggable')) { - return false; - } else { - return true; - } - }, - }).on('drag', function (el) { - $reassignable.addClass('show-reassignable-targets') - }).on('drop', function (el) { - $reassignable.removeClass('show-reassignable-targets') - saveAssignments($container); - }).on('cancel', function (el) { - $reassignable.removeClass('show-reassignable-targets') - saveAssignments($container); - }).on('over', function (el, container) { - $(document.activeElement).blur(); - }); - }); - }, 500); -} - -$(document).on('turbo:load', function() { - console.log("🍩 Reassignable: Enabling on after a Turbo load.") - enableReassignable($('body')); -}) - -$(document).on('sprinkles:update', function(event) { - console.log("🍩 Reassignable: Enabling on the following element after a Sprinkles content update:") - console.log(event.target); - enableReassignable($(event.target)); -}) diff --git a/bullet_train-sortable/app/javascript/index.js b/bullet_train-sortable/app/javascript/index.js index 41afcfd88..e8faaff98 100644 --- a/bullet_train-sortable/app/javascript/index.js +++ b/bullet_train-sortable/app/javascript/index.js @@ -1,2 +1 @@ -import "./concerns" export * from './controllers' \ No newline at end of file