PageSourceSearch

https://houseplansandmore.com/js/favorites.js

js houseplansandmore.com collected 2026-09-26 11:03:17 UTC 5,844 bytes, 155 lines download raw bytes

1/**
2 * Favorites — cookie-backed favorites for SSR pages.
3 *
4 * Mirrors the legacy anonymous-favorites behavior (the old site tracked favorites
5 * in the ASP.NET Session regardless of login). Here we use a first-party cookie so
6 * the /favorites page can render the list server-side under static SSR, for both
7 * anonymous and logged-in users. No framework dependencies; vanilla JS only.
8 *
9 * Cookie: hpm_favorites = URL-encoded JSON array of
10 *   { planNumber, planType, planName, imageUrl }.
11 */
12(function () {
13    'use strict';
14
15    var COOKIE_NAME = 'hpm_favorites';
16    var MAX_FAVORITES = 100;
17    var MAX_AGE_DAYS = 365;
18
19    /** Read the raw cookie value by name, or '' if absent. */
20    function readCookie(name) {
21        var match = document.cookie.match('(?:^|; )' + name.replace(/([.$?*|{}()\[\]\\\/+^])/g, '\\$1') + '=([^;]*)');
22        return match ? decodeURIComponent(match[1]) : '';
23    }
24
25    /** Write a cookie (path=/, SameSite=Lax so it rides along with navigations). */
26    function writeCookie(name, value) {
27        var expires = new Date(Date.now() + MAX_AGE_DAYS * 864e5).toUTCString();
28        document.cookie = name + '=' + encodeURIComponent(value) +
29            '; path=/; expires=' + expires + '; SameSite=Lax';
30    }
31
32    /** Current favorites list. Returns [] on error. */
33    function getFavorites() {
34        try {
35            var raw = readCookie(COOKIE_NAME);
36            return raw ? JSON.parse(raw) : [];
37        } catch (_) { return []; }
38    }
39
40    /** Persist the favorites list to the cookie. */
41    function saveFavorites(list) {
42        writeCookie(COOKIE_NAME, JSON.stringify(list));
43    }
44
45    /** True when the plan number is currently a favorite. */
46    function isFavorite(planNumber) {
47        return getFavorites().some(function (p) { return p.planNumber === planNumber; });
48    }
49
50    /** Add a favorite. Returns false if already present or at the limit. */
51    function addFavorite(planNumber, planName, imageUrl, planType) {
52        var list = getFavorites();
53        if (list.some(function (p) { return p.planNumber === planNumber; })) return false;
54        if (list.length >= MAX_FAVORITES) {
55            alert('You can save up to ' + MAX_FAVORITES + ' favorites. Remove one first.');
56            return false;
57        }
58        list.push({
59            planNumber: planNumber,
60            planName: planName || '',
61            imageUrl: imageUrl || '',
62            planType: planType || 'House'
63        });
64        saveFavorites(list);
65        return true;
66    }
67
68    /** Remove a favorite by plan number. */
69    function removeFavorite(planNumber) {
70        saveFavorites(getFavorites().filter(function (p) { return p.planNumber !== planNumber; }));
71    }
72
73    /** Reflect favorite state on a single toggle button. */
74    function paintButton(btn, fav) {
75        var icon = btn.querySelector('i');
76        if (fav) {
77            btn.classList.add('active');
78            btn.setAttribute('aria-pressed', 'true');
79            btn.setAttribute('title', 'Remove from favorites');
80            if (icon) { icon.classList.remove('fa-regular'); icon.classList.add('fa-solid'); }
81        } else {
82            btn.classList.remove('active');
83            btn.setAttribute('aria-pressed', 'false');
84            btn.setAttribute('title', 'Save to favorites');
85            if (icon) { icon.classList.remove('fa-solid'); icon.classList.add('fa-regular'); }
86        }
87    }
88
89    /** Paint every favorite toggle button on the page from the current cookie. */
90    function updateButtons() {
91        var favs = {};
92        getFavorites().forEach(function (p) { favs[p.planNumber] = true; });
93        var buttons = document.querySelectorAll('[data-fav-plan]');
94        for (var i = 0; i < buttons.length; i++) {
95            paintButton(buttons[i], !!favs[buttons[i].getAttribute('data-fav-plan')]);
96        }
97    }
98
99    /** Toggle a plan's favorite state from its button and repaint. */
100    function onToggleClick(btn) {
101        var planNumber = btn.getAttribute('data-fav-plan');
102        if (!planNumber) return;
103        if (isFavorite(planNumber)) {
104            removeFavorite(planNumber);
105        } else {
106            addFavorite(
107                planNumber,
108                btn.getAttribute('data-fav-name') || '',
109                btn.getAttribute('data-fav-img') || '',
110                btn.getAttribute('data-fav-type') || 'House'
111            );
112        }
113        updateButtons();
114    }
115
116    /** Remove a favorite from the /favorites page, then reload to re-render server-side. */
117    function removeFromFavoritesPage(planNumber) {
118        removeFavorite(planNumber);
119        window.location.reload();
120    }
121
122    function init() {
123        updateButtons();
124
125        document.addEventListener('click', function (e) {
126            var btn = e.target.closest('[data-fav-plan]');
127            if (btn) {
128                e.preventDefault();
129                onToggleClick(btn);
130            }
131        });
132    }
133
134    if (document.readyState === 'loading') {
135        document.addEventListener('DOMContentLoaded', init);
136    } else {
137        init();
138    }
139
140    // Repaint after bfcache restore and Blazor enhanced navigation (DOM replaced
141    // without firing DOMContentLoaded).
142    window.addEventListener('pageshow', function (e) { if (e.persisted) updateButtons(); });
143    if (typeof Blazor !== 'undefined' && Blazor.addEventListener) {
144        Blazor.addEventListener('enhancedload', updateButtons);
145    }
146
147    window.Favorites = {
148        add: addFavorite,
149        remove: removeFavorite,
150        toggle: onToggleClick,
151        removeFromPage: removeFromFavoritesPage,
152        getFavorites: getFavorites,
153        isFavorite: isFavorite
154    };
155})();

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.