remove hard-coded URLs for JS libraries
[sitka/overdrive-evergreen-opac.git] / src / overdrive.coffee
CommitLineData
8ebc3fff
SC
1# TODO memory leaks
2#
3# TODO Author/Title links could specify ebook filter
4#
5# TODO If logged in, could bypass place hold page and use action dialogue directly
6#
7# TODO Simple, cheap two-way data binding:
8# We could publish a partial request object as an abstract way of making
9# an API request, ie, od.$.triggerHandler 'od.metadata', id: id
10# Subscribe to same event to receive reply object, ie,
11# od.$.on 'od.metadata', (ev, reply) -> # do something with reply
12
8ebc3fff
SC
13require [
14 'jquery'
15 'lodash'
16 'cookies'
17 'od_api'
2221a640 18 'od_config'
8ebc3fff
SC
19 'od_pages_opac'
20 'od_pages_myopac'
21 'od_action'
2221a640 22], ($, _, C, od, config) ->
8ebc3fff
SC
23
24 # Indicate the logged in status; the value is determined within document
25 # ready handler.
26 logged_in = false
27
28 # Various debugging functions; not used in production
29 log_page = -> console.log window.location.pathname
30 notify = (what) -> console.log "#{what} is in progress"
31 failed = (what) -> console.log "#{what} failed"
32 reload_page = -> window.location.reload true
33 replace_page = (href) -> window.location.replace href
34
35 # Query a search string of the current page for the value or existence of a
36 # property
37 search_params = (p) ->
895eed6f
SC
38 # Convert for example, '?a=1&b=2' to { a:1, b:2 }.
39 # Also, convert any pluses to spaces.
8ebc3fff 40 o =
cef83683 41 if xs = (decodeURIComponent window.location.search)?.split('?')?[1]?.split(/&|;/)
895eed6f 42 _.zipObject( x.replace(/\+/g, ' ').split('=') for x in xs )
8ebc3fff
SC
43 else
44 {}
45 # Return either the value of a specific property, whether the property
46 # exists, or the whole object
47 if arguments.length is 1 then o[p] or o.hasOwnProperty p else o
48
49
50 # Return an abbreviation of the pathname of the current page,
51 # eg, if window.location.pathname equals 'eg/opac/record' or
52 # 'eg/opac/record/123', then return 'record', otherwise return ''
53 page_name = ->
54 xs = window.location.pathname.match /eg\/opac\/(.+)/
55 if xs then xs[1].replace /\/\d+/, '' else ''
56
8ebc3fff
SC
57 # Routing table: map an URL pattern to a handler that will perform actions
58 # or modify areas on the screen.
59 routes =
60
61 # Scan through property names and execute the function value if the
62 # name pattern matches against the window.location.pathname, eg,
63 # routes.handle(). handle() does not try to execute itself. Returns a
64 # list of results for each handler that was executed. A result is
65 # undefined if no subscriptions to an OD service was needed.
66 handle: (p = window.location.pathname) ->
c6c9faec 67 for own n, v of @ when n isnt 'handle'
8ebc3fff
SC
68 v() if (new RegExp n).test p
69
70 'eg\/opac': ->
71
72 # Add a new dashboard to show total counts of e-items.
73 # Start the dashboard w/ zero counts.
74 $dash = $('#dash_wrapper')._dashboard()
75
76 od.$.on
77
78 # Set the dashboard counts to summarize the patron's account
79 'od.interests': (ev, x) -> $dash._dashboard
80 ncheckouts: x.nCheckouts
81 nholds: x.nHolds
82 nholdsready: x.nHoldsReady
83
84 # Decrement the dashboard counts because an item has been
85 # removed from the holds or checkout list
86 'od.hold.delete': -> $dash._dashboard nholds: -1
87 'od.checkout.delete': -> $dash._dashboard ncheckouts: -1
88
89 # Log out of EG if we are logged in and if an OD patron access
90 # token seems to have expired
91 'od.logout': (ev, x) ->
92 if x is 'od'
d0717859 93 window.location.replace '/eg/opac/logout' if logged_in
8ebc3fff
SC
94
95 'opac\/myopac': ( this_page = page_name() ) ->
96
97 # Add a new tab for e-items to the current page if it is showing a
98 # system of tabs
99 $('#acct_holds_tabs, #acct_checked_tabs')._etabs this_page, search_params 'e_items'
100 # Relabel history tabs if they are showing on current page
101 $('#tab_holds_history, #tab_circs_history')._tab_history()
102 return
103
104 'opac\/home': ->
105
106 # Signal that EG may have logged out
107 od.$.triggerHandler 'od.logout', 'eg' unless logged_in
108
109 'opac\/login': ->
110
111 # On submitting the login form, we initiate the login sequence with the
112 # username/password from the login form
113 $('form', '#login-form-box').one 'submit', ->
114 od.login
115 username: $('[name=username]').val()
116 password: $('[name=password]').val()
117
118
119 # TODO In order to perform OD login after EG login, we could
120 # automatically get the prefs page and scrape the barcode value,
121 # but in the general case, we would also need the password value
122 # that was previously submitted on the login page.
123
124 # We could scrape the barcode value from the prefs page by having it
125 # being parsed into DOM within an iframe (using an inscrutable sequence
126 # of DOM traversal). Unfortunately, it will reload script tags and
127 # make XHR calls unnecessarily.
128 #
129 # The alternative is to GET the prefs page and parse the HTML string
130 # directly for the barcode value, but admittedly, we need to use an
131 # inscrutable regex pattern.
132
133 # On the myopac account summary area, add links to hold list and
134 # checkout list of e-items
135 'myopac\/main': ( $table = $('.acct_sum_table') ) ->
136 return unless $table.length
137
c34f0459 138 $table._account_summary()
8ebc3fff
SC
139
140 od.$.on 'od.interests', (ev, x) ->
141
142 $table._account_summary
8ebc3fff
SC
143 n_checkouts: x.nCheckouts
144 n_holds: x.nHolds
145 n_ready: x.nHoldsReady
146
147 # Each time the patron's preferences page is shown, publish values that
148 # might have changed because the patron has edited them. Example
149 # scenario: patron changes email address on the prefs page and then
150 # places hold, expecting the place hold form to default to the newer
151 # address.
152 'myopac\/prefs': ->
153 $tr = $('#myopac_summary_tbody > tr')
154 em = $tr.eq(6).find('td').eq(1).text()
155 bc = $tr.eq(7).find('td').eq(1).text()
156 hl = $tr.eq(8).find('td').eq(1).text()
157 od.$.triggerHandler 'od.prefs', email_address:em, barcode:bc, home_library:hl
158
159 'opac\/results': (interested = {}) ->
160
161 # List of hrefs which correspond to Overdrive e-items
162 # TODO this list is duplicated in module od_pages_opac
5783f765
JD
163 hrefs = []
164 hrefs.push('a[href*="' + productbaseURL + '"]') for productbaseURL in config.productbaseURLs
8ebc3fff
SC
165
166 # Prepare each row of the results table which has an embedded
167 # Overdrive product ID. A list of Overdrive product IDs is
168 # returned, which can be used to find each row directly.
169 ids = $(hrefs.join ',').closest('.result_table_row')._results()
170 return if ids?.length is 0
171
172 od.$.on
173
174 # When patron holds and checkouts become available...
175 'od.interests': (ev, x) ->
176
177 # Initiate request for each Overdrive product ID
178 for id in ids
8ebc3fff 179 od.apiAvailability id: id
e07737bf
SC
180 # If the user isn't logged in, the format list will
181 # always be found from Metadata
182 od.apiMetadata id: id unless logged_in
8ebc3fff
SC
183
184 # Cache the relationship between product IDs and patron
185 # holds and checkouts, ie, has the patron placed a hold on
186 # an ID or checked out an ID?
187 interested = x.byID
188
189 # Fill in format values when they become available
190 'od.metadata': (ev, x) -> $("##{x.id}")._results_meta x
191
192 # Fill in availability values when they become available
193 'od.availability': (ev, x) ->
194 $("##{x.id}")
195 ._results_avail x
196 ._replace_place_hold_link x, interested[x.id]?.type
197
e07737bf
SC
198 # Irregular logic warning: If the user is logged in, the
199 # format list might be found from Availability if the item
200 # is available for checkout, otherwise it will be found
201 # from Metadata.
202 if logged_in
203 if x.available then $("##{x.id}")._results_meta x else od.apiMetadata id: x.id
204
205
8ebc3fff
SC
206 'opac\/record': (interested = {}) ->
207
208 # Add an empty container of format and availability values
209 return unless id = $('div.rdetail_uris')._record()
210
211 od.$.on
212
213 # When patron holds and checkouts become available...
214 'od.interests': (ev, x) ->
215
216 # Initiate request for metadata and availability values when
e07737bf 217 od.apiMetadata id: id unless logged_in
8ebc3fff
SC
218 od.apiAvailability id: id
219
220 # Has the user placed a hold on an ID or checked out an ID?
221 interested = x.byID
222
223 # Fill in format values when they become available
224 'od.metadata': (ev, x) -> $("##{x.id}")._record_meta x
225
226 # Fill in availability values when they become available
227 'od.availability': (ev, x) ->
228 $("##{x.id}")._record_avail x
229 $('#rdetail_actions_div')._replace_place_hold_link x, interested[x.id]?.type
230
e07737bf
SC
231 if logged_in
232 if x.available then $("##{x.id}")._record_meta x else od.apiMetadata id: x.id
233
8ebc3fff
SC
234 # For the case where the patron is trying to place a hold if not logged
235 # in, there is a loophole in the Availability API; if using a patron
236 # access token and patron already has a hold on it, avail.actions.hold
237 # will still be present, falsely indicating that patron may place a
238 # hold, which will lead to a server error. The same situation will
239 # occur if patron has already checked out. It seems the OD server does
240 # not check the status of the item wrt the patron before generating the
241 # server response.
242 #
243 # To fix the problem, we will check if avail.id is already held or
244 # checked out, and if so, then go back history two pages so that
245 # original result list or record page is shown, with the proper action
246 # link generated when the page reloads.
247
248 # Replace the original Place Hold form with a table row to show
249 # available actions, either 'Check out' or 'Place hold', depending on
250 # whether the item is available or not, respectively.
251 #
252 # The following page handler does not replace the place_hold page, but
253 # is meant to be called by the place hold link.
254 # If the place_hold page is encountered, the handler will return
255 # without doing anything, because no id is passed in.
256 'opac\/place_hold': (id, interested = {}) ->
257 return unless id
258
259 $('#myopac_holds_div')._replace_title 'Place E-Item on Hold'
260 $('#myopac_checked_div')._replace_title 'Check out E-Item'
261
262 $('#holds_main, #checked_main, .warning_box').remove()
263
264 $div = $('<div id="#holds_main">')
265 ._holds_main() # Add an empty table
266 ._holdings_row id # Add an empty row
267 .appendTo $('#myopac_holds_div, #myopac_checked_div')
268
07895f87 269 # Fill in empty row when data becomes available
8ebc3fff
SC
270 od.$.on
271
272 'od.interests': (ev, x) ->
e07737bf 273
8ebc3fff
SC
274 # Has the user placed a hold on an ID or checked out an ID?
275 interested = x.byID
276
e07737bf
SC
277 $.when(
278 od.apiMetadata id: id
279 od.apiAvailability id: id
280 )
281 .then (x, y) ->
282
283 # Check if this patron has checked out or placed a hold on
284 # avail.id and if so, then go back two pages to the result list
285 # or record page. The page being skipped over is the login page
286 # that comes up because the user needs to log in before being
287 # able to see the place hold page. Thus, the logic is only
288 # relevant if the user has not logged in before trying to place
289 # a hold.
290 if interested[y.id]?.type
291 window.history.go -2
292
293 else
294 $("##{x.id}")._row_meta(x, 'thumbnail', 'title', 'author')
295 $("##{x.id}")._row_meta (if y.available then y else x), 'formats'
296
297 $("##{y.id}")
298 # Fill in availability column
299 ._holdings_row_avail y
300 # Auto-focus on place hold or checkout button
301 .find '.opac-button.hold, .opac-button.checkout'
302 .focus()
303 .end()
8ebc3fff
SC
304
305 'myopac\/holds': ->
306
307 # If we arrive here with an interested ID value, we are intending
308 # to place a hold on an e-item
309 if id = search_params 'interested'
310 return routes['opac\/place_hold'] id
311
312 # Rewrite the text in the warning box to distinguish physical items from e-items
313 unless search_params 'e_items'
314 $('.warning_box').text $('.warning_box').text().replace ' holds', ' physical holds'
315 return
316
317 return unless ($holds_div = $('#myopac_holds_div')).length
318
319 $holds_div._replace_title 'Current E-Items on Hold'
320
321 $('#holds_main, .warning_box').remove()
322
323 # Replace with an empty table for a list of holds for e-items
324 $div = $('<div id="#holds_main">')
325 ._holds_main()
326 .appendTo $holds_div
327
328 # Subscribe to notifications of relevant data objects
329 od.$.on
330
331 'od.interests': (ev, x) ->
332
333 # Focus on patron's hold interests, and if the search
334 # parameters say so, further focus on holds of items that
335 # are ready to be checked out
336 holds = x?.ofHolds
337 holds = _.filter(holds, (x) -> x.actions.checkout) if search_params 'available'
338
339 # Add an empty list of holds
340 ids = $div._holds_rows holds
341
342 # Try to get the metadata and availability values for
343 # this hold
344 for id in ids
345 od.apiMetadata id: id
346 od.apiAvailability id: id
347
348 # Add metadata values to a hold
349 'od.metadata': (ev, x) -> $("##{x.id}")._row_meta x, 'thumbnail', 'title', 'author', 'formats'
350 # Add availability values to a hold
351 'od.availability': (ev, x) -> $("##{x.id}")._holds_row_avail x
352
9669700c
SC
353 'od.hold.update': (ev, x) ->
354 x = x.holds[0]
355 $("##{x.reserveId}")._holds_row x
356
357 'od.hold.delete': (ev, id) -> $("##{id}").remove()
8ebc3fff
SC
358
359 'myopac\/circs': ->
360
361 # If we arrive here with an interested ID value, we are intending
362 # to checking out an e-item
363 if id = search_params 'interested'
364 return routes['opac\/place_hold'] id
365
3865fb23
SC
366 # If there is an error message embedded in the search parameters
367 # that resulted from an attempt at downloading content, show it in
368 # a floating notification box.
639929ef
SC
369 if code = search_params 'read_error'
370 $('<div>')._notify 'Error message', """
371 <p>Could not get content</p>
372 <div>Error code: #{code}</div>
373 <div>Reserve ID: #{search_params 'reserveid'}</div>
374 """
375 else if code = search_params 'ErrorCode'
3865fb23
SC
376 $('<div>')._notify 'Error message', """
377 <p>#{search_params 'ErrorDescription'}</p>
639929ef
SC
378 <div>Error code: #{code}</div>
379 <div>Details: #{search_params 'ErrorDetails'}</div>
3865fb23
SC
380 """
381
8ebc3fff
SC
382 # Rewrite the text in the warning box to distinguish physical items from e-items
383 unless search_params 'e_items'
384 $('.warning_box').text $('.warning_box').text().replace ' items', ' physical items'
385 return
386
387 return unless ($checked_div = $('#myopac_checked_div')).length
388
389 $checked_div._replace_title 'Current E-Items Checked Out'
390
391 $('#checked_main, .warning_box').remove()
392
393 # Build an empty table for a list of checkouts of e-items
394 $div = $('<div id="#checked_main">')
395 ._checkouts_main()
396 .appendTo $checked_div
397
398 # Subscribe to notifications of relevant data objects
399 od.$.on
400
401 'od.interests': (ev, x) ->
402
403 # Fill in checkout list
404 ids = $div._checkouts_rows x?.ofCheckouts
405
406 # Try to get metadata values for these checkouts
407 od.apiMetadata id: id for id in ids
408
409 # Add metadata values to a checkout
410 'od.metadata': (ev, x) -> $("##{x.id}")._row_meta x, 'thumbnail', 'title', 'author'
411
9669700c
SC
412 'od.checkout.update': (ev, x) ->
413 x = x.checkouts[0]
414 $("##{x.reserveId}")._row_checkout x
415
416 'od.checkout.delete': (ev, id) -> $("##{id}").remove()
8ebc3fff
SC
417
418 # Begin sequence after the DOM is ready...
419 $ ->
420
421 return if window.IAMXUL # Comment out to run inside XUL staff client
422
2221a640
SC
423 # Do not implement if hostname is blacklisted
424 return if config.blacklisted()
425
8ebc3fff
SC
426 # We are logged into EG if indicated by a cookie or if running
427 # inside XUL staff client.
428 logged_in = Boolean C('eg_loggedin') or window.IAMXUL
429
430 # Dispatch handlers corresponding to the current location
431 # and return immediately if none of them require OD services
432 return if _.every routes.handle() , (r) -> r is undefined
433
434 # Try to get library account info
9669700c 435 od.apiLibraryInfo()
8ebc3fff
SC
436
437 # If we are logged in, we 'compute' the patron's interests in product
438 # IDs; otherwise, we set patron interests to an empty object.
439 .then ->
440
441 # If logged in, ensure that we have a patron access token from OD
442 # before getting patron's 'interests'
443 if logged_in
444 od.login().then od.apiInterestsGet
445
446 # Otherwise, return no interests
447 # TODO should do the following in od_api module
448 else
449 interests = byID: {}
450 od.$.triggerHandler 'od.interests', interests
451 return interests
452
453 return
454 return