[{"content":"مساله در برنامه‌ام به فرمی نیاز داشتم که بتوانم در آن، کاربران را به یک نقش تخصیص دهم. در این فرم ادمین باید بتواند:\nکاربر مورد نظر خود را جستجو کند بتواند چند کاربر را انتخاب و اضافه کند بتواند چند کاربر را انتخاب و حذف کند بتواند همه کاربران را انتخاب کند و حذف و اضافه انجام دهد 🔔 آیتم Select Many قابلیت چند انتخابی و جستجو را دارد، اما نیاز است که کاربر منو کشویی را باز کند و تک تک نفرات را انتخاب کند.\nShuttle گزینه خوبی برای انتخاب چندتایی است، ولی قابلیت جستجو ندارد.\nساختار shuttle shuttle از دو المنت select\rبرای نمایش مقادیر استفاده می‌کند. id ایجاد شده برای این دو المنت براساس نام آیتم و پسوندهای left و right است. 🔔 این دو select از پسوندهای left و right استفاده می‌کنند، که در حالت چپچین معنای درستی دارند ولی در حالت راستچین خیر. بنابراین در این مطلب من از ستون اول (شروع) و دوم (پایان) برای نام بردن از آن‌ها استفاده ‌می‌کنم؛ اما در کد، من هم از پسوند left و right استفاده می‌کنم.\nپیاده سازی ایجاد فیلتر در صفحه مورد نظرمان (مثلا صفحه 5) سه آیتم نیاز داریم:\nP5_USER_ID: آیتمی از نوع shuttle. به sort آن نیاز نداریم، بنابراین آن را حذف می‌کنیم: Settings | Show Controls: Moving Only P5_SEARCH_TEXT_L: آیتمی برای جستجو در ستون اول مقادیر (left) P5_SEARCH_TEXT_R آیتمی برای جستجو در ستون دوم مقادیر (right) دکمه پاک کردن جستجو برای پاک کردن مقادیر یک آیتم، از دستور زیر استفاده می‌کنیم:\napex.item(\u0026#39;نام آیتم مورد نظر\u0026#39;).setValue(\u0026#39;\u0026#39;) یا دستور کوتاهتر:\n$s(\u0026#39;نام آیتم مورد نظر\u0026#39;) در این دستور چون مقداری برای پارامتر دوم آن ننوشته‌ایم، مقدار خالی در نظر گرفته می‌شود.\nبرای امکان ایجاد حذف متن در باکس‌های جستجو، برای هرکدام از آیتم‌های جستجو تغییرات زیر را انجام می‌دهیم:\nAppearance | Template Options Advanced | Item Post Text: Display as Block Advanced | Post Text: \u0026lt;span onclick=\u0026#34;$s(\u0026#39;نام آیتم مورد نظر\u0026#39;)\u0026#34; style=\u0026#34;cursor: pointer;\u0026#34; class=\u0026#34;fa fa-trash-o\u0026#34; title=\u0026#34;پاک کردن\u0026#34; aria-label=\u0026#34;پاک کردن جستجو\u0026#34; aria-hidden=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;/span\u0026gt; Dynamic Action می‌خواهیم با تایپ کردن کاربر، فیلتر انجام شود. بنابراین از دو رویداد زیر استفاده می‌کنیم:\nChange: برای زمان تایپ کردن اصلا مناسب نیست و فعال نمی‌شود. اما زمانی که باکس focus را از دست می‌دهد و یا متن به صورت کامل پاک می‌شود فعال می‌شود. Key Release: این رویداد پس از رها کردن کلید فعال خواهد شد. بنابراین برای هر دو باکس مربوط به جستجو، این DAها را اضافه کرده و برای هرکدام یک action از نوع Execute JavaScript Code اضافه می‌کنیم: در داخل Execute JavaScript Code از دو متد با امضا زیر استفاده می‌کنیم:\nپارامتر اول: نام آیتم جستجو پارامتر دوم: نام آیتم شاتل برای ستون اول (filter left side)\nitem_Shuttle_FilterLeftSide( \u0026#34;P5_SEARCH_TEXT_L\u0026#34;, \u0026#34;P5_USER_ID\u0026#34; ); برای ستون دوم (filter right side)\nitem_Shuttle_FilterRightSide( \u0026#34;P5_SEARCH_TEXT_R\u0026#34;, \u0026#34;P5_USER_ID\u0026#34; ); متدهای فیلتر در Attribute صفحه کدهای زیر را می‌نویسیم Page | JavaScript | Function and Global Variable Declaration در دو متد ابتدایی، پسوند _LEFT یا _RIGHT به نام آیتم وصل می‌شوند تا به id این ستون‌ها برسیم. سپس متد اصلی یعنی item_Shuttle_Filter را فراخوانی می‌کنیم.\n/** * فیلتر کردن مقادیر ستون چپ (اول) * * @param {string} searchItemName \u0026gt; نام آیتم جستجو * @param {string} shuttleName \u0026gt; نام شاتل */ function item_Shuttle_FilterLeftSide(searchItemName, shuttleName) { let leftSide = shuttleName + \u0026#39;_LEFT\u0026#39;; item_Shuttle_Filter(searchItemName, leftSide); } /** * فیلتر کردن مقادیر ستون راست (دوم) * * @param {string} searchItemName \u0026gt; نام آیتم جستجو * @param {string} shuttleName \u0026gt; نام شاتل */ function item_Shuttle_FilterRightSide(searchItemName, shuttleName) { let rightSide = shuttleName + \u0026#39;_RIGHT\u0026#39;; item_Shuttle_Filter(searchItemName, rightSide); } item_Shuttle_Filter: وظیفه این متد مخفی کردن گزینه‌های موجود در لیست هست. با استفاده از حلقه، تمام گزینه‌ها بررسی می‌شوند و موارد غیرمشابه مخفی می‌شوند.\n/** * جستجو در شاتل چپ یا راست * توسط متدهای دیگر فراخوانی می‌شود * * @param {string} searchItemName \u0026gt; نام آیتم جستجو * @param {string} shuttleSideName \u0026gt; نام ستون شاتل (_LEFT, _RIGHT) */ function item_Shuttle_Filter(searchItemName, shuttleSideName) { const searchText = apex.item(searchItemName).getValue().toLowerCase(); const selectEl = document.getElementById(shuttleSideName); const options = selectEl.options; for (let i = 0; i \u0026lt; options.length; i++) { const optionText = options[i].text.toLowerCase(); options[i].hidden = !optionText.includes(searchText); // options[i].style.display = optionText.includes(searchText) ? \u0026#34;\u0026#34; : \u0026#34;none\u0026#34;; } } 💡 برای این کار می‌توان از دو روش استفاده کرد:\nhidden\r\u0026lt;option value=\u0026#34;305\u0026#34; hidden=\u0026#34;\u0026#34;\u0026gt;کاربر 305\u0026lt;/option\u0026gt; display none\r\u0026lt;option value=\u0026#34;21\u0026#34; style=\u0026#34;display: none;\u0026#34;\u0026gt;کاربر 21\u0026lt;/option\u0026gt; بازنویسی رویداد دکمه‌ها مشکل بعدی این هست که دکمه‌های Move All و Remove All هم گزینه‌های مخفی و هم غیر مخفی را انتقال می‌دهند. پس آن‌ها را بازنویسی می‌کنیم.\nشناسه دکمه‌ها id مورد استفاده برای دکمه‌ها، نام آیتم شاتل + پسوند _MOVE_ALL یا _REMOVE_ALL است. متدها در خصوصیات صفحه:\nPage | JavaScript | Function and Global Variable Declaration item_Shuttle_MoveAllButton: بازنویسی رویداد دکمه Move All item_Shuttle_RemoveAllButton: بازنویسی رویداد دکمه Remove All /** * فعال کردن انتقال از لیست یک به دو * move all بازنویسی دکمه * @param {string} shuttleName */ function item_Shuttle_MoveLeftToRight(shuttleName) { //ltr: left to right item_Shuttle_MoveAll(shuttleName, \u0026#39;ltr\u0026#39;); } /** * فعال کردن انتقال از لیست دو به یک * remove all بازنویسی دکمه * @param {string} shuttleName */ function item_Shuttle_MoveRightToLeft(shuttleName) { //rtl: right to left item_Shuttle_MoveAll(shuttleName, \u0026#39;rtl\u0026#39;); } item_Shuttle_MoveAll: در این متد، رویداد کلیک بر روی این دکمه‌ها را بازنویسی می‌کنیم. پارامتر moveType جهت انتقال و نوع دکمه را مشخص می‌کند؛ از لیست چپ به راست، یا راست به چپ.\nبدین ترتیب source و target را مشخص می‌کنیم (ایجاد id) و در نهایت با استفاده از یک حلقه موارد hidden را نادیده گرفته و انتقال انجام می‌شود.\n/** * در زمان فیلتر کردن ستون چپ یا راست move all, remove all بازنویسی دکمه * در این حالت مقادیری که فیلتر شده‌اند نباید به ستون کناری منتقل شوند * @param {string} shuttleName \u0026gt; نام شاتل * @param {string} moveType \u0026gt; سمت حرکت (ltr, rtl) */ function item_Shuttle_MoveAll(shuttleName, moveType) { //ltr: left to right //rtl: right to left const moveAllBtn = document.getElementById(shuttleName + (moveType == \u0026#39;ltr\u0026#39; ? \u0026#39;_MOVE_ALL\u0026#39; : \u0026#39;_REMOVE_ALL\u0026#39;)); moveAllBtn.addEventListener(\u0026#34;click\u0026#34;, function (e) { e.preventDefault(); e.stopImmediatePropagation(); const source = document.getElementById(shuttleName + (moveType == \u0026#39;ltr\u0026#39; ? \u0026#39;_LEFT\u0026#39; : \u0026#39;_RIGHT\u0026#39;)); const target = document.getElementById(shuttleName + (moveType == \u0026#39;ltr\u0026#39; ? \u0026#39;_RIGHT\u0026#39; : \u0026#39;_LEFT\u0026#39;)); for (let i = source.options.length - 1; i \u0026gt;= 0; i--) { const option = source.options[i]; if (!option.hidden) { target.add(option); } } }, true); // capture phase } فراخوانی در صفحه و در صفحه این متدها را فراخوانی می‌کنیم: Page | JavaScript | Execute when Page Loads بدین ترتیب با load شدن صفحه، رویداد دکمه‌ها را بازنویسی می‌کنیم.\nlet shuttleId = \u0026#39;P5_USER_ID\u0026#39;; item_Shuttle_MoveAllButton(shuttleId); item_Shuttle_RemoveAllButton(shuttleId); 💡\rنامگذاری متدهای من\rمن از پیشوند item برای این متدها استفاده می‌کنم و بعد نام آیتم (در اینجا shuttle) و در نهایت کاری که می‌خواهم انجام دهم.\nاین کار برای دسته بندی فایل‌های JS من هست. در نهایت تمام فایل‌های JS را در site.js ادغام می‌کنم و داخل محیط production استفاده می‌کنم.\nsite_item.js\r","permalink":"https://abedinitips.ir/posts/oracle-apex/%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-%D8%AC%D8%B3%D8%AA%D8%AC%D9%88-%D8%A8%D8%B1%D8%A7%DB%8C-shuttle/","summary":"\u003ch2 id=\"مساله\"\u003eمساله\u003c/h2\u003e\n\u003cp\u003eدر برنامه‌ام به فرمی نیاز داشتم که بتوانم در آن، کاربران را به یک نقش تخصیص دهم. در این فرم ادمین باید بتواند:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eکاربر مورد نظر خود را جستجو کند\u003c/li\u003e\n\u003cli\u003eبتواند چند کاربر را انتخاب و اضافه کند\u003c/li\u003e\n\u003cli\u003eبتواند چند کاربر را انتخاب  و حذف کند\u003c/li\u003e\n\u003cli\u003eبتواند همه کاربران را انتخاب کند و حذف و اضافه انجام دهد\u003c/li\u003e\n\u003c/ul\u003e\n \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n  \r\n\r\n\r\n\r\n\u003cblockquote class=\"alert alert-note\"\u003e\r\n\r\n  \u003cp\u003e\u003cspan class=\"alert-icon\"\u003e🔔\u003c/span\u003e آیتم Select Many قابلیت چند انتخابی و جستجو را دارد،\nاما نیاز است که کاربر منو کشویی را باز کند و تک تک نفرات را انتخاب کند.\u003c/p\u003e","title":"ایجاد جستجو برای Shuttle"},{"content":"در این ورژن این گزینه به نوع پسورد اضافه شده است (با استفاده از تگ button) و به صورت پیش فرض فعال است. برای مشاهده این گزینه، بر روی آیتم پسورد کلیک کرده و به قسمت خصوصیات آن می‌رویم:\nPage Items | Appearance | Template Options | Common ⬛ Hide Password Visibility\nدر صورت عدم نیاز، تیک این گزینه را فعال ✅ می‌کنیم.\nبرای نمایش آن از تگ button استفاده می‌شود: 🔔 پس از نمایش پسورد در صورتی که focus از روی پسورد یا آیکون چشم (button) برداشته شود، پسورد دوباره به حالت مخفی برخواهد گشت.\n","permalink":"https://abedinitips.ir/posts/oracle-apex/%D9%86%D9%85%D8%A7%DB%8C%D8%B4-%D9%BE%D8%B3%D9%88%D8%B1%D8%AF-%D8%AF%D8%B1-oracle-apex-24-2/","summary":"\u003cp\u003eدر این ورژن این گزینه به نوع پسورد اضافه شده است (با استفاده از تگ button) و به صورت پیش فرض فعال است.\nبرای مشاهده این گزینه، بر روی آیتم پسورد کلیک کرده و به قسمت خصوصیات آن می‌رویم:\u003c/p\u003e\n\u003cfigure class=\"align-center \"\u003e\n    \u003cimg loading=\"lazy\" src=\"images/p9999_password.png#center\"/\u003e \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\u003cdiv dir=\"ltr\"\u003e\n  \n    \u003cul\u003e\n\u003cli\u003ePage Items | Appearance | Template Options | Common\u003c/li\u003e\n\u003c/ul\u003e\n\n  \n\u003c/div\u003e\n\n\u003cfigure class=\"align-center ltr\"\u003e\n    \u003cimg loading=\"lazy\" src=\"images/hide%20password%20visibility.png#center\"\n         alt=\"⬛ Hide Password Visibility\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e⬛ Hide Password Visibility\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eدر صورت عدم نیاز، تیک این گزینه را فعال ✅ می‌کنیم.\u003c/p\u003e\n\u003cbr\u003e\nبرای نمایش آن از تگ button استفاده می‌شود:\n\u003cfigure class=\"align-center ltr\"\u003e\n    \u003cimg loading=\"lazy\" src=\"images/html-button.png#center\"\n         alt=\"button\"/\u003e \n\u003c/figure\u003e\n\n \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n  \r\n\r\n\r\n\r\n\u003cblockquote class=\"alert alert-note\"\u003e\r\n\r\n  \u003cp\u003e\u003cspan class=\"alert-icon\"\u003e🔔\u003c/span\u003e پس از نمایش پسورد در صورتی که focus از روی پسورد یا آیکون چشم (button) برداشته شود، پسورد دوباره به حالت مخفی برخواهد گشت.\u003c/p\u003e","title":"نمایش پسورد در Oracle APEX 24.2"},{"content":"نگهداری پسورد در مرورگرها مقدار پسورد وارد شده، به صورت متن ساده (Plain Text) و بدون رمزگذاری در 1 DOM ذخیره می‌گردد. در واقع مرورگر در زمان نمایش فقط ظاهر آن را تغییر می‌دهد (mask). مثلا همه کاراکترهای ورودی را تبدیل به ● می‌کند.\n💡 در source صفحه، مقدار پسورد وجود نخواهد داشت.\nبنابراین مقدار Value برابر با \u0026quot;\u0026quot; خواهد بود و یا اصلا value برای input وجود نخواهد داشت.\nروش بدست آوردن با JavaScript در DevTools مرورگر و در زبانه console: با استفاده از متد getElementById و نام فیلد که در html به عنوان id استفاده می‌شود document.getElementById(\u0026#39;P9999_PASSWORD\u0026#39;).value; با استفاده از متد querySelector و نوع password document.querySelector(\u0026#39;input[type=\u0026#34;password\u0026#34;]\u0026#39;).value با تبدیل نوع در DevTools (کلید F12) مرورگر و در زبانه insepector:\nمی‌توانیم نوع input را از passwprd به text تغییر دهیم پیاده سازی در Oracle APEX روش اول می‌خواهیم کاربر با کلیک کردن بر روی آیکون، پسورد را مشاهده کند/نکند.\nدر صفحه Login بر روی آیتم Password کلیک می‌کنیم: Identification Name: P9999_PASSWORD Advanced Post Text: \u0026lt;span style = \u0026#34;cursor: pointer;\u0026#34; class=\u0026#34;fa fa-eye-slash\u0026#34; title = \u0026#39;نمایش گذرواژه\u0026#39; onClick = \u0026#34;togglePass(\u0026#39;P9999_PASSWORD\u0026#39;, this)\u0026#34; \u0026gt; \u0026lt;/span\u0026gt; در قسمت Post Text یک عنصر span به صفحه اضافه می‌کنیم تا آیکون چشم را بتوانیم نمایش دهیم و با کلیک کردن بر روی آن متد togglePass اجرا شود. این متد دارای دو پارامتر است:\npass: نام فیلد پسورد icon: محلی که کلیک کرده‌ایم و می‌خواهیم کلاس آیکون آن را تغییر دهیم؛ بنابراین از کلمه کلیدی this استفاده می‌کنیم. آیکون چشم: fa fa-eye آیکون چشم خط خورده: fa fa-eye-slash 2. در قسمت خصوصیات Page متد JavaScript زیر را اضافه می‌کنیم: با استفاده از این متد و با هر کلیک، آیکون‌ها را تعویض و نوع input را از password به text و برعکس تغییر می‌دهیم.\nPage | JavaScript | Function and Global Variable Declaration function togglePass(itemId, icon) { const pass = document.getElementById(itemId); if (pass.type === \u0026#39;password\u0026#39;) { pass.type = \u0026#39;text\u0026#39;; icon.className = \u0026#39;fa fa-eye-slash\u0026#39;; icon.title = \u0026#39;پنهان کردن گذرواژه\u0026#39; } else { pass.type = \u0026#39;password\u0026#39;; icon.className = \u0026#39;fa fa-eye\u0026#39;; icon.title = \u0026#39;نمایش گذرواژه\u0026#39; } } 🔔 نمونه دیگر، استفاده از یک checkbox به جای آیکون: Checkbox\rروش دوم در این روش می‌خواهیم ریسک دیده شدن پسورد را کاهش دهیم، بدین شکل که با از دست دادن focus در محدوده پسورد، پسورد مخفی شود. برای span پسورد:\nبرای ایجاد focus بر روی آن از tabindex=0 استفاده می‌کنیم. ( tabindex\r) 💡 اگر نخواهیم کاربر با کلید tab به این قسمت برسد و فقط focus را داشته باشیم، مقدار tabindex را «-1» می‌نویسیم.\ntabindex = -1 برای جلوگیری از اجرای متد پیش‌فرض، onclick آن را برابر با return false قرار می‌دهیم. در این مورد اگر ننویسیم هم اتفاقی نخواهد افتاد! Pasword Item (P9999_PASSWORD) | Advanced | Post Text: \u0026lt;span id=\u0026#34;my-toggle-icon\u0026#34; tabindex=\u0026#34;0\u0026#34; class=\u0026#34;fa fa-eye-slash\u0026#34; title=\u0026#34;نمایش گذرواژه\u0026#34; style=\u0026#34;cursor:pointer\u0026#34; onclick=\u0026#34;return false;\u0026#34;\u0026gt; \u0026lt;/span\u0026gt; در خصوصیات صفحه یک متد به نام initPasswordToggle ایجاد می‌کنیم که شامل موارد زیر باشد:\nدر APEX فیلد پسورد و span (نمایش/عدم نمایش)، داخل یک div با کلاس t-Form-itemWrapper قرار می‌گیرند. می‌خواهیم با از دست دادن focus در هر یک از فرزندان این div، متد hide اجرا شود. با استفاده از فیلد پسورد و متد closest\rبه اولین والد آن که div مورد نظر ما هست ، خواهیم رسید. نام آن را wrapper می‌گذاریم. const pass = document.getElementById(passwordId); const icon = document.getElementById(iconId); const wrapper = pass.closest(\u0026#39;.t-Form-itemWrapper\u0026#39;); دو متد hide و show اضافه می‌کنیم. برای span، رویداد کلیک را تعریف می‌کنیم. function hide() { pass.type = \u0026#39;password\u0026#39;; icon.className = \u0026#39;fa fa-eye-slash\u0026#39;; icon.title = \u0026#39;نمایش گذرواژه\u0026#39;; } function show() { pass.type = \u0026#39;text\u0026#39;; icon.className = \u0026#39;fa fa-eye\u0026#39;; icon.title = \u0026#39;پنهان کردن گذرواژه\u0026#39;; } icon.addEventListener(\u0026#39;click\u0026#39;, function (e) { // e.preventDefault(); // جلوگیری از رفتار پیش‌فرض pass.type === \u0026#39;password\u0026#39; ? show() : hide(); icon.focus(); }); بر روی wrapper یک listener برای رویداد focusout\rایجاد می‌کنیم تا خروج focus را بر روی تمام فرزندان آن مدیریت کنیم. تا زمانی که focus از wrapper خارج نشود، پسورد نمایش داده خواهد شد. در غیر این صورت متد hide اجرا خواهد شد. e.relatedTarget\rعنصری است که قرار است focus به آن برود. مثلا کاربر بر روی فیلد نام کاربری کلیک می‌کند و مقدار relatedTarget برابر با این عنصر خواهد شد. اگر این عنصر جزو فرزندان wrapper باشد (input یا span)، متد خاتمه می‌یابد، در غیر این صورت متد hide اجرا می‌شود. wrapper.addEventListener(\u0026#39;focusout\u0026#39;, function (e) { const focusTo = e.relatedTarget; if (focusTo \u0026amp;\u0026amp; wrapper.contains(focusTo)) { return; } hide(); }); کد نهایی\nPage | JavaScript | Function and Global Variable Declaration function initPasswordToggle(passwordId, iconId) { const pass = document.getElementById(passwordId); const icon = document.getElementById(iconId); const wrapper = pass.closest(\u0026#39;.t-Form-itemWrapper\u0026#39;); // if (!pass || !icon || !wrapper) return; function hide() { pass.type = \u0026#39;password\u0026#39;; icon.className = \u0026#39;fa fa-eye-slash\u0026#39;; icon.title = \u0026#39;نمایش گذرواژه\u0026#39;; } function show() { pass.type = \u0026#39;text\u0026#39;; icon.className = \u0026#39;fa fa-eye\u0026#39;; icon.title = \u0026#39;پنهان کردن گذرواژه\u0026#39;; } icon.addEventListener(\u0026#39;click\u0026#39;, function (e) { // e.preventDefault(); // جلوگیری از رفتار پیش‌فرض pass.type === \u0026#39;password\u0026#39; ? show() : hide(); icon.focus(); }); wrapper.addEventListener(\u0026#39;focusout\u0026#39;, function (e) { const focusTo = e.relatedTarget; if (focusTo \u0026amp;\u0026amp; wrapper.contains(focusTo)) { return; } hide(); }); } // فراخوانی متد initPasswordToggle(\u0026#39;P9999_PASSWORD\u0026#39;, \u0026#39;my-toggle-icon\u0026#39;); DOM: Document Object Model\r\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://abedinitips.ir/posts/oracle-apex/%D9%86%D9%85%D8%A7%DB%8C%D8%B4-%D9%BE%D8%B3%D9%88%D8%B1%D8%AF-%D8%A8%D9%87-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1/","summary":"\u003ch2 id=\"نگهداری-پسورد-در-مرورگرها\"\u003eنگهداری پسورد در مرورگرها\u003c/h2\u003e\n\u003cp\u003eمقدار پسورد وارد شده، به صورت متن ساده (Plain Text) و بدون رمزگذاری  در \u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e DOM ذخیره می‌گردد. در واقع مرورگر در زمان نمایش فقط ظاهر آن را تغییر می‌دهد (mask). مثلا همه کاراکترهای ورودی را تبدیل به \u003ccode\u003e●\u003c/code\u003e می‌کند.\u003c/p\u003e\n \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n  \r\n\r\n\r\n\r\n\u003cblockquote class=\"alert alert-tip\"\u003e\r\n\r\n  \u003cp\u003e\u003cspan class=\"alert-icon\"\u003e💡\u003c/span\u003e در source صفحه، مقدار پسورد وجود نخواهد داشت.\u003cbr\u003e\nبنابراین مقدار Value برابر با \u0026quot;\u0026quot; خواهد بود و یا اصلا value برای input وجود نخواهد داشت.\u003c/p\u003e","title":"نمایش Password به کاربر"},{"content":"مساله یک صفحه برای تعریف کاربران ایجاد کرده‌ایم که خاصیت Form Auto Complete آن off می‌باشد.\nدر این صفحه فرمی (region) قرارداده‌ایم که دارای یک آیتم از نوع password می‌باشد. با استفاده از مرورگر firefox (مورد استفاده من) وارد برنامه شده و username و password خود را در مرورگر ذخیره می‌کنیم. در زمانی که بخواهیم کاربری ایجاد/ویرایش کنیم مرورگر به صورت خودکار آیتم‌های مربوط به username و password را مقدار دهی می‌کند. 🔔 این مورد زمانی رخ می‌دهد که تنها یک کاربر مربوط به application خود را در firefox ذخیره کرده باشیم.\nآیتمی که ایجاد کرده‌ایم در html توسط تگ input با نوع password\rساخته می‌شود.\n\u0026lt;input type=\u0026#34;password\u0026#34; name=\u0026#34;P1009_PASSWORD_HASH\u0026#34; size=\u0026#34;30\u0026#34; value=\u0026#34;\u0026#34; id=\u0026#34;P1009_PASSWORD_HASH\u0026#34; required=\u0026#34;\u0026#34; class=\u0026#34;password apex-item-text\u0026#34;\u0026gt; راهکار 1 برای اینکه به مرورگر بگوییم می‌خواهیم در این قسمت از رمز جدیدی استفاده کنیم باید به ویژگی autocomplete مقدار new-password\rبدهیم. بر روی آیتم پسورد کلیک کرده و موارد زیر را اعمال می‌کنیم: Advanced Custom Attributes: autocomplete=\u0026ldquo;new-password\u0026rdquo; Advanced | Custom Attributes: autocomplete= \u0026ldquo;new-password\u0026rdquo;\n\u0026lt;input type=\u0026#34;password\u0026#34; name=\u0026#34;P1009_PASSWORD_HASH\u0026#34; size=\u0026#34;30\u0026#34; value=\u0026#34;\u0026#34; id=\u0026#34;P1009_PASSWORD_HASH\u0026#34; required=\u0026#34;\u0026#34; class=\u0026#34;password apex-item-text\u0026#34; autocomplete=\u0026#34;new-password\u0026#34;\u0026gt; بدین ترتیب مرورگر متوجه می‌شود که در این قسمت قصد ایجاد پسورد را خواهیم داشت، نه استفاده از آن! راهکار 2 استفاده از js\nاین کد را در قسمت JavaScript صفحه قرار می‌دهیم:\nJavaScript Function and Global Variable Declaration setTimeout(function () { document.querySelectorAll(\u0026#39;input[type=\u0026#34;password\u0026#34;]\u0026#39;).forEach(e =\u0026gt; e.value = \u0026#39;\u0026#39;); }, 100); این متد پس از 100 میلی‌ثانیه از load صفحه اجرا خواهد شد و تمام فیلدهایی که از نوع password هستند را پیدا و مقدار آن‌ها را خالی می‌کند.\n💡 استفاده از setValue در Execute when Page Loads بی‌تاثیر خواهد بود.\napex.item(\"نام آیتم\").setValue(\" \") 💡 تغییر نام فیلد هم بی‌تاثیر خواهد بود. مثلا فیلد password را به pw تغییر نام دهیم.\n","permalink":"https://abedinitips.ir/posts/oracle-apex/%D9%BE%D8%B1-%D8%B4%D8%AF%D9%86-%D8%AE%D9%88%D8%AF%DA%A9%D8%A7%D8%B1-username-%D9%88-password-%D8%AF%D8%B1-%D8%B2%D9%85%D8%A7%D9%86-%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF/","summary":"\u003ch2 id=\"مساله\"\u003eمساله\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eیک صفحه برای تعریف کاربران ایجاد کرده‌ایم که خاصیت \u003ccode\u003eForm Auto Complete\u003c/code\u003e آن \u003ccode\u003eoff\u003c/code\u003e می‌باشد.\u003cbr\u003e\n\u003cimg loading=\"lazy\" src=\"/posts/oracle-apex/%D9%BE%D8%B1-%D8%B4%D8%AF%D9%86-%D8%AE%D9%88%D8%AF%DA%A9%D8%A7%D8%B1-username-%D9%88-password-%D8%AF%D8%B1-%D8%B2%D9%85%D8%A7%D9%86-%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF/images/security-form%20auto%20complete.png#center\"\u003e\u003c/li\u003e\n\u003cli\u003eدر این صفحه فرمی (region) قرارداده‌ایم که دارای یک آیتم از نوع password می‌باشد.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/oracle-apex/%D9%BE%D8%B1-%D8%B4%D8%AF%D9%86-%D8%AE%D9%88%D8%AF%DA%A9%D8%A7%D8%B1-username-%D9%88-password-%D8%AF%D8%B1-%D8%B2%D9%85%D8%A7%D9%86-%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF/images/password-item.png#center\"\u003e\u003c/p\u003e\n\u003col start=\"3\"\u003e\n\u003cli\u003eبا استفاده از مرورگر firefox (مورد استفاده من) وارد برنامه شده و \u003cstrong\u003eusername\u003c/strong\u003e و \u003cstrong\u003epassword\u003c/strong\u003e خود را در مرورگر ذخیره می‌کنیم.\u003c/li\u003e\n\u003cli\u003eدر زمانی که بخواهیم کاربری ایجاد/ویرایش کنیم مرورگر به صورت خودکار آیتم‌های مربوط به \u003cstrong\u003eusername\u003c/strong\u003e و \u003cstrong\u003epassword\u003c/strong\u003e را مقدار دهی می‌کند.\u003c/li\u003e\n\u003c/ol\u003e\n \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n  \r\n\r\n\r\n\r\n\u003cblockquote class=\"alert alert-note\"\u003e\r\n\r\n  \u003cp\u003e\u003cspan class=\"alert-icon\"\u003e🔔\u003c/span\u003e این مورد زمانی رخ می‌دهد که تنها یک کاربر مربوط به application خود را در firefox ذخیره کرده باشیم.\u003c/p\u003e","title":"پر شدن ناخواسته‌ی username و password در زمان ایجاد!!"},{"content":"در صفحه‌ای که region ای از نوع Form دارد، کارهای زیر را انجام می‌دهیم:\nایجاد region نمایش عکس یک region از نوع static content ایجاد می‌کنیم\nنمای کلی region\nIdentification Type: Static Content Appearance Template: Standard حالت بدون عکس می‌خواهیم زمانی نمایش داده شود که دارای تصویر باشد، بنابراین به قسمت Server-side Condition می‌رویم:\nبررسی می‌کنیم که id وجود دارد یا خیر؟ یعنی فرم در حالت ویرایش باز شده باشد.\nآیا در جدول مربوطه عکسی وجود دارد یا خیر؟\nاستفاده از متد dbms_lob.getlenght برای سنجش اندازه فایل Server-side Condition Type: Expression\nLanguage: SQL\nSQL Expression:\n:P1004_ID is not null and ( select nvl(dbms_lob.getlength(image),0) from cms_news where id = :P1004_ID ) \u0026gt; 0 🔔 بدین ترتیب با استفاده از region دیگر برای اشیای داخل آن نیازی به بررسی شرط نخواهیم داشت.\nآیتم عکس ایجاد Item یک آیتم از نوع Display Image به region اضافه می‌کنیم و مقدار عکس را از آیتم عکس در داخل Form می‌خوانیم.\nIdentification Type: Display Image Settings Based On: BLOB Column specified in Item Source Filename Column: فیلد مربوطه در جدول MIME Type Column: فیلد مربوطه در جدول BLOB Last Update Column: فیلد مربوطه در جدول Source Form Region: انتخاب فرم Column: فیلد تصویر Data Type: BLOB Query Only: On دکمه حذف عکس یک دکمه جهت حذف به region اضافه می‌کنیم\nIdentification\nButton Name: DELETE_IMAGE Appearance | Template Options | Type: Danger Icon: fa-trash Behavior\nAction: Redirect to URL Target: javascript:apex.confirm(\u0026lsquo;آیا عکس حذف شود؟\u0026rsquo; , \u0026lsquo;DELETE_IMAGE\u0026rsquo;); Process حذف به قسمت Processing می‌رویم و یک Process ایجاد می‌کنیم\nدر قسمت کد، از دستور update برای خالی کردن مقادیر فایل استفاده می‌کنیم و یا اگر تصاویر در جدولی جداگانه قرار داشت از دستور delete استفاده می‌کنیم. همچنین شرط اجرای این روال را فشردن دکمه حذف قرار می‌دهیم.\nSource\nLocation: Local Database\nLanguage: PL/SQL\nPL/SQL Code:\nupdate cms_news set image = null, image_mimetype = null, image_filename = null, image_lastupd = null where id = :P1004_ID; Success Message\nSuccess Message: حذف با موفقیت انجام شد Server-side Condition\nWhen Button Pressed: Delete_IMAGE چینش نهایی (Layout) نمای فرم ","permalink":"https://abedinitips.ir/posts/oracle-apex/%D9%86%D9%85%D8%A7%DB%8C%D8%B4-%D8%AA%D8%B5%D9%88%DB%8C%D8%B1-%D8%AB%D8%A8%D8%AA-%D8%B4%D8%AF%D9%87-%D8%AF%D8%B1-form/","summary":"\u003cp\u003eدر صفحه‌ای که region ای از نوع Form دارد، کارهای زیر را انجام می‌دهیم:\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"ایجاد-region-نمایش-عکس\"\u003eایجاد region نمایش عکس\u003c/h2\u003e\n\u003cp\u003eیک region از نوع  static content ایجاد می‌کنیم\u003c/p\u003e\n\u003cfigure class=\"align-center \"\u003e\n    \u003cimg loading=\"lazy\" src=\"images/region.png#center\"\n         alt=\"نمای کلی region\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003eنمای کلی region\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\n\u003cdiv dir=\"ltr\"\u003e\n  \n    \u003cul\u003e\n\u003cli\u003eIdentification\n\u003cul\u003e\n\u003cli\u003eType: Static Content\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eAppearance\n\u003cul\u003e\n\u003cli\u003eTemplate: Standard\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n  \n\u003c/div\u003e\n\n\u003chr\u003e\n\u003ch2 id=\"حالت-بدون-عکس\"\u003eحالت بدون عکس\u003c/h2\u003e\n\u003cp\u003eمی‌خواهیم زمانی نمایش داده شود که دارای تصویر باشد، بنابراین به قسمت Server-side Condition می‌رویم:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eبررسی می‌کنیم که id وجود دارد یا خیر؟ یعنی فرم در حالت ویرایش باز شده باشد.\u003c/p\u003e","title":"نمایش تصویر ثبت شده در Form"},{"content":"🤔چطور می‌توانم یک آیتم از نوع Number Filed را تبدیل به Numeric Spinner کنم؟\nنیازمندی‌ها دو دکمه در جلوی آیتم که روی هم قراربگیرند و برای کم و زیاد کردن مقدار استفاده شوند.\nبرای دکمه‌ها +/- مقدار پیش‌فرض مقدار یک باشد و توسط کاربر قابل تنظیم باشد.\nاگر آیتم دارای focus بود، با حرکت غلتک (scroll) ماوس، مقدار کم و زیاد شود.\nبرای غلتک، مقدار پیش‌فرض 10 باشد و توسط کاربر هم قابل تنظیم باشد.\nامکان منفی شدن مقدار، توسط کاربر مشخص شود. در حالت پیش‌فرض مقدار منفی مجاز نیست.\nاعداد ممکن است دارای جدا کننده (Grouping Separator) باشند.\n🔔 به صورت پیش‌فرض برای این نوع آیتم type = text هست که می‌شود آن را به number تغییر داد تا دکمه‌های inc/dec نمایان شوند. اما من از این روش استفاده نمی‌کنم.\nپیاده‌سازی بر روی آیتم مورد نظر (مثلا P7_ORDER_NO) کلیک کرده و برای Post Text آن، مقداری به دلخواه می‌نویسیم. من از * استفاده کردم. بدین ترتیب span این قسمت با کلاس «t-Form-itemText\u0026ndash;post» فعال می‌شود. با استفاده از jQuery ، دکمه‌ها را در این قسمت قرار می‌دهیم. Advanced | Post Text: * برای قرارگرفتن کادر دور دکمه‌ها Display as Block مربوط به آیتم را انتخاب می‌کنیم. Appearance | Template Options | Item Post Text: Display as Block\nدر قسمت JavaScript صفحه در Execute when Page Loads از متد Item_NumericSpinner استفاده می‌کنیم. کد JS را می‌توانیم در قسمت Function and Global Variable Declaration بنویسیم. من از فایل در قسمت static fileها استفاده می‌کنم ( لینک GitHub\r).\n/** * ایجاد حالت +/- کردن به فیلد عددی * ** type: number field ** برای قرارگرفتن دکمه \u0026gt; post text = * * * * @param itemName \u0026gt; نام آیتم * @param wheel_step \u0026gt; مقداری که با چرخ موس باید اضافه/کم شود * @param ud_step \u0026gt; up-down مقداری که باید اضافه/کم شود * @param negative \u0026gt; باشد false جلوگیری از مقدار منفی اگر مقدار * @param groupingSymbol \u0026gt; کاراکتر جداکننده ارقام */ function Item_NumericSpinner(itemName, wheel_step = 10, ud_step = 1, negative = false, groupingSymbol = \u0026#39;,\u0026#39;) { let containter$ = $(\u0026#34;#\u0026#34; + itemName + \u0026#34;_CONTAINER .t-Form-itemText--post\u0026#34;); let css_btn = { background: \u0026#34;none\u0026#34;, border: \u0026#34;none\u0026#34;, cursor: \u0026#34;pointer\u0026#34; }; let css_div = { display: \u0026#34;inline-flex\u0026#34;, \u0026#34;flex-direction\u0026#34;: \u0026#34;column\u0026#34;, \u0026#34;vertical-align\u0026#34;: \u0026#34;middle\u0026#34; }; let div$ = $(\u0026#39;\u0026lt;div\u0026gt;\u0026#39;).css(css_div); let btnUp$ = $(\u0026#39;\u0026lt;button\u0026gt;\u0026#39;, { type: \u0026#34;button\u0026#34;, text: \u0026#39;▲\u0026#39; }).css(css_btn); let btnDown$ = $(\u0026#39;\u0026lt;button\u0026gt;\u0026#39;, { type: \u0026#34;button\u0026#34;, text: \u0026#39;▼\u0026#39; }).css(css_btn); div$.append(btnUp$); div$.append(btnDown$); containter$.html(div$); //---------------------------------------------- let item$ = $(\u0026#34;#\u0026#34; + itemName); btnUp$.on(\u0026#34;click\u0026#34;, inc.bind(null,ud_step)); btnDown$.on(\u0026#34;click\u0026#34;, dec.bind(null,ud_step)); item$.on(\u0026#34;wheel\u0026#34;, function (e) { e.preventDefault(); // آیا فوکوس دارد؟ if (document.activeElement.id !== itemName) return; let delta = e.originalEvent.deltaY; if (delta \u0026lt; 0) { inc(wheel_step); } else { dec(wheel_step); } }); function inc(step) { let val = parseFloat(item$.val().replace(groupingSymbol, \u0026#39;\u0026#39;)) || 0; item$.val(val + step); // apex.item(itemName).setValue(item$.val()); } function dec(step) { let val = parseFloat(item$.val().replace(groupingSymbol, \u0026#39;\u0026#39;)) || 0; val = val - step if (negative == false \u0026amp; val \u0026lt; 0) val = 0; item$.val(val); // apex.item(itemName).setValue(item$.val()); } } 🧐🧠خلاصه کارهایی که انجام دادم آیا می‌شود در قسمت Post Text از کد HTML استفاده کرد؟ بله از ChatGPT خواستم کد این کار را برایم ایجاد کند … که در نهایت خروجی آن شامل سه قسمت شد: کد دکمه‌ها، CSS و کد JS. چرخه‌ی تست، رفع اشکال، بازبینی، ایده‌ی جدید و … برای اینکه بتوانم از این کد به دفعات استفاده کنم، آن را تبدیل به function کردم. برای اینکه کاربر مجبور به استفاده از کد CSS و کد دکمه‌ها نباشد، آن را در متد JS ادغام کردم. برای حذف تکرار، از nested function استفاده کردم (تابع تو در تو). چون این کد را برای Item استفاده کردم، برای دسته بندی بهتر، آن را در فایلی به نام site_Item.js نگهداری می‌کنم که در این فایل برای نام متدها از پیشوند Item استفاده کردم و خواهم کرد. 💡 تمام فایل‌های JS را در نهایت به فایل site.js تبدیل می‌کنم و در محیط production استفاده می‌کنم.\n","permalink":"https://abedinitips.ir/posts/oracle-apex/%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-numeric-spinner-updown/","summary":"\u003cp\u003e🤔چطور می‌توانم یک آیتم از نوع Number Filed را تبدیل به Numeric Spinner کنم؟\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"نیازمندیها\"\u003eنیازمندی‌ها\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eدو دکمه در جلوی آیتم که روی هم قراربگیرند و برای کم و زیاد کردن مقدار استفاده شوند.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eبرای دکمه‌ها +/- مقدار پیش‌فرض مقدار یک باشد و توسط کاربر قابل تنظیم باشد.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eاگر آیتم دارای focus بود، با حرکت غلتک (scroll) ماوس، مقدار کم و زیاد شود.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eبرای غلتک، مقدار پیش‌فرض 10 باشد و توسط کاربر هم قابل تنظیم باشد.\u003c/p\u003e","title":"ایجاد Numeric Spinner (up-down)"},{"content":" می‌خواهیم فونت وزیر\r(vazirmatn) را جایگزین فونت پیش‌فرض سایت ساز Hugo\rقرار بدهیم. همچنین می‌خواهیم این تغییرات وابسته به قالب سایت نباشد؛ یعنی با تغییر قالب، فونت ما همچنان vazirmatn بماند.\nفونت وزیر را از گیتهاب دانلود می‌کنیم: GitHub\rپوشه و فایل‌های مورد استفاده برای این کار:\nپوشه fonts دو فایل font face vazirmatn-variable-font-face.css vazirmatn-font-face.css انتقال فونت‌ها به مسیر static/fonts/vazirmatn در پوشه assets پوشه‌ی extended را با مسیر css/extended ایجاد می‌کنیم و فایل‌های زیر را در آن قرار می‌دهیم. site.css vazirmatn-variable-font-face.css یک فایل به نام site.css برای ایجاد تنظیمات مورد نظر در داخل آن ایجاد می‌کنیم\n/* فونت‌ها */ /*@import url(vazirmatn-font-face.css);*/ @import url(vazirmatn-variable-font-face.css); /* متغیر سراسری */ html { --my-font1: Vazirmatn; --my-font2: IRANSansX; --main-font: var(--my-font1); } body { font-family: var(--main-font) !important; font-feature-settings: \u0026#34;tnum\u0026#34;; /* عددها هم‌عرض */ } /* وقتی زبان فارسی است → اعداد فارسی */ html[lang=\u0026#34;fa\u0026#34;] body { font-feature-settings: \u0026#34;ss01\u0026#34;, \u0026#34;tnum\u0026#34;; } /* تیترها و ورودی‌ها */ h1, h2, h3, h4, h5, h6, input, textarea { font-family: var(--main-font) !important; } مسیر داخل font-face باید به قسمت static اشاره کند. بنابراین باید شروع آن با /fonts/vazirmatn باشد.\n@font-face { font-family: \u0026#39;Vazirmatn\u0026#39;; src: url(\u0026#39;/fonts/vazirmatn/webfonts/Vazirmatn[wght].woff2\u0026#39;) format(\u0026#39;woff2 supports variations\u0026#39;), url(\u0026#39;/fonts/vazirmatn/webfonts/Vazirmatn[wght].woff2\u0026#39;) format(\u0026#39;woff2-variations\u0026#39;); font-weight: 100 900; font-style: normal; font-display: swap; } ","permalink":"https://abedinitips.ir/posts/hugo/%D8%AA%D8%BA%DB%8C%DB%8C%D8%B1-%D9%81%D9%88%D9%86%D8%AA-%D8%AF%D8%B1-hugo/","summary":"\u003chr\u003e\n\u003cp\u003eمی‌خواهیم \u003ca href=\"https://rastikerdar.github.io/vazirmatn/\" \r\ntarget=\"_blank\" rel=\"noopener\"\u003e \u003ci class=\"fa-solid fa-external-link fa-xs\" style=\"color:rgb(37, 153, 182)\"\u003e\u003c/i\u003e فونت وزیر\u003c/a\u003e\r\n (vazirmatn) را جایگزین فونت پیش‌فرض سایت ساز \u003ca href=\"https://gohugo.io/\" \r\ntarget=\"_blank\" rel=\"noopener\"\u003e \u003ci class=\"fa-solid fa-external-link fa-xs\" style=\"color:rgb(37, 153, 182)\"\u003e\u003c/i\u003e Hugo\u003c/a\u003e\r\n قرار بدهیم. همچنین می‌خواهیم این تغییرات وابسته به قالب سایت نباشد؛ یعنی با تغییر قالب، فونت ما همچنان vazirmatn بماند.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eفونت وزیر را از گیتهاب دانلود می‌کنیم: \u003ca href=\"https://rastikerdar.github.io/vazirmatn/\" \r\ntarget=\"_blank\" rel=\"noopener\"\u003e \u003ci class=\"fa-solid fa-external-link fa-xs\" style=\"color:rgb(37, 153, 182)\"\u003e\u003c/i\u003e GitHub\u003c/a\u003e\r\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cimg alt=\"پوشه وزیرمتن\" loading=\"lazy\" src=\"/posts/hugo/%D8%AA%D8%BA%DB%8C%DB%8C%D8%B1-%D9%81%D9%88%D9%86%D8%AA-%D8%AF%D8%B1-hugo/images/fonts-vazirmatn.png#center\"\u003e\nپوشه و فایل‌های مورد استفاده برای این کار:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eپوشه fonts\u003c/li\u003e\n\u003cli\u003eدو فایل font face\n\u003cul\u003e\n\u003cli\u003evazirmatn-variable-font-face.css\u003c/li\u003e\n\u003cli\u003evazirmatn-font-face.css\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003eانتقال فونت‌ها به مسیر static/fonts/vazirmatn\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cimg alt=\"static/fonts/vazirmatn\" loading=\"lazy\" src=\"/posts/hugo/%D8%AA%D8%BA%DB%8C%DB%8C%D8%B1-%D9%81%D9%88%D9%86%D8%AA-%D8%AF%D8%B1-hugo/images/static-fonts-vazirmatn.png#center\"\u003e\u003c/p\u003e","title":"تغییر فونت در Hugo"}]