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.