(function($) { 'use strict'; function parseJSON(value, fallback) { try { var parsed = JSON.parse(value); return parsed && typeof parsed === 'object' ? parsed : fallback; } catch (e) { return fallback; } } function csvToArray(text) { var rows = []; var row = []; var current = ''; var inQuotes = false; var i = 0; text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); for (i = 0; i < text.length; i++) { var char = text[i]; var next = text[i + 1]; if (char === '"') { if (inQuotes && next === '"') { current += '"'; i++; } else { inQuotes = !inQuotes; } continue; } if (char === ',' && !inQuotes) { row.push(current); current = ''; continue; } if (char === '\n' && !inQuotes) { row.push(current); rows.push(row); row = []; current = ''; continue; } current += char; } if (current !== '' || row.length) { row.push(current); rows.push(row); } return rows; } function TableBuilder($editor) { this.$editor = $editor; this.state = parseJSON($editor.attr('data-state') || '{}', {}); this.state.columns = this.state.columns || []; this.state.rows = this.state.rows || []; this.state.config = this.state.config || {}; this.state.style = this.state.style || {}; this.state.filters = this.state.filters || {}; this.state.responsive = this.state.responsive || {}; this.selected = { row: 0, col: 0 }; this.$grid = $editor.find('#kng-table-grid'); this.$columnList = $editor.find('.kng-column-list'); this.normalizeState(); this.bindEvents(); this.render(); } TableBuilder.prototype.normalizeState = function() { if (!this.state.columns.length) { this.state.columns = [{ label: 'Column 1', type: 'text', sortable: true, hide_mobile: false, align: 'left' }]; } var colCount = this.state.columns.length; this.state.rows = this.state.rows.map(function(row) { row = Array.isArray(row) ? row : []; while (row.length < colCount) { row.push({ value: '', tooltip: '', rowspan: 1, colspan: 1 }); } return row; }); if (!this.state.rows.length) { this.state.rows = [[{ value: '', tooltip: '', rowspan: 1, colspan: 1 }]]; } }; TableBuilder.prototype.bindEvents = function() { var self = this; var $segment = $('#ka-v3-theme-segment'); var $buttons = $segment.find('.ka-v3-segmented-btn'); var mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null; var mode = ($segment.attr('data-active') || 'dark').toString(); var mqlHandler = null; function saveUISetting(value) { if (!window.KNGTableBuilder) { return; } $.post(KNGTableBuilder.ajaxUrl, { action: 'king_addons_save_dashboard_ui', nonce: KNGTableBuilder.themeNonce, key: 'theme_mode', value: value }); } function updateSegment(activeMode) { $segment.attr('data-active', activeMode); $buttons.each(function() { var theme = ($(this).data('theme') || 'dark').toString(); $(this).attr('aria-pressed', theme === activeMode ? 'true' : 'false'); }); } function applyTheme(isDark) { $('body').toggleClass('ka-v3-dark', isDark); document.documentElement.classList.toggle('ka-v3-dark', isDark); } function setThemeMode(nextMode, save) { mode = (nextMode || 'dark').toString(); updateSegment(mode); if (mqlHandler && mql) { if (mql.removeEventListener) { mql.removeEventListener('change', mqlHandler); } else if (mql.removeListener) { mql.removeListener(mqlHandler); } mqlHandler = null; } if (mode === 'auto') { applyTheme(!!(mql && mql.matches)); mqlHandler = function(e) { if (mode !== 'auto') { return; } applyTheme(!!e.matches); }; if (mql) { if (mql.addEventListener) { mql.addEventListener('change', mqlHandler); } else if (mql.addListener) { mql.addListener(mqlHandler); } } } else { applyTheme(mode === 'dark'); } if (save) { saveUISetting(mode); } } $segment.on('click', '.ka-v3-segmented-btn', function(e) { e.preventDefault(); setThemeMode(($(this).data('theme') || 'dark').toString(), true); }); window.kaV3ToggleDark = function() { var isDark = $('body').hasClass('ka-v3-dark'); setThemeMode(isDark ? 'light' : 'dark', true); }; setThemeMode(mode, false); $('.kng-add-row').on('click', function() { self.addRow(); }); $('.kng-remove-row').on('click', function() { self.removeRow(); }); $('.kng-add-column').on('click', function() { self.addColumn(); }); $('.kng-remove-column').on('click', function() { self.removeColumn(); }); $('.kng-apply-span').on('click', function() { self.applySpan(); }); $('.kng-apply-import').on('click', function() { var text = $('.kng-table-import-text').val() || ''; self.applyImport(text); }); $('.kng-paste-clipboard').on('click', function() { if (navigator.clipboard && navigator.clipboard.readText) { navigator.clipboard.readText().then(function(text) { $('.kng-table-import-text').val(text); }); } }); $('.kng-table-form').on('submit', function() { self.syncHiddenFields(); }); }; TableBuilder.prototype.render = function() { this.renderTable(); this.renderColumns(); this.bindDynamicInputs(); this.updateInspector(); }; TableBuilder.prototype.renderTable = function() { var self = this; var columns = this.state.columns; var rows = this.state.rows; var $thead = $(''); columns.forEach(function(column, index) { var $th = $(''); $th.text(column.label || ('Column ' + (index + 1))); $th.attr('data-col', index); $thead.append($th); }); this.$grid.find('thead').html($thead); var $tbody = $(''); var skipMap = []; rows.forEach(function(row, rowIndex) { var $tr = $(''); var colIndex = 0; while (colIndex < columns.length) { if (skipMap[colIndex] && skipMap[colIndex] > 0) { skipMap[colIndex]--; colIndex++; continue; } var cell = row[colIndex] || { value: '', tooltip: '', rowspan: 1, colspan: 1 }; var colspan = parseInt(cell.colspan || 1, 10); var rowspan = parseInt(cell.rowspan || 1, 10); var $td = $(''); $td.text(cell.value || ''); $td.attr('data-row', rowIndex); $td.attr('data-col', colIndex); $td.attr('data-tooltip', cell.tooltip || ''); $td.attr('data-rowspan', rowspan); $td.attr('data-colspan', colspan); if (rowspan > 1) { $td.attr('rowspan', rowspan); for (var spanCol = 0; spanCol < colspan; spanCol++) { skipMap[colIndex + spanCol] = rowspan - 1; } } if (colspan > 1) { $td.attr('colspan', colspan); } $tr.append($td); colIndex += colspan; } $tbody.append($tr); }); this.$grid.find('tbody').replaceWith($tbody); this.$grid.find('td, th').on('click', function() { var $cell = $(this); self.selectCell($cell); }); this.$grid.find('td').on('input', function() { var $cell = $(this); self.updateCellValue($cell); }); this.$grid.find('th').on('input', function() { var $cell = $(this); self.updateColumnLabel($cell); }); }; TableBuilder.prototype.renderColumns = function() { var self = this; this.$columnList.empty(); this.state.columns.forEach(function(column, index) { var $row = $('
'); var $label = $(''); $label.val(column.label || ''); $label.attr('data-col', index); var $type = $(''); $type.append(''); $type.append(''); $type.append(''); $type.val(column.type || 'text'); $type.attr('data-col', index); var $hide = $(''); $hide.find('input').prop('checked', !!column.hide_mobile).attr('data-col', index); var $wrap = $('
'); $wrap.append($label).append($type).append($hide); $row.append($wrap); self.$columnList.append($row); }); }; TableBuilder.prototype.bindDynamicInputs = function() { var self = this; this.$columnList.find('.kng-column-label').off('input').on('input', function() { var col = parseInt($(this).attr('data-col'), 10); self.state.columns[col].label = $(this).val(); self.$grid.find('th[data-col="' + col + '"]').text($(this).val()); }); this.$columnList.find('.kng-column-type').off('change').on('change', function() { var col = parseInt($(this).attr('data-col'), 10); self.state.columns[col].type = $(this).val(); }); this.$columnList.find('.kng-column-hide').off('change').on('change', function() { var col = parseInt($(this).attr('data-col'), 10); self.state.columns[col].hide_mobile = $(this).is(':checked'); }); this.$editor.find('[data-config]').off('change input').on('change input', function() { var key = $(this).attr('data-config'); if ($(this).is(':checkbox')) { self.state.config[key] = $(this).is(':checked'); } else { self.state.config[key] = $(this).val(); } }); this.$editor.find('[data-style]').off('change').on('change', function() { var key = $(this).attr('data-style'); if ($(this).is(':checkbox')) { self.state.style[key] = $(this).is(':checked'); } else { self.state.style[key] = $(this).val(); } }); this.$editor.find('[data-responsive]').off('change').on('change', function() { var key = $(this).attr('data-responsive'); if ($(this).is(':checkbox')) { self.state.responsive[key] = $(this).is(':checked'); } else { self.state.responsive[key] = $(this).val(); } }); this.$editor.find('.kng-cell-tooltip').off('input').on('input', function() { var $cell = self.getSelectedCell(); if (!$cell) { return; } var tooltip = $(this).val(); $cell.attr('data-tooltip', tooltip); self.updateCellMeta($cell); }); }; TableBuilder.prototype.selectCell = function($cell) { this.$grid.find('.kng-selected').removeClass('kng-selected'); $cell.addClass('kng-selected'); if ($cell.is('th')) { this.selected = { row: 0, col: parseInt($cell.attr('data-col'), 10) }; } else { this.selected = { row: parseInt($cell.attr('data-row'), 10), col: parseInt($cell.attr('data-col'), 10) }; } this.updateInspector(); }; TableBuilder.prototype.getSelectedCell = function() { var row = this.selected.row; var col = this.selected.col; return this.$grid.find('td[data-row="' + row + '"][data-col="' + col + '"]'); }; TableBuilder.prototype.updateInspector = function() { var $cell = this.getSelectedCell(); if (!$cell || !$cell.length) { return; } var tooltip = $cell.attr('data-tooltip') || ''; var rowspan = $cell.attr('data-rowspan') || 1; var colspan = $cell.attr('data-colspan') || 1; this.$editor.find('.kng-cell-tooltip').val(tooltip); this.$editor.find('.kng-cell-rowspan').val(rowspan); this.$editor.find('.kng-cell-colspan').val(colspan); }; TableBuilder.prototype.applySpan = function() { var $cell = this.getSelectedCell(); if (!$cell || !$cell.length) { return; } var rowspan = parseInt(this.$editor.find('.kng-cell-rowspan').val(), 10) || 1; var colspan = parseInt(this.$editor.find('.kng-cell-colspan').val(), 10) || 1; $cell.attr('data-rowspan', rowspan).attr('data-colspan', colspan); $cell.attr('rowspan', rowspan).attr('colspan', colspan); this.updateCellMeta($cell); this.render(); }; TableBuilder.prototype.updateCellValue = function($cell) { var row = parseInt($cell.attr('data-row'), 10); var col = parseInt($cell.attr('data-col'), 10); this.state.rows[row][col].value = $cell.text(); }; TableBuilder.prototype.updateCellMeta = function($cell) { var row = parseInt($cell.attr('data-row'), 10); var col = parseInt($cell.attr('data-col'), 10); var cell = this.state.rows[row][col]; cell.tooltip = $cell.attr('data-tooltip') || ''; cell.rowspan = parseInt($cell.attr('data-rowspan'), 10) || 1; cell.colspan = parseInt($cell.attr('data-colspan'), 10) || 1; }; TableBuilder.prototype.updateColumnLabel = function($cell) { var col = parseInt($cell.attr('data-col'), 10); this.state.columns[col].label = $cell.text(); this.$columnList.find('.kng-column-label[data-col="' + col + '"]').val($cell.text()); }; TableBuilder.prototype.addRow = function() { if (window.KNGTableBuilder && !KNGTableBuilder.isPro && this.state.rows.length >= KNGTableBuilder.maxRows) { alert('Free limit reached. Upgrade to Pro for more rows.'); return; } var row = []; for (var i = 0; i < this.state.columns.length; i++) { row.push({ value: '', tooltip: '', rowspan: 1, colspan: 1 }); } this.state.rows.push(row); this.render(); }; TableBuilder.prototype.removeRow = function() { if (this.state.rows.length <= 1) { return; } this.state.rows.pop(); this.render(); }; TableBuilder.prototype.addColumn = function() { if (window.KNGTableBuilder && !KNGTableBuilder.isPro && this.state.columns.length >= KNGTableBuilder.maxCols) { alert('Free limit reached. Upgrade to Pro for more columns.'); return; } var index = this.state.columns.length + 1; this.state.columns.push({ label: 'Column ' + index, type: 'text', sortable: true, hide_mobile: false, align: 'left' }); this.state.rows.forEach(function(row) { row.push({ value: '', tooltip: '', rowspan: 1, colspan: 1 }); }); this.render(); }; TableBuilder.prototype.removeColumn = function() { if (this.state.columns.length <= 1) { return; } this.state.columns.pop(); this.state.rows.forEach(function(row) { row.pop(); }); this.render(); }; TableBuilder.prototype.applyImport = function(text) { if (!text.trim()) { return; } var raw = text.trim(); if (raw.indexOf('\t') !== -1 && raw.indexOf(',') === -1) { raw = raw.replace(/\t/g, ','); } var rows = csvToArray(raw); if (!rows.length) { return; } var header = rows.shift(); var columns = header.map(function(label, index) { return { label: label || ('Column ' + (index + 1)), type: 'text', sortable: true, hide_mobile: false, align: 'left' }; }); if (window.KNGTableBuilder && !KNGTableBuilder.isPro && columns.length > KNGTableBuilder.maxCols) { alert('CSV exceeds free column limit.'); return; } var dataRows = rows.map(function(row) { return columns.map(function(_, colIndex) { return { value: row[colIndex] || '', tooltip: '', rowspan: 1, colspan: 1 }; }); }); if (window.KNGTableBuilder && !KNGTableBuilder.isPro && dataRows.length > KNGTableBuilder.maxRows) { alert('CSV exceeds free row limit.'); return; } this.state.columns = columns; this.state.rows = dataRows; this.render(); }; TableBuilder.prototype.syncHiddenFields = function() { var responsive = $.extend({}, this.state.responsive); responsive.hide_columns = []; this.state.columns.forEach(function(column, index) { if (column.hide_mobile) { responsive.hide_columns.push(index); } }); $('#kng-table-data').val(JSON.stringify({ columns: this.state.columns, rows: this.state.rows, schema_version: 1 })); $('#kng-table-config').val(JSON.stringify(this.state.config)); $('#kng-table-style').val(JSON.stringify(this.state.style)); $('#kng-table-responsive').val(JSON.stringify(responsive)); }; $(document).ready(function() { var $editor = $('.kng-table-editor'); if ($editor.length) { new TableBuilder($editor); } }); })(jQuery);