Punk-Queue

 view release on metacpan or  search on metacpan

lib/Punk/Plugin/Queue/assets/funky/js/35-table.js  view on Meta::CPAN

	/**
	 * Set live connection status
	 */
	TableInstance.prototype._setLiveConnected = function(connected) {
		if (!this._liveBinding) return;

		this._liveBinding.connected = connected;

		// Update UI indicator
		var indicator = this.container.querySelector('.funky-live-indicator');
		if (indicator) {
			Funky.Dom.one(indicator)
				.classRemove('funky-live-connected', 'funky-live-disconnected')
				.classAdd(connected ? 'funky-live-connected' : 'funky-live-disconnected');
		}

		// Emit event
		this._emit(connected ? 'liveConnected' : 'liveDisconnected', {});
	};

	/**
	 * Create live status indicator
	 */
	TableInstance.prototype._createLiveIndicator = function() {
		var config = this.config.liveBinding;
		if (!config || !config.enabled) return;
		if (config.showIndicator === false) return;

		var D = Funky.Dom;

		var indicator = D.create('span')
			.classAdd('funky-live-indicator')
			.classAdd('funky-live-disconnected')
			.attr('title', 'Live updates')
			.append(D.create('span').classAdd('funky-live-dot'))
			.append(D.create('span').classAdd('funky-live-label').text('Live'));

		// Add to header actions
		var headerActions = D.one(this.container).one('.funky-table-header-actions');
		if (headerActions) {
			headerActions.append(indicator);
		} else {
			// Add before table
			D.one(this.container).prepend(indicator);
		}
	};

	// =========================================================================
	// Responsive (Phase 17)
	// =========================================================================

	/**
	 * Default breakpoints
	 */
	var DEFAULT_BREAKPOINTS = [
		{ name: 'mobile', width: 480 },
		{ name: 'mobile-l', width: 640 },
		{ name: 'tablet', width: 768 },
		{ name: 'tablet-l', width: 1024 },
		{ name: 'laptop', width: 1280 },
		{ name: 'desktop', width: 10000 }
	];

	/**
	 * Determine current breakpoint based on container width
	 * @param {boolean} force - Force recalculation even if breakpoint hasn't changed
	 */
	TableInstance.prototype._determineBreakpoint = function(force) {
		if (!this.config.responsive) return;

		// Use wrapper element for width detection (container may be replaced)
		var widthElement = this.wrapper && this.wrapper.el ? this.wrapper.el : this.container;
		var containerWidth = widthElement.offsetWidth || window.innerWidth;
		
		// Skip if container has no width (still hidden)
		if (containerWidth === 0) return;
		
		if (this.config.debug) {
			console.log('[Funky.Table] _determineBreakpoint containerWidth:', containerWidth, 'force:', !!force);
		}
		
		// Support responsive as object { enabled: true, breakpoints: [...] } or just breakpoints at config level
		var responsiveConfig = typeof this.config.responsive === 'object' ? this.config.responsive : {};
		var breakpoints = responsiveConfig.breakpoints || this.config.breakpoints || DEFAULT_BREAKPOINTS;
		
		// Ensure breakpoints is an array
		if (!Array.isArray(breakpoints)) {
			breakpoints = DEFAULT_BREAKPOINTS;
		}
		
		var newBreakpoint = 'desktop';

		// Sort breakpoints by width descending
		var sorted = breakpoints.slice().sort(function(a, b) {
			return b.width - a.width;
		});

		for (var i = 0; i < sorted.length; i++) {
			if (containerWidth <= sorted[i].width) {
				newBreakpoint = sorted[i].name;
			}
		}

		if (force || newBreakpoint !== this.currentBreakpoint) {
			this.currentBreakpoint = newBreakpoint;
			this._updateResponsiveColumns();

			if (this.config.debug) {
				console.log('[Funky.Table] Breakpoint changed:', newBreakpoint, '(' + containerWidth + 'px)', force ? '(forced)' : '');
			}

			this._emit('breakpointChange', { breakpoint: newBreakpoint, width: containerWidth });
		}
	};

	/**
	 * Handle window resize
	 */
	TableInstance.prototype._handleResize = function() {
		var self = this;

		// Debounce resize
		if (this._resizeTimeout) {
			clearTimeout(this._resizeTimeout);
		}

		this._resizeTimeout = setTimeout(function() {
			self._determineBreakpoint();
		}, 100);
	};

	/**
	 * Update visible columns based on breakpoint
	 */
	TableInstance.prototype._updateResponsiveColumns = function() {
		if (!this.config.responsive) return;

		if (this.config.debug) {
			console.log('[Funky.Table] _updateResponsiveColumns called, breakpoint:', this.currentBreakpoint);
			console.log('[Funky.Table] tbody rows before:', this.tbody && this.tbody.el ? this.tbody.el.querySelectorAll('tr').length : 0);
		}

		var self = this;
		var breakpoint = this.currentBreakpoint;
		// Use this.columns (processed) not this.config.columns (original)
		var columns = this.columns;

		// Get minimum priority for this breakpoint
		var maxPriority = this._getMaxPriorityForBreakpoint(breakpoint);

		if (this.config.debug) {

lib/Punk/Plugin/Queue/assets/funky/js/35-table.js  view on Meta::CPAN

				if (col.responsive[breakpoint] === false) {
					columnsToHide.push(index);
					return;
				}
			}

			// Check priority - columns without explicit responsivePriority always show
			// Lower priority numbers = more important = stay visible longer
			// Higher priority numbers = less important = hide first
			var colPriority = col.responsivePriority;
			if (colPriority === undefined || colPriority === null) {
				// No priority set - always show
				columnsToShow.push(index);
			} else if (colPriority > maxPriority) {
				// Priority exceeds breakpoint threshold - hide (less important)
				columnsToHide.push(index);
			} else {
				columnsToShow.push(index);
			}
		});

		// Store visibility info (include manually hidden in hidden count for control column)
		this.visibleColumns = columnsToShow;
		this.hiddenColumns = columnsToHide;
		this.manuallyHiddenColumns = manuallyHiddenColumns;

		if (this.config.debug) {
			console.log('[Funky.Table] Responsive: columnsToShow:', columnsToShow, 'columnsToHide:', columnsToHide, 'manuallyHidden:', manuallyHiddenColumns);
			console.log('[Funky.Table] Total columns:', columns.length, 'visible:', columnsToShow.length, 'hidden:', columnsToHide.length, 'manuallyHidden:', manuallyHiddenColumns.length);
		}

		// Apply visibility (only for responsive columns, not manually hidden ones)
		this._applyColumnVisibility(columnsToShow, columnsToHide);

		if (this.config.debug) {
			console.log('[Funky.Table] After _applyColumnVisibility, tbody rows:', this.tbody && this.tbody.el ? this.tbody.el.querySelectorAll('tr').length : 0);
		}

		// Show control column if ANY columns are hidden (responsive OR manual)
		var totalHidden = columnsToHide.length + manuallyHiddenColumns.length;
		this._updateControlColumn(totalHidden > 0);

		// Re-render aggregation row to update hidden columns
		if (this._aggregations) {
			this._renderAggregations();
		}

		// Collapse all expanded details rows when breakpoint changes
		this._collapseAllDetails();

		if (this.config.debug) {
			console.log('[Funky.Table] After _collapseAllDetails, tbody rows:', this.tbody && this.tbody.el ? this.tbody.el.querySelectorAll('tr').length : 0);
		}
	};

	/**
	 * Get breakpoint priority number
	 */
	TableInstance.prototype._getBreakpointPriority = function(breakpoint) {
		var priorities = {
			'desktop': 1,
			'laptop': 2,
			'tablet-l': 3,
			'tablet': 4,
			'mobile-l': 5,
			'mobile': 6
		};
		return priorities[breakpoint] || 1;
	};

	/**
	 * Get maximum priority to show at breakpoint
	 * Priority scale: 1-10 (lower = more important = stays visible longer)
	 * Columns with no responsivePriority always remain visible
	 */
	TableInstance.prototype._getMaxPriorityForBreakpoint = function(breakpoint) {
		// Threshold = maximum priority value to SHOW at this breakpoint
		// Lower priority number = more important = stays visible longer
		// Columns with priority > threshold will be hidden
		// At desktop (widest), threshold is high - show all columns
		// At mobile (narrowest), threshold is low - only priority 1-2 shown
		var thresholds = {
			'desktop': 10000,   // Show all (priority 1-10000)
			'laptop': 6,        // Show priority 1-6
			'tablet-l': 5,      // Show priority 1-5
			'tablet': 4,        // Show priority 1-4
			'mobile-l': 3,      // Show priority 1-3
			'mobile': 2         // Show priority 1-2
		};
		return thresholds[breakpoint] || 10000;
	};

	/**
	 * Apply column visibility to DOM
	 */
	TableInstance.prototype._applyColumnVisibility = function(show, hide) {
		var D = Funky.Dom;
		var self = this;

		// Update column state
		var columns = this.config.columns;
		columns.forEach(function(col, index) {
			col._responsive_hidden = hide.indexOf(index) !== -1;
		});

		// Check if any columns are being hidden (responsive mode active)
		var hasHiddenColumns = hide.length > 0;

		// Headers - update visibility and widths
		if (this._headerRow) {
			var headers = this._headerRow.querySelectorAll('th');
			for (var i = 0; i < headers.length; i++) {
				var th = headers[i];
				var index = parseInt(th.getAttribute('data-column-index'), 10);
				if (!isNaN(index)) {
					if (hide.indexOf(index) !== -1) {
						th.classList.add('funky-table-hidden');
					} else {
						th.classList.remove('funky-table-hidden');
						// When responsive mode is active, clear fixed widths so columns can expand
						// When back to desktop, restore original widths
						if (hasHiddenColumns) {
							// Store original width if not already stored
							if (!th.hasAttribute('data-original-width') && th.style.width) {
								th.setAttribute('data-original-width', th.style.width);
							}
							th.style.width = '';
						} else {
							// Restore original width if we stored one
							var originalWidth = th.getAttribute('data-original-width');
							if (originalWidth) {
								th.style.width = originalWidth;
							}
						}
					}
				}
			}
		}

		// Body cells
		if (this.tbody && this.tbody.el) {
			var rows = this.tbody.el.querySelectorAll('tr:not(.funky-table-details-row)');
			for (var r = 0; r < rows.length; r++) {
				var cells = rows[r].querySelectorAll('td');
				for (var c = 0; c < cells.length; c++) {
					var td = cells[c];
					var colIndex = parseInt(td.getAttribute('data-column-index'), 10);
					if (!isNaN(colIndex)) {
						if (hide.indexOf(colIndex) !== -1) {
							td.classList.add('funky-table-hidden');
						} else {
							td.classList.remove('funky-table-hidden');
						}
					}
				}
			}
		}

		// Footer cells
		if (this._tfoot) {
			var footerCells = this._tfoot.querySelectorAll('td');
			for (var f = 0; f < footerCells.length; f++) {
				var ftd = footerCells[f];
				var fIndex = parseInt(ftd.getAttribute('data-column-index'), 10);
				if (!isNaN(fIndex)) {
					if (hide.indexOf(fIndex) !== -1) {
						ftd.classList.add('funky-table-hidden');
					} else {
						ftd.classList.remove('funky-table-hidden');
					}
				}
			}
		}
	};

	/**
	 * Update control column visibility
	 */
	TableInstance.prototype._updateControlColumn = function(show) {
		var controlHeader = this._headerRow ? this._headerRow.querySelector('th.funky-table-control-header') : null;
		var controlCells = this.tbody && this.tbody.el ? this.tbody.el.querySelectorAll('td.funky-table-control-cell') : [];



( run in 0.423 second using v1.01-cache-2.11-cpan-b16cb0d3907 )