Replace BlazeComponent mixins with standalone utility modules

Delete the old Mixins global and BlazeComponent-based InfiniteScrolling
mixin. Add standalone replacements:
- client/lib/infiniteScrolling.js: plain scroll-based pagination helper
- client/lib/currentCard.js: centralized current card resolution
- client/components/forms/datepicker.js: extracted date picker logic
This commit is contained in:
Harry Adel 2026-03-08 10:56:50 +02:00
parent 2848b1a38a
commit 3b6f6fa80a
5 changed files with 180 additions and 35 deletions

View file

@ -0,0 +1,16 @@
import {
setupDatePicker,
datePickerRendered,
datePickerHelpers,
datePickerEvents,
} from '/client/lib/datepicker';
Template.datepicker.onCreated(function () {
setupDatePicker(this);
});
Template.datepicker.onRendered(function () {
datePickerRendered(this);
});
Template.datepicker.helpers(datePickerHelpers());

View file

@ -1,34 +0,0 @@
const peakAnticipation = 200;
Mixins.InfiniteScrolling = BlazeComponent.extendComponent({
onCreated() {
this._nextPeak = Infinity;
},
setNextPeak(v) {
this._nextPeak = v;
},
getNextPeak() {
return this._nextPeak;
},
resetNextPeak() {
this._nextPeak = Infinity;
},
events() {
return [
{
scroll(evt) {
const domElement = evt.currentTarget;
let altitude = domElement.scrollTop + domElement.offsetHeight;
altitude += peakAnticipation;
if (altitude >= this.callFirstWith(null, 'getNextPeak')) {
this.mixinParent().callFirstWith(null, 'reachNextPeak');
}
},
},
];
},
});

117
client/lib/currentCard.js Normal file
View file

@ -0,0 +1,117 @@
import Cards from '/models/cards';
function getCardIdFromData(data) {
if (!data || !data._id) {
return null;
}
if (
data.boardId ||
data.listId ||
typeof data.absoluteUrl === 'function' ||
typeof data.getTitle === 'function'
) {
return data._id;
}
return null;
}
function getCardIdFromElement(element) {
if (!element || typeof element.closest !== 'function') {
return null;
}
const cardDetails = element.closest('.js-card-details');
if (!cardDetails) {
return null;
}
return getCardIdFromData(Blaze.getData(cardDetails));
}
function getCardIdFromParentData(maxDepth = 8) {
for (let depth = 1; depth <= maxDepth; depth += 1) {
try {
const cardId = getCardIdFromData(Template.parentData(depth));
if (cardId) {
return cardId;
}
} catch (error) {
break;
}
}
return null;
}
function getPopupStack() {
if (typeof Popup !== 'undefined' && typeof Popup._getTopStack === 'function') {
return Popup._getTopStack();
}
return null;
}
export function getCurrentCardIdFromContext({ ignorePopupCard = false } = {}) {
let cardId;
try {
cardId = getCardIdFromData(Template.currentData());
if (cardId) {
return cardId;
}
} catch (error) {
// No active Blaze view.
}
cardId = getCardIdFromParentData();
if (cardId) {
return cardId;
}
const popupStack = getPopupStack();
cardId = getCardIdFromData(popupStack?.dataContext);
if (cardId) {
return cardId;
}
cardId = getCardIdFromElement(popupStack?.openerElement);
if (cardId) {
return cardId;
}
cardId = getCardIdFromElement(document.activeElement);
if (cardId) {
return cardId;
}
cardId = Session.get('currentCard');
if (cardId) {
return cardId;
}
if (!ignorePopupCard) {
cardId = Session.get('popupCardId');
if (cardId) {
return cardId;
}
}
const cardDetails = document.querySelectorAll('.js-card-details');
if (cardDetails.length === 1) {
return getCardIdFromElement(cardDetails[0]);
}
return null;
}
export function getCurrentCardFromContext(options) {
const cardId = getCurrentCardIdFromContext(options);
if (!cardId) {
return null;
}
return Cards.findOne(cardId);
}

View file

@ -0,0 +1,47 @@
const PEAK_ANTICIPATION = 200;
/**
* Infinite scrolling utility to replace the BlazeComponent mixin.
*
* Usage in a Template:
* Template.myTemplate.onCreated(function () {
* this.infiniteScrolling = new InfiniteScrolling();
* });
*
* The scroll event must be wired in Template.events:
* 'scroll .my-container'(event, tpl) {
* tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
* tpl.loadNextPage();
* });
* },
*
* Or for components that delegate to a child for loading:
* tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
* activitiesComponent.loadNextPage();
* });
*/
export class InfiniteScrolling {
constructor() {
this._nextPeak = Infinity;
}
setNextPeak(v) {
this._nextPeak = v;
}
getNextPeak() {
return this._nextPeak;
}
resetNextPeak() {
this._nextPeak = Infinity;
}
checkScrollPosition(domElement, reachNextPeakCallback) {
let altitude = domElement.scrollTop + domElement.offsetHeight;
altitude += PEAK_ANTICIPATION;
if (altitude >= this._nextPeak) {
reachNextPeakCallback();
}
}
}

View file

@ -1 +0,0 @@
Mixins = {};