Back to "फ़िल्टर पट्टी प्रगति"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET HTMX JavaScript

फ़िल्टर पट्टी प्रगति

Monday, 10 November 2025

परिचय

पिछले कुछ दिनों में मैं-'-मैं नए ब्लॉग फिल्टर पट्टी पर hammering away कर रहा हूँ।

याद रखें यह साइट एक काम प्रगति में है।

क्या बदला है

यहाँ>'> उच्च है।

  • भाषा चुनें जो आपके वर्तमान फ़िल्टरों को सुरक्षित रखती है और HTMX के माध्यम से सूची की मांग करती है
  • क्रमबद्ध क्रम चुनें (dateM SK1title asc/desc ) जो संदर्भ को भी सुरक्षित करता है
  • एक तिथि रेंज पिकर (Flatpickr रेंज मोड में) सर्वर के साथM SK2प्रचालित दिन हाइलाइट्स
  • यूआरएल स्थिति हमेशा समक्रमण में रखी जाती है (back/forward worksMSC2 deep links workM SK3
  • सर्वर कैचिंग अब फ़िल्टर पैरामीटरों द्वारा सही रूप से बदलता है

इस अद्यतन में नया (द बिग फिक्स

  • दृश्यता फिल्टर: डाक अब छुपा जा सकता है
  • तिथि रेंज अंतबिन्दु: नया /blog/date-range API sensible picker bounds के लिए min/max तिथियां लौटाता है
  • पारम टैग मददगार साफ करें: फ़िल्टरों को साफ करने का आसान तरीका उपयोग कर <clear-param> के साथ टैग मददगार Alpine.js

रास्ते में मेरे पास कुछ बगें थीं-: भाषा पर तिथि रेंजों को हरा जा रहा था।

डोम में फ़िल्टर पट्टी

यह स्क्रिप्ट की प्रत्याशित rough संरचना है

<div id="filters" hx-target="#content">
  <select id="languageSelect">…</select>
  <select id="orderSelect">
    <option value="date_desc">Newest first</option>
    <option value="date_asc">Oldest first</option>
    <option value="title_asc">Title A–Z</option>
    <option value="title_desc">Title Z–A</option>
  </select>
  <input id="dateRange" type="text" placeholder="YYYY-MM-DD → YYYY-MM-DD" />
  <button id="clearDateFilter">Clear</button>
  <div id="filterSummary"></div>
</div>

<div id="content"><!-- HTMX swaps blog list here --></div>

क्लाइंट: HTMX + फ्लैट पिक्सर तार

व्यवहार के मूल में निवास करता है Mostlylucid/src/js/blog-index.jsकुछ महत्वपूर्ण टुकड़े

  • यूआरएल संरक्षण और HTMX नेविगेशन
function applyNavigation(u){
  const target = document.querySelector('#content');
  try{ window.history.pushState({}, '', u.toString()); }catch{}
  if(window.htmx && target){
    window.htmx.ajax('GET', u.toString(), {
      target: '#content',
      swap: 'outerHTML show:none',
      headers: {'pagerequest': 'true'}
    });
  } else {
    window.location.href = u.toString();
  }
}
  • आरंभीकरण, यूआरएल से डिफ़ॉल्ट हो रहा है, और सारांश UI को समक्रमित बनाए रखना
const url = new URL(window.location.href);
const existingStart = url.searchParams.get('startDate');
const existingEnd   = url.searchParams.get('endDate');
const existingLang  = url.searchParams.get('language') || 'en';
const existingOrderBy = (url.searchParams.get('orderBy') || 'date').toLowerCase();
const existingOrderDir = (url.searchParams.get('orderDir') || 'desc').toLowerCase();

langSelect.value = existingLang;
orderSelect.value = `${existingOrderBy}_${existingOrderDir}`;
updateSummary();
  • सर्वर के साथ Flatpickr दायरा
async function fetchMonth(year, month, language){
  const res = await fetch(`/blog/calendar-days?year=${year}&month=${month}&language=${encodeURIComponent(language||'en')}`);
  if(!res.ok) return new Set();
  const j = await res.json();
  return new Set(j.dates || []);
}

function formatYMD(d){ return d.toISOString().substring(0,10); }

let highlightDates = new Set();
const fp = window.flatpickr(input, {
  mode: 'range',
  dateFormat: 'Y-m-d',
  defaultDate: [existingStart, existingEnd].filter(Boolean),
  onDayCreate: function(_dObj,_dStr,fpInstance,dayElem){
    const ymd = formatYMD(dayElem.dateObj);
    if(highlightDates.has(ymd)){
      dayElem.classList.add('has-post');
      dayElem.style.background = 'rgba(76,175,80,0.35)';
      dayElem.style.borderRadius = '6px';
    }
  },
  onMonthChange: async function(_sd,_ds,fpInstance){
    highlightDates = await fetchMonth(fpInstance.currentYear, fpInstance.currentMonth+1, langSelect.value);
    fpInstance.redraw();
  },
  onOpen: async function(_sd,_ds,fpInstance){
    highlightDates = await fetchMonth(fpInstance.currentYear, fpInstance.currentMonth+1, langSelect.value);
    fpInstance.redraw();
  },
  onChange: function(selectedDates){
    if(selectedDates.length === 2){
      const [start,end] = selectedDates;
      const u = new URL(window.location.href);
      u.searchParams.set('startDate', formatYMD(start));
      u.searchParams.set('endDate', formatYMD(end));
      u.searchParams.set('page','1');
      u.searchParams.set('language', langSelect.value);
      const [ob,od] = orderSelect.value.split('_');
      u.searchParams.set('orderBy', ob);
      u.searchParams.set('orderDir', od);
      updateSummary();
      applyNavigation(u);
    }
  }
});
  • भाषा या आदेश बदलने पर तारीखें सुरक्षित रखें
langSelect.addEventListener('change', async function(){
  const u = new URL(window.location.href);
  u.searchParams.set('language', langSelect.value);
  u.searchParams.set('page', '1');
  const [ob,od] = (orderSelect.value||'date_desc').split('_');
  u.searchParams.set('orderBy', ob);
  u.searchParams.set('orderDir', od);
  if(input._flatpickr && input._flatpickr.selectedDates.length===2){
    const [s,e] = input._flatpickr.selectedDates;
    u.searchParams.set('startDate', formatYMD(s));
    u.searchParams.set('endDate', formatYMD(e));
  }
  // refresh calendar highlights for new language
  if(input._flatpickr){
    const fp = input._flatpickr;
    highlightDates = await fetchMonth(fp.currentYear, fp.currentMonth+1, langSelect.value);
    fp.redraw();
  }
  updateSummary();
  applyNavigation(u);
});
  • जब विषयवस्तु टॉगल करता है तो पुनः -draw कैलेन्डर | ( | dark | / | light | ) so Flatpickr repaints
const obs = new MutationObserver(() => {
  const dr = root.querySelector('#dateRange');
  const fp = dr && dr._flatpickr;
  if(fp && typeof fp.redraw === 'function') fp.redraw();
});
obs.observe(document.documentElement, { attributes:true, attributeFilter:['class'] });

सर्वर: अंतबिंदु और कैशिंग

पृष्ठ को तीन अंतबिंदुओं से शक्ति प्रदान करता है

  • स्वयं अनुक्रमणिका, जो पृष्ठ को स्वीकार करता है।*
  • ए calendar-days अंतबिंदु जो एक दिए गए महीने में दिनों की सेट को लौटाता है जिसमें स्तम्भ हैं
  • ए date-range अंतबिंदु जो सभी पदों पर न्यूनतम /max तारीख को लौटाता है
// GET /blog
[HttpGet]
[ResponseCache(Duration = 300, VaryByHeader = "hx-request",
  VaryByQueryKeys = new[] { "page", "pageSize", nameof(startDate), nameof(endDate), nameof(language), nameof(orderBy), nameof(orderDir) },
  Location = ResponseCacheLocation.Any)]
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request" },
  VaryByQueryKeys = new[] { nameof(page), nameof(pageSize), nameof(startDate), nameof(endDate), nameof(language), nameof(orderBy), nameof(orderDir) })]
public async Task<IActionResult> Index(int page = 1, int pageSize = 20, DateTime? startDate = null, DateTime? endDate = null,
  string language = MarkdownBaseService.EnglishLanguage, string orderBy = "date", string orderDir = "desc")
{
    var posts = await blogViewService.GetPagedPosts(page, pageSize, language: language, startDate: startDate, endDate: endDate);
    posts.LinkUrl = Url.Action("Index", "Blog", new { startDate, endDate, language, orderBy, orderDir });
    if (Request.IsHtmx()) return PartialView("_BlogSummaryList", posts);
    return View("Index", posts);
}

// GET /blog/calendar-days?year=2025&month=11&language=en
[HttpGet("calendar-days")]
[ResponseCache(Duration = 300, VaryByHeader = "hx-request", VaryByQueryKeys = new[] { nameof(year), nameof(month), nameof(language) }, Location = ResponseCacheLocation.Any)]
[OutputCache(Duration = 1800, VaryByHeaderNames = new[] { "hx-request" }, VaryByQueryKeys = new[] { nameof(year), nameof(month), nameof(language) })]
public async Task<IActionResult> CalendarDays(int year, int month, string language = MarkdownBaseService.EnglishLanguage)
{
    if (year < 2000 || month < 1 || month > 12) return BadRequest("Invalid year or month");
    var start = new DateTime(year, month, 1);
    var end = start.AddMonths(1).AddDays(-1);
    var posts = await blogViewService.GetPostsForRange(start, end, language: language);
    if (posts is null) return Json("");
    var dates = posts.Select(p => p.PublishedDate.Date).Distinct().OrderBy(d => d).Select(d => d.ToString("yyyy-MM-dd")).ToList();
    return Json(new { dates });
}

// GET /blog/date-range?language=en
[HttpGet("date-range")]
[ResponseCache(Duration = 3600, VaryByHeader = "hx-request", VaryByQueryKeys = new[] { nameof(language) }, Location = ResponseCacheLocation.Any)]
[OutputCache(Duration = 7200, VaryByHeaderNames = new[] { "hx-request" }, VaryByQueryKeys = new[] { nameof(language) })]
public async Task<IActionResult> DateRange(string language = MarkdownBaseService.EnglishLanguage)
{
    var allPosts = await blogViewService.GetAllPosts();
    if (allPosts is null || !allPosts.Any())
    {
        return Json(new
        {
            minDate = DateTime.UtcNow.AddYears(-1).ToString("yyyy-MM-dd"),
            maxDate = DateTime.UtcNow.ToString("yyyy-MM-dd")
        });
    }

    var posts = allPosts;
    if (!string.IsNullOrEmpty(language) && language != MarkdownBaseService.EnglishLanguage)
    {
        posts = allPosts.Where(p => p.Language.Equals(language, StringComparison.OrdinalIgnoreCase)).ToList();
    }

    if (!posts.Any()) posts = allPosts;

    var minDate = posts.Min(p => p.PublishedDate.Date);
    var maxDate = posts.Max(p => p.PublishedDate.Date);

    return Json(new
    {
        minDate = minDate.ToString("yyyy-MM-dd"),
        maxDate = maxDate.ToString("yyyy-MM-dd")
    });
}

एक त्वरित किनारा-:-पैगिंग मॉडल है LinkUrl इस तरह कि पृष्ठीकरण वर्तमान फिल्टर संदर्भ को सुरक्षित करता है जब rendered सर्वर

public class BasePagingModel<T> : Interfaces.IPagingModel<T> where T : class
{
    public int Page { get; set; }
    public int TotalItems { get; set; } = 0;
    public int PageSize { get; set; }
    public ViewType ViewType { get; set; } = ViewType.TailwindAndDaisy;
    public string LinkUrl { get; set; }
    public List<T> Data { get; set; }
}

अनुक्रम: कैसे एक फिल्टर परिवर्तन प्रवाहित होता है

sequenceDiagram
  participant U as User
  participant FP as Flatpickr
  participant JS as blog-index.js
  participant HT as HTMX
  participant C as BlogController

  U->>FP: Selects 2025-11-01 → 2025-11-10
  FP-->>JS: onChange([start,end])
  JS->>JS: Update URLSearchParams (startDate, endDate, language, order)
  JS->>HT: htmx.ajax('GET', /blog?...)
  HT->>C: GET /blog with query
  C-->>HT: Partial _BlogSummaryList
  HT-->>U: Swap #content
  JS->>FP: (after swap) ensure highlights + redraw

फ्लो

flowchart TD
  A[Open calendar / Month change] --> B[Fetch /blog/calendar-days]
  B -->|JSON dates: yyyy-mm-dd array| C[Set highlightDates]
  C --> D[flatpickr redraw]
  D --> E[onDayCreate adds .has-post]

नई सुविधाएँ: दृश्यता और सामग्री नियंत्रण

दृश्यता फ़िल्टर पोस्ट करें

इस अद्यतन में सबसे बड़ा सुधार उचित पोस्ट दृश्यता प्रबंधन है BlogPostEntity:

public class BlogPostEntity
{
    // ... existing fields ...

    public bool IsPinned { get; set; }
    public bool IsHidden { get; set; }
    public DateTimeOffset? ScheduledPublishDate { get; set; }
}

BlogService अब पोस्ट उचित रूप से फिल्टर करता है

// Filter out hidden posts and posts scheduled for the future
var now = DateTimeOffset.UtcNow;
postQuery = postQuery.Where(x =>
    !x.IsHidden &&
    (x.ScheduledPublishDate == null || x.ScheduledPublishDate <= now));

// For page 1, prioritize pinned posts
var isFirstPage = page == null || page.Value == 1;
if (isFirstPage)
{
    postQuery = postQuery.OrderByDescending(x => x.IsPinned)
                         .ThenByDescending(x => x.PublishedDate.DateTime);
}

इसका मतलब है

  • छुपे पोस्ट (IsHidden = true) सूची में कभी नहीं दिखाई देता
  • अनुसूचित पदों केवल उनके बाद प्रकट ScheduledPublishDate
  • पिन्ड पोस (IsPinned = true) हमेशा पृष्ठ पर पहली बार दिखाई देता है 1, घोषणाओं या विशिष्ट सामग्री के लिए उपयुक्त

पैराम टैग मददगार साफ करें

एक नया ASP.NET कोर टैग मददगार क्वेरी पैरामीटरों को साफ करने के लिए आसान करता है

[HtmlTargetElement("clear-param")]
public class ClearParamTagHelper : TagHelper
{
    [HtmlAttributeName("name")]
    public string? Name { get; set; }

    [HtmlAttributeName("all")]
    public bool All { get; set; } = false;

    [HtmlAttributeName("exclude")]
    public string Exclude { get; set; } = "";

    // ... styling and Alpine.js integration ...
}

रजोर दृश्यों में उपयोग

<!-- Clear a specific parameter -->
<clear-param name="startDate">Clear Date</clear-param>

<!-- Clear all parameters except language -->
<clear-param all="true" exclude="language">Clear All Filters</clear-param>

टैग सहायक Alpine के साथ एकीकृत करता है window.queryParamClearer युआरएल परिचालन और HTMX स्वाप को संभालने के लिए घटक.

नोटBoth the paging tag helper and the query parameter clearer deserve deeper exploration

बग्स मैं Hit (and the fixes)

  • भाषा के बदलने पर तिथि दायरा हरा गया

    • कारण: मैं नए यूआरएल को शून्य से बना दिया था startDate/endDate.
    • Fix: हमेशा के साथ शुरू करें new URL(window.location.href) और केवल उन भागों को बदलें जो बदल गए हैं; या Flatpickr से तिथियां पढ़ने के लिए अगर मौजूद है language/order उपर्युक्त हैंडलर बदलें
  • पंचांग हाइलाइट HTMX स्क्वाप के बाद ताज़ा नहीं हुआ

    • कारण
    • ठीक करें: इनिट पर, किसी भी मौजूदा इंस्टैंस को नष्ट कर दें , फिर पुनः‑नृजित करें M SK4 फिर दृश्यात्मक माह के लिए पूर्वलोड हाइलाइट्स लाएँ।
  • अंधेरा मोड ने कैलेंडर को टूटा दिखाई

    • कारण: शैली वर्गों में परिवर्तन हुआ लेकिन फ्लैटपिकर ने नहीं किया
    • स्थिर <html class> परिवर्तन और कॉल fp.redraw().
  • पृष्ठीकरण लिंक फ़िल्टर संदर्भ छोड़ दिया

    • कारण LinkUrl कुछ मामलों में क्वेरी शामिल करने के लिए सेट नहीं था
    • सर्वर पर posts.LinkUrl = Url.Action("Index","Blog", new { startDate, endDate, language, orderBy, orderDir }) तो पेजर सही रूप से यूआरएल बनाता है

HTMX स्क्वाप सुरक्षा

अगर आप आंशिक स्वैप का उपयोग करते हैं, तो आपके JS को idempotent होना चाहिए.

(function(){
  function initFromRoot(root){ /* …the big function shown above… */ }
  if(window.htmx){
    document.body.addEventListener('htmx:afterSwap', function(ev){
      const tgt = ev.detail.target;
      // re-init if the content swapped includes the filters/content area
      if(tgt && (tgt.id === 'content' || tgt.querySelector?.('#filters'))){
        initFromRoot(document);
      }
    });
  }
  // also run on first load
  initFromRoot(document);
})();

आगे क्या है

  • त्वरित पूर्वनिर्धारित जोड़ें (अंतिम | 7 | दिन |/ | МSK3
  • पृष्ठ को बीजित करने के लिए स्थानीय में स्थायी भाषा
  • तिथि शृंखला के लिए कुंजीपटल आवंटन

यदि आप फ़िल्टरों के साथ कोई अजीब बात पाते हैं तो कृपया अपने ब्राउज़र से एक टिप्पणी छोड़ें

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.