PageSourceSearch

https://hct.ac.ae/wp-content/themes/HCTtheme/assets/uae-ds/js/services.js?ver=1774959536

js hct.ac.ae collected 2026-10-02 05:43:16 UTC 15,327 bytes, 467 lines download raw bytes

1(function ($) {
2    var $page = $('.services-page').first();
3    var config = window.HCTServicesPage || {};
4    var labels = config.labels || {};
5    var messages = config.messages || {};
6    var khadamatiNames = config.khadamatiNames || {};
7    var $search;
8    var $category;
9    var $trigger;
10    var $listing;
11    var $status;
12    var $paginationWrap;
13    var $desktopPages;
14    var $mobilePages;
15    var $first;
16    var $prev;
17    var $next;
18    var $last;
19    var state;
20
21    if (!$page.length) {
22        return;
23    }
24
25    $search = $('#service-keyword');
26    $category = $('#service-category');
27    $trigger = $('.js-services-search-trigger');
28    $listing = $('#services-listing');
29    $status = $('#services-status');
30    $paginationWrap = $('#services-pagination-wrap');
31    $desktopPages = $('#services-pagination-pages');
32    $mobilePages = $('#services-mobile-pages');
33    $first = $('.js-services-pagination-first');
34    $prev = $('.js-services-pagination-prev');
35    $next = $('.js-services-pagination-next');
36    $last = $('.js-services-pagination-last');
37
38    if (!$search.length || !$category.length || !$trigger.length || !$listing.length) {
39        return;
40    }
41
42    state = {
43        search: '',
44        category: $category.val() || '',
45        page: 1,
46        perPage: 16,
47        totalPages: 1,
48        totalItems: 0,
49        loading: false
50    };
51
52    function safeText(value) {
53        return $('<div/>').text(value == null ? '' : String(value)).html();
54    }
55
56    function decodeEntities(value) {
57        var element = document.createElement('textarea');
58
59        element.innerHTML = String(value == null ? '' : value);
60
61        return element.value;
62    }
63
64    function normalizeUrl(value) {
65        return String(value == null ? '' : value).trim();
66    }
67
68    function getDisplayTitle(item) {
69        var serviceId = item && item.acf && item.acf.khadamati_service_id ? String(item.acf.khadamati_service_id) : '';
70        var wpTitle = item && item.title && item.title.rendered ? decodeEntities(item.title.rendered) : '';
71        var khadamatiTitle = serviceId && khadamatiNames[serviceId] ? decodeEntities(khadamatiNames[serviceId]) : '';
72
73        return khadamatiTitle || wpTitle;
74    }
75
76    function getDescription(item) {
77        var description = item && item.acf && item.acf.service_description ? item.acf.service_description : '';
78
79        return decodeEntities(description);
80    }
81
82    function getBookmarks() {
83        var raw;
84
85        try {
86            raw = window.localStorage.getItem('hct-services-bookmarks');
87            return raw ? JSON.parse(raw) : {};
88        } catch (error) {
89            return {};
90        }
91    }
92
93    function setBookmarks(bookmarks) {
94        try {
95            window.localStorage.setItem('hct-services-bookmarks', JSON.stringify(bookmarks));
96        } catch (error) {
97            return;
98        }
99    }
100
101    function getBookmarkKey(item) {
102        if (item && item.id != null) {
103            return String(item.id);
104        }
105
106        return normalizeUrl(item && item.link ? item.link : '');
107    }
108
109    function setStatus(message, visible) {
110        if (!message || visible === false) {
111            $status.addClass('hidden').text('');
112            return;
113        }
114
115        $status.removeClass('hidden').text(message);
116    }
117
118    function setLoading(isLoading) {
119        state.loading = isLoading;
120
121        if (isLoading) {
122            setStatus(messages.loading || 'Loading services...', true);
123            $paginationWrap.addClass('opacity-50');
124            return;
125        }
126
127        if ($status.text() === (messages.loading || 'Loading services...')) {
128            setStatus('', false);
129        }
130
131        $paginationWrap.removeClass('opacity-50');
132    }
133
134    function renderMessage(message) {
135        $listing.html('<div class="col-span-full py-8 text-center">' + safeText(message) + '</div>');
136    }
137
138    function renderItems(items) {
139        var content = '';
140
141        $.each(items, function (index, item) {
142            var title = getDisplayTitle(item);
143            var description = getDescription(item);
144            var detailUrl = normalizeUrl(item && item.link ? item.link : '#');
145            var startUrl = normalizeUrl(item && item.acf && item.acf.service_start ? item.acf.service_start : '');
146            var startMarkup = '';
147            var bookmarkKey = getBookmarkKey(item);
148            var isSaved = !!getBookmarks()[bookmarkKey];
149            var bookmarkTitle = isSaved ? (labels.saved || 'Service saved') : (labels.save || 'Save service');
150
151            if (startUrl !== '') {
152                startMarkup = '<a href="' + safeText(startUrl) + '" class="aegov-link" target="_blank" rel="noopener">' +
153                    safeText(labels.start_service || 'Start service') +
154                    '<svg class="link-icon rtl:-scale-x-100" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"></rect>
154<line x1="40" y1="128" x2="216" y2="128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line><polyline points="144 56 216 128 144 200" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></polyline></svg>' +
155                    '</a>';
156            }
157
158            content += '<div class="aegov-card card-bordered card-glow card-service h-full flex flex-col justify-between gap-4">';
159            content += '<div class="flex flex-col gap-3">';
160            content += '<h3 class="card-service-title mb-0"><a href="' + safeText(detailUrl) + '" class="line-clamp-2">' + safeText(title) + '</a></h3>';
161
162            if (description !== '') {
163                content += '<p class="line-clamp-3 mb-0">' + safeText(description) + '</p>';
164            }
165
166            content += '</div>';
167            content += '<div class="flex items-center justify-between gap-4 flex-wrap">';
168            content += '<div class="flex items-center flex-wrap gap-4">';
169            content += startMarkup;
170            content += '</div>';
171            content += '<button type="button" class="text-primary-600 hover:text-primary-500 group js-service-bookmark' + (isSaved ? ' checked' : '') + '" data-bookmark-key="' + safeText(bookmarkKey) + '" aria-pressed="' + (isSaved ? 'true' : 'false') + '" aria-label="' + safeText(bookmarkTitle) + '" title="' + safeText(bookmarkTitle) + '">';
172            content += '<svg class="group-[.checked]:hidden" xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256"><path d="M184,32H72A16,16,0,0,0,56,48V224a8,8,0,0,0,12.24,6.78L128,193.43l59.77,37.35A8,8,0,0,0,200,224V48A16,16,0,0,0,184,32Zm0,177.57-51.77-32.35a8,8,0,0,0-8.48,0L72,209.57V48H184Z"></path></svg>';
173            content += '<svg class="hidden group-[.checked]:block fill-primary-600" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path d="M184,32H72A16,16,0,0,0,56,48V224a8,8,0,0,0,12.24,6.78L128,193.43l59.77,37.35A8,8,0,0,0,200,224V48A16,16,0,0,0,184,32Z"></path></svg>';
174            content += '</button>';
175            content += '</div>';
176            content += '</div>';
177        });
178
179        $listing.html(content);
180    }
181
182    function buildPageList(currentPage, totalPages) {
183        var pages = [];
184        var start;
185        var end;
186        var page;
187
188        if (totalPages <= 7) {
189            for (page = 1; page <= totalPages; page += 1) {
190                pages.push(page);
191            }
192
193            return pages;
194        }
195
196        pages.push(1);
197        start = Math.max(2, currentPage - 1);
198        end = Math.min(totalPages - 1, currentPage + 1);
199
200        if (currentPage <= 3) {
201            end = 4;
202        }
203
204        if (currentPage >= totalPages - 2) {
205            start = totalPages - 3;
206        }
207
208        if (start > 2) {
209            pages.push('ellipsis-start');
210        }
211
212        for (page = start; page <= end; page += 1) {
213            pages.push(page);
214        }
215
216        if (end < totalPages - 1) {
217            pages.push('ellipsis-end');
218        }
219
220        pages.push(totalPages);
221
222        return pages;
223    }
224
225    function setControlState($elements, disabled) {
226        $elements
227            .toggleClass('pointer-events-none opacity-50', !!disabled)
228            .attr('aria-disabled', disabled ? 'true' : 'false')
229            .attr('tabindex', disabled ? '-1' : '0');
230    }
231
232    function renderPagination() {
233        var pages;
234        var desktopMarkup = '';
235
236        if (state.totalItems < 1 || state.totalPages <= 1) {
237            $paginationWrap.addClass('hidden').removeClass('flex');
238            return;
239        }
240
241        pages = buildPageList(state.page, state.totalPages);
242
243        $.each(pages, function (index, item) {
244            if (typeof item === 'string') {
245                desktopMarkup += '<span class="aegov-pagination-extend">...</span>';
246                return;
247            }
248
249            if (item === state.page) {
250                desktopMarkup += '<span aria-current="page" class="aegov-pagination-current">' + safeText(item) + '</span>';
251                return;
252            }
253
254            desktopMarkup += '<a class="aegov-pagination-page aegov-pagination-larger js-services-pagination-page" title="' + s
254afeText((labels.page || 'Page') + ' ' + item) + '" data-page="' + item + '" href="#">' + safeText(item) + '</a>';
255        });
256
257        $desktopPages.html(desktopMarkup);
258        $mobilePages.text((labels.page || 'Page') + ' ' + state.page + ' ' + (labels.of || 'of') + ' ' + state.totalPages);
259        $paginationWrap.removeClass('hidden').addClass('flex');
260
261        setControlState($first, state.page === 1);
262        setControlState($prev, state.page === 1);
263        setControlState($next, state.page === state.totalPages);
264        setControlState($last, state.page === state.totalPages);
265    }
266
267    function renderResults(items) {
268        if (!Array.isArray(items) || !items.length) {
269            renderMessage(messages.noResults || 'No results found.');
270            $paginationWrap.addClass('hidden').removeClass('flex');
271            return;
272        }
273
274        setStatus('', false);
275        renderItems(items);
276        renderPagination();
277    }
278
279    function fetchServices() {
280        var params = new URLSearchParams();
281
282        if (state.loading) {
283            return;
284        }
285
286        setLoading(true);
287
288        params.set('per_page', String(state.perPage));
289        params.set('page', String(state.page));
290        params.set('lang', config.lang || 'en');
291        params.append('_fields[]', 'title');
292        params.append('_fields[]', 'link');
293        params.append('_fields[]', 'id');
294        params.append('_fields[]', 'acf.service_start');
295        params.append('_fields[]', 'acf.service_description');
296        params.append('_fields[]', 'acf.khadamati_service_id');
297
298        if (state.search) {
299            params.set('search', state.search);
300        }
301
302        if (state.category) {
303            params.set('service_category', state.category);
304        }
305
306        $.ajax({
307            url: '/wp-json/wp/v2/service?' + params.toString(),
308            method: 'GET',
309            dataType: 'json'
310        })
311            .done(function (data, textStatus, jqXHR) {
312                var totalItems = parseInt(jqXHR.getResponseHeader('X-WP-Total') || '0', 10);
313                var totalPages = parseInt(jqXHR.getResponseHeader('X-WP-TotalPages') || '1', 10);
314
315                state.totalItems = isNaN(totalItems) ? 0 : totalItems;
316                state.totalPages = Math.max(1, isNaN(totalPages) ? 1 : totalPages);
317
318                renderResults(data);
319            })
320            .fail(function (jqXHR, textStatus) {
321                console.error('[Services] Error:', textStatus, jqXHR.responseText);
322                setStatus(messages.requestFailed || 'Unable to load services.', true);
323                renderMessage(messages.requestFailed || 'Unable to load services.');
324                $paginationWrap.addClass('hidden').removeClass('flex');
325            })
326            .always(function () {
327                setLoading(false);
328            });
329    }
330
331    function submitSearch() {
332        state.search = String($search.val() || '').trim();
333        state.category = String($category.val() || '').trim();
334        state.page = 1;
335        fetchServices();
336    }
337
338    $search.on('keydown', function (event) {
339        if (event.key !== 'Enter') {
340            return;
341        }
342
343        event.preventDefault();
344        submitSearch();
345    });
346
347    $search.on('input', debounce(function () {
348        submitSearch();
349    }, 250));
350
351    $category.on('change', function () {
352        submitSearch();
353    });
354
355    $trigger.on('click', function (event) {
356        event.preventDefault();
357        submitSearch();
358    });
359
360    $(document).on('click', '.js-services-pagination-page', function (event) {
361        var targetPage;
362
363        event.preventDefault();
364
365        if (state.loading) {
366            return;
367        }
368
369        targetPage = parseInt($(this).attr('data-page') || '0', 10);
370
371        if (!targetPage || targetPage === state.page) {
372            return;
373        }
374
375        state.page = targetPage;
376        fetchServices();
377    });
378
379    $(document).on('click', '.js-service-bookmark', function (event) {
380        var $button = $(this);
381        var bookmarkKey = String($button.attr('data-bookmark-key') || '');
382        var bookmarks;
383        var isSaved;
384        var label;
385
386        event.preventDefault();
387
388        if (!bookmarkKey) {
389            return;
390        }
391
392        bookmarks = getBookmarks();
393        isSaved = !$button.hasClass('checked');
394
395        if (isSaved) {
396            bookmarks[bookmarkKey] = true;
397        } else {
398            delete bookmarks[bookmarkKey];
399        }
400
401        setBookmarks(bookmarks);
402        $button.toggleClass('checked', isSaved);
403        $button.attr('aria-pressed', isSaved ? 'true' : 'false');
404        label = isSaved ? (labels.saved || 'Service saved') : (labels.save || 'Save service');
405        $button.attr('aria-label', label).attr('title', label);
406    });
407
408    $first.on('click', function (event) {
409        event.preventDefault();
410
411        if (state.loading || state.page === 1) {
412            return;
413        }
414
415        state.page = 1;
416        fetchServices();
417    });
418
419    $prev.on('click', function (event) {
420        event.preventDefault();
421
422        if (state.loading || state.page === 1) {
423            return;
424        }
425
426        state.page -= 1;
427        fetchServices();
428    });
429
430    $next.on('click', function (event) {
431        event.preventDefault();
432
433        if (state.loading || state.page >= state.totalPages) {
434            return;
435        }
436
437        state.page += 1;
438        fetchServices();
439    });
440
441    $last.on('click', function (event) {
442        event.preventDefault();
443
444        if (state.loading || state.page === state.totalPages) {
445            return;
446        }
447
448        state.page = state.totalPages;
449        fetchServices();
450    });
451
452    function debounce(callback, wait) {
453        var timeoutId;
454
455        return function () {
456            var context = this;
457            var args = arguments;
458
459            clearTimeout(timeoutId);
460            timeoutId = setTimeout(function () {
461                callback.apply(context, args);
462            }, wait);
463        };
464    }
465
466    fetchServices();
467})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.