1 dojo.require('openils.BibTemplate');
3 var recordsHandled = 0;
7 var googleBooksLink = true;
9 var resultFetchAllRecords = false;
10 var resultCompiledSearch = null;
12 /* set up the event handlers */
13 if( findCurrentPage() == MRESULT || findCurrentPage() == RRESULT ) {
14 G.evt.result.hitCountReceived.push(resultSetHitInfo);
15 G.evt.result.recordReceived.push(resultDisplayRecord, resultAddCopyCounts);
16 G.evt.result.copyCountsReceived.push(resultDisplayCopyCounts);
17 G.evt.result.allRecordsReceived.push( function(){unHideMe($('result_info_2'))}, fetchGoogleBooksLink, fetchChiliFreshReviews);
19 attachEvt('result','lowHits',resultLowHits);
20 attachEvt('result','zeroHits',resultZeroHits);
21 attachEvt( "common", "locationUpdated", resultSBSubmit );
22 /* do this after we have ID's so the rank for mr pages will be correct */
23 attachEvt("result", "preCollectRecords", resultPaginate);
26 function resultSBSubmit(){searchBarSubmit();}
28 /* returns the last 'index' postion ocurring in this page */
29 function resultFinalPageIndex() {
30 if(getHitCount() < (getOffset() + getDisplayCount()))
31 return getHitCount() - 1;
32 return getOffset() + getDisplayCount() - 1;
38 /* generic search method */
39 function resultCollectSearchIds( type, method, handler ) {
41 var sort = (getSort() == SORT_TYPE_REL) ? null : getSort();
42 var sortdir = (sort) ? ((getSortDir()) ? getSortDir() : SORT_DIR_ASC) : null;
49 var form = parseForm(getForm());
50 item_type = form.item_type;
51 item_form = form.item_form;
54 item_type = (getItemType()) ? getItemType().split(/,/) : null;
55 item_form = (getItemForm()) ? getItemForm().split(/,/) : null;
58 var limit = (resultFetchAllRecords) ? 1000 : getDisplayCount();
60 if( getOffset() > 0 ) {
61 if( getHitCount() > 0 && (getOffset() + getDisplayCount()) > getHitCount() )
62 limit = getHitCount() - getOffset();
65 var lasso = getLasso();
67 if (lasso) args.org_unit = -lasso;
68 else args.org_unit = getLocation();
70 args.depth = getDepth();
72 args.offset = getOffset();
73 args.visibility_limit = 3000;
74 args.default_class = getStype();
76 if(sort) args.sort = sort;
77 if(sortdir) args.sort_dir = sortdir;
78 if(item_type) args.item_type = item_type;
79 if(item_form) args.item_form = item_form;
80 if(getAvail()) args.available = 1;
83 if(getFacet()) args.facets = getFacet();
85 if(getAudience()) args.audience = getAudience().split(/,/);
86 if(getLitForm()) args.lit_form = getLitForm().split(/,/);
87 if(getLanguage()) args.language = getLanguage().split(/,/);
88 if(getBibLevel()) args.bib_level = getBibLevel().split(/,/);
89 if(getCopyLocs()) args.locations = getCopyLocs().split(/,/);
90 if(getPubdBefore()) args.before = getPubdBefore();
91 else if(getPubdAfter()) args.after = getPubdAfter();
92 else if(getPubdBetween()) args.between = getPubdBetween().split(/,/);
94 _debug('Search args: ' + js2JSON(args));
95 _debug('Raw query: ' + getTerm());
97 var atomfeed = "/opac/extras/opensearch/1.1/" + findOrgUnit(args.org_unit).shortname() + "/atom-full/" + getStype() + '?searchTerms=' + getTerm();
98 if (args.facets) { atomfeed += ' ' + args.facets; }
99 if (sort) { atomfeed += '&searchSort=' + sort; }
100 if (sortdir) { atomfeed += '&searchSortDir=' + sortdir; }
101 dojo.create('link', {"rel":"alternate", "href":atomfeed, "type":"application/atom+xml"}, dojo.query('head')[0]);
103 var req = new Request(method, args, getTerm(), 1);
104 req.callback(handler);
112 /* set the search result info, number of hits, which results we're
113 displaying, links to the next/prev pages, etc. */
114 function resultSetHitInfo() {
116 var lasso = getLasso();
118 /* tell the user where the results are coming from */
119 var baseorg = findOrgUnit(getLocation());
120 var depth = getDepth();
121 var mydepth = findOrgDepth(baseorg);
122 if( findOrgDepth(baseorg) != depth ) {
123 var tmporg = baseorg;
124 while( mydepth > depth ) {
126 tmporg = findOrgUnit(tmporg.parent_ou());
128 unHideMe($('including_results_for'));
129 $('including_results_location').appendChild(text(tmporg.name()));
134 try{searchTimer.stop()}catch(e){}
136 //if( findCurrentPage() == MRESULT ) {
137 if( findCurrentPage() == MRESULT ||
139 (findCurrentPage() == RRESULT &&
141 getRtype() == RTYPE_TITLE ||
142 getRtype() == RTYPE_AUTHOR ||
143 getRtype() == RTYPE_SUBJECT ||
144 getRtype() == RTYPE_SERIES ||
145 getRtype() == RTYPE_KEYWORD
150 if(getHitCount() <= lowHitCount && getTerm())
151 runEvt('result', 'lowHits');
154 if(getHitCount() == 0) {
155 runEvt('result', 'zeroHits');
160 var pages = getHitCount() / getDisplayCount();
161 if(pages % 1) pages = parseInt(pages) + 1;
165 var cpage = (getOffset()/getDisplayCount()) + 1;
167 G.ui.result.current_page.appendChild(text(cpage));
168 G.ui.result.num_pages.appendChild(text(pages + ")")); /* the ) is dumb */
170 $('current_page2').appendChild(text(cpage));
171 $('num_pages2').appendChild(text(pages + ")")); /* the ) is dumb */
173 /* set the offsets */
174 var offsetEnd = getDisplayCount() + getOffset();
175 if( getDisplayCount() > (getHitCount() - getOffset()))
176 offsetEnd = getHitCount();
178 G.ui.result.offset_end.appendChild(text(offsetEnd));
179 G.ui.result.offset_start.appendChild(text(getOffset() + 1));
181 $('offset_end2').appendChild(text(offsetEnd));
182 $('offset_start2').appendChild(text(getOffset() + 1));
184 G.ui.result.result_count.appendChild(text(getHitCount()));
185 unHideMe(G.ui.result.info);
187 $('result_count2').appendChild(text(getHitCount()));
188 unHideMe($('result_info_div2'));
191 function resultLowHits() {
193 unHideMe($('result_low_hits'));
194 if(getHitCount() > 0)
195 unHideMe($('result_low_hits_msg'));
198 for(var key in resultCompiledSearch.searches)
199 words.push(resultCompiledSearch.searches[key].term);
201 var sreq = new Request(CHECK_SPELL, words.join(' '));
202 sreq.callback(resultSuggestSpelling);
205 for(var key in resultCompiledSearch.searches) {
206 var areq = new Request(FETCH_CROSSREF, key, resultCompiledSearch.searches[key].term);
207 areq.callback(resultLowHitXRef);
211 if( !(getForm() == null || getForm() == 'all' || getForm() == "") ) {
213 a[PARAM_FORM] = "all";
214 $('low_hits_remove_format_link').setAttribute('href',buildOPACLink(a));
215 unHideMe($('low_hits_remove_format'));
218 resultSuggestSearchClass();
220 if(getTerm()) resultExpandSearch(); /* advanced search */
223 var lowHitsXRefSet = {};
225 var lowHitsXRefLinkParent;
226 function resultLowHitXRef(r) {
227 if(!lowHitsXRefLink){
228 lowHitsXRefLinkParent = $('low_hits_xref_link').parentNode;
229 lowHitsXRefLink = lowHitsXRefLinkParent.removeChild($('low_hits_xref_link'));
231 var res = r.getResultObject();
233 arr.concat(res.also);
234 if(arr && arr.length > 0) {
235 unHideMe($('low_hits_cross_ref'));
238 while( word = arr.shift() ) {
240 if (lowHitsXRefSet[word] == 1) continue;
241 lowHitsXRefSet[word] = 1;
245 a[PARAM_TERM] = word;
246 var template = lowHitsXRefLink.cloneNode(true);
247 template.setAttribute('href',buildOPACLink(a));
248 template.appendChild(text(word));
249 lowHitsXRefLinkParent.appendChild(template);
250 lowHitsXRefLinkParent.appendChild(text(' '));
255 function resultZeroHits() {
257 unHideMe($('result_low_hits'));
258 unHideMe($('result_zero_hits_msg'));
259 //if(getTerm()) resultExpandSearch(); /* advanced search */
262 function resultExpandSearch() {
263 var top = findOrgDepth(globalOrgTree);
264 if(getDepth() == top) return;
265 unHideMe($('low_hits_expand_range'));
266 var par = $('low_hits_expand_link').parentNode;
267 var template = par.removeChild($('low_hits_expand_link'));
269 var bottom = getDepth();
270 while( top < bottom ) {
272 a[PARAM_DEPTH] = top;
273 var temp = template.cloneNode(true);
274 temp.appendChild(text(findOrgTypeFromDepth(top).opac_label()))
275 temp.setAttribute('href',buildOPACLink(a));
276 par.appendChild(temp);
281 function resultSuggestSearchClass() {
282 var stype = getStype();
283 if(stype == STYPE_KEYWORD) return;
285 unHideMe($('low_hits_search_type'));
286 if(stype != STYPE_TITLE) {
287 ref = $('low_hits_title_search');
289 a[PARAM_STYPE] = STYPE_TITLE;
290 ref.setAttribute('href',buildOPACLink(a));
292 if(stype != STYPE_AUTHOR) {
293 ref = $('low_hits_author_search');
295 a[PARAM_STYPE] = STYPE_AUTHOR;
296 ref.setAttribute('href',buildOPACLink(a));
298 if(stype != STYPE_SUBJECT) {
299 ref = $('low_hits_subject_search');
301 a[PARAM_STYPE] = STYPE_SUBJECT;
302 ref.setAttribute('href',buildOPACLink(a));
304 if(stype != STYPE_KEYWORD) {
305 ref = $('low_hits_keyword_search');
307 a[PARAM_STYPE] = STYPE_KEYWORD;
308 ref.setAttribute('href',buildOPACLink(a));
310 if(stype != STYPE_SERIES) {
311 ref = $('low_hits_series_search');
313 a[PARAM_STYPE] = STYPE_SERIES;
314 ref.setAttribute('href',buildOPACLink(a));
318 function resultSuggestSpelling(r) {
319 var res = r.getResultObject();
320 var phrase = getTerm();
321 var words = phrase.split(/ /);
325 for( var w = 0; w < words.length; w++ ) {
327 var blob = grep(res, function(i){return (i.word == word);});
328 if( blob ) blob = blob[0];
330 if( blob.word == word ) {
331 if( blob.suggestions && blob.suggestions[0] ) {
332 newterm += " " + blob.suggestions[0];
333 unHideMe($('did_you_mean'));
335 newterm += " " + word;
341 arg[PARAM_TERM] = newterm;
342 $('spell_check_link').setAttribute('href', buildOPACLink(arg));
343 $('spell_check_link').appendChild(text(newterm));
347 function resultPaginate() {
350 if( !( ((o) + getDisplayCount()) >= getHitCount()) ) {
353 args[PARAM_OFFSET] = o + getDisplayCount();
354 args[PARAM_SORT] = SORT;
355 args[PARAM_SORT_DIR] = SORT_DIR;
356 args[PARAM_RLIST] = new CGI().param(PARAM_RLIST);
358 G.ui.result.next_link.setAttribute("href", buildOPACLink(args));
359 addCSSClass(G.ui.result.next_link, config.css.result.nav_active);
361 $('next_link2').setAttribute("href", buildOPACLink(args));
362 addCSSClass($('next_link2'), config.css.result.nav_active);
364 args[PARAM_OFFSET] = getHitCount() - (getHitCount() % getDisplayCount());
366 /* when hit count is divisible by display count, we have to adjust */
367 if( getHitCount() % getDisplayCount() == 0 )
368 args[PARAM_OFFSET] -= getDisplayCount();
371 G.ui.result.end_link.setAttribute("href", buildOPACLink(args));
372 addCSSClass(G.ui.result.end_link, config.css.result.nav_active);
374 $('end_link2').setAttribute("href", buildOPACLink(args));
375 addCSSClass($('end_link2'), config.css.result.nav_active);
382 args[PARAM_SORT] = SORT;
383 args[PARAM_SORT_DIR] = SORT_DIR;
384 args[PARAM_RLIST] = new CGI().param(PARAM_RLIST);
386 args[PARAM_OFFSET] = o - getDisplayCount();
387 G.ui.result.prev_link.setAttribute( "href", buildOPACLink(args));
388 addCSSClass(G.ui.result.prev_link, config.css.result.nav_active);
390 $('prev_link2').setAttribute( "href", buildOPACLink(args));
391 addCSSClass($('prev_link2'), config.css.result.nav_active);
393 args[PARAM_OFFSET] = 0;
394 G.ui.result.home_link.setAttribute( "href", buildOPACLink(args));
395 addCSSClass(G.ui.result.home_link, config.css.result.nav_active);
397 $('search_home_link2').setAttribute( "href", buildOPACLink(args));
398 addCSSClass($('search_home_link2'), config.css.result.nav_active);
401 if(getDisplayCount() < getHitCount()) {
402 unHideMe($('start_end_links_span'));
403 unHideMe($('start_end_links_span2'));
407 try{searchTimer.stop()}catch(e){}
410 function buildunAPISpan (span, type, id) {
414 addCSSClass(span,'unapi-id');
418 'tag:' + cgi.server_name + ',' +
420 ':' + type + '/' + id
424 function unhideGoogleBooksLink (data) {
425 for ( var i in data ) {
426 //if (data[i].preview == 'noview') continue;
428 var gbspan = $n(document.documentElement, 'googleBooksLink-' + i);
429 var gba = $n(gbspan, "googleBooks-link");
435 removeCSSClass( gbspan, 'hide_me' );
439 /* display the record info in the record display table 'pos' is the
440 zero based position the record should have in the display table */
441 function resultDisplayRecord(rec, pos, is_mr) {
443 fieldmapper.IDL.load(['mvr']);
444 if(rec == null) rec = new mvr(); /* so the page won't die if there was an error */
446 recordsCache.push(rec);
448 var r = table.rows[pos + 1];
449 var currentISBN = cleanISBN(rec.isbn());
451 if (googleBooksLink) {
452 var gbspan = $n(r, "googleBooksLink");
456 gbspan.getAttribute('name') + '-' + currentISBN
459 if (isbnList) isbnList += ', ';
460 isbnList += currentISBN;
464 if (currentISBN && chilifresh && chilifresh != '(none)') {
465 var cfrow = $n(r, "chilifreshReview");
467 removeCSSClass( cfrow, 'hide_me' );
469 var cflink = $n(r, "chilifreshReviewLink");
473 'isbn_' + currentISBN
476 var cfdiv = $n(r, "chilifreshReviewResult");
480 'chili_review_' + currentISBN
487 var rank = parseFloat(ranks[pos + getOffset()]);
488 rank = parseInt( rank * 100 );
489 var relspan = $n(r, "relevancy_span");
490 relspan.appendChild(text(rank));
491 unHideMe(relspan.parentNode);
495 var pic = $n(r, config.names.result.item_jacket);
496 pic.setAttribute("src", buildISBNSrc(currentISBN));
498 var title_link = $n(r, config.names.result.item_title);
499 var author_link = $n(r, config.names.result.item_author);
503 onlyrec = onlyrecord[ getOffset() + pos ];
505 buildunAPISpan($n(r,'unapi'), 'biblio-record_entry', onlyrec);
509 args[PARAM_OFFSET] = 0;
510 args[PARAM_RID] = onlyrec;
511 args[PARAM_MRID] = rec.doc_id();
512 pic.parentNode.setAttribute("href", buildOPACLink(args));
513 title_link.setAttribute("href", buildOPACLink(args));
514 title_link.appendChild(text(normalize(truncate(rec.title(), 65))));
517 buildunAPISpan($n(r,'unapi'), 'metabib-metarecord', rec.doc_id());
519 buildTitleLink(rec, title_link);
522 args[PARAM_OFFSET] = 0;
523 args[PARAM_MRID] = rec.doc_id();
524 pic.parentNode.setAttribute("href", buildOPACLink(args));
527 unHideMe($n(r,'place_hold_span'));
528 $n(r,'place_hold_link').setAttribute(
529 'href','javascript:holdsDrawEditor({record:"'+rec.doc_id()+'",type:"M"});');
532 onlyrec = rec.doc_id();
533 buildunAPISpan($n(r,'unapi'), 'biblio-record_entry', rec.doc_id());
535 buildTitleDetailLink(rec, title_link);
538 args[PARAM_OFFSET] = 0;
539 args[PARAM_RID] = rec.doc_id();
540 pic.parentNode.setAttribute("href", buildOPACLink(args));
542 unHideMe($n(r,'place_hold_span'));
543 $n(r,'place_hold_link').setAttribute(
544 'href','javascript:holdsDrawEditor({record:"'+rec.doc_id()+'",type:"T"});');
548 buildSearchLink(STYPE_AUTHOR, rec.author(), author_link);
552 if(!isNull(rec.edition())) {
553 unHideMe( $n(r, "result_table_extra_span"));
554 $n(r, "result_table_edition_span").appendChild( text( rec.edition()) );
556 if(!isNull(rec.pubdate())) {
557 unHideMe( $n(r, "result_table_extra_span"));
558 unHideMe($n(r, "result_table_pub_span"));
559 $n(r, "result_table_pub_span").appendChild( text( rec.pubdate() ));
561 if(!isNull(rec.publisher()) ) {
562 unHideMe( $n(r, "result_table_extra_span"));
563 unHideMe($n(r, "result_table_pub_span"));
564 $n(r, "result_table_pub_span").appendChild( text( " " + rec.publisher() ));
567 if(!isNull(rec.physical_description()) ) {
568 unHideMe( $n(r, "result_table_extra_span"));
569 var t = " " + rec.physical_description();
570 //$n(r, "result_table_phys_span").appendChild( text(t.replace(/:.*/g,'')));
571 $n(r, "result_table_phys_span").appendChild( text(t));
576 resultBuildFormatIcons( r, rec, is_mr );
582 org_unit : findOrgUnit(getLocation()).shortname(),
587 bt_params = dojo.mixin( bt_params, { record : onlyrec } );
589 bt_params = dojo.mixin( bt_params, { metarecord : onlyrec } );
592 if (findOrgType(findOrgUnit(getLocation()).ou_type()).can_have_vols())
593 unHideMe($n(r,'local_callnumber_list'));
595 new openils.BibTemplate( bt_params ).render();
599 runEvt("result", "recordDrawn", rec.doc_id(), title_link);
602 if(resultPageIsDone()) {
603 runEvt('result', 'allRecordsReceived', recordsCache);
608 function _resultFindRec(id) {
609 for( var i = 0; i != recordsCache.length; i++ ) {
610 var rec = recordsCache[i];
611 if( rec && rec.doc_id() == id )
618 function resultBuildFormatIcons( row, rec, is_mr ) {
620 var ress = rec.types_of_resource();
622 for( var i in ress ) {
627 var link = $n(row, res + "_link");
629 var img = link.getElementsByTagName("img")[0];
630 removeCSSClass( img, config.css.dim );
634 if( f == modsFormatToMARC(res) )
635 addCSSClass( img, "dim2_border");
639 args[PARAM_OFFSET] = 0;
643 args[PARAM_TFORM] = modsFormatToMARC(res);
644 args[PARAM_MRID] = rec.doc_id();
648 args[PARAM_RID] = rec.doc_id();
651 link.setAttribute("href", buildOPACLink(args));
656 function fetchGoogleBooksLink () {
657 if (isbnList && googleBooksLink) {
658 var scriptElement = document.createElement("script");
659 scriptElement.setAttribute("id", "jsonScript");
660 scriptElement.setAttribute("src",
661 "http://books.google.com/books?bibkeys=" +
662 escape(isbnList) + "&jscmd=viewapi&callback=unhideGoogleBooksLink");
663 scriptElement.setAttribute("type", "text/javascript");
664 // make the request to Google Book Search
665 document.documentElement.firstChild.appendChild(scriptElement);
669 function fetchChiliFreshReviews() {
670 if (chilifresh && chilifresh != '(none)') {
671 try { chili_init(); } catch(E) { console.log(E + '\n'); }
675 function resultPageIsDone(pos) {
677 return (recordsHandled == getDisplayCount()
678 || recordsHandled + getOffset() == getHitCount());
681 var resultCCHeaderApplied = false;
683 /* -------------------------------------------------------------------- */
684 /* dynamically add the copy count rows based on the org type 'countsrow'
685 is the row into which we will add TD's to hold the copy counts
686 This code generates copy count cells with an id of
687 'copy_count_cell_<depth>_<pagePosition>' */
688 function resultAddCopyCounts(rec, pagePosition) {
690 var r = table.rows[pagePosition + 1];
691 var countsrow = $n(r, config.names.result.counts_row );
692 var ccell = $n(countsrow, config.names.result.count_cell);
694 var nodes = orgNodeTrail(findOrgUnit(getLocation()));
696 var orgHiding = checkOrgHiding();
698 for (var i = 0; i < nodes.length; i++) {
699 if (orgHiding.depth == findOrgDepth(nodes[i])) {
705 var node = nodes[start_here];
706 var type = findOrgType(node.ou_type());
707 ccell.id = "copy_count_cell_" + type.depth() + "_" + pagePosition;
708 ccell.title = type.opac_label();
709 //addCSSClass(ccell, config.css.result.cc_cell_even);
711 var lastcell = ccell;
712 var lastheadcell = null;
715 var ccheadcell = null;
716 if(!resultCCHeaderApplied && !getLasso()) {
717 ccrow = $('result_thead_row');
718 ccheadcell = ccrow.removeChild($n(ccrow, "result_thead_ccell"));
719 var t = ccheadcell.cloneNode(true);
721 t.appendChild(text(type.opac_label()));
722 ccrow.appendChild(t);
723 resultCCHeaderApplied = true;
726 if(nodes[start_here+1]) {
728 var x = start_here+1;
729 var d = findOrgDepth(nodes[start_here+1]);
730 var d2 = findOrgDepth(nodes[nodes.length -1]);
732 for( var i = d; i <= d2 ; i++ ) {
734 ccell = ccell.cloneNode(true);
736 //if((i % 2)) removeCSSClass(ccell, "copy_count_cell_even");
737 //else addCSSClass(ccell, "copy_count_cell_even");
739 var node = nodes[x++];
740 var type = findOrgType(node.ou_type());
742 ccell.id = "copy_count_cell_" + type.depth() + "_" + pagePosition;
743 ccell.title = type.opac_label();
744 countsrow.insertBefore(ccell, lastcell);
748 var t = ccheadcell.cloneNode(true);
749 t.appendChild(text(type.opac_label()));
750 ccrow.insertBefore(t, lastheadcell);
756 unHideMe($("search_info_table"));
759 /* collect copy counts for a record using method 'methodName' */
760 function resultCollectCopyCounts(rec, pagePosition, methodName) {
761 if(rec == null || rec.doc_id() == null) return;
763 var loc = getLasso();
765 else loc= getLocation();
767 var req = new Request(methodName, loc, rec.doc_id(), getForm() );
768 req.request.userdata = [ rec, pagePosition ];
769 req.callback(resultHandleCopyCounts);
773 function resultHandleCopyCounts(r) {
774 runEvt('result', 'copyCountsReceived', r.userdata[0], r.userdata[1], r.getResultObject());
778 /* XXX Needs to understand Lasso copy counts... */
779 /* display the collected copy counts */
780 function resultDisplayCopyCounts(rec, pagePosition, copy_counts) {
781 if(copy_counts == null || rec == null) return;
784 var copy_counts_lasso = {
790 org_unit : getLasso()
793 for (var i in copy_counts) {
794 copy_counts_lasso.transcendant = copy_counts[i].transcendant;
795 copy_counts_lasso.count += parseInt(copy_counts[i].count);
796 copy_counts_lasso.unshadow += parseInt(copy_counts[i].unshadow);
797 copy_counts_lasso.available += parseInt(copy_counts[i].available);
800 copy_counts = [ copy_counts_lasso ];
804 while(copy_counts[i] != null) {
805 var cell = $("copy_count_cell_" + i +"_" + pagePosition);
807 var cts = copy_counts[i];
808 cell.appendChild(text(cts.available + " / " + cts.count));
811 /* here we style opac-invisible records for xul */
813 if( cts.depth == 0 ) {
814 if(cts.transcendant == null && cts.unshadow == 0) {
815 _debug("found an opac-shadowed record: " + rec.doc_id());
816 var row = cell.parentNode.parentNode.parentNode.parentNode.parentNode;
818 addCSSClass( row, 'no_copies' );
820 addCSSClass( row, 'shadowed' );