PageSourceSearch

https://saddleback.com/scripts/Components/Views/WeekendMessagesLis…?v=517cdeafa93af7720f65d0a6f449ca34186eeedc

js saddleback.com collected 2026-09-24 17:19:04 UTC 11,853 bytes, 260 lines download raw bytes

1import ListItem from '../../Resources/ListItem.js';
2import SeriesCarousel from '../../Resources/SeriesCarousel.js';
3import LoadingSpinner from '../../VueComponents/LoadingSpinner.js';
4import weekendMessagesEndpoints from '../../utils/weekendMessagesEndpoints.js';
5import resourcesUtils from '../../Resources/resourcesUtils.js';
6import GridLayout from '../../VueComponents/Layouts/GridLayout.js';
7import GridItem from '../../VueComponents/Layouts/GridItem.js';
8import FilterCard from '../../VueComponents/Cards/FilterCard.js';
9import FilterPanelMobile from '../../VueComponents/Cards/FilterPanelMobile.js';
10
11export default {
12    name: 'WeekendMessagesListView',
13    components: {
14        ListItem,
15        SeriesCarousel,
16        LoadingSpinner,
17        GridLayout,
18        GridItem,
19        FilterCard,
20        FilterPanelMobile,
21    },
22    props: {
23        healthyChurchDomain: {
24            type: String,
25            required: true,
26        },
27        campusId: {
28            type: Number,
29            required: false,
30            default: null,
31        }
32    },
33    data() {
34        return {
35            isLoading: true,
36            itemList: [],
37            sortBy: 'newest',
38            // Single source of truth for the series filter, shared by the carousel and
39            // the dropdown. null = "all". Holds the Sardius seriesAssetId string (e.g.
40            // "series_Live Sent") — the only key that matches across the series list and sermons.
41            selectedSeriesId: null,
42            // Stable reference so changing another filter doesn't rebuild this dropdown.
43            sortOptions: [
44                { id: 'newest', text: 'Newest First' },
45                { id: 'oldest', text: 'Oldest First' },
46                { id: 'title', text: 'Title (A-Z)' },
47                { id: 'titleReversed', text: 'Title (Z-A)' },
48            ],
49        };
50    },
51    computed: {
52        resolvedCampusId() {
53            return this.campusId ?? 12; // Lake Forest
54        },
55        hasNoMessages() {
56            // The feed came back with nothing — either an empty result set or a request
57            // that failed and was caught, leaving the list empty. Without messages there
58            // are no series to filter by either, so the whole grid has nothing to show.
59            return this.itemList.length === 0;
60        },
61        selectedSeriesName() {
62            if (this.selectedSeriesId === null) return null;
63            const match = this.itemList.find((item) => item.seriesAssetId === this.selectedSeriesId);
64            return match?.seriesName ?? null;
65        },
66        headingText() {
67            // Show the series name when filtered to one; otherwise the default heading.
68            return this.selectedSeriesName ?? 'Latest Messages';
69        },
70        filterDropdowns() {
71            return [
72                {
73                    key: 'sort',
74                    label: 'Sort By',
75                    ariaLabel: 'Sort messages by',
76                    value: this.sortBy,
77                    default: 'newest',
78                    options: this.sortOptions,
79                },
80                {
81                    key: 'series',
82                    label: 'Series',
83                    ariaLabel: 'Filter messages by series',
84                    value: this.selectedSeriesId ?? 'all',
85                    default: 'all',
86                    options: this.seriesOptions,
87                },
88            ];
89        },
90        seriesOptions() {
91            // Dedupe by seriesAssetId so the dropdown speaks the same language as the
92            // carousel and the sermon feed. Keyed by assetId; value is the display name.
93            const seriesByAssetId = new Map();
94            this.itemList.forEach((item) => {
95                if (item.seriesAssetId && item.seriesName && !seriesByAssetId.has(item.seriesAssetId)) {
96                    seriesByAssetId.set(item.seriesAssetId, item.seriesName);
97                }
98            });
99
100            const options = [...seriesByAssetId.entries()]
101                .map(([id, text]) => ({ id, text }))
102                .sort((a, b) => a.text.localeCompare(b.text));
103
104            return [{ id: 'all', text: 'All Series' }, ...options];
105        },
106        sortedItemList() {
107            let items = [...this.itemList];
108
109            if (this.selectedSeriesId !== null) {
110                items = items.filter((item) => item.seriesAssetId === this.selectedSeriesId);
111            }
112
113            switch (this.sortBy) {
114                case 'oldest':
115                    return items.sort((a, b) =>
115 this.getSortDate(a) - this.getSortDate(b));
116                case 'title':
117                    return items.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''));
118                case 'titleReversed':
119                    return items.sort((a, b) => (b.name ?? '').localeCompare(a.name ?? ''));
120                case 'newest':
121                default:
122                    return items.sort((a, b) => this.getSortDate(b) - this.getSortDate(a));
123            }
124        },
125    },
126    mounted() {
127        this.fetchLatestSermons();
128    },
129    methods: {
130        fetchLatestSermons(pageNumber = 0, pageSize = 250, endDate = null) {
131            this.isLoading = true;
132            const url = weekendMessagesEndpoints.getLatestSermons(this.healthyChurchDomain, pageNumber, pageSize, this.resolvedCampusId, endDate);
133            window.sbDebug && console.log('[WeekendMessages] getLatestSermons:', url);
134            axios
135                .get(url)
136                .then((response) => {
137                    // Guard: the feed returns campusServiceTemplate: null when a campus/domain
138                    // has no sermons (e.g. the dev HC domain), which would otherwise crash
139                    // seriesOptions/sortedItemList (forEach / spread on null).
140                    this.itemList = response.data.campusServiceTemplate ?? [];
141                    if (this.hasNoMessages) {
142                        // The request succeeded but carried nothing — the error state the
143                        // visitor sees is the feed being empty, not a failed call.
144                        console.error(
145                            `[WeekendMessagesListView] Showing the error state: the sermon feed returned no messages for campus ${this.resolvedCampusId}. Requested ${url}`,
146                            response.data
147                        );
148                    }
149                })
150                .catch((error) => {
151                    console.error(
152                        `[WeekendMessagesListView] Showing the error state: the sermon feed request failed. Requested ${url}`,
153                        error
154                    );
155                })
156                .finally(() => {
157                    this.isLoading = false;
158                })
159        },
160        getSermonDescription(sermonItem) {
161            return resourcesUtils.getSpeakerDateString(sermonItem);
162        },
163        getSermonTags(sermonItem) {
164            return resourcesUtils.getTags(sermonItem, ["weekendmessages"]);
165        },
166        getSermonLink(sermonItem) {
167            const origin = window.location.origin;
168            return resourcesUtils.generateMediaDetailsUrl(sermonItem, origin);
169        },
170        getSortDate(sermonItem) {
171            return sermonItem?.occurrences?.[0]?.actualDate ?? 0;
172        },
173        onSeriesSelected(series) {
174            // series is the selected series object, or null when the user deselects.
175            // Filter by seriesAssetId — the Sardius key that matches sermon.seriesAssetId.
176            this.selectedSeriesId = series?.seriesAssetId ?? null;
177        },
178        onDropdownChanged(key, value) {
179            switch (key) {
180                case 'sort':
181                    this.sortBy = value;
182                    break;
183                case 'series':
184                    // Values are the seriesAssetId string (or "all").
185                    this.selectedSeriesId = value === 'all' ? null : value;
186                    break;
187            }
188        },
189    },
190    template: `
191        <section class="courses-list-view">
192            <div class="clamp">
193                <LoadingSpinner v-show="isLoading" />
194                <Transition>
195                    <GridLayout
196                        v-show="!isLoading"
197                        class="clv-grid-layout"
198                        :columns="2"
199                        gridTemplateColumns="1fr 338px"
200                    >
201                        <!-- Nothing came back from the feed. span="all" rather than a
202                             number so it spans both columns whatever the grid resolves to. -->
203                        <GridItem
204                            v-if="hasNoMessages"
205                            span="all"
206                            class="list-empty-state"
207                            role="status"
208                        >
209                            <h3>Oops!</h3>
210                            <p class="message-text">An error has occurred, please refresh the page and try again.</p>
211                        </GridItem>
212                        <!-- With no messages there are no series to filter by either, so the
213                             carousel, heading, filters and list all have nothing to show. -->
214                        <template v-else>
215                            <GridItem :span="2">
216                                <SeriesCarousel
217                                    :healthyChurchDomain="healthyChurchDomain"
218                                    :selectedSeriesId="selectedSeriesId"
219                                    @series-selected="onSeriesSelected"
220                                />
221                            </GridItem>
222                            <GridItem :span="2">
223                                <h3 style="margin: 0">{{ headingText }}</h3>
224                            </GridItem>
225                            <FilterPanelMobile
226                                :dropdowns="filterDropdowns"
227                                idPrefix="weekendMessagesMobile"
228                                @dropdown-changed="onDropdownChanged"
229                            />
230                            <GridItem
231                                class="list-item-container"
232                                :span="1"
233                            >
234                                <ListItem
235                                    v-for="item in sortedItemList"
236                                    :key="item.id"
237                                    :imageUrl="item.thumbnailImage"
238                                    :wideImageUrl="item.appHeaderImage"
239                                    :subHeader="item.seriesName"
240                                    :title="item.name"
241                                    :description="getSermonDescription(item)"
242                                    :tags="getSermonTags(item)"
243                                    ctaLabel="Watch Message"
244                                    :ctaUrl="getSermonLink(item)"
245                                    :ctaIsExternal="false"
246                                />
247                            </GridItem>
248                            <FilterCard
249                                :dropdowns="filterDropdowns"
250                                idPrefix="weekendMessages"
251                                hideOnSingleColumn
252                                @dropdown-changed="onDropdownChanged"
253                            />
254                        </template>
255                    </GridLayout>
256                </Transition>
257            </div>
258        </section>
259    `,
260};

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.