PageSourceSearch

https://www.wbs.ac.uk/sites/wbs2020/includes/themes/wbs2020/templates/js/events-related.js?v=1.4

js wbs.ac.uk collected 2026-09-24 08:39:05 UTC 20,685 bytes, 561 lines download raw bytes

1//TODO: additional props?
2//TODO: 'book this event' link - modal?
3//TODO: error handling
4//TODO: reduce duplication
5
6const formatDate = (date) => {
7    const d = new Date(date);
8    const time = d.toLocaleTimeString("en-GB",
9        {timeZone: 'Europe/London', timeStyle: 'long', hour12: false});
10    return d.toLocaleDateString() + " - " + time;
11};
12
13//NOTE: when passing an event id to this component via 'event' prop, it overrides most of the other filter props and the API will determine
14//NOTE: how events are related
15//NOTE: if you need more control, you can explicitly pass filter params
16const EventListingAlumni = {
17    props: {
18        size: {
19            type: Number,
20            required: false,
21            default: 5
22        },
23        location: {
24            type: Array,
25            required: false
26        },
27        programme: {
28            type: Array,
29            required: false
30        },
31        type: {
32            type: Array,
33            required: false
34        },
35        title: {
36            type: Array,
37            required: true
38        },
39        audience: {
40            type: Array,
41            required: true
42        },
43        category: {
44            type: Array,
45            required: true
46        }
47    },
48    filters: {
49        month: function(date) {
50            const months = [
51                "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
52            ];
53            return months[new Date(date).getUTCMonth()];
54        },
55        day: function(date) {
56            return new Date(date).getDate();
57        }
58    },
59    methods: {
60        setup() {
61            this.renderCarousel();
62            this.addMobileInfoToggleEvent();
63        },
64        renderCarousel() {
65            // Slider carousel
66            $('section.slider-carousel').each(fnSliderCarousel);
67        },
68        truncate(text, length, clamp) {
69            var thisClamp = clamp || ' ...';
70            var node = document.createElement('div');
71            node.innerHTML = text;
72            var content = node.textContent;
73            if (content.length > length ) {
74                const index = content.indexOf(" ", length);
75                return index === -1 ? content : content.substring(0, index) + thisClamp;
76            } else {
77                return text;
78            }
79        },
80        addMobileInfoToggleEvent: function () {
81            $(document).ready(function () {
82              // init all event handlers see global.js
83              $('section.slider-carousel > #AlumniRelatedEventsApp').each(fnEventsIndex);
84            });
85        }
86    },
87    template: `
88        <related-events :size="size" :location="location" :programme="programme" :title="title" :type="type" :category="category" :audience="audience"
89                        @events-fetched="setup">
90            <template v-slot:default="listProps">
91            <div v-if="listProps.events.length > 0" class="container">
92                <div class="heading">
93                    <h2>Forthcoming events</h2>
94                    <a href="/events">View All</a>
95                </div>
96                <div class="content">
97                    <div class="slider">
98                        <div v-for="event in listProps.events">
99                            <div class="block">
100                                <article class="event">
101                                    <header>
102                                        <aside class="date">
103                                            <div class="date-inner">
104                                               <span class="day">{{event.startDate | day}}</span>
105                                               <span class="month">{{event.startDate | month}}</span>
106                                            </div>
107                                        </aside>
108                                        <div class="meta">
109                                            <div class="meta-inner">
110                                            <a :href="'/events/view/' + event.id + '/'"> <span class="title" style="color: ##0254A7;">{{event.title}}</span> </a>
111                                                <!-- <span class="type">{{event.type}}
111 </span> --->
112                                                <span class="location">{{event.location}}</span>
113                                            </div>
114                                        </div>
115                                    </header>
116                                    <main>
117                                        <div class="more-info">
118                                            <span>More info</span>
119                                        </div>
120                                        <div class="description" style="display: none;">
121                                            <p v-html="truncate(event.teaser, 140)"></p>
122                                        </div>
123                                    </main>
124                                    <footer>
125                                        <div class="permalink">
126                                            <a class="button" :href="'/events/view/' + event.id + '/'">Book this event</a>
127                                        </div>
128                                    </footer>
129                                </article>
130                            </div>
131                        </div>
132                    </div>
133                </div>
134                <div v-if="listProps.events.length != 0" class="footer">
135                    <div class="controls">
136                        <span class="prev">
137                            <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11.2 18.12">
138                                <defs>
139                                    <style>
140                                        .cls-1 {
141                                            fill: none;
142                                            stroke: #fff;
143                                            stroke-width: 3px;
144                                        }
145                                    </style>
146                                </defs>
147                                <g id="Group-5-Copy-4">
148                                    <polyline id="Path-4" class="cls-1" points="10.12 1.04 2.12 9.3 10.12 17.04" />
149                                </g>
150                            </svg>
151                        </span>
152                        <span class="next">
153                            <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11.2 18.12">
154                                <defs>
155                                    <style>
156                                        .cls-1 {
157                                            fill: none;
158                                            stroke: #fff;
159                                            stroke-width: 3px;
160                                        }
161                                    </style>
162                                </defs>
163                                <polyline id="Path-4" class="cls-1" points="1.08 1.04 9.08 9.3 1.08 17.04" />
164                            </svg>
165                        </span>
166                    </div>
167                    <div class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100">
168                        <span class="completion"></span>
169                    </div>
170                </div>
171            </div>
172            <div v-if="listProps.events.length == 0">
173                There are no related events at this time.
174            </div>
175            </template>
176        </related-events>
177    `
178};
179
180const EventListingComponentByTheme = {
181    props: {
182        size: {
183            type: Number,
184            required: false,
185            default: 5
186        },
187        themes: {
188            type: Array,
189            required: false
190        },
191        groups: {
192            type: Array,
193            required: false
194        },
195        programmes: {
196            type: Array,
197            required: false
198        }
199    },
200    filters: {
201        formatDate: function(date) {
202            return formatDate(date);
203        }
204    },
205    template: `
206        <related-events :size="size" :themes="themes" :groups="groups" :programme="programmes">
207            <template v-slot:default="listProps">
208                <div class="sidebar-block" v-if="listProps.events.length > 0" >
209                <span class="block-title">Related Events</span>
210                <section class="simple-events  ">
211                <ul class="dates">
212                    <li class="date" v-for="event in listProps.events">
213                        <div class="category">
214                            <i class="colour" style="background-color: #0254A4;"></i>
215                            <span class="label">Featured</span>
216                        </div>
217                        <div class="text">
218                        <a :href="'/events/view/' + event.id"><span class="title">{{event.title}}</span></a>
219                            <span class="details">{{event.startDate | formatDate}}<br/>{{event.location}}</span>
220                        </div>
221                        <a class="permalink" :href="'/events/view/' + event.id" title="" target="_self"></a>
222                    </li>
223                    <li v-if="listProps.events.length == 0" class="date">
224                        <div class="text">
225                            <span class="title">No Related Events</span>
226                            <span class="details">Click to search our available events</span>
227                        </div>
228                        <a class="permalink" href="/events" title="" target="_self"></a>
229                    </li>
230                </ul>
231                </section>
232                </div>
233            </template>
234        </related-events>
235    `
236};
237
238const EventListingComponentShowcase = {
239    props: {
240        size: {
241            type: Number,
242            required: false,
243            default: 5
244        }
245    },
246    filters: {
247        formatDate: function(date) {
248            return formatDate(date);
249        }
250    },
251    template: `
252        <related-events :size="size" :featured="true">
253            <template v-slot:default="listProps">
254                <div v-if="listProps.events.length > 0">
255                <div class="events-showcase-header"><!-- events-showcase-header -->
256                    <h2 class="showcase-title">Events</h2>
257                    <a href="/events/" class="showcase-view-all link-view-all">View all</a>
258                </div>
259                <div class="events-showcase-loop"><!-- events-showcase-loop -->
260                    <div class="events-showcase-item" v-for="event in listProps.events"><!-- events-showcase-item -->
261                        <ul class="showcase-item-cats">
262                            <li>
263                                <span class="marker" style="background-color: #0254A4;"></span>
264                                <span>Featured</span>
265                            </li>
266                        </ul>
267                        <h3 class="showcase-item-title">
268                            <a :href="'/events/view/' + event.id + '/'">{{event.title}}</a>
269                        </h3>
270                        <p class="showcase-item-description">{{event.startDate | formatDate}}<br>{{event.location}}</p>
271                    </div>
272                    <div v-if="listProps.events.length == 0">
273                        There are no related events.
274                    </div>
275                </div>
276                </div>
277            </template>
278        </related-events>
279    `
280};
281
282const EventListingComponentVertical = {
283    props: {
284        size: {
285            type: Number,
286            required: false,
287            default: 3
288        },
289        programme: {
290            type: Array,
291            required: false
292        },
293        type: {
294            type: Array,
295            required: false
296        },
297        audience: {
298            type: Array,
299            required: true
300        },
301        location: {
302            type: Array,
303            required: false
304        },
305    },
306    filters: {
307        formatDate: function(date) {
308            return formatDate(date);
309        }
310    },
311    template: `
312        <related-events :size="size" :location="location" :audience="audience" :programme="programme" :type="type">
313            <template v-slot:default="listProps">
314                <span v-if="listProps.events.length > 0" class="block-title">Related Events</span>
315                <section v-if="listProps.events.length > 0" class="simple-calendar  ">
316                <ul class="dates">
317                    <li class="date" v-for="event in listProps.events">
318                    <a :href="'/events/view/' + event.id"><span class="title">{{event.title}}</span></a>
319                        <span class="details">{{event.startDate | formatDate}}<br />{{event.location}}</span>
320                        <a class="permalink" :href="'/events/view/' + event.id" title="" target="_self"></a>
321                    </li>
322                    <li v-if="listProps.events.length == 0" class="date">
323                        <span class="title">No Events</span>
324                        <span class="details">Click to search our available events</span>
325                        <a class="permalink" href="/events" title="" target="_self"></a>
326                    </li>
327                </ul>
328                </section>
329            </template>
330        </related-events>
331    `
332};
333
334const EventListingComponent = {
335    props: {
336        size: {
337            type: Number,
338            required: false,
339            default: 3
340        },
341        eid: {
342            type: Number,
343            required: false
344        }
345    },
346    filters: {
347        month: function(date) {
348            const months = [
349                "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
350            ];
351            return months[new Date(date).getUTCMonth()];
352        },
353        day: function(date) {
354            return new Date(date).getDate();
355        }
356    },
357    template: `
358        <related-events :size="size" :eid="eid">
359            <template v-slot:default="listProps"> 
360                <main class="list">
361                
362                    <article class="event" v-if="listProps.events.length > 0" v-for="event in listProps.events" :key="event.id">
363                
364                        <header>
365                
366                        <aside class="date">
367                        <div class="date-inner">
368                            <span class="day">{{event.startDate | day}}</span>
369                            <span class="month">{{event.startDate | month}}</span>
370                        </div>
371                        </aside>
372                
373                        <div class="meta">
374                        <div class="meta-inner">
375                        <a :href="'/events/view/' + event.id"> <span class="title" style="color: ##0254A7;">{{event.title}}</span> </a>
376                            <span class="type">{{event.type}}</span>
377                            <span class="location">{{event.location}}</span>
378                        </div>
379                        </div>
380                
381                        </header>
382                
383                        <main>
384                
385                        <div class="more-info">
386                        <span>More info</span>
387                        </div>
388                
389                        <div class="description" v-html="event.teaser" style="display: none;">
390                        </div>
391                
392                        </main>
393                
394                        <footer>
395                
396                        <div class="permalink">
397                        <!-- TODO -->
398                        <a class="button" :href="'/events/view/' + event.id">Book this event</a>
399                        </div>
400                
401                        </footer>
402                
403                    </article>
404                    <div v-if="listProps.events.length == 0">
405                        There are no related events.
406                    </div>
407                </main>
408            </template>
409        </related-events>    
410    `
411};
412
413const RelatedEventsComponent = {
414    props: {
415        size: {
416            type: Number,
417            required: false,
418            default: 3
419        },
420        themes: {
421            type: Array,
422            required: false
423        },
424        groups: {
425            type: Array,
426            required: false
427        },
428        excludeEventIds: {
429            type: Array,
430            required: false
431        },
432        programme: {
433            type: Array,
434            required: false
435        },
436        from: {
437            type: Date,
438            required: false
439        },
440        eid: {
441            type: Number,
442            required: false
443        },
444        featured: {
445            type: Boolean,
446            required: false
447        },
448        type: {
449            type: Array,
450            required: false
451        },
452        location: {
453            type: Array,
454            required: false
455        },
456        audience: {
457            type: Array,
458            required: false
459        },
460        category: {
461            type: Array,
462            required: true
463        }
464    },
465    data: function() {
466        return {
467            events: []
468        }
469    },
470    created: function() {
471        this.fetchEvents();
472    },
473    methods: {
474        async fetchEvents() {
475            $.ajaxSettings.traditional = true;
476            if (this.eid) {
477                await this.fetchEventsByEventId(this.eid);
478            } else {
479                await this.fetchEventsByFilter();
480            }
481            this.$emit('events-fetched');
482            this.addMobileInfoToggleEvent();
483        },
484        fetchEventsByFilter() {
485            const component = this;
486            const apiEndpoint = window.wbs.webApiHost + "/events/filter";
487            return $.get(apiEndpoint, this.getFilterRequestParam
487s())
488                .done(function (data) {
489                    component.events = data;
490                })
491                .fail(function (error) {
492                    console.log(error);
493                });
494        },
495        fetchEventsByEventId(eventId) {
496            const component = this;
497            const apiEndpoint = window.wbs.webApiHost + "/events/" + eventId + "/related";
498            return $.get(apiEndpoint, {size: this.size})
499                .done(function (data) {
500                    component.events = data;
501                })
502                .fail(function (error) {
503                    console.log(error);
504                });
505        },
506        getFilterRequestParams() {
507            const params = {};
508            params['size'] = this.size;
509            if (this.themes) {
510                params['themes'] = this.themes;
511            }
512            if (this.groups) {
513                params['group'] = this.groups;
514            }
515            if (this.programme) {
516                params['programme_level'] = this.programme;
517            }
518            if (this.excludeEventIds) {
519                params['exclude_events'] = this.excludeEventIds;
520            }
521            if (this.from) {
522                params['from'] = new Date(this.from).toISOString();
523            }
524            if (this.featured) {
525                params['featured'] = this.featured;
526            }
527            if (this.type) {
528                params['type'] = this.type;
529            }
530            if (this.location) {
531                params['location'] = this.location;
532            }
533            if (this.audience) {
534                params['audience'] = this.audience;
535            }
536            if (this.category) {
537                params['category'] = this.category;
538            }
539            return params;
540        },
541        addMobileInfoToggleEvent: function () {
542            $(document).ready(function () {
543              // init all event handlers see global.js
544              $('section.related-events > .container').each(fnEventsIndex);
545            });
546        }
547    },
548    template: `
549        <div>
550            <slot v-bind:events="events"/>
551        </div>
552    `
553};
554
555//TODO: need better naming for how these are styled..
556Vue.component('related-events', RelatedEventsComponent);
557Vue.component('related-events-horizontal', EventListingComponent);
558Vue.component('related-events-vertical', EventListingComponentVertical);
559Vue.component('related-events-showcase', EventListingComponentShowcase);
560Vue.component('related-events-theme', EventListingComponentByTheme);
561Vue.component('related-events-alumni', EventListingAlumni);

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.