Leaderboard
Popular Content
Showing content with the highest reputation since 09/08/26 in all areas
-
Date validation is also performed for months with 28, 30, and 31 days. UniComboBox1.ClientEvents.ExtEvents.afterrender function afterrender(sender, eOpts) { var me = sender; if (me.store && me.store.getCount() === 0) { me.store.add({ text: '', value: '' }); me.setValue(''); } if (me._wheelSetup) return; me._wheelSetup = true; // ============ Настройка ширины и высоты пикера ============ var adjustPickerSize = function () { try { var picker = me.getPicker(); if (!picker) return; var minPickerWidth = 300; if (me.getWidth() < minPickerWidth) { picker.setWidth(minPickerWidth); picker.minWidth = minPickerWidth; } else { picker.setWidth(me.getWidth()); } picker.setHeight(175); if (typeof picker.updateLayout === 'function') { picker.updateLayout(); } } catch (e) { console.error('adjustPickerSize error:', e); } }; // ---------- вспомогательные функции ---------- var pad2 = function(n){ return (n < 10 ? '0' : '') + n; }; var monthNamesRu = ["январь","февраль","март","апрель","май","июнь", "июль","август","сентябрь","октябрь","ноябрь","декабрь"]; var parseDisplay = function(s) { if (!s) return null; var m = String(s).match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/); if (!m) return null; return { d: +m[1], m: +m[2], y: +m[3] }; }; var fireAjax = function (ev, params) { if (typeof ajaxRequest === 'function') { ajaxRequest(me, ev, params); } else if (me.ajaxRequest) { me.ajaxRequest(ev, params); } else { var c = me; while (c && !c.ajaxRequest) c = c.ownerCt; if (c) c.ajaxRequest(ev, params, false, me); } }; // ---------- Построение HTML ---------- var buildHtml = function (pickerId) { return '' + '<style>' + '.wpc-wrap{background:#fff;padding:8px;width:100%;box-sizing:border-box;font-family:Tahoma,Arial;}' + '.wpc-picker{position:relative;display:flex;justify-content:center;height:120px;overflow:hidden;' + 'background:#f0f0f0;border:1px solid #ccc;border-radius:8px;}' + '.wpc-picker::before{content:"";position:absolute;top:40px;left:0;right:0;height:40px;' + 'border-top:2px solid #007bff;border-bottom:2px solid #007bff;pointer-events:none;z-index:2;}' + '.wpc-col{height:120px;overflow-y:scroll;flex:1 1 0;text-align:center;' + 'scrollbar-width:none;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;}' + '.wpc-col::-webkit-scrollbar{display:none;}' + '.wpc-item{height:40px;line-height:40px;font-size:14px;color:#000;white-space:nowrap;padding:0 4px;}' + '.wpc-pad{height:40px;}' + '.wpc-actions{display:flex;gap:6px;margin-top:8px;}' + '.wpc-actions button{flex:1;padding:8px 0;border:none;border-radius:3px;cursor:pointer;font-weight:bold;}' + '.wpc-clear{background:#e0e0e0;color:#333;}' + '.wpc-apply{background:#4CAF50;color:#fff;}' + '</style>' + '<div class="wpc-wrap">' + '<div class="wpc-picker">' + '<div class="wpc-col" id="' + pickerId + '_dayCol"></div>' + '<div class="wpc-col" id="' + pickerId + '_monthCol"></div>' + '<div class="wpc-col" id="' + pickerId + '_yearCol"></div>' + '</div>' + '<div class="wpc-actions">' + '<button type="button" class="wpc-clear">Очистить</button>' + '<button type="button" class="wpc-apply">Применить</button>' + '</div>' + '</div>'; }; // ---------- Выполнение JS ---------- var runWheelJs = function (rootEl, pickerId, init) { var years = []; var curY = new Date().getFullYear(); for (var y = curY; y >= 1900; y--) years.push(String(y)); var months = monthNamesRu.slice(); var days = []; var dayCol = rootEl.querySelector('#' + pickerId + '_dayCol'); var monthCol = rootEl.querySelector('#' + pickerId + '_monthCol'); var yearCol = rootEl.querySelector('#' + pickerId + '_yearCol'); var applyBtn = rootEl.querySelector('.wpc-apply'); var clearBtn = rootEl.querySelector('.wpc-clear'); if (!dayCol || !monthCol || !yearCol) { console.error('cols not found'); return; } // ============ Вспомогательные функции ============ var fill = function (col, list) { var h = '<div class="wpc-pad"></div>'; for (var i = 0; i < list.length; i++) h += '<div class="wpc-item">' + list[i] + '</div>'; h += '<div class="wpc-pad"></div>'; col.innerHTML = h; }; // ✅ Количество дней в месяце для указанного года (високосный год учитывается автоматически) var daysInMonth = function (year, monthIndex) { return new Date(year, monthIndex + 1, 0).getDate(); }; // ✅ Перестроить колонку дней на основе текущего состояния (месяц/год) var refillDays = function () { var y = parseInt(years[state.yearCol], 10); var m = state.monthCol; var n = daysInMonth(y, m); // Если количество дней изменилось, перезаполнить колонку дней if (days.length !== n) { days = []; for (var d = 1; d <= n; d++) days.push(String(d)); fill(dayCol, days); } // Если выбранный день превышает допустимый, перейти к последнему допустимому дню if (state.dayCol >= n) { state.dayCol = n - 1; smoothScrollTo(dayCol, state.dayCol * 40, 220); } }; // ============ state и анимация ============ var state = { dayCol: 0, monthCol: 0, yearCol: 0 }; var smoothScrollTo = function (col, target, duration) { var start = col.scrollTop, change = target - start, st = null; if (col._animId) cancelAnimationFrame(col._animId); var easeOut = function (t) { return 1 - Math.pow(1 - t, 3); }; var step = function (ts) { if (!st) st = ts; var t = Math.min((ts - st) / duration, 1); col.scrollTop = start + change * easeOut(t); if (t < 1) col._animId = requestAnimationFrame(step); else { col._animId = null; col.scrollTop = target; } }; col._animId = requestAnimationFrame(step); }; var goTo = function (col, idx, list) { if (idx < 0) idx = 0; if (idx >= list.length) idx = list.length - 1; state[col.id.replace(pickerId + '_', '')] = idx; smoothScrollTo(col, idx * 40, 220); // ✅ Если месяц или год изменился, перестроить дни if (col === monthCol || col === yearCol) { refillDays(); } }; // ============ Начальное значение ============ var setDate = function (y, m, d) { var yi = years.indexOf(String(y)); if (yi < 0) yi = 0; var mi = months.indexOf(m); if (mi < 0) mi = 0; // ✅ Сначала установить state года и месяца state.yearCol = yi; state.monthCol = mi; // ✅ Построить дни на основе нового месяца/года var n = daysInMonth(y, mi); days = []; for (var i = 1; i <= n; i++) days.push(String(i)); fill(dayCol, days); var di = days.indexOf(String(d)); if (di < 0) di = n - 1; goTo(yearCol, yi, years); goTo(monthCol, mi, months); goTo(dayCol, di, days); }; // ============ Первоначальное заполнение колонок ============ // Колонка дней на основе начального месяца/года (function initDays() { var n = daysInMonth(init.y, init.m - 1); for (var d = 1; d <= n; d++) days.push(String(d)); })(); fill(yearCol, years); fill(monthCol, months); fill(dayCol, days); // ============ Ручная прокрутка ============ var attachScroll = function (col, list, key) { col.addEventListener('scroll', function () { if (col._animId) return; clearTimeout(col._timer); col._timer = setTimeout(function () { var idx = Math.round(col.scrollTop / 40); if (idx < 0) idx = 0; if (idx >= list.length) idx = list.length - 1; var t = idx * 40; if (Math.abs(col.scrollTop - t) > 0.5) smoothScrollTo(col, t, 180); state[key] = idx; // ✅ Если месяц или год изменился, перестроить дни if (key === 'monthCol' || key === 'yearCol') { refillDays(); } }, 100); }, { passive: true }); }; attachScroll(yearCol, years, 'yearCol'); attachScroll(monthCol, months, 'monthCol'); attachScroll(dayCol, days, 'dayCol'); // ============ Колесо мыши ============ var attachWheel = function (col, list, key) { col.addEventListener('wheel', function (e) { e.preventDefault(); var dir = e.deltaY > 0 ? 1 : -1; var idx = state[key] + dir; goTo(col, idx, list); }, { passive: false }); }; attachWheel(yearCol, years, 'yearCol'); attachWheel(monthCol, months, 'monthCol'); attachWheel(dayCol, days, 'dayCol'); // ============ Кнопки ============ applyBtn.addEventListener('click', function () { var d = days[state.dayCol]; var m = state.monthCol + 1; var y = years[state.yearCol]; var val = pad2(+d) + '.' + pad2(m) + '.' + y; me.setValue(val); fireAjax('dateSelected', ['date=' + encodeURIComponent(val)]); me.collapse(); }); clearBtn.addEventListener('click', function () { me.setValue(''); me.collapse(); }); // ============ Применить начальное значение ============ setDate(init.y, monthNamesRu[init.m - 1], init.d); // Сохранить для последующих обновлений rootEl._setDate = setDate; }; // ---------- Основное внедрение ---------- var initializePicker = function () { try { adjustPickerSize(); var picker = me.getPicker(); if (!picker || !picker.el) return; var pickerEl = picker.el.dom; var pickerId = picker.id; // Если уже создано, только обновить значение if (pickerEl.querySelector('#' + pickerId + '_wheelContainer')) { var existing = pickerEl.querySelector('#' + pickerId + '_wheelContainer'); var p = parseDisplay(me.getValue()); if (existing._setDate && p) { existing._setDate(p.y, monthNamesRu[p.m - 1], p.d); } return; } // Скрыть стандартный список ExtJS var listCt = picker.el.down('.x-boundlist-list-ct'); if (listCt) listCt.setStyle('display', 'none'); // Начальное значение var now = new Date(); var init = parseDisplay(me.getValue()) || { d: now.getDate(), m: now.getMonth() + 1, y: now.getFullYear() }; // Контейнер var container = document.createElement('div'); container.id = pickerId + '_wheelContainer'; container.innerHTML = buildHtml(pickerId); if (pickerEl.firstChild) pickerEl.insertBefore(container, pickerEl.firstChild); else pickerEl.appendChild(container); // Выполнить JS runWheelJs(container, pickerId, init); // После внедрения снова настроить размер setTimeout(adjustPickerSize, 50); } catch (err) { console.error('Wheel init error:', err); } }; // ---------- Инициализация ---------- me.on('expand', function () { setTimeout(function () { adjustPickerSize(); initializePicker(); }, 50); }); setTimeout(initializePicker, 200); }4 points
-
Build 1618 will be about: TLS 1.3 support (Through HyperServer) Many SSL related improvements Multiple SSL related bug fixes (Both Windows & Linux) Ability to define folders for SSL library and SSL certificate paths Improved SSL documents Additional bug fixes and stability improvements New TUniSignaturePad component3 points
-
3 points
-
Add this code to the project so that it automatically creates a divider between each item for the following components. I think it would make the project much more beautiful. TUniCheckComboBox TUniListBox TUniComboBox .x-boundlist-item { border-bottom: 1px solid #e8e8e8 !important; } .x-boundlist-item:last-child { border-bottom: none !important; } .x-boundlist-item { border-bottom: 1px dashed #d0d0d0 !important; }2 points
-
A complete, working setup for deploying a uniGUI application on Linux: HyperServer running as a systemd service, Nginx in front as a reverse proxy, and a free Let's Encrypt certificate for HTTPS. Follow it top to bottom and you'll have a production-ready deployment. What we're building: Domain: example.com App binary: yourapp HyperServer listens on port 8077 (internal only) Users hit port 443 (HTTPS) What you need first: A Linux server (Ubuntu or Debian recommended) Root or sudo access Your uniGUI app compiled for Linux64 A domain name (like example.com) with an A record set to your server's IP address. You do this at whoever you bought the domain from — GoDaddy, Namecheap, Cloudflare, etc. If you're new to the Linux side of uniGUI, read the official page first — it covers the Linux64 requirements and the runtime install: https://www.unigui.com/doc/online_help/index.html?linux64.htm Step 1 — One change in Delphi before you compile Your app needs to know where the uniGUI runtime files live on Linux. Open ServerModule.pas and drop this into the BeforeInit event: procedure TUniServerModule.UniGUIServerModuleBeforeInit(Sender: TObject); begin {$ifdef Linux64} // Same path we create in Step 2 FrameworkFilesRoot := '/etc/fmsoft/unigui'; {$endif} end; Build in Release mode. Step 2 — Install the uniGUI runtime on the server Log in to the FMSoft customer portal and open Manage > Products. Select your product row, then click Download — a dialog opens with your releases on the left and Available Files on the right. Pick your release, expand the build number, and you'll see several files for that build. The one you want is the .tar.xz, e.g. fmsoft_unigui_complete_runtime_1.99.0.1614.tar.xz. Upload it to /tmp on your server (SFTP or SCP), then: sudo mkdir -p /etc/fmsoft/unigui sudo tar -xf /tmp/fmsoft_unigui_complete_runtime_1.99.0.1614.tar.xz \ -C /etc/fmsoft/unigui --strip-components=1 Use whatever version number your file has. It should match the uniGUI version you compiled with. Step 3 — Upload the app Make a folder for everything: sudo mkdir /hyper_server Upload three files into it: hyper_server — the Linux64 binary that comes with your uniGUI install yourapp — your compiled app hyper_server.cfg — your config file Then fix the permissions. Binaries need to be executable, the config doesn't: cd /hyper_server sudo chmod 0755 hyper_server sudo chmod 0755 yourapp sudo chmod 0644 hyper_server.cfg Don't write this file from scratch. HyperServer ships with a default hyper_server.cfg — copy that one and change only these keys: [hyper_server] binary_name=yourapp framework_files_root_linux=/etc/fmsoft/unigui port=8077 Step 4 — Make it a systemd service This way it starts on boot and comes back up if it crashes. sudo nano /etc/systemd/system/yourapp.service [Unit] Description=My uniGUI Application Service After=network.target [Service] Type=simple Restart=always WorkingDirectory=/hyper_server ExecStart=/hyper_server/hyper_server # Ask HyperServer to shut down cleanly instead of killing it ExecStop=/usr/bin/curl --max-time 10 http://127.0.0.1:8077/?action=terminate ExecStop=/bin/sleep 5 [Install] WantedBy=multi-user.target Turn it on: sudo systemctl daemon-reload sudo systemctl enable yourapp.service sudo systemctl start yourapp Step 5 — Put Nginx in front HyperServer stays on 8077 where nobody outside can reach it. Nginx takes the public traffic and passes it along. sudo apt update sudo apt install nginx sudo nano /etc/nginx/sites-available/example.com Start with plain HTTP — Certbot will add the SSL part for us in the next step: server { listen 80; server_name example.com; location / { proxy_pass http://127.0.0.1:8077/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSockets — uniGUI needs this proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 600; } } Enable it: sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx Step 6 — Free SSL with Let's Encrypt Certbot does the certificate and rewrites the Nginx config for you: sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d example.com Answer the prompts. When it asks about redirecting HTTP to HTTPS, pick option 2 (Redirect). Step 7 — Check it Your app: https://example.com/ HyperServer control panel: https://example.com/server If something doesn't work, the usual suspects are a mismatched binary_name, missing execute permissions on the binaries, or a FrameworkFilesRoot that doesn't point at the runtime directory. Post below and I'll help where I can.2 points
-
function beforeInit(sender, config) { Ext.apply(sender, { allowBlank: true, msgTarget: 'side', blankText: 'این فیلد الزامی است!', validateOnChange: true, validateOnBlur: true, validator: function (value) { if (!value || value.trim() === '') { return true; } for (var i = 0; i < value.length; i++) { var c = value.charAt(i); if (c !== '/' && (c < '0' || c > '9')) { return 'فقط رقم و اسلش مجاز است (0-9, /)!'; } } var parts = String(value).split('/'); if (parts.length !== 3) { return 'فرمت تاریخ نادرست است! از قالب yyyy/mm/dd استفاده کنید (مثال: 1403/09/15)'; } if (parts[0].length !== 4 || parts[1].length < 1 || parts[1].length > 2 || parts[2].length < 1 || parts[2].length > 2) { return 'فرمت تاریخ نادرست است! از قالب yyyy/mm/dd استفاده کنید (مثال: 1403/09/15)'; } var jy = parseInt(parts[0], 10); var jm = parseInt(parts[1], 10); var jd = parseInt(parts[2], 10); if (isNaN(jy) || isNaN(jm) || isNaN(jd)) { return 'فرمت تاریخ نادرست است! از قالب yyyy/mm/dd استفاده کنید (مثال: 1403/09/15)'; } if (jm < 1 || jm > 12) { return 'ماه باید بین 01 تا 12 باشد!'; } var maxYear = 1400 + 100; if (jy < 1300 || jy > maxYear) { return 'سال باید بین 1300 تا ' + maxYear + ' باشد!'; } if (jd < 1) { return 'روز باید بزرگتر از 0 باشد!'; } var isLeap = function (y) { var r = ((y * 25) + 11) % 33; return r < 8; }; var maxDay; if (jm <= 6) maxDay = 31; else if (jm <= 11) maxDay = 30; else maxDay = isLeap(jy) ? 30 : 29; if (jd > maxDay) { var monthNamesFa = ["فروردین","اردیبهشت","خرداد","تیر","مرداد","شهریور", "مهر","آبان","آذر","دی","بهمن","اسفند"]; return 'در ماه ' + monthNamesFa[jm - 1] + ' سال ' + jy + ' فقط ' + maxDay + ' روز وجود دارد!'; } return true; } }); if (!document.getElementById('_datefield_invalid_css')) { var style = document.createElement('style'); style.id = '_datefield_invalid_css'; style.innerHTML = '' + '.x-form-invalid-field,' + '.x-form-invalid-field-default {' + ' background-color: #ffcdd2 !important;' + ' background-image: none !important;' + ' border-color: #e53935 !important;' + ' color: #b71c1c !important;' + '}' + '.x-form-invalid-field:focus,' + '.x-form-invalid-field-default:focus {' + ' background-color: #ffcdd2 !important;' + ' background-image: none !important;' + ' border-color: #c62828 !important;' + '}' + '.x-form-trigger-wrap-invalid,' + '.x-form-trigger-wrap-default.x-form-trigger-wrap-invalid {' + ' background-color: #ffcdd2 !important;' + '}' + '.x-form-invalid-under {' + ' color: #e53935 !important;' + ' font-weight: bold;' + '}' + '.x-form-invalid-icon {' + ' background-color: #e53935 !important;' + '}'; document.head.appendChild(style); } } function afterCreate(sender, eOpts) { var me = sender; if (me._datePickerSetup) return; me._datePickerSetup = true; // ============ Fix for mobile: disable Ext.Msg animations ============ try { if (Ext.Msg && Ext.Msg.defaultAllowedConfig) { Ext.Msg.defaultAllowedConfig.showAnimation = false; Ext.Msg.defaultAllowedConfig.hideAnimation = false; } } catch (e) { console.error('Ext.Msg animation config error:', e); } try { var pad2 = function(n){ return (n < 10 ? '0' : '') + n; }; // ============ Persian (Jalali) calendar names ============ var monthNamesFa = ["فروردین","اردیبهشت","خرداد","تیر","مرداد","شهریور", "مهر","آبان","آذر","دی","بهمن","اسفند"]; // ============ Gregorian <-> Jalali conversion ============ var div = function (a, b) { return ~~(a / b); }; var gregorianToJalali = function (gy, gm, gd) { var g_d_m = [0,31,59,90,120,151,181,212,243,273,304,334]; var jy = (gy <= 1600) ? 0 : 979; gy -= (gy <= 1600) ? 621 : 1600; var gy2 = (gm > 2) ? (gy + 1) : gy; var days = (365 * gy) + div((gy2 + 3), 4) - div((gy2 + 99), 100) + div((gy2 + 399), 400) - 80 + gd + g_d_m[gm - 1]; jy += 33 * div(days, 12053); days %= 12053; jy += 4 * div(days, 1461); days %= 1461; if (days > 365) { jy += div((days - 1), 365); days = (days - 1) % 365; } var jm = (days < 186) ? 1 + div(days, 31) : 7 + div((days - 186), 30); var jd = 1 + ((days < 186) ? (days % 31) : ((days - 186) % 30)); return { jy: jy, jm: jm, jd: jd }; }; var isJalaliLeap = function (jy) { var r = ((jy * 25) + 11) % 33; return r < 8; }; var daysInJalaliMonth = function (jy, jm) { if (jm <= 6) return 31; if (jm <= 11) return 30; return isJalaliLeap(jy) ? 30 : 29; }; var formatJalali = function (jy, jm, jd) { return jy + '/' + pad2(jm) + '/' + pad2(jd); }; var parseJalali = function (s) { if (!s) return null; var parts = String(s).split('/'); if (parts.length !== 3) return null; if (parts[0].length !== 4) return null; if (parts[1].length < 1 || parts[1].length > 2) return null; if (parts[2].length < 1 || parts[2].length > 2) return null; var jy = parseInt(parts[0], 10); var jm = parseInt(parts[1], 10); var jd = parseInt(parts[2], 10); if (isNaN(jy) || isNaN(jm) || isNaN(jd)) return null; return { jy: jy, jm: jm, jd: jd }; }; var validateFullJalali = function (jy, jm, jd) { if (isNaN(jy) || isNaN(jm) || isNaN(jd)) { return 'تاریخ وارد نشده یا نادرست است!'; } if (jm < 1 || jm > 12) { return 'ماه باید بین 01 تا 12 باشد!'; } var maxYear = 1400 + 100; if (jy < 1300 || jy > maxYear) { return 'سال باید بین 1300 تا ' + maxYear + ' باشد!'; } if (jd < 1) { return 'روز باید بزرگتر از 0 باشد!'; } var maxDay = daysInJalaliMonth(jy, jm); if (jd > maxDay) { var mName = monthNamesFa[jm - 1]; return 'در ماه ' + mName + ' سال ' + jy + ' فقط ' + maxDay + ' روز وجود دارد!'; } return null; }; var showAlert = function (title, msg) { try { Ext.Msg.alert(title, msg); } catch (e) { console.error('showAlert error:', e); try { window.alert(title + '\n' + msg); } catch (e2) {} } }; var fireAjax = function (ev, params) { try { if (typeof ajaxRequest === 'function') { ajaxRequest(me, ev, params); } else if (me.ajaxRequest) { me.ajaxRequest(ev, params); } else { var c = me; while (c && !c.ajaxRequest) c = c.ownerCt; if (c) c.ajaxRequest(ev, params, false, me); } } catch (e) { console.error('fireAjax error:', e); } }; // ============ 1. Placeholder ============ var setPlaceholder = function () { try { var inputDom = null; if (me.input && me.input.dom) inputDom = me.input.dom; else if (me.el) { var inp = me.el.down('input'); if (inp) inputDom = inp.dom || inp; } if (inputDom && inputDom.setAttribute) { inputDom.setAttribute('placeholder', 'yyyy/mm/dd'); me._realInput = inputDom; } } catch (e) { console.error('setPlaceholder error:', e); } }; // ============ 2. Build slots ============ var todayJalali = (function () { var now = new Date(); return gregorianToJalali(now.getFullYear(), now.getMonth() + 1, now.getDate()); })(); var buildYearSlots = function () { var arr = []; var endYear = todayJalali.jy + 100; for (var y = 1300; y <= endYear; y++) arr.push({ text: String(y), value: String(y) }); return arr; }; var buildMonthSlots = function () { var arr = []; for (var i = 0; i < 12; i++) arr.push({ text: monthNamesFa[i], value: String(i + 1) }); return arr; }; var buildDaySlots = function () { var arr = []; for (var d = 1; d <= 31; d++) arr.push({ text: String(d), value: String(d) }); return arr; }; // ============ 3. Read date from picker ============ var readPickerDate = function (picker) { try { if (!picker) return null; var values = null; if (picker.getValues) { try { values = picker.getValues(); } catch (e) {} } if (!values && picker.getValue) { try { values = picker.getValue(); } catch (e) {} } if (!values) return null; if (Ext.isArray && Ext.isArray(values)) { var obj = {}; for (var i = 0; i < values.length; i++) { if (values[i] && values[i].name !== undefined) { obj[values[i].name] = values[i].value; } } values = obj; } var jd = parseInt(values.day, 10); var jm = parseInt(values.month, 10); var jy = parseInt(values.year, 10); if (isNaN(jd) || isNaN(jm) || isNaN(jy)) return null; return { jy: jy, jm: jm, jd: jd }; } catch (e) { console.error('readPickerDate error:', e); return null; } }; // ============ 4. Apply date ============ var applyDate = function (jy, jm, jd) { var full = formatJalali(jy, jm, jd); try { me.setValue(full); } catch (e) { console.error('setValue error:', e); } fireAjax('dateSelected', ['date=' + encodeURIComponent(full)]); console.log('Jalali date applied:', full); }; // ================================================================ // 5. تخریب کامل picker // ================================================================ var destroyMyPicker = function () { try { if (me._myDatePicker) { if (me._myDatePicker.destroy) { try { me._myDatePicker.destroy(); } catch (e) {} } me._myDatePicker = null; } } catch (e) { console.error('destroyMyPicker error:', e); } // هر picker بومی احتمالی Ext را هم نابود کن try { var natives = Ext.Viewport.query('.x-picker'); if (natives) { for (var i = 0; i < natives.length; i++) { try { var cmp = Ext.getCmp(natives[i].id); if (cmp && cmp.destroy) cmp.destroy(); } catch (e) {} } } } catch (e) {} // حذف ماسکهای باقیمانده try { var masks = Ext.getBody().query('.x-mask'); for (var j = 0; j < masks.length; j++) { if (masks[j] && masks[j].parentNode) { masks[j].parentNode.removeChild(masks[j]); } } } catch (e) {} }; // ============ 5.1 Highlight selected items ============ var highlightSelectedItems = function (picker) { if (!picker) return; try { var el = picker.el; if (!el) return; var dom = el.dom || el; if (!dom) return; var allItems = dom.querySelectorAll('.x-picker-item'); for (var i = 0; i < allItems.length; i++) { allItems[i].classList.remove('x-picker-item-selected-highlight'); } var selected = dom.querySelectorAll('.x-picker-item.x-picker-item-selected, .x-picker-item-selected'); for (var j = 0; j < selected.length; j++) { selected[j].classList.add('x-picker-item-selected-highlight'); } } catch (e) { console.error('highlightSelectedItems error:', e); } }; // ================================================================ // 6. ساخت picker (هر بار از صفر) // ================================================================ var createDatePicker = function () { // همیشه اول picker قبلی را نابود کن destroyMyPicker(); var parsed = parseJalali(me.getValue()); var init = parsed || { jy: todayJalali.jy, jm: todayJalali.jm, jd: todayJalali.jd }; if (init.jd > 31) init.jd = 31; var daySlots = buildDaySlots(); var monthSlots = buildMonthSlots(); var yearSlots = buildYearSlots(); var picker = Ext.create('Ext.Picker', { slots: [ { name: 'year', title: 'سال', align: 'center', itemTpl: '{text}', store: { fields: ['text', 'value'], data: yearSlots } }, { name: 'month', title: 'ماه', align: 'center', itemTpl: '{text}', store: { fields: ['text', 'value'], data: monthSlots } }, { name: 'day', title: 'روز', align: 'center', itemTpl: '{text}', store: { fields: ['text', 'value'], data: daySlots } } ] }); try { if (picker.el) { var titles = picker.el.query('.x-picker-slot-title'); if (titles) { for (var i = 0; i < titles.length; i++) { if (titles[i] && titles[i].hide) titles[i].hide(); } } } } catch (e) {} try { picker.setValue({ day: String(init.jd), month: String(init.jm), year: String(init.jy) }); } catch (e) { console.error('setValue error:', e); } // ============ Toolbar with Persian buttons ============ try { var toolbar = picker.down ? (picker.down('titlebar') || picker.down('toolbar')) : null; if (toolbar) { var btns = toolbar.query ? toolbar.query('button') : []; for (var b = 0; b < btns.length; b++) { var btn = btns[b]; var btnText = btn.getText ? btn.getText() : (btn.text || ''); if (btnText === 'Отмена' || btnText === 'Cancel' || btnText === 'Done' || btnText === 'Выполнить' || btnText === 'Готово') { try { btn.hide(); } catch (e) {} } } try { toolbar.setLayout({ type: 'hbox', pack: 'end', align: 'middle' }); } catch (e) { console.error('setLayout error:', e); } toolbar.add({ xtype: 'button', text: 'انصراف', handler: function () { destroyMyPicker(); } }); toolbar.add({ xtype: 'button', text: 'پاک کردن', ui: 'decline', handler: function () { try { me.setValue(''); fireAjax('dateSelected', ['date=']); } catch (e) { console.error(e); } destroyMyPicker(); } }); toolbar.add({ xtype: 'button', text: 'امروز', style: 'background-color: #FFA500; color: #FFFFFF; border-color: #E69500;', handler: function () { var n = todayJalali; try { picker.setValue({ day: String(n.jd), month: String(n.jm), year: String(n.jy) }); setTimeout(function () { highlightSelectedItems(picker); }, 50); } catch (e) { console.error('today setValue error:', e); } } }); toolbar.add({ xtype: 'button', text: 'اعمال', ui: 'confirm', handler: function () { try { var dt = readPickerDate(picker); if (!dt) { showAlert('خطا', 'امکان خواندن تاریخ انتخابشده وجود ندارد. لطفاً دوباره انتخاب کنید.'); return; } var err = validateFullJalali(dt.jy, dt.jm, dt.jd); if (err) { showAlert('تاریخ نامعتبر', err + '\nلطفاً تاریخ صحیحی انتخاب کنید.'); return; } applyDate(dt.jy, dt.jm, dt.jd); destroyMyPicker(); // نابودسازی کامل } catch (e) { console.error('apply error:', e); showAlert('خطا', 'هنگام اعمال تاریخ خطایی رخ داد.'); } } }); } } catch (e) { console.error('toolbar setup error:', e); } // Inject highlight CSS try { if (!document.getElementById('_date_picker_highlight_css')) { var style = document.createElement('style'); style.id = '_date_picker_highlight_css'; style.innerHTML = '' + '.x-picker-item {' + ' color: #9e9e9e !important;' + ' opacity: 0.7;' + ' transition: all 0.15s ease;' + '}' + '.x-picker-item.x-picker-item-selected-highlight {' + ' color: #0d47a1 !important;' + ' background-color: #bbdefb !important;' + ' font-weight: bold !important;' + ' opacity: 1 !important;' + ' border-radius: 6px;' + '}'; document.head.appendChild(style); } } catch (e) { console.error('highlight css error:', e); } me._myDatePicker = picker; setTimeout(function () { highlightSelectedItems(picker); }, 100); return picker; }; // ================================================================ // 7. Show picker (همیشه از صفر) // ================================================================ var showDatePicker = function () { try { var picker = createDatePicker(); if (picker.show) picker.show(); setTimeout(function () { highlightSelectedItems(picker); }, 100); try { if (me._realInput && me._realInput.blur) me._realInput.blur(); } catch (e) {} } catch (e) { console.error('showDatePicker error:', e); } }; // ================================================================ // 8. Override کردن متدهای بومی Ext // ================================================================ // getPicker هرگز picker بومی برنگرداند me.getPicker = function () { return null; }; // onFocus و onClick فقط picker سفارشی را باز کنند me.onFocus = function (e) { try { if (e && e.preventDefault) e.preventDefault(); if (e && e.stopPropagation) e.stopPropagation(); } catch (err) {} showDatePicker(); }; me.onClick = function (e) { try { if (e && e.preventDefault) e.preventDefault(); if (e && e.stopPropagation) e.stopPropagation(); } catch (err) {} showDatePicker(); }; // ================================================================ // 8.1 اتصال رویدادهای لمسی مستقیم // ================================================================ var attachTouchOpen = function () { try { if (!me.el) return; var el = me.el.dom; if (!el || el._datePickerTouchAttached) return; el._datePickerTouchAttached = true; var openHandler = function (e) { try { if (e && e.preventDefault) e.preventDefault(); if (e && e.stopPropagation) e.stopPropagation(); if (e && e.stopImmediatePropagation) e.stopImmediatePropagation(); } catch (err) {} showDatePicker(); }; el.addEventListener('touchstart', openHandler, true); el.addEventListener('mousedown', openHandler, true); } catch (e) { console.error('attachTouchOpen error:', e); } }; // ================================================================ // 9. Manual typing // ================================================================ var setupManualTyping = function () { try { var inputDom = me._realInput; if (!inputDom) { setPlaceholder(); inputDom = me._realInput; } if (!inputDom || me._manualTypingSetup) return; me._manualTypingSetup = true; inputDom.addEventListener('focus', function (e) { // هیچ کاری نکن، فقط picker بومی احتمالی را نابود کن destroyMyPicker(); }, true); inputDom.addEventListener('beforeinput', function (e) { try { var data = e.data; if (data === null || data === undefined) return; var isDigit = (data >= '0' && data <= '9'); if (!isDigit && data !== '/') { e.preventDefault(); return; } var val = inputDom.value || ''; var ss = inputDom.selectionStart, se = inputDom.selectionEnd; var newVal = val.substring(0, ss) + data + val.substring(se); if (newVal.length > 10) { e.preventDefault(); return; } if (data === '/') { var slashCount = 0; for (var i = 0; i < newVal.length; i++) { if (newVal.charAt(i) === '/') slashCount++; } if (slashCount > 2) { e.preventDefault(); return; } if (ss === 0) { e.preventDefault(); return; } if (val.charAt(ss - 1) === '/') { e.preventDefault(); return; } } } catch (err) {} }); inputDom.addEventListener('input', function () { try { var val = inputDom.value || ''; var cleaned = ''; for (var i = 0; i < val.length; i++) { var c = val.charAt(i); if ((c >= '0' && c <= '9') || c === '/') cleaned += c; } var firstSlash = cleaned.indexOf('/'); var lastSlash = cleaned.lastIndexOf('/'); if (firstSlash >= 0 && lastSlash > firstSlash) { var count = 0, out = ''; for (var j = 0; j < cleaned.length; j++) { var ch = cleaned.charAt(j); if (ch === '/') { count++; if (count > 2) continue; } out += ch; } cleaned = out; } if (cleaned.length > 10) cleaned = cleaned.substring(0, 10); if (cleaned !== val) { var pos = inputDom.selectionStart; inputDom.value = cleaned; try { inputDom.setSelectionRange(pos, pos); } catch (e) {} } } catch (err) {} }); var handleEnter = function () { try { var val = inputDom.value || ''; var p = parseJalali(val); if (!p) return; var err = validateFullJalali(p.jy, p.jm, p.jd); if (err) { showAlert('تاریخ نامعتبر', err + '\nلطفاً تاریخ صحیحی وارد کنید.'); return; } applyDate(p.jy, p.jm, p.jd); try { inputDom.blur(); } catch (e) {} } catch (err) {} }; inputDom.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.keyCode === 13) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); handleEnter(); return false; } }, true); } catch (e) { console.error('setupManualTyping error:', e); } }; // ================================================================ // 10. Initialization // ================================================================ var initAttempts = 0; var initInterval = setInterval(function () { initAttempts++; try { setPlaceholder(); attachTouchOpen(); setupManualTyping(); if ((me._manualTypingSetup && me._realInput) || initAttempts >= 10) { clearInterval(initInterval); console.log('UnimComboBox Jalali date setup finished after', initAttempts, 'attempts'); } } catch (e) { console.error('init interval error:', e); clearInterval(initInterval); } }, 300); } catch (globalErr) { console.error('afterCreate global error:', globalErr); } }1 point
-
Hello @WSIINNDA This calendar is very beautiful. Could you give me some guidance? Is it possible to create one for the Jalali calendar (Iran's solar calendar) as well? Regards.1 point
-
Именно. Для мелких справочников BufferedStore и не нужен — он для очень больших таблиц (виртуальный скролл). Оставляйте его выключенным для справочных сеток, и инлайн-добавление /редактирование там работает штатно; а буфер включайте только для больших таблиц, где добавление удобнее через форму. Ваш SetDBGridBufferedStore как раз и позволяет выбирать режим под каждую сетку.1 point
-
function afterrender(sender, eOpts) { var me = sender; if (me.store && me.store.getCount() === 0) { me.store.add({ text: '', value: '' }); me.setValue(''); } // ۴. Маска-подсказка для TUniComboBox if (me.inputEl && me.inputEl.dom) { me.inputEl.dom.setAttribute('placeholder', 'дд.мм.гггг'); } if (me.emptyText !== undefined) { me.emptyText = 'дд.мм.гггг'; } if (me._wheelSetup) return; me._wheelSetup = true; // ============ Настройка ширины и высоты пикера ============ var adjustPickerSize = function () { try { var picker = me.getPicker(); if (!picker) return; var minPickerWidth = 300; if (me.getWidth() < minPickerWidth) { picker.setWidth(minPickerWidth); picker.minWidth = minPickerWidth; } else { picker.setWidth(me.getWidth()); } picker.setHeight(175); if (typeof picker.updateLayout === 'function') { picker.updateLayout(); } } catch (e) { console.error('adjustPickerSize error:', e); } }; // ---------- Вспомогательные функции ---------- var pad2 = function(n){ return (n < 10 ? '0' : '') + n; }; var monthNamesRu = ["Январь","Февраль","Март","Апрель","Май","Июнь", "Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"]; var parseDisplay = function(s) { if (!s) return null; var m = String(s).match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/); if (!m) return null; return { d: +m[1], m: +m[2], y: +m[3] }; }; var fireAjax = function (ev, params) { if (typeof ajaxRequest === 'function') { ajaxRequest(me, ev, params); } else if (me.ajaxRequest) { me.ajaxRequest(ev, params); } else { var c = me; while (c && !c.ajaxRequest) c = c.ownerCt; if (c) c.ajaxRequest(ev, params, false, me); } }; // ============ ✅ Живое открытие пикера при ручном вводе даты ============ if (me.inputEl && me.inputEl.dom && !me._manualTypingSetup) { me._manualTypingSetup = true; var inputDom = me.inputEl.dom; var parseLive = function (val) { var m = String(val).match(/^(\d{0,2})(?:\.(\d{0,2}))?(?:\.(\d{0,4}))?/); if (!m) return null; var now = new Date(); var dPart = m[1] || ''; var mPart = m[2] || ''; var yPart = m[3] || ''; var d = dPart ? parseInt(dPart, 10) : now.getDate(); var mo = mPart ? parseInt(mPart, 10) : (now.getMonth() + 1); var y = yPart ? parseInt(yPart, 10) : now.getFullYear(); if (mo < 1 || mo > 12) mo = now.getMonth() + 1; if (d < 1) d = 1; if (y < 1900) y = 1900; return { d: d, m: mo, y: y }; }; var parseExact = function (val) { var m = String(val).match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/); if (!m) return null; var d = parseInt(m[1], 10); var mo = parseInt(m[2], 10); var y = parseInt(m[3], 10); if (mo < 1 || mo > 12) return null; if (d < 1 || d > 31) return null; if (y < 1900 || y > 2100) return null; var maxD = new Date(y, mo, 0).getDate(); if (d > maxD) return null; return { d: d, m: mo, y: y }; }; var syncWheels = function (val) { var p = parseLive(val); if (!p) return; try { var picker = me.getPicker(); if (picker && picker.el) { var container = picker.el.dom.querySelector('#' + picker.id + '_wheelContainer'); if (container && container._setDate) { container._setDate(p.y, monthNamesRu[p.m - 1], p.d); } } } catch (e) { console.error('syncWheels error:', e); } }; var keepOpen = function () { if (me._forceClose) return; if (!me.isExpanded) { me.expand(); setTimeout(function () { syncWheels(inputDom.value || ''); }, 80); } else { syncWheels(inputDom.value || ''); } }; // Фильтр ввода: только цифры и точка в правильной позиции inputDom.addEventListener('beforeinput', function (e) { var data = e.data; if (data === null || data === undefined) return; if (!/^[0-9.]$/.test(data)) { e.preventDefault(); return; } var val = inputDom.value || ''; var selStart = inputDom.selectionStart; var selEnd = inputDom.selectionEnd; var newVal = val.substring(0, selStart) + data + val.substring(selEnd); if (newVal.length > 10) { e.preventDefault(); return; } if (data === '.') { var dots = (newVal.match(/\./g) || []).length; if (dots > 2) { e.preventDefault(); return; } if (selStart === 0) { e.preventDefault(); return; } if (val.charAt(selStart - 1) === '.') { e.preventDefault(); return; } var beforeDot = newVal.split('.')[0]; if (dots === 1 && (beforeDot.length < 1 || beforeDot.length > 2)) { e.preventDefault(); return; } if (dots === 2) { var parts = newVal.split('.'); if (parts[1].length < 1 || parts[1].length > 2) { e.preventDefault(); return; } } } }); // Финальная фильтрация на input inputDom.addEventListener('input', function () { var val = inputDom.value || ''; var cleaned = val.replace(/[^0-9.]/g, ''); var parts = cleaned.split('.'); if (parts.length > 3) { cleaned = parts[0] + '.' + parts[1] + '.' + parts.slice(2).join(''); } if (cleaned.length > 10) { cleaned = cleaned.substring(0, 10); } if (cleaned !== val) { var selPos = inputDom.selectionStart; inputDom.value = cleaned; try { inputDom.setSelectionRange(selPos, selPos); } catch (e) {} } if (/\d/.test(cleaned)) { keepOpen(); } }); inputDom.addEventListener('focus', function () { me._isTypingFocus = true; }); inputDom.addEventListener('blur', function () { me._isTypingFocus = false; }); me.on('collapse', function () { if (me._forceClose) { me._forceClose = false; return; } if (me._isTypingFocus && /\d/.test(inputDom.value || '')) { setTimeout(function () { if (me._isTypingFocus && !me._forceClose) { keepOpen(); } }, 10); } }); // Enter → точное подтверждение var handleEnter = function () { var val = inputDom.value || ''; var p = parseExact(val); if (!p) return; var full = pad2(p.d) + '.' + pad2(p.m) + '.' + p.y; me.setValue(full); fireAjax('dateSelected', ['date=' + encodeURIComponent(full)]); me._isTypingFocus = false; me._forceClose = true; try { inputDom.blur(); } catch (e) {} setTimeout(function () { me.collapse(); }, 50); }; me.on('specialkey', function (field, e) { if (e.getKey() === e.ENTER) { e.stopEvent(); handleEnter(); } }); inputDom.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.keyCode === 13) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); handleEnter(); return false; } }, true); } // ---------- Построение HTML ---------- var buildHtml = function (pickerId) { return '' + '<style>' + '.wpc-wrap{background:#fff;padding:8px;width:100%;box-sizing:border-box;font-family:Tahoma,Arial;}' + '.wpc-picker{position:relative;display:flex;justify-content:center;height:120px;overflow:hidden;' + 'background:#f0f0f0;border:1px solid #ccc;border-radius:8px;}' + '.wpc-line-top-left{position:absolute;top:40px;left:6%;width:26%;height:0;' + 'border-top:2px solid #007bff;pointer-events:none;z-index:4;}' + '.wpc-line-top-mid{position:absolute;top:40px;left:37%;width:26%;height:0;' + 'border-top:2px solid #007bff;pointer-events:none;z-index:4;}' + '.wpc-line-top-right{position:absolute;top:40px;right:6%;width:26%;height:0;' + 'border-top:2px solid #007bff;pointer-events:none;z-index:4;}' + '.wpc-line-bot-left{position:absolute;top:80px;left:6%;width:26%;height:0;' + 'border-top:2px solid #007bff;pointer-events:none;z-index:4;}' + '.wpc-line-bot-mid{position:absolute;top:80px;left:37%;width:26%;height:0;' + 'border-top:2px solid #007bff;pointer-events:none;z-index:4;}' + '.wpc-line-bot-right{position:absolute;top:80px;right:6%;width:26%;height:0;' + 'border-top:2px solid #007bff;pointer-events:none;z-index:4;}' + '.wpc-picker .wpc-mask{position:absolute;left:0;right:0;pointer-events:none;z-index:3;background:rgba(240,240,240,0.8);}' + '.wpc-mask-top{top:0;height:40px;}' + '.wpc-mask-bottom{bottom:0;height:40px;}' + '.wpc-col{height:120px;overflow-y:scroll;flex:1 1 0;text-align:center;' + 'scrollbar-width:none;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;position:relative;z-index:1;}' + '.wpc-col::-webkit-scrollbar{display:none;}' + '.wpc-item{height:40px;line-height:1;font-size:14px;color:#000;white-space:nowrap;' + 'padding:0 4px 2px 4px;box-sizing:border-box;display:flex;align-items:center;' + 'justify-content:center;overflow:hidden;font-family:Tahoma,Arial,sans-serif;}' + '.wpc-col-day .wpc-item{padding-left:14px;padding-right:0;}' + '.wpc-col-month .wpc-item{padding-left:0;padding-right:0;}' + '.wpc-col-year .wpc-item{padding-left:0;padding-right:14px;}' + '.wpc-pad{height:40px;}' + '.wpc-actions{display:flex;gap:6px;margin-top:8px;}' + '.wpc-actions button{flex:1;padding:8px 0;border:none;border-radius:3px;cursor:pointer;font-weight:bold;font-size:12px;}' + '.wpc-clear{background:#e53935;color:#fff;}' + '.wpc-today{background:#2196F3;color:#fff;}' + '.wpc-apply{background:#4CAF50;color:#fff;}' + '</style>' + '<div class="wpc-wrap">' + '<div class="wpc-picker">' + '<div class="wpc-line-top-left"></div>' + '<div class="wpc-line-top-mid"></div>' + '<div class="wpc-line-top-right"></div>' + '<div class="wpc-line-bot-left"></div>' + '<div class="wpc-line-bot-mid"></div>' + '<div class="wpc-line-bot-right"></div>' + '<div class="wpc-mask wpc-mask-top"></div>' + '<div class="wpc-mask wpc-mask-bottom"></div>' + '<div class="wpc-col wpc-col-day" id="' + pickerId + '_dayCol"></div>' + '<div class="wpc-col wpc-col-month" id="' + pickerId + '_monthCol"></div>' + '<div class="wpc-col wpc-col-year" id="' + pickerId + '_yearCol"></div>' + '</div>' + '<div class="wpc-actions">' + '<button type="button" class="wpc-clear">Очистить</button>' + '<button type="button" class="wpc-today">Сегодня</button>' + '<button type="button" class="wpc-apply">Применить</button>' + '</div>' + '</div>'; }; // ---------- Выполнение JS ---------- var runWheelJs = function (rootEl, pickerId, init) { var years = []; var curY = new Date().getFullYear(); for (var y = curY; y >= 1900; y--) years.push(String(y)); var months = monthNamesRu.slice(); var days = []; var dayCol = rootEl.querySelector('#' + pickerId + '_dayCol'); var monthCol = rootEl.querySelector('#' + pickerId + '_monthCol'); var yearCol = rootEl.querySelector('#' + pickerId + '_yearCol'); var applyBtn = rootEl.querySelector('.wpc-apply'); var clearBtn = rootEl.querySelector('.wpc-clear'); var todayBtn = rootEl.querySelector('.wpc-today'); if (!dayCol || !monthCol || !yearCol) { console.error('cols not found'); return; } var fill = function (col, list) { var h = '<div class="wpc-pad"></div>'; for (var i = 0; i < list.length; i++) h += '<div class="wpc-item">' + list[i] + '</div>'; h += '<div class="wpc-pad"></div>'; col.innerHTML = h; }; var daysInMonth = function (year, monthIndex) { return new Date(year, monthIndex + 1, 0).getDate(); }; var state = { dayCol: 0, monthCol: 0, yearCol: 0 }; var smoothScrollTo = function (col, target, duration) { var start = col.scrollTop, change = target - start, st = null; if (col._animId) cancelAnimationFrame(col._animId); var easeOut = function (t) { return 1 - Math.pow(1 - t, 3); }; var step = function (ts) { if (!st) st = ts; var t = Math.min((ts - st) / duration, 1); col.scrollTop = start + change * easeOut(t); if (t < 1) col._animId = requestAnimationFrame(step); else { col._animId = null; col.scrollTop = target; } }; col._animId = requestAnimationFrame(step); }; var refillDays = function () { var y = parseInt(years[state.yearCol], 10); var m = state.monthCol; var n = daysInMonth(y, m); if (days.length !== n) { days.length = 0; for (var d = 1; d <= n; d++) days.push(String(d)); fill(dayCol, days); } if (state.dayCol >= n) { state.dayCol = n - 1; smoothScrollTo(dayCol, state.dayCol * 40, 220); } }; var goTo = function (col, idx, list) { if (idx < 0) idx = 0; if (idx >= list.length) idx = list.length - 1; state[col.id.replace(pickerId + '_', '')] = idx; smoothScrollTo(col, idx * 40, 220); if (col === monthCol || col === yearCol) { refillDays(); } }; var setDate = function (y, m, d) { var yi = years.indexOf(String(y)); if (yi < 0) yi = 0; var mi = months.indexOf(m); if (mi < 0) mi = 0; state.yearCol = yi; state.monthCol = mi; var n = daysInMonth(y, mi); days.length = 0; for (var i = 1; i <= n; i++) days.push(String(i)); fill(dayCol, days); var di = days.indexOf(String(d)); if (di < 0) di = n - 1; goTo(yearCol, yi, years); goTo(monthCol, mi, months); goTo(dayCol, di, days); }; (function initDays() { var n = daysInMonth(init.y, init.m - 1); for (var d = 1; d <= n; d++) days.push(String(d)); })(); fill(yearCol, years); fill(monthCol, months); fill(dayCol, days); var attachScroll = function (col, list, key) { col.addEventListener('scroll', function () { if (col._animId) return; clearTimeout(col._timer); col._timer = setTimeout(function () { var actualList = (key === 'dayCol') ? days : list; var idx = Math.round(col.scrollTop / 40); if (idx < 0) idx = 0; if (idx >= actualList.length) idx = actualList.length - 1; var t = idx * 40; if (Math.abs(col.scrollTop - t) > 0.5) smoothScrollTo(col, t, 180); state[key] = idx; if (key === 'monthCol' || key === 'yearCol') { refillDays(); } }, 100); }, { passive: true }); }; attachScroll(yearCol, years, 'yearCol'); attachScroll(monthCol, months, 'monthCol'); attachScroll(dayCol, days, 'dayCol'); var attachWheel = function (col, list, key) { col.addEventListener('wheel', function (e) { e.preventDefault(); var dir = e.deltaY > 0 ? 1 : -1; var idx = state[key] + dir; var actualList = (key === 'dayCol') ? days : list; goTo(col, idx, actualList); }, { passive: false }); }; attachWheel(yearCol, years, 'yearCol'); attachWheel(monthCol, months, 'monthCol'); attachWheel(dayCol, days, 'dayCol'); applyBtn.addEventListener('click', function () { var d = days[state.dayCol]; var m = state.monthCol + 1; var y = years[state.yearCol]; var val = pad2(+d) + '.' + pad2(m) + '.' + y; me.setValue(val); fireAjax('dateSelected', ['date=' + encodeURIComponent(val)]); me._isTypingFocus = false; me._forceClose = true; try { if (me.inputEl && me.inputEl.dom) me.inputEl.dom.blur(); } catch (e) {} setTimeout(function () { me.collapse(); }, 50); }); clearBtn.addEventListener('click', function () { me.setValue(''); me._isTypingFocus = false; me._forceClose = true; try { if (me.inputEl && me.inputEl.dom) me.inputEl.dom.blur(); } catch (e) {} setTimeout(function () { me.collapse(); }, 50); }); todayBtn.addEventListener('click', function () { var now = new Date(); setDate(now.getFullYear(), monthNamesRu[now.getMonth()], now.getDate()); }); setDate(init.y, monthNamesRu[init.m - 1], init.d); rootEl._setDate = setDate; }; // ---------- Основное внедрение ---------- var initializePicker = function () { try { adjustPickerSize(); var picker = me.getPicker(); if (!picker || !picker.el) return; var pickerEl = picker.el.dom; var pickerId = picker.id; if (pickerEl.querySelector('#' + pickerId + '_wheelContainer')) { var existing = pickerEl.querySelector('#' + pickerId + '_wheelContainer'); var p = parseDisplay(me.getValue()); if (existing._setDate && p) { existing._setDate(p.y, monthNamesRu[p.m - 1], p.d); } return; } var listCt = picker.el.down('.x-boundlist-list-ct'); if (listCt) listCt.setStyle('display', 'none'); var now = new Date(); var init = parseDisplay(me.getValue()) || { d: now.getDate(), m: now.getMonth() + 1, y: now.getFullYear() }; var container = document.createElement('div'); container.id = pickerId + '_wheelContainer'; container.innerHTML = buildHtml(pickerId); if (pickerEl.firstChild) pickerEl.insertBefore(container, pickerEl.firstChild); else pickerEl.appendChild(container); runWheelJs(container, pickerId, init); setTimeout(adjustPickerSize, 50); } catch (err) { console.error('Wheel init error:', err); } }; // ---------- Инициализация ---------- me.on('expand', function () { setTimeout(function () { adjustPickerSize(); initializePicker(); }, 50); }); setTimeout(initializePicker, 200); } function beforeInit(sender, config) { Ext.apply(sender, { allowBlank: true, msgTarget: 'side', blankText: 'Это поле обязательно к заполнению!', validateOnChange: true, validateOnBlur: true, validator: function (value) { // Если пусто — допустимо if (!value || value.trim() === '') { return true; } // Разрешены только цифры и точка if (!/^[0-9.]+$/.test(value)) { return 'Разрешены только цифры и точка (0-9, .)!'; } // Полный шаблон дд.мм.гггг var m = String(value).match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/); if (!m) { return 'Неверный формат даты! Используйте дд.мм.гггг (например: 29.09.2026)'; } var d = parseInt(m[1], 10); var mo = parseInt(m[2], 10); var y = parseInt(m[3], 10); // Проверка месяца if (mo < 1 || mo > 12) { return 'Месяц должен быть от 01 до 12!'; } // Проверка года if (y < 1900 || y > 2100) { return 'Год должен быть от 1900 до 2100!'; } // Проверка дня if (d < 1) { return 'День должен быть больше 0!'; } // Проверка количества дней в месяце с учётом високосного года var maxDay = new Date(y, mo, 0).getDate(); if (d > maxDay) { return 'В этом месяце только ' + maxDay + ' дней!'; } return true; } }); // ✅ Пользовательский CSS: красный фон для невалидного поля if (!document.getElementById('_datefield_invalid_css')) { var style = document.createElement('style'); style.id = '_datefield_invalid_css'; style.innerHTML = '' // Невалидный input → светло-красный фон + '.x-form-invalid-field,' + '.x-form-invalid-field-default {' + ' background-color: #ffcdd2 !important;' + ' background-image: none !important;' + ' border-color: #e53935 !important;' + ' color: #b71c1c !important;' + '}' // Когда фокус на невалидном поле + '.x-form-invalid-field:focus,' + '.x-form-invalid-field-default:focus {' + ' background-color: #ffcdd2 !important;' + ' background-image: none !important;' + ' border-color: #c62828 !important;' + '}' // Родитель input (trigger wrap) — для надёжности + '.x-form-trigger-wrap-invalid,' + '.x-form-trigger-wrap-default.x-form-trigger-wrap-invalid {' + ' background-color: #ffcdd2 !important;' + '}' // Сообщение об ошибке + '.x-form-invalid-under {' + ' color: #e53935 !important;' + ' font-weight: bold;' + '}' // Иконка ошибки + '.x-form-invalid-icon {' + ' background-color: #e53935 !important;' + '}'; document.head.appendChild(style); } }1 point
-
Hello, //need for fuser command _> apt install psmisc This line, Kill ALL "8077" port listener application(s) _>fuser -k -9 8077/tcp || true Why need this, paserver not kill appliacation sometimes, thats still working, linux NOT LOCK file if copy same name, override it. So you have many working copy appliacaiton on memory. And this mean you can goes mad my not applied my changed code now 'cos you don't know wich build is answer to you. solution: Kill all them. And if you lazy like me echo 'alias öl_8077="fuser -k -9 8077/tcp || true"' >> /root/.bashrc root is your username, in dockker container is root1 point
-
1 point
-
If it's IIS, it's a configuration that needs to be increased in size.1 point
-
In reality, build 1617 will be published to address this issue and current 1617 will be published as 1618 when it is ready.1 point
-
Hello, RowHeight has no effect in vertical mode, so CSS is used instead. Change height to the value you need. Try this approach: ServerModule → CustomCSS: .tallvg .x-grid-cell-inner { height: 40px; /* row height */ line-height: normal; /* text at the top (use line-height: 40px to vertical-center) */ padding-top: 4px; padding-bottom: 0; } .tallvg .x-grid-cell-inner-property-name { background-repeat: repeat-y; /* stretch the left accent bar to full row height */ } VertGrid.ClientEvents.UniEvents → beforeInit: function beforeInit(sender, config){ config.cls = "tallvg"; // apply the CSS to this grid only }1 point
-
1 point
-
1 point
-
Попробуйте применить и этот фикс: Ext.define('UniFix.data.virtual.Store.teardown', { override: 'Ext.data.virtual.Store', fireEvent: function (ev) { if (ev === 'load' && this.grid && (this.grid.destroying || this.grid.destroyed)) { return false; // не диспатчим load при teardown грида } return this.callParent(arguments); } });1 point
-
1 point
-
1 point
-
When I transfer my Windows project to Linux, there is no problem with the build, but the linking process takes up to 3 minutes, which is very annoying. When I look with Process Explorer, all vcl paths are sent to ld-linux.exe with the L parameter. I think this is causing the delay. But I couldn't find them. AI couldn't find a solution either Does anyone have experience with this issue? "Go to Farshad Lazarus as soon as possible Dealing with this stupid delphi IDE is a waste of life :)"1 point
-
1 point
-
The example provided here is not entirely correct! I accomplished this task using the 'TMS FNC Push Notifications' component within this framework. This is only possible with this component.1 point
-
Hello, Try this workaround: Create a file files\unibufferedfix.js in your project folder: Ext.define('UniFix.data.virtual.Store', { override: 'Ext.data.virtual.Store', load: function (options) { var me = this, pageMap = me.pageMap, groups; if (pageMap && pageMap.getPageCount() === 0) { pageMap.clear(); pageMap.setPageCount(null); groups = me.getGroups && me.getGroups(); if (groups) { groups.clear(); } } return me.callParent(arguments); } }); And add it to ServerModule.CustomFiles: files/unibufferedfix.js1 point
-
1 point
-
1 point
-
Добрый вечер, Это по дизайну. При BufferedStore сетка работает на Ext.data.BufferedStore — "карте" серверных данных, которая не принимает локальную вставку (add/insert в ней отключены by design). Поэтому инлайн-добавление новой строки с открытым редактором при буфере невозможно — это ограничение самого Ext JS. При включённом BufferedStore записи лучше добавлять НЕ инлайн в сетке, а через отдельное окно (форму ввода): пользователь заполняет поля в форме, по OK делаете Append + Post — и сетка показывает уже закоммиченную строку. Так вы обходите ограничение буфер-стора и получаете предсказуемое поведение.0 points
