UNPKG

node-red-contrib-knx-ultimate

Version:

Control your KNX and KNX Secure intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.

216 lines (203 loc) 8.17 kB
// 31/03/2020 Search Helper function htmlUtilsfullCSVSearch (sourceText, searchString) { let aSearchWords = [] if (searchString.toLowerCase().includes('exactmatch')) { // Find only the strict exact match of group address. For example, if the string is 0/0/2exactmatch, i return only the item in the csv having // group address 0/0/2 (and not also, for example 0/0/20) // I can have also an exact string like '0/0/1exactmatch 1.000', (GA or any text, plus the datapoint) and i must take it into consideration const aSearchStrings = searchString.split(' ') for (let index = 0; index < aSearchStrings.length; index++) { const element = aSearchStrings[index] if (element.includes('exactmatch')) { aSearchWords.push(element.replace('exactmatch', ' ')) // The last ' ' allow to return the exact match } else { aSearchWords.push(element) // The last ' ' allow to return the exact match } } } else { aSearchWords = searchString.toLowerCase().split(' ') } // This searches for all words in a string let i = 0 for (let index = 0; index < aSearchWords.length; index++) { if (sourceText.toLowerCase().indexOf(aSearchWords[index]) > -1) i += 1 } return i == aSearchWords.length } // 2025-09 Secure KNX helpers for GA autocompletes // Cache for secure GA lists per serverId window.__knxSecureGAsCache = window.__knxSecureGAsCache || {} function KNX_fetchSecureGAs (serverId) { return new Promise((resolve) => { try { if (window.__knxSecureGAsCache[serverId] instanceof Set) { resolve(window.__knxSecureGAsCache[serverId]) return } $.getJSON('knxUltimateKeyringDataSecureGAs?serverId=' + serverId + '&_=' + new Date().getTime(), (data) => { try { const set = new Set() if (Array.isArray(data)) data.forEach(ga => { if (typeof ga === 'string') set.add(ga) }) window.__knxSecureGAsCache[serverId] = set resolve(set) } catch (e) { resolve(new Set()) } }).fail(function () { resolve(new Set()) }) } catch (e) { resolve(new Set()) } }) } function KNX_enableSecureFormatting ($input, serverId) { try { KNX_fetchSecureGAs(serverId).then((secureSet) => { try { const inst = $input.autocomplete('instance') if (!inst) return inst._renderItem = function (ul, item) { // Try to detect GA from item.ga or from item.value string let ga = item.ga if (!ga && typeof item.value === 'string') { const m = item.value.match(/\b\d{1,2}\/\d{1,3}\/\d{1,3}\b/) if (m) ga = m[0] } const isSecure = ga ? secureSet.has(ga) : false const colorStyle = isSecure ? 'color: green;' : '' const shield = isSecure ? '<i class="fa fa-shield"></i> ' : '' const label = (typeof item.label === 'string') ? item.label : (item.value || '') return $('<li>').append(`<div style="${colorStyle}">${shield}${label}</div>`).appendTo(ul) } } catch (e) { } }) } catch (e) { } } // Expose helpers window.htmlUtilsfullCSVSearch = htmlUtilsfullCSVSearch window.KNX_fetchSecureGAs = KNX_fetchSecureGAs window.KNX_enableSecureFormatting = KNX_enableSecureFormatting // 2025-09: Make DPT selects searchable via jQuery UI Autocomplete function KNX_makeSelectSearchable ($select) { try { if (!($select && $select.length)) return const id = $select.attr('id') || ('knx-dpt-' + Math.random().toString(36).slice(2)) $select.attr('id', id) const prevCount = $select.data('knx-options-count') const curCount = $select.find('option').length const already = $select.data('knx-searchable') === true const needsSync = already && prevCount !== curCount function buildSource () { const items = [] $select.find('option').each(function () { const v = $(this).attr('value') const t = $(this).text() items.push({ label: t, value: v }) }) return items } function syncFromSelect ($input) { try { const val = $select.val() const txt = ($select.find('option:selected').text()) || '' if ($input) { $input.val(txt) } // refresh source if ($input && $input.data('ui-autocomplete')) { $input.autocomplete('option', 'source', buildSource()) } $select.data('knx-options-count', curCount) } catch (e) { } } if (!already) { // Create input next to select const width = $select.outerWidth() || 200 const $input = $('<input type="text" class="knx-dpt-combobox" autocomplete="off" />') .attr('id', id + '-search') .css('width', width + 'px') $select.after($input) // Hide original select but keep in DOM for value binding $select.hide() // Init autocomplete $input.autocomplete({ minLength: 0, source: buildSource(), select: function (event, ui) { try { event.preventDefault() } catch (e) {} $input.val(ui.item.label) $select.val(ui.item.value).trigger('change') }, focus: function (event, ui) { try { event.preventDefault() } catch (e) {} $input.val(ui.item.label) } }).on('focus click', function () { // Always show full list on click/focus try { $(this).autocomplete('search', '') } catch (e) {} }) // Initial sync syncFromSelect($input) // When select value changes programmatically, keep input in sync $select.on('change', function () { syncFromSelect($input) }) $select.data('knx-searchable', true) } else if (needsSync) { // Only refresh source and text const $input = $('#' + id + '-search') syncFromSelect($input) } } catch (e) { } } // Auto-enhance any select whose id contains 'dpt' (function () { if (window.__knx_dptObserverSetup) return; window.__knx_dptObserverSetup = true function enhance (root) { try { $(root).find('select[id*="dpt"]').each(function () { KNX_makeSelectSearchable($(this)) }) } catch (e) {} } try { const observer = new MutationObserver(function (mutations) { mutations.forEach(function (m) { if (!m.addedNodes) return m.addedNodes.forEach(function (n) { if (n.nodeType !== 1) return // If a select is added if (n.matches && n.matches('select[id*="dpt"]')) { enhance(n); return } // If options are added to an existing select if (n.matches && n.matches('option')) { const sel = n.closest && n.closest('select') if (sel && sel.id && sel.id.indexOf('dpt') > -1) { KNX_makeSelectSearchable($(sel)) } return } enhance(n) }) }) }) observer.observe(document.body, { childList: true, subtree: true }) // Initial pass if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { enhance(document.body) }) } else { enhance(document.body) } } catch (e) {} })() window.KNX_makeSelectSearchable = KNX_makeSelectSearchable; // Ensure autocomplete lists open immediately on focus/click (function ($) { if (!$.ui || !$.ui.autocomplete) return const _create = $.ui.autocomplete.prototype._create $.ui.autocomplete.prototype._create = function () { _create.call(this) const self = this const showAll = (val) => { if (self.options.disabled) return const term = (typeof val === 'string') ? val : self.element.val() || '' const minLen = self.options.minLength || 0 const query = term.length >= minLen ? term : '' try { self.search(query) } catch (error) { } } this.element.on('focus.knxAutocomplete click.knxAutocomplete', function () { clearTimeout(self._knxAutoTimer) self._knxAutoTimer = setTimeout(function () { showAll() }, 0) }) this.element.on('mousedown.knxAutocomplete', function () { clearTimeout(self._knxAutoTimer) self._knxAutoTimer = setTimeout(function () { showAll('') }, 0) }) } })(jQuery)