318cba5aa1443c703a208df516f0d31f92f73b9d
[Evergreen.git] / Open-ILS / web / opac / skin / default / js / result_common.js
1
2 var recordsHandled = 0;
3 var recordsCache = [];
4 var lowHitCount = 4;
5
6 var resultFetchAllRecords = false;
7 var resultCompiledSearch = null;
8
9 /* set up the event handlers */
10 if( findCurrentPage() == MRESULT || findCurrentPage() == RRESULT ) {
11         G.evt.result.hitCountReceived.push(resultSetHitInfo);
12         G.evt.result.recordReceived.push(resultDisplayRecord, resultAddCopyCounts);
13         G.evt.result.copyCountsReceived.push(resultDisplayCopyCounts);
14         G.evt.result.allRecordsReceived.push(resultBuildCaches, resultDrawSubjects, 
15       resultDrawAuthors, resultDrawSeries, function(){unHideMe($('result_info_2'))});
16         attachEvt('result','lowHits',resultLowHits);
17         attachEvt('result','zeroHits',resultZeroHits);
18         attachEvt( "common", "locationUpdated", resultSBSubmit );
19         /* do this after we have ID's so the rank for mr pages will be correct */
20         attachEvt("result", "preCollectRecords", resultPaginate);
21 }
22
23 function resultSBSubmit(){searchBarSubmit();}
24
25 /* returns the last 'index' postion ocurring in this page */
26 function resultFinalPageIndex() {
27         if(getHitCount() < (getOffset() + getDisplayCount())) 
28                 return getHitCount() - 1;
29         return getOffset() + getDisplayCount() - 1;
30 }
31
32
33
34
35 /* generic search method */
36 function resultCollectSearchIds( type, method, handler ) {
37
38         var sort                = (getSort() == SORT_TYPE_REL) ? null : getSort(); 
39         var sortdir = (sort) ? ((getSortDir()) ? getSortDir() : SORT_DIR_ASC) : null;
40
41         var item_type;
42         var item_form;
43         var args = {};
44
45         if( type ) {
46                 var form = parseForm(getForm());
47                 item_type = form.item_type;
48                 item_form = form.item_form;
49
50         } else {
51                 item_type = (getItemType()) ? getItemType().split(/,/) : null;
52                 item_form = (getItemForm()) ? getItemForm().split(/,/) : null;
53         }
54
55         var limit = (resultFetchAllRecords) ? 1000 : getDisplayCount();
56
57         if( getOffset() > 0 ) {
58                 if( getHitCount() > 0 && (getOffset() + getDisplayCount()) > getHitCount() ) 
59                         limit = getHitCount() - getOffset();
60         }
61
62         args.org_unit = getLocation();
63         args.depth    = getDepth();
64         args.limit    = limit;
65         args.offset   = getOffset();
66         args.visibility_limit = 3000;
67     args.default_class = getStype();
68
69         if(sort) args.sort = sort;
70         if(sortdir) args.sort_dir = sortdir;
71         if(item_type) args.item_type    = item_type;
72         if(item_form) args.item_form    = item_form;
73     if(getAvail()) args.available = 1;
74
75
76         if(getAudience()) args.audience  = getAudience().split(/,/);
77         if(getLitForm()) args.lit_form  = getLitForm().split(/,/);
78         if(getLanguage()) args.language = getLanguage().split(/,/);
79
80         _debug('Search args: ' + js2JSON(args));
81         _debug('Raw query: ' + getTerm());
82
83         var req = new Request(method, args, getTerm(), 1);
84         req.callback(handler);
85         req.send();
86 }
87
88
89
90
91
92 /* set the search result info, number of hits, which results we're 
93         displaying, links to the next/prev pages, etc. */
94 function resultSetHitInfo() { 
95
96
97         /* tell the user where the results are coming from */
98         var baseorg = findOrgUnit(getLocation());
99         var depth = getDepth();
100         var mydepth = findOrgDepth(baseorg);
101         if( findOrgDepth(baseorg) != depth ) {
102                 var tmporg = baseorg;
103                 while( mydepth > depth ) {
104                         mydepth--;
105                         tmporg = findOrgUnit(tmporg.parent_ou());
106                 }
107                 unHideMe($('including_results_for'));
108                 $('including_results_location').appendChild(text(tmporg.name()));
109         }
110
111
112         try{searchTimer.stop()}catch(e){}
113
114         //if( findCurrentPage() == MRESULT ) {
115         if( findCurrentPage() == MRESULT || 
116
117                 (findCurrentPage() == RRESULT &&
118                         (
119                                 getRtype() == RTYPE_TITLE ||
120                                 getRtype() == RTYPE_AUTHOR ||
121                                 getRtype() == RTYPE_SUBJECT ||
122                                 getRtype() == RTYPE_SERIES ||
123                                 getRtype() == RTYPE_KEYWORD 
124                         )
125
126                 ) ) {
127
128                 if(getHitCount() <= lowHitCount && getTerm())
129                         runEvt('result', 'lowHits');
130         }
131
132         if(getHitCount() == 0) {
133                 runEvt('result', 'zeroHits');
134                 return;
135         }
136
137
138         var pages = getHitCount() / getDisplayCount();
139         if(pages % 1) pages = parseInt(pages) + 1;
140
141         
142
143         var cpage = (getOffset()/getDisplayCount()) + 1;
144
145         G.ui.result.current_page.appendChild(text(cpage));
146         G.ui.result.num_pages.appendChild(text(pages + ")")); /* the ) is dumb */
147
148         $('current_page2').appendChild(text(cpage));
149         $('num_pages2').appendChild(text(pages + ")")); /* the ) is dumb */
150
151         /* set the offsets */
152         var offsetEnd = getDisplayCount() + getOffset();  
153         if( getDisplayCount() > (getHitCount() - getOffset()))  
154                 offsetEnd = getHitCount();
155
156         G.ui.result.offset_end.appendChild(text(offsetEnd));
157         G.ui.result.offset_start.appendChild(text(getOffset() + 1));
158
159         $('offset_end2').appendChild(text(offsetEnd));
160         $('offset_start2').appendChild(text(getOffset() + 1));
161
162         G.ui.result.result_count.appendChild(text(getHitCount()));
163         unHideMe(G.ui.result.info);
164
165         $('result_count2').appendChild(text(getHitCount()));
166         unHideMe($('result_info_div2'));
167 }
168
169 function resultLowHits() {
170         showCanvas();
171         unHideMe($('result_low_hits'));
172         if(getHitCount() > 0)
173                 unHideMe($('result_low_hits_msg'));
174
175     alert(js2JSON(resultCompiledSearch));
176
177     var words = [];
178     for(var key in resultCompiledSearch.searches) 
179         words.push(resultCompiledSearch.searches[key].term);
180
181         var sreq = new Request(CHECK_SPELL, words.join(' '));
182         sreq.callback(resultSuggestSpelling);
183         sreq.send();
184
185     for(var key in resultCompiledSearch.searches) {
186                 var areq = new Request(FETCH_CROSSREF, key, resultCompiledSearch.searches[key].term);
187                 areq.callback(resultLowHitXRef);
188                 areq.send();
189         }
190
191         if( !(getForm() == null || getForm() == 'all' || getForm() == "") ) {
192                 var a = {};
193                 a[PARAM_FORM] = "all";
194                 $('low_hits_remove_format_link').setAttribute('href',buildOPACLink(a));
195                 unHideMe($('low_hits_remove_format'));
196         }
197
198         resultSuggestSearchClass();
199
200         if(getTerm()) resultExpandSearch(); /* advanced search */
201 }
202
203 var lowHitsXRefLink;
204 var lowHitsXRefLinkParent;
205 function resultLowHitXRef(r) {
206         if(!lowHitsXRefLink){
207                 lowHitsXRefLinkParent = $('low_hits_xref_link').parentNode;
208                 lowHitsXRefLink = lowHitsXRefLinkParent.removeChild($('low_hits_xref_link'));
209         }
210         var res = r.getResultObject();
211         var arr = res.from;
212         arr.concat(res.also);
213         if(arr && arr.length > 0) {
214                 unHideMe($('low_hits_cross_ref'));
215                 var word;
216                 var c = 0;
217                 while( word = arr.shift() ) {
218                         if(c++ > 20) break;
219                         var a = {};
220                         a[PARAM_TERM] = word;
221                         var template = lowHitsXRefLink.cloneNode(true);
222                         template.setAttribute('href',buildOPACLink(a));
223                         template.appendChild(text(word));
224                         lowHitsXRefLinkParent.appendChild(template);
225                         lowHitsXRefLinkParent.appendChild(text(' '));
226                 }
227         }
228 }
229
230 function resultZeroHits() {
231         showCanvas();
232         unHideMe($('result_low_hits'));
233         unHideMe($('result_zero_hits_msg'));
234         //if(getTerm()) resultExpandSearch(); /* advanced search */
235 }
236
237 function resultExpandSearch() {
238         var top = findOrgDepth(globalOrgTree);
239         if(getDepth() == top) return;
240         unHideMe($('low_hits_expand_range'));
241         var par = $('low_hits_expand_link').parentNode;
242         var template = par.removeChild($('low_hits_expand_link'));
243
244         var bottom = getDepth();
245         while( top < bottom ) {
246                 var a = {};
247                 a[PARAM_DEPTH] = top;
248                 var temp = template.cloneNode(true);
249                 temp.appendChild(text(findOrgTypeFromDepth(top).opac_label()))
250                 temp.setAttribute('href',buildOPACLink(a));
251                 par.appendChild(temp);
252                 top++;
253         }
254 }
255
256 function resultSuggestSearchClass() {
257         var stype = getStype();
258         if(stype == STYPE_KEYWORD) return;
259         var a = {}; var ref;
260         unHideMe($('low_hits_search_type'));
261         if(stype != STYPE_TITLE) {
262                 ref = $('low_hits_title_search');
263                 unHideMe(ref);
264                 a[PARAM_STYPE] = STYPE_TITLE;
265                 ref.setAttribute('href',buildOPACLink(a));
266         }
267         if(stype != STYPE_AUTHOR) {
268                 ref = $('low_hits_author_search');
269                 unHideMe(ref);
270                 a[PARAM_STYPE] = STYPE_AUTHOR;
271                 ref.setAttribute('href',buildOPACLink(a));
272         }
273         if(stype != STYPE_SUBJECT) {
274                 ref = $('low_hits_subject_search');
275                 unHideMe(ref);
276                 a[PARAM_STYPE] = STYPE_SUBJECT;
277                 ref.setAttribute('href',buildOPACLink(a));
278         }
279         if(stype != STYPE_KEYWORD) {
280                 ref = $('low_hits_keyword_search');
281                 unHideMe(ref);
282                 a[PARAM_STYPE] = STYPE_KEYWORD;
283                 ref.setAttribute('href',buildOPACLink(a));
284         }
285         if(stype != STYPE_SERIES) {
286                 ref = $('low_hits_series_search');
287                 unHideMe(ref);
288                 a[PARAM_STYPE] = STYPE_SERIES;
289                 ref.setAttribute('href',buildOPACLink(a));
290         }
291 }
292
293 function resultSuggestSpelling(r) {
294         var res = r.getResultObject();
295         var phrase = getTerm();
296         var words = phrase.split(/ /);
297
298         var newterm = "";
299
300         for( var w = 0; w < words.length; w++ ) {
301                 var word = words[w];
302                 var blob = grep(res, function(i){return (i.word == word);});
303                 if( blob ) blob = blob[0];
304                 else continue;
305                 if( blob.word == word ) {
306                         if( blob.suggestions && blob.suggestions[0] ) {
307                                 newterm += " " + blob.suggestions[0];
308                                 unHideMe($('did_you_mean'));
309                         } else {
310                                 newterm += " " + word;
311                         }
312                 }
313         }
314
315         var arg = {};
316         arg[PARAM_TERM] = newterm;
317         $('spell_check_link').setAttribute('href', buildOPACLink(arg));
318         $('spell_check_link').appendChild(text(newterm));
319 }
320
321
322 function resultPaginate() {
323         var o = getOffset();
324
325         if( !(  ((o) + getDisplayCount()) >= getHitCount()) ) {
326
327                 var args = {};
328                 args[PARAM_OFFSET]      = o + getDisplayCount();
329                 args[PARAM_SORT]                = SORT;
330                 args[PARAM_SORT_DIR] = SORT_DIR;
331                 args[PARAM_RLIST]               = new CGI().param(PARAM_RLIST);
332
333                 G.ui.result.next_link.setAttribute("href", buildOPACLink(args)); 
334                 addCSSClass(G.ui.result.next_link, config.css.result.nav_active);
335
336                 $('next_link2').setAttribute("href", buildOPACLink(args)); 
337                 addCSSClass($('next_link2'), config.css.result.nav_active);
338
339                 args[PARAM_OFFSET] = getHitCount() - (getHitCount() % getDisplayCount());
340
341                 /* when hit count is divisible by display count, we have to adjust */
342                 if( getHitCount() % getDisplayCount() == 0 ) 
343                         args[PARAM_OFFSET] -= getDisplayCount();
344
345                 G.ui.result.end_link.setAttribute("href", buildOPACLink(args)); 
346                 addCSSClass(G.ui.result.end_link, config.css.result.nav_active);
347
348                 $('end_link2').setAttribute("href", buildOPACLink(args)); 
349                 addCSSClass($('end_link2'), config.css.result.nav_active);
350         }
351
352         if( o > 0 ) {
353
354                 var args = {};
355                 args[PARAM_SORT]                = SORT;
356                 args[PARAM_SORT_DIR] = SORT_DIR;
357                 args[PARAM_RLIST]               = new CGI().param(PARAM_RLIST);
358
359                 args[PARAM_OFFSET] = o - getDisplayCount();
360                 G.ui.result.prev_link.setAttribute( "href", buildOPACLink(args)); 
361                 addCSSClass(G.ui.result.prev_link, config.css.result.nav_active);
362
363                 $('prev_link2').setAttribute( "href", buildOPACLink(args)); 
364                 addCSSClass($('prev_link2'), config.css.result.nav_active);
365
366                 args[PARAM_OFFSET] = 0;
367                 G.ui.result.home_link.setAttribute( "href", buildOPACLink(args)); 
368                 addCSSClass(G.ui.result.home_link, config.css.result.nav_active);
369
370                 $('search_home_link2').setAttribute( "href", buildOPACLink(args)); 
371                 addCSSClass($('search_home_link2'), config.css.result.nav_active);
372         }
373
374         if(getDisplayCount() < getHitCount()) {
375                 unHideMe($('start_end_links_span'));
376                 unHideMe($('start_end_links_span2'));
377    }
378
379         showCanvas();
380         try{searchTimer.stop()}catch(e){}
381 }
382
383 function buildunAPISpan (span, type, id) {
384         var cgi = new CGI();
385         var d = new Date();
386
387         addCSSClass(span,'unapi-id');
388
389         span.setAttribute(
390                 'title',
391                 'tag:' + cgi.server_name + ',' +
392                         d.getFullYear() +
393                         ':' + type + '/' + id
394         );
395 }
396
397 /* display the record info in the record display table 'pos' is the 
398                 zero based position the record should have in the display table */
399 function resultDisplayRecord(rec, pos, is_mr) {
400
401         if(rec == null) rec = new mvr(); /* so the page won't die if there was an error */
402         recordsHandled++;
403         recordsCache.push(rec);
404
405         var r = table.rows[pos + 1];
406
407         
408         try {
409                 var rank = parseFloat(ranks[pos + getOffset()]);
410                 rank            = parseInt( rank * 100 );
411                 var relspan = $n(r, "relevancy_span");
412                 relspan.appendChild(text(rank));
413                 unHideMe(relspan.parentNode);
414         } catch(e){ }
415
416         var pic = $n(r, config.names.result.item_jacket);
417         pic.setAttribute("src", buildISBNSrc(cleanISBN(rec.isbn())));
418
419         var title_link = $n(r, config.names.result.item_title);
420         var author_link = $n(r, config.names.result.item_author);
421
422         if( is_mr )  {
423                 var onlyrec = onlyrecord[ getOffset() + pos ];
424                 if(onlyrec) {
425                         buildunAPISpan($n(r,'unapi'), 'biblio-record_entry', onlyrec);
426
427                         var args = {};
428                         args.page = RDETAIL;
429                         args[PARAM_OFFSET] = 0;
430                         args[PARAM_RID] = onlyrec;
431                         args[PARAM_MRID] = rec.doc_id();
432                         pic.parentNode.setAttribute("href", buildOPACLink(args));
433                         title_link.setAttribute("href", buildOPACLink(args));
434                         title_link.appendChild(text(normalize(truncate(rec.title(), 65))));
435                         
436                 } else {
437                         buildunAPISpan($n(r,'unapi'), 'metabib-metarecord', rec.doc_id());
438
439                         buildTitleLink(rec, title_link); 
440                         var args = {};
441                         args.page = RRESULT;
442                         args[PARAM_OFFSET] = 0;
443                         args[PARAM_MRID] = rec.doc_id();
444                         pic.parentNode.setAttribute("href", buildOPACLink(args));
445                 }
446
447         } else {
448                 buildunAPISpan($n(r,'unapi'), 'biblio-record_entry', rec.doc_id());
449
450                 buildTitleDetailLink(rec, title_link); 
451                 var args = {};
452                 args.page = RDETAIL;
453                 args[PARAM_OFFSET] = 0;
454                 args[PARAM_RID] = rec.doc_id();
455                 pic.parentNode.setAttribute("href", buildOPACLink(args));
456
457                 unHideMe($n(r,'place_hold_span'));
458                 $n(r,'place_hold_link').setAttribute(
459                         'href','javascript:holdsDrawEditor({record:"'+rec.doc_id()+'",type:"T"});');
460         }
461
462         buildSearchLink(STYPE_AUTHOR, rec.author(), author_link);
463
464         if(! is_mr ) {
465         
466                 if(!isNull(rec.edition()))      {
467                         unHideMe( $n(r, "result_table_extra_span"));
468                         $n(r, "result_table_edition_span").appendChild( text( rec.edition()) );
469                 }
470                 if(!isNull(rec.pubdate())) {
471                         unHideMe( $n(r, "result_table_extra_span"));
472                         unHideMe($n(r, "result_table_pub_span"));
473                         $n(r, "result_table_pub_span").appendChild( text( rec.pubdate() ));
474                 }
475                 if(!isNull(rec.publisher()) ) {
476                         unHideMe( $n(r, "result_table_extra_span"));
477                         unHideMe($n(r, "result_table_pub_span"));
478                         $n(r, "result_table_pub_span").appendChild( text( " " + rec.publisher() ));
479                 }
480
481                 if(!isNull(rec.physical_description()) ) {
482                         unHideMe( $n(r, "result_table_extra_span"));
483                         var t = " " + rec.physical_description();
484                         //$n(r, "result_table_phys_span").appendChild( text(t.replace(/:.*/g,'')));
485                         $n(r, "result_table_phys_span").appendChild( text(t));
486                 }
487
488         }
489
490         resultBuildFormatIcons( r, rec, is_mr );
491
492         unHideMe(r);
493         
494         runEvt("result", "recordDrawn", rec.doc_id(), title_link);
495
496         /*
497         if(resultPageIsDone())  {
498                 runEvt('result', 'allRecordsReceived', recordsCache);
499         }
500         */
501 }
502
503 function _resultFindRec(id) {
504         for( var i = 0; i != recordsCache.length; i++ ) {
505                 var rec = recordsCache[i];
506                 if( rec && rec.doc_id() == id )
507                         return rec;
508         }
509         return null;
510 }
511
512
513 function resultBuildFormatIcons( row, rec, is_mr ) {
514
515         var ress = rec.types_of_resource();
516
517         for( var i in ress ) {
518
519                 var res = ress[i];
520                 if(!res) continue;
521
522                 var link = $n(row, res + "_link");
523                 link.title = res;
524                 var img = link.getElementsByTagName("img")[0];
525                 removeCSSClass( img, config.css.dim );
526
527                 var f = getForm();
528                 if( f != "all" ) {
529                         if( f == modsFormatToMARC(res) ) 
530                                 addCSSClass( img, "dim2_border");
531                 }
532
533                 var args = {};
534                 args[PARAM_OFFSET] = 0;
535
536                 if(is_mr) {
537                         args.page = RRESULT;
538                         args[PARAM_TFORM] = modsFormatToMARC(res);
539                         args[PARAM_MRID] = rec.doc_id();
540
541                 } else {
542                         args.page = RDETAIL
543                         args[PARAM_RID] = rec.doc_id();
544                 }
545
546                 link.setAttribute("href", buildOPACLink(args));
547
548         }
549 }
550
551
552 function resultPageIsDone(pos) {
553         return (recordsHandled == getDisplayCount() 
554                 || recordsHandled + getOffset() == getHitCount());
555 }
556
557 var resultCCHeaderApplied = false;
558
559 /* -------------------------------------------------------------------- */
560 /* dynamically add the copy count rows based on the org type 'countsrow' 
561         is the row into which we will add TD's to hold the copy counts 
562         This code generates copy count cells with an id of
563         'copy_count_cell_<depth>_<pagePosition>'  */
564 function resultAddCopyCounts(rec, pagePosition) {
565
566         var r = table.rows[pagePosition + 1];
567         var countsrow = $n(r, config.names.result.counts_row );
568         var ccell = $n(countsrow, config.names.result.count_cell);
569
570         var nodes = orgNodeTrail(findOrgUnit(getLocation()));
571         var node = nodes[0];
572         var type = findOrgType(node.ou_type());
573         ccell.id = "copy_count_cell_" + type.depth() + "_" + pagePosition;
574         ccell.title = type.opac_label();
575         //addCSSClass(ccell, config.css.result.cc_cell_even);
576
577         var lastcell = ccell;
578         var lastheadcell = null;
579
580         var cchead = null;
581         var ccheadcell = null;
582         if(!resultCCHeaderApplied) {
583                 ccrow = $('result_thead_row');
584                 ccheadcell =  ccrow.removeChild($n(ccrow, "result_thead_ccell"));
585                 var t = ccheadcell.cloneNode(true);
586                 lastheadcell = t;
587                 t.appendChild(text(type.opac_label()));
588                 ccrow.appendChild(t);
589                 resultCCHeaderApplied = true;
590         }
591
592         if(nodes[1]) {
593
594                 var x = 1;
595                 var d = findOrgDepth(nodes[1]);
596                 var d2 = findOrgDepth(nodes[nodes.length -1]);
597
598                 for( var i = d; i <= d2 ; i++ ) {
599         
600                         ccell = ccell.cloneNode(true);
601
602                         //if((i % 2)) removeCSSClass(ccell, "copy_count_cell_even");
603                         //else addCSSClass(ccell, "copy_count_cell_even");
604
605                         var node = nodes[x++];
606                         var type = findOrgType(node.ou_type());
607         
608                         ccell.id = "copy_count_cell_" + type.depth() + "_" + pagePosition;
609                         ccell.title = type.opac_label();
610                         countsrow.insertBefore(ccell, lastcell);
611                         lastcell = ccell;
612
613                         if(ccheadcell) {
614                                 var t = ccheadcell.cloneNode(true);
615                                 t.appendChild(text(type.opac_label()));
616                                 ccrow.insertBefore(t, lastheadcell);
617                                 lastheadcell = t;
618                         }
619                 }
620         }
621
622         unHideMe($("search_info_table"));
623 }
624
625 /* collect copy counts for a record using method 'methodName' */
626 function resultCollectCopyCounts(rec, pagePosition, methodName) {
627         if(rec == null || rec.doc_id() == null) return;
628         var req = new Request(methodName, getLocation(), rec.doc_id(), getForm() );
629         req.request.userdata = [ rec, pagePosition ];
630         req.callback(resultHandleCopyCounts);
631         req.send();
632 }
633
634 function resultHandleCopyCounts(r) {
635         runEvt('result', 'copyCountsReceived', r.userdata[0], r.userdata[1], r.getResultObject()); 
636 }
637
638
639 /* display the collected copy counts */
640 function resultDisplayCopyCounts(rec, pagePosition, copy_counts) {
641         if(copy_counts == null || rec == null) return;
642         var i = 0;
643         while(copy_counts[i] != null) {
644                 var cell = $("copy_count_cell_" + i +"_" + pagePosition);
645                 var cts = copy_counts[i];
646                 cell.appendChild(text(cts.available + " / " + cts.count));
647
648                 if(isXUL()) {
649                         /* here we style opac-invisible records for xul */
650
651                         if( cts.depth == 0 ) {
652                                 if(!cts.transcendant && !cts.unshadow) {
653                                         _debug("found an opac-shadowed record: " + rec.doc_id());
654                                         var row = cell.parentNode.parentNode.parentNode.parentNode.parentNode; 
655                                         if( cts.count == 0 ) 
656                                                 addCSSClass( row, 'no_copies' );
657                                         else 
658                                                 addCSSClass( row, 'shadowed' );
659                                 }
660                         }
661                 }
662                 i++;
663         }
664 }
665
666