-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbase.js
223 lines (187 loc) · 6.43 KB
/
base.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import Brand from './components/brand';
import Site from './components/site'
export default class Base {
/**
* To be overwritten by child classes.
* Builds/initialises a model object, using the given data object.
*/
buildModelObject = function (model) {
return model
}
/**
* Takes a model property object (e.g. site, cluster, tag etc.) and adds it to the
* {key}Options and {key}s arrays if not already there, to use for dropdowns.
* @param {string} key - The JSON key to look at (e.g. category)
* @param {Object} property - The property to add (e.g. an event site) with id and name properties
* @param {string} [legacyKey=null] - An alternative legacy key, if not the key with an 's' (e.g. 'categories')
*/
buildIdNameOption = function (key, property, legacyKey = null) {
// check the property is set
if (property == null) return
// if legacy key provided use it, otherwise pluralise key
legacyKey = legacyKey ?? key + 's'
let options = this[key + 'Options']
let optionIds = options.map(o => o.id)
if (!optionIds.includes(property.id)) {
// populate id and name options array
this[key + 'Options'].push({
id: property.id,
name: property.name,
})
// populate legacy key with just name options
this[legacyKey].push(property.name)
}
}
/**
* Returns true if the given model should be visible, based on the filters.
*/
filterModel = function (model) {
return true
}
/**
* Returns true if we should be filtering models.
*/
filterModelsEnabled = function () {
return true
}
/**
* Filters models based on the UI's filters.
*/
filterModels = function () {
this.loading = true
if (this.filterModelsEnabled()) {
// first update the searchQuery so we don't do it for every model in this.filterModel() - replace date separators with spaces
let q = this.search || ''
this.searchQuery = q.length ? q.replace(/[\s\/\-\.]+/gi, ' ').toLowerCase() : null
this.models = this.modelsAll.filter(model => this.filterModel(model))
}
this.$dispatch('models-updated') // always do this!
this.loading = false
}
/**
* Gets a filter value either as an array or null
* This also ignores 0 and '0' as values and returns null in place (for resetting selects)
*/
filterValue = function (key) {
let parent = arguments.length > 1 ? arguments[1] : this
let value = parent[key]
let result = null
if (Array.isArray(value)) {
// it's an array - filter out empty values
value = value.filter(v => v !== 0 && v !== '0' && v !== null && v !== '')
// return the string cast array result if it's got length otherwise null
result = value.length ? value.map(v => '' + v) : null
} else if (value === null) {
// always return null
result = null
} else {
// return the value as an array if it has length and isn't empty otherwise null
value = '' + value
result = value.length && value !== '0' ? [value] : null
}
// if the result is null then reset the key reactively
if (result == null) parent[key] = null
// return the result
return result
}
/**
* Initialises the JSON feed asynchronously.
*/
async init() {
this.$watch(this.filterKeys.join(', '), () => this.filterModels())
try {
// start by checking for IE - this throw if there is an issue
this.runIECheck();
// if not IE then run the JSON fetch
let response = await CS.fetchJSON(this.resourceModule, Object.assign({}, this.options))
// set the default image to the brand emblem
if (response.hasOwnProperty('brand')) {
this.emblemImage = response.brand.emblem ? response.brand.emblem.px_200 : null
this.brand = new Brand(response.brand)
}
let key = {
bookings: 'booked_resources',
calendar: 'events',
smallgroups: 'groups',
}[this.resourceModule]
if (response.hasOwnProperty('configuration')) {
// new style configuration data
this.configuration = this.buildConfiguration(response.configuration)
response[key].forEach(model => {
this.modelsAll.push(this.buildModelObject(model))
})
}
/**
* For efficiency, we send over the sites once on page 1
*/
if (response.hasOwnProperty('sites')) {
response.sites.forEach(site => this.sites.push(new Site(site)))
}
this.postInit(response)
this.filterModels()
// go and fetch the rest of the paginated data
if (response.pagination.num_results <= response.pagination.per_page) return
const totalPages = Math.ceil(response.pagination.num_results / response.pagination.per_page)
this.$nextTick(() => {
let promises = []
for (let page = 2; page <= totalPages; page++) {
let options = Object.assign({}, this.options)
options.page = page
promises.push(CS.fetchJSON(this.resourceModule, options)
.then(response => response[key].forEach(model => {
this.modelsAll.push(this.buildModelObject(model))
}))
.catch(error => {
console.log(error)
this.error = error
this.errorMessage = error.message
}))
}
Promise.allSettled(promises).then(() => this.filterModels())
})
} catch (error) {
// something went wrong - set the error and message
this.error = error
// set up the error type as http
this.errorType = error.type ?? 'http'
// the load failed but it's finished so set to false
this.loading = false
console.log(error)
}
}
/**
* An empty function that runs at the end of the init() method for each module.
* Overload if you need to run code at the end of initialisation.
*/
postInit = function (response) {}
/**
* Runs on init to check for IE - if it is IE then we throw an error
*/
runIECheck = function() {
if (window.document.documentMode) {
throw {
message: 'Internet Explorer is no longer supported',
type: 'ie',
}
}
}
constructor() {
// set the locale before init(), so it runs before we generate days of week
dayjs.locale(CS.locale)
// Configuration & Options
this.brand = {} // the brand for this configuration
this.configuration = {} // the embed configuration
this.filterKeys = [] // the names of the filters for this feed
this.options = {} // options for fetching json
this.resourceModule = '' // the module we're in
this.loading = true // boolean to control loading pulses and spinners
// Model Data
this.models = [] // our filtered model objects
this.modelsAll = [] // all of our model objects
this.search = null // search terms
this.searchQuery = null // search query string
// Error
this.error = null
this.errorType = null
}
}