SFX customization
authorgliu <gliu@6d9bc8c9-1ec2-4278-b937-99fde70a366f>
Mon, 14 Dec 2009 02:19:33 +0000 (02:19 +0000)
committergliu <gliu@6d9bc8c9-1ec2-4278-b937-99fde70a366f>
Mon, 14 Dec 2009 02:19:33 +0000 (02:19 +0000)
git-svn-id: svn://svn.open-ils.org/ILS-Contrib/conifer/trunk@746 6d9bc8c9-1ec2-4278-b937-99fde70a366f

web/opac/skin/uwin/js/check_sfx.js [new file with mode: 0644]
web/opac/skin/uwin/js/rdetail.js
web/opac/skin/uwin/js/rdetail_custom.js [new file with mode: 0644]
web/opac/skin/uwin/js/xregexp.js [new file with mode: 0644]
web/opac/skin/uwin/local/images/sfx_button.gif [new file with mode: 0644]
web/opac/skin/uwin/xml/page_rdetail.xml
web/opac/skin/uwin/xml/rdetail/rdetail_summary.xml

diff --git a/web/opac/skin/uwin/js/check_sfx.js b/web/opac/skin/uwin/js/check_sfx.js
new file mode 100644 (file)
index 0000000..0680563
--- /dev/null
@@ -0,0 +1,102 @@
+/**
+       Functions to build SFX url and check holdings in SFX for the current record
+       2009 Guoying Liu
+ */
+
+//For now, only valid ISSN enables SFX button 
+function rdetailShowSFXButton( issnText ){
+       if (document.getElementById("rdetail_sfx_check"))
+               if( build_sfx_url(issnText) )
+                       unHideMe($("rdetail_sfx_check"));
+}
+
+//Build SFX url based on ISSN, return true if the url is successfully built
+//issnText can be retrieved from marcrec_State_Change, so simply pass it
+function build_sfx_url( issnText ) {
+       issnText = cleanText(issnText, " ")
+
+       if (issnText == "")
+               return false;
+
+       var http_str = "http://sfx.scholarsportal.info/windsor-default?url_ver=Z39.88-2004"
+                       + "&url_ctx_fmt=infofi/fmt:kev:mtx:ctx&ctx_enc=info:ofi/enc:UTF-8&ctx_ver=Z39.88-2004"
+                       + "&rfr_id=info:sid/sfxit.com:azlist&sfx.ignore_date_threshold=1"
+                       + "&rft.issn=" + escape( issnText )
+
+       var issnCheck = $("rdetail_sfx_check");
+       issnCheck.setAttribute(
+               "href",
+               http_str
+       );
+       issnCheck.setAttribute("target", "SFXWindow");
+
+       return true;
+}
+
+/*     As directly retrieving ISSN text from marcrec_State_Change, following ISSN or MARC related methods are obsolete 
+       but may be useful for some other MARC data field customizations
+ */
+//only return first ISSN subfield value with code "a"
+function getMarcDataISSNText(){
+       var issnItems = getMarcDataFieldItems("022");
+       if (issnItems == null)
+               return "";
+
+       //only retrieve code a subfiled for the first ISSN 
+       var issnSubfildItems = getMarcSubFieldItems(issnItems[0],"a");
+       if (issnSubfildItems == null)
+               return "";
+       
+       //only return first subfield value with code "a" 
+       return getISSNText(issnSubfildItems[0]);
+}
+
+function getMarcDataFieldItems( dataFieldTag ) {
+       var marcItems;
+       var MARCXML;
+
+       MARCXML = MARCRequest.responseXML;
+
+       if ( MARCXML == null )
+               return null;
+
+       //Marc data tag always 3 digits long.
+       if (dataFieldTag.length != 3)
+                return null;
+
+       //for MARC21, 00X: control fields, others are data fields
+       if ((dataFieldTag.charAt(0) == "0") && (dataFieldTag.charAt(1) == "0"))
+               marcItems = getElementsByAttribute( MARCXML, "controlfield", "tag", dataFieldTag );
+       else
+               marcItems = getElementsByAttribute( MARCXML, "datafield", "tag", dataFieldTag );
+
+       return marcItems;
+}
+
+//passing the parent item and subfield code, if code not set, return all code 
+function getMarcSubFieldItems( dataFieldItem, strSubFieldCode ) {
+       return getElementsByAttribute( dataFieldItem, "subfield", "code", ( strSubFieldCode != null ? strSubFieldCode : "*" ) );
+}
+
+function getISSNText( issnElement ) {
+//     if (BrowserDetect.browser == "Explorer"){
+       if (navigator.appName == "Microsoft Internet Explorer"){
+               return cleanText(dojox.data.dom.textContent( issnElement ), " ");
+       }else {
+               return cleanText(issnElement.textContent, " ");
+       } 
+}
+
+/*     Clean text, trim beginning space, and only return a substring from beginning till the Delimiter
+       The Delimiter can be a character or a string 
+ */
+function cleanText(fullText, seperator) {
+       if(fullText) {
+               fullText= fullText.toString().replace(/^\s+/,"");
+               var idx = fullText.indexOf(seperator);
+               if(idx > -1) { fullText = fullText.substring(0, idx); }
+       } else 
+               fullText = "";
+
+       return fullText;
+}
\ No newline at end of file
index 1a70c79..c125e5a 100644 (file)
@@ -298,7 +298,14 @@ function _rdetailDraw(r) {
 
        G.ui.rdetail.title.appendChild(text(record.title()));
        buildSearchLink(STYPE_AUTHOR, record.author(), G.ui.rdetail.author);
-       G.ui.rdetail.isbn.appendChild(text(cleanISBN(record.isbn())));
+//     G.ui.rdetail.isbn.appendChild(text(cleanISBN(record.isbn())));
+       var isbnText= text(cleanISBN(record.isbn()));
+       if (isbnText != null){
+               if (isbnText.length > 0){
+               G.ui.rdetail.isbn.appendChild(isbnText);
+               unHideMe($('rdetail_isbn_row'));                
+               }
+       }
        G.ui.rdetail.edition.appendChild(text(record.edition()));
        G.ui.rdetail.pubdate.appendChild(text(record.pubdate()));
        G.ui.rdetail.publisher.appendChild(text(record.publisher()));
@@ -411,6 +418,8 @@ function _rdetailDraw(r) {
 
        // grab added content 
        acCollectData(cleanISBN(record.isbn()), rdetailhandleAC);
+       
+       rdetailShowCustomizedMARCs ();
 }
 
 
diff --git a/web/opac/skin/uwin/js/rdetail_custom.js b/web/opac/skin/uwin/js/rdetail_custom.js
new file mode 100644 (file)
index 0000000..bbc8ddc
--- /dev/null
@@ -0,0 +1,152 @@
+/**
+       Customizing MARC record data fields based on Alexander O'Neill's work
+       2009 Guoying Liu
+ */
+
+function rdetailShowCustomizedMARCs( ) {
+       loadMARCRecord();
+}
+
+var MARCRequest;
+var marctags;
+var firstResultRow;
+
+function loadMARCRecord( ) {
+       MARCRequest = null;
+
+       // Code for all new browsers
+       if (window.XMLHttpRequest)
+               MARCRequest = new XMLHttpRequest();
+       // Code for IE 5 and 6
+       else if ( window.ActiveXObject )
+               MARCRequest = new ActiveXObject( "Microsoft.XMLHTTP" );
+
+       if ( MARCRequest != null ) {
+               MARCRequest.onreadystatechange = marcrec_State_Change;
+               MARCRequest.open( "GET", "/opac/extras/supercat/retrieve/marcxml/record/" + record.doc_id(), true );
+               MARCRequest.send(null);
+       }
+}
+
+function marcrec_State_Change() {
+       var hasFirstISSN = false;
+
+       // 4 means "loaded"
+       if ( MARCRequest.readyState == 4 ) {
+               // 200 means "OK"
+               if ( MARCRequest.status == 200 ) {
+                       marctags = document.getElementsByTagName("MARC");
+
+                       for ( i = 0; i < marctags.length; i++ ) {
+                               dataField = marctags.item(i).getAttribute('dataField');
+                               controlField = marctags.item(i).getAttribute('controlField');
+                               marcItems = getElementsByAttribute( MARCRequest.responseXML, 'controlfield', 'tag', controlField );
+        
+                               if ( marcItems.length > 0 ) {
+                                       // This is a control field, which has no subfields, rather than a data field.
+                                       itemString = getText( marcItems[0] );
+                                       currentField = document.createElement( 'span' );
+            
+                                       setText( currentField, itemString );
+                                       marctags[i].parentNode.appendChild( currentField );
+                                       continue;
+                               }
+                               marcItems = getElementsByAttribute( MARCRequest.responseXML, 'datafield', 'tag', dataField );
+
+                               buildSearchString = ( marctags[i].getAttribute( 'searchfield' ) != null ? true : false );
+       
+                               for ( j = 0; j < marcItems.length; j++ ) {
+                                       nextItem = getElementsByAttribute( marcItems[j], 'subfield', 'code', ( marctags[i].getAttribute('subfield') != null ? marctags[i].getAttribute('subfield') : '*' ) );
+
+                                       for ( k = 0; k < nextItem.length; k++ ) {
+                                               itemString = nextItem[k].firstChild.nodeValue;                  
+                                               currentSubField = document.createElement( (buildSearchString ? 'a' : 'span') );
+                                               setText( currentSubField, itemString );
+                                               marctags[i].parentNode.appendChild( currentSubField );
+       
+                                               //show SFX Button if dataField "022" has value;
+                                               if (dataField == "022"){
+                                                       if(hasFirstISSN == false){
+                                                               rdetailShowSFXButton( itemString );
+                                                               hasFirstISSN = true;
+                                                       }
+                                               }
+
+                                               if ( buildSearchString ) {
+                                                       href = '../xml/rresult.xml?rt=' + marctags[i].getAttribute('searchfield') + '&tp=' + marctags[i].getAttribute('searchfield') + '&t=';
+
+                                                       for ( l = 0; l <= k; l++ ) {
+                                                               href += nextItem[l].firstChild.nodeValue + '%20'; // it's ok to have a space at the end.
+                                                       }
+                                                       href += '&l=1&d=0&f=&av=';
+                                              currentSubField.setAttribute('href', href);
+                                              currentSubField.setAttribute('title', 'Perform a search on this subject' );
+                                               }
+
+                                               separatorItem = document.createElement('span');
+                       
+                                               if ( k < nextItem.length - 1) {
+                                               setText( separatorItem, ( marctags[i].getAttribute('separator') != null ? ' ' + marctags[i].getAttribute('separator') + ' ' : ' ' ) );
+                                              marctags[i].parentNode.appendChild( separatorItem );
+                                               } else {
+                                                       if ( marctags[i].getAttribute( 'newline' ) != null ) {
+                                                               if ( marctags[i].getAttribute( 'newline' ) == 'no' ){
+                                                                       setText( separatorItem, ' ' );
+                                                                       marctags[i].parentNode.appendChild( separatorItem );
+                                                               } else {
+                                                                       marctags[i].parentNode.appendChild( document.createElement('br') );
+                                                               }
+                                                       } else {
+                                                               marctags[i].parentNode.appendChild( document.createElement('br') );
+                                                       }  
+                                               }
+                                       }//for k
+
+                               } // for j
+                               if ( getText(marctags[i].parentNode).replace(/^\s+|\s+$/g, '') == '' ) {
+                                       hideMe(marctags[i].parentNode.parentNode);
+                               }
+                       }
+               } 
+       } 
+}
+
+function getElementsByAttribute(oElm, strTagName, strAttributeName, strAttributeValue) {
+       var arrElements = oElm.getElementsByTagName(strTagName );
+       var arrReturnElements = new Array();
+       var oAttributeValue = new XRegExp( "(^|\\s)" + strAttributeValue + "(\\s|$)", "i" );
+       var oCurrent;
+       var oAttribute;
+
+       for ( var i=0; i < arrElements.length; i++ ) {
+               oCurrent = arrElements[i];
+               oAttribute = oCurrent.getAttribute( strAttributeName );
+               if( oAttribute != null && oAttribute.length > 0) 
+                       if( oAttributeValue && oAttributeValue.test( oAttribute ) )
+                               arrReturnElements.push(oCurrent);
+       }
+       return arrReturnElements;
+}
+
+function getText( control ) {
+       if ( control == null )
+               return '';
+
+       if (document.all)
+               return control.innerText;
+       else
+               return trimStr(control.textContent);
+}
+
+function setText(control, value) {
+       if (document.all)
+               control.innerText = value;
+       else
+               control.textContent = value; 
+}
+
+function trimStr( str ) {
+       if (str == null)
+               return '';
+       return str.replace(/^\s+|\s+$/g, '');
+}
diff --git a/web/opac/skin/uwin/js/xregexp.js b/web/opac/skin/uwin/js/xregexp.js
new file mode 100644 (file)
index 0000000..96349d7
--- /dev/null
@@ -0,0 +1,515 @@
+/*\r
+    XRegExp 0.6.1\r
+    (c) 2007-2008 Steven Levithan\r
+    <http://stevenlevithan.com/regex/xregexp/>\r
+    MIT License\r
+*/\r
+\r
+/** provides an augmented, cross-browser implementation of regular expressions\r
+    including support for additional modifiers and syntax. several convenience\r
+    methods and a recursive-construct parser are also included.\r
+*/\r
+\r
+// prevent running twice, which would break references to native globals\r
+if (!window.XRegExp) {\r
+// anonymous function to avoid global variables\r
+(function () {\r
+// copy various native globals for reference. can't use the name ``native``\r
+// because it's a reserved JavaScript keyword.\r
+var real = {\r
+        exec:    RegExp.prototype.exec,\r
+        match:   String.prototype.match,\r
+        replace: String.prototype.replace,\r
+        split:   String.prototype.split\r
+    },\r
+    /* regex syntax parsing with support for all the necessary cross-\r
+       browser and context issues (escapings, character classes, etc.) */\r
+    lib = {\r
+        part:       /(?:[^\\([#\s.]+|\\(?!k<[\w$]+>|[pP]{[^}]+})[\S\s]?|\((?=\?(?!#|<[\w$]+>)))+|(\()(?:\?(?:(#)[^)]*\)|<([$\w]+)>))?|\\(?:k<([\w$]+)>|[pP]{([^}]+)})|(\[\^?)|([\S\s])/g,\r
+        replaceVar: /(?:[^$]+|\$(?![1-9$&`']|{[$\w]+}))+|\$(?:([1-9]\d*|[$&`'])|{([$\w]+)})/g,\r
+        extended:   /^(?:\s+|#.*)+/,\r
+        quantifier: /^(?:[?*+]|{\d+(?:,\d*)?})/,\r
+        classLeft:  /&&\[\^?/g,\r
+        classRight: /]/g\r
+    },\r
+    indexOf = function (array, item, from) {\r
+        for (var i = from || 0; i < array.length; i++)\r
+            if (array[i] === item) return i;\r
+        return -1;\r
+    },\r
+    brokenExecUndef = /()??/.exec("")[1] !== undefined,\r
+    plugins = {};\r
+\r
+/*** XRegExp\r
+    accepts a pattern and flags, returns a new, extended RegExp object.\r
+    differs from a native regex in that additional flags and syntax are\r
+    supported and browser inconsistencies are ameliorated.\r
+*/\r
+XRegExp = function (pattern, flags) {\r
+    if (pattern instanceof RegExp) {\r
+        if (flags !== undefined)\r
+            throw TypeError("can't supply flags when constructing one RegExp from another");\r
+        return pattern.addFlags(); // new copy\r
+    }\r
+\r
+    var flags           = flags || "",\r
+        singleline      = flags.indexOf("s") > -1,\r
+        extended        = flags.indexOf("x") > -1,\r
+        hasNamedCapture = false,\r
+        captureNames    = [],\r
+        output          = [],\r
+        part            = lib.part,\r
+        match, cc, len, index, regex;\r
+\r
+    part.lastIndex = 0; // in case the last XRegExp compilation threw an error (unbalanced character class)\r
+\r
+    while (match = real.exec.call(part, pattern)) {\r
+        // comment pattern. this check must come before the capturing group check,\r
+        // because both match[1] and match[2] will be non-empty.\r
+        if (match[2]) {\r
+            // keep tokens separated unless the following token is a quantifier\r
+            if (!lib.quantifier.test(pattern.slice(part.lastIndex)))\r
+                output.push("(?:)");\r
+        // capturing group\r
+        } else if (match[1]) {\r
+            captureNames.push(match[3] || null);\r
+            if (match[3])\r
+                hasNamedCapture = true;\r
+            output.push("(");\r
+        // named backreference\r
+        } else if (match[4]) {\r
+            index = indexOf(captureNames, match[4]);\r
+            // keep backreferences separate from subsequent literal numbers\r
+            // preserve backreferences to named groups that are undefined at this point as literal strings\r
+            output.push(index > -1 ?\r
+                "\\" + (index + 1) + (isNaN(pattern.charAt(part.lastIndex)) ? "" : "(?:)") :\r
+                match[0]\r
+            );\r
+        // unicode element (requires plugin)\r
+        } else if (match[5]) {\r
+            output.push(plugins.unicode ?\r
+                plugins.unicode.get(match[5], match[0].charAt(1) === "P") :\r
+                match[0]\r
+            );\r
+        // character class opening delimiter ("[" or "[^")\r
+        // (non-native unicode elements are not supported within character classes)\r
+        } else if (match[6]) {\r
+            if (pattern.charAt(part.lastIndex) === "]") {\r
+                // for cross-browser compatibility with ECMA-262 v3 behavior,\r
+                // convert [] to (?!) and [^] to [\S\s].\r
+                output.push(match[6] === "[" ? "(?!)" : "[\\S\\s]");\r
+                part.lastIndex++;\r
+            } else {\r
+                // parse the character class with support for inner escapes and\r
+                // ES4's infinitely nesting intersection syntax ([&&[^&&[]]]).\r
+                cc = XRegExp.matchRecursive("&&" + pattern.slice(match.index), lib.classLeft, lib.classRight, "", {escapeChar: "\\"})[0];\r
+                output.push(match[6] + cc + "]");\r
+                part.lastIndex += cc.length + 1;\r
+            }\r
+        // dot ("."), pound sign ("#"), or whitespace character\r
+        } else if (match[7]) {\r
+            if (singleline && match[7] === ".") {\r
+                output.push("[\\S\\s]");\r
+            } else if (extended && lib.extended.test(match[7])) {\r
+                len = real.exec.call(lib.extended, pattern.slice(part.lastIndex - 1))[0].length;\r
+                // keep tokens separated unless the following token is a quantifier\r
+                if (!lib.quantifier.test(pattern.slice(part.lastIndex - 1 + len)))\r
+                    output.push("(?:)");\r
+                part.lastIndex += len - 1;\r
+            } else {\r
+                output.push(match[7]);\r
+            }\r
+        } else {\r
+            output.push(match[0]);\r
+        }\r
+    }\r
+\r
+    regex = RegExp(output.join(""), real.replace.call(flags, /[sx]+/g, ""));\r
+    regex._x = {\r
+        source:       pattern,\r
+        captureNames: hasNamedCapture ? captureNames : null\r
+    };\r
+    return regex;\r
+};\r
+\r
+// barebones plugin support for now (intentionally undocumented)\r
+XRegExp.addPlugin = function (name, o) {\r
+    plugins[name] = o;\r
+};\r
+\r
+/*** RegExp.prototype.exec\r
+    adds named capture support, with values returned as ``result.name``.\r
+    also fixes two cross-browser issues, following the ECMA-262 v3 spec:\r
+     - captured values for non-participating capturing groups should be returned\r
+       as ``undefined``, rather than the empty string.\r
+     - the regex's ``lastIndex`` should not be incremented after zero-length\r
+       matches.\r
+*/\r
+RegExp.prototype.exec = function (str) {\r
+    var match = real.exec.call(this, str),\r
+        name, i, r2;\r
+    if (match) {\r
+        // fix browsers whose exec methods don't consistently return\r
+        // undefined for non-participating capturing groups\r
+        if (brokenExecUndef && match.length > 1) {\r
+            // r2 doesn't need /g or /y, but they shouldn't hurt\r
+            r2 = new RegExp("^" + this.source + "$(?!\\s)", this.getNativeFlags());\r
+            real.replace.call(match[0], r2, function () {\r
+                for (i = 1; i < arguments.length - 2; i++) {\r
+                    if (arguments[i] === undefined) match[i] = undefined;\r
+                }\r
+            });\r
+        }\r
+        // attach named capture properties\r
+        if (this._x && this._x.captureNames) {\r
+            for (i = 1; i < match.length; i++) {\r
+                name = this._x.captureNames[i - 1];\r
+                if (name) match[name] = match[i];\r
+            }\r
+        }\r
+        // fix browsers that increment lastIndex after zero-length matches\r
+        if (this.global && this.lastIndex > (match.index + match[0].length))\r
+            this.lastIndex--;\r
+    }\r
+    return match;\r
+};\r
+\r
+/*** String.prototype.match\r
+    run the altered ``exec`` when called with a non-global regex.\r
+*/\r
+String.prototype.match = function (regex) {\r
+    if (!(regex instanceof RegExp))\r
+        regex = new XRegExp(regex);\r
+    if (regex.global)\r
+        return real.match.call(this, regex);\r
+    return regex.exec(this); // run the altered exec\r
+};\r
+\r
+/*** String.prototype.replace\r
+    add named capture support to replacement strings using the syntax\r
+    ``${name}``, and to replacement functions as ``arguments[0].name``.\r
+*/\r
+String.prototype.replace = function (search, replacement) {\r
+    var captureNames = (search._x || {}).captureNames;\r
+\r
+    // if search is not a regex which uses named capture, use the native replace method\r
+    if (!(search instanceof RegExp && captureNames))\r
+        return real.replace.apply(this, arguments);\r
+\r
+    if (typeof replacement === "function") {\r
+        return real.replace.call(this, search, function () {\r
+            // change the arguments[0] string primitive to a String object which can store properties\r
+            arguments[0] = new String(arguments[0]);\r
+            // store named backreferences on arguments[0] before calling replacement\r
+            for (var i = 0; i < captureNames.length; i++) {\r
+                if (captureNames[i])\r
+                    arguments[0][captureNames[i]] = arguments[i + 1];\r
+            }\r
+            return replacement.apply(window, arguments);\r
+        });\r
+    } else {\r
+        return real.replace.call(this, search, function () {\r
+            var args = arguments;\r
+            return real.replace.call(replacement, lib.replaceVar, function ($0, $1, $2) {\r
+                // numbered backreference or special variable\r
+                if ($1) {\r
+                    switch ($1) {\r
+                        case "$": return "$";\r
+                        case "&": return args[0];\r
+                        case "`": return args[args.length - 1].slice(0, args[args.length - 2]);\r
+                        case "'": return args[args.length - 1].slice(args[args.length - 2] + args[0].length);\r
+                        // numbered backreference\r
+                        default:\r
+                            /* what does "$10" mean?\r
+                                - backreference 10, if 10 or more capturing groups exist\r
+                                - backreference 1 followed by "0", if 1-9 capturing groups exist\r
+                                - otherwise, it's the string "$10"\r
+                            */\r
+                            var literalNumbers = "";\r
+                            $1 = +$1; // type-convert\r
+                            while ($1 > captureNames.length) {\r
+                                literalNumbers = real.split.call($1, "").pop() + literalNumbers;\r
+                                $1 = Math.floor($1 / 10); // drop the last digit\r
+                            }\r
+                            return ($1 ? args[$1] : "$") + literalNumbers;\r
+                    }\r
+                // named backreference\r
+                } else if ($2) {\r
+                    /* what does "${name}" mean?\r
+                        - backreference to named capture "name", if it exists\r
+                        - otherwise, it's the string "${name}"\r
+                    */\r
+                    var index = indexOf(captureNames, $2);\r
+                    return index > -1 ? args[index + 1] : $0;\r
+                } else {\r
+                    return $0;\r
+                }\r
+            });\r
+        });\r
+    }\r
+};\r
+\r
+/*** String.prototype.split\r
+    a consistent cross-browser, ECMA-262 v3 compliant split method\r
+*/\r
+String.prototype.split = function (s /* separator */, limit) {\r
+    // if separator is not a regex, use the native split method\r
+    if (!(s instanceof RegExp))\r
+        return real.split.apply(this, arguments);\r
+\r
+    var output = [],\r
+        origLastIndex = s.lastIndex,\r
+        lastLastIndex = 0,\r
+        i = 0, match, lastLength;\r
+\r
+    /* behavior for limit: if it's...\r
+        - undefined: no limit\r
+        - NaN or zero: return an empty array\r
+        - a positive number: use limit after dropping any decimal\r
+        - a negative number: no limit\r
+        - other: type-convert, then use the above rules\r
+    */\r
+    if (limit === undefined || +limit < 0) {\r
+        limit = false;\r
+    } else {\r
+        limit = Math.floor(+limit);\r
+        if (!limit)\r
+            return [];\r
+    }\r
+\r
+    if (s.global)\r
+        s.lastIndex = 0;\r
+    else\r
+        s = s.addFlags("g");\r
+\r
+    while ((!limit || i++ <= limit) && (match = s.exec(this))) { // run the altered exec!\r
+        if (s.lastIndex > lastLastIndex) {\r
+            output = output.concat(this.slice(lastLastIndex, match.index));\r
+            if (1 < match.length && match.index < this.length)\r
+                output = output.concat(match.slice(1));\r
+            lastLength = match[0].length; // only needed if s.lastIndex === this.length\r
+            lastLastIndex = s.lastIndex;\r
+        }\r
+        if (!match[0].length)\r
+            s.lastIndex++; // avoid an infinite loop\r
+    }\r
+\r
+    // since this uses test(), output must be generated before restoring lastIndex\r
+    output = lastLastIndex === this.length ?\r
+        (s.test("") && !lastLength ? output : output.concat("")) :\r
+        (limit ? output : output.concat(this.slice(lastLastIndex)));\r
+    s.lastIndex = origLastIndex; // only needed if s.global, else we're working with a copy of the regex\r
+    return output;\r
+};\r
+})(); // end anonymous function\r
+} // end if(!window.XRegExp)\r
+\r
+// intentionally undocumented\r
+RegExp.prototype.getNativeFlags = function () {\r
+    return (this.global     ? "g" : "") +\r
+           (this.ignoreCase ? "i" : "") +\r
+           (this.multiline  ? "m" : "") +\r
+           (this.extended   ? "x" : "") +\r
+           (this.sticky     ? "y" : "");\r
+};\r
+\r
+/*** RegExp.prototype.addFlags\r
+    accepts flags; returns a new XRegExp object generated by recompiling\r
+    the regex with the additional flags (may include non-native flags).\r
+    the original regex object is not altered.\r
+*/\r
+RegExp.prototype.addFlags = function (flags) {\r
+    var regex = new XRegExp(this.source, (flags || "") + this.getNativeFlags());\r
+    if (this._x) {\r
+        regex._x = {\r
+            source:       this._x.source,\r
+            captureNames: this._x.captureNames ? this._x.captureNames.slice(0) : null\r
+        };\r
+    }\r
+    return regex;\r
+};\r
+\r
+/*** RegExp.prototype.call\r
+    accepts a context object and string; returns the result of calling\r
+    ``exec`` with the provided string. the context is ignored but is\r
+    accepted for congruity with ``Function.prototype.call``.\r
+*/\r
+RegExp.prototype.call = function (context, str) {\r
+    return this.exec(str);\r
+};\r
+\r
+/*** RegExp.prototype.apply\r
+    accepts a context object and arguments array; returns the result of\r
+    calling ``exec`` with the first value in the arguments array. the context\r
+    is ignored but is accepted for congruity with ``Function.prototype.apply``.\r
+*/\r
+RegExp.prototype.apply = function (context, args) {\r
+    return this.exec(args[0]);\r
+};\r
+\r
+/*** XRegExp.cache\r
+    accepts a pattern and flags; returns an XRegExp object. if the pattern\r
+    and flag combination has previously been cached, the cached copy is\r
+    returned, otherwise the new object is cached.\r
+*/\r
+XRegExp.cache = function (pattern, flags) {\r
+    var key = "/" + pattern + "/" + (flags || "");\r
+    return XRegExp.cache[key] || (XRegExp.cache[key] = new XRegExp(pattern, flags));\r
+};\r
+\r
+/*** XRegExp.escape\r
+    accepts a string; returns the string with regex metacharacters escaped.\r
+    the returned string can safely be used within a regex to match a literal\r
+    string. escaped characters are [, ], {, }, (, ), -, *, +, ?, ., \, ^, $,\r
+    |, #, [comma], and whitespace.\r
+*/\r
+XRegExp.escape = function (str) {\r
+    return str.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, "\\$&");\r
+};\r
+\r
+/*** XRegExp.matchRecursive\r
+    accepts a string to search, left and right delimiters as regex pattern\r
+    strings, optional regex flags (may include non-native s, x, and y flags),\r
+    and an options object which allows setting an escape character and changing\r
+    the return format from an array of matches to a two-dimensional array of\r
+    string parts with extended position data. returns an array of matches\r
+    (optionally with extended data), allowing nested instances of left and right\r
+    delimiters. use the g flag to return all matches, otherwise only the first\r
+    is returned. if delimiters are unbalanced within the subject data, an error\r
+    is thrown.\r
+\r
+    this function admittedly pushes the boundaries of what can be accomplished\r
+    sensibly without a "real" parser. however, by doing so it provides flexible\r
+    and powerful recursive parsing capabilities with minimal code weight.\r
+\r
+    warning: the ``escapeChar`` option is considered experimental and might be\r
+    changed or removed in future versions of XRegExp.\r
+\r
+    unsupported features:\r
+     - backreferences within delimiter patterns when using ``escapeChar``.\r
+     - although providing delimiters as regex objects adds the minor feature of\r
+       independent delimiter flags, it introduces other limitations and is only\r
+       intended to be done by the ``XRegExp`` constructor (which can't call\r
+       itself while building a regex).\r
+*/\r
+XRegExp.matchRecursive = function (str, left, right, flags, options) {\r
+    var options      = options || {},\r
+        escapeChar   = options.escapeChar,\r
+        vN           = options.valueNames,\r
+        flags        = flags || "",\r
+        global       = flags.indexOf("g") > -1,\r
+        ignoreCase   = flags.indexOf("i") > -1,\r
+        multiline    = flags.indexOf("m") > -1,\r
+        sticky       = flags.indexOf("y") > -1,\r
+        /* sticky mode has its own handling in this function, which means you\r
+           can use flag "y" even in browsers which don't support it natively */\r
+        flags        = flags.replace(/y/g, ""),\r
+        left         = left  instanceof RegExp ? (left.global  ? left  : left.addFlags("g"))  : new XRegExp(left,  "g" + flags),\r
+        right        = right instanceof RegExp ? (right.global ? right : right.addFlags("g")) : new XRegExp(right, "g" + flags),\r
+        output       = [],\r
+        openTokens   = 0,\r
+        delimStart   = 0,\r
+        delimEnd     = 0,\r
+        lastOuterEnd = 0,\r
+        outerStart, innerStart, leftMatch, rightMatch, escaped, esc;\r
+\r
+    if (escapeChar) {\r
+        if (escapeChar.length > 1) throw SyntaxError("can't supply more than one escape character");\r
+        if (multiline)             throw TypeError("can't supply escape character when using the multiline flag");\r
+        escaped = XRegExp.escape(escapeChar);\r
+        /* Escape pattern modifiers:\r
+            /g - not needed here\r
+            /i - included\r
+            /m - **unsupported**, throws error\r
+            /s - handled by XRegExp when delimiters are provided as strings\r
+            /x - handled by XRegExp when delimiters are provided as strings\r
+            /y - not needed here; supported by other handling in this function\r
+        */\r
+        esc = new RegExp(\r
+            "^(?:" + escaped + "[\\S\\s]|(?:(?!" + left.source + "|" + right.source + ")[^" + escaped + "])+)+",\r
+            ignoreCase ? "i" : ""\r
+        );\r
+    }\r
+\r
+    while (true) {\r
+        /* advance the starting search position to the end of the last delimiter match.\r
+           a couple special cases are also covered:\r
+            - if using an escape character, advance to the next delimiter's starting position,\r
+              skipping any escaped characters\r
+            - first time through, reset lastIndex in case delimiters were provided as regexes\r
+        */\r
+        left.lastIndex = right.lastIndex = delimEnd +\r
+            (escapeChar ? (esc.exec(str.slice(delimEnd)) || [""])[0].length : 0);\r
+\r
+        leftMatch  = left.exec(str);\r
+        rightMatch = right.exec(str);\r
+\r
+        // only keep the result which matched earlier in the string\r
+        if (leftMatch && rightMatch) {\r
+            if (leftMatch.index <= rightMatch.index)\r
+                 rightMatch = null;\r
+            else leftMatch  = null;\r
+        }\r
+\r
+        /* paths*:\r
+        leftMatch | rightMatch | openTokens | result\r
+        1         | 0          | 1          | ...\r
+        1         | 0          | 0          | ...\r
+        0         | 1          | 1          | ...\r
+        0         | 1          | 0          | throw\r
+        0         | 0          | 1          | throw\r
+        0         | 0          | 0          | break\r
+        * - does not include the sticky mode special case\r
+          - the loop ends after the first completed match if not in global mode\r
+        */\r
+\r
+        if (leftMatch || rightMatch) {\r
+            delimStart = (leftMatch || rightMatch).index;\r
+            delimEnd   = (leftMatch ? left : right).lastIndex;\r
+        } else if (!openTokens) {\r
+            break;\r
+        }\r
+\r
+        if (sticky && !openTokens && delimStart > lastOuterEnd)\r
+            break;\r
+\r
+        if (leftMatch) {\r
+            if (!openTokens++) {\r
+                outerStart = delimStart;\r
+                innerStart = delimEnd;\r
+            }\r
+        } else if (rightMatch && openTokens) {\r
+            if (!--openTokens) {\r
+                if (vN) {\r
+                    if (vN[0] && outerStart > lastOuterEnd)\r
+                               output.push([vN[0], str.slice(lastOuterEnd, outerStart), lastOuterEnd, outerStart]);\r
+                    if (vN[1]) output.push([vN[1], str.slice(outerStart,   innerStart), outerStart,   innerStart]);\r
+                    if (vN[2]) output.push([vN[2], str.slice(innerStart,   delimStart), innerStart,   delimStart]);\r
+                    if (vN[3]) output.push([vN[3], str.slice(delimStart,   delimEnd),   delimStart,   delimEnd]);\r
+                } else {\r
+                    output.push(str.slice(innerStart, delimStart));\r
+                }\r
+                lastOuterEnd = delimEnd;\r
+                if (!global)\r
+                    break;\r
+            }\r
+        } else {\r
+            // reset lastIndex in case delimiters were provided as regexes\r
+            left.lastIndex = right.lastIndex = 0;\r
+            throw Error("subject data contains unbalanced delimiters");\r
+        }\r
+\r
+        // if the delimiter matched an empty string, advance delimEnd to avoid an infinite loop\r
+        if (delimStart === delimEnd)\r
+            delimEnd++;\r
+    }\r
+\r
+    if (global && !sticky && vN && vN[0] && str.length > lastOuterEnd)\r
+        output.push([vN[0], str.slice(lastOuterEnd), lastOuterEnd, str.length]);\r
+\r
+    // reset lastIndex in case delimiters were provided as regexes\r
+    left.lastIndex = right.lastIndex = 0;\r
+\r
+    return output;\r
+};\r
+\r
diff --git a/web/opac/skin/uwin/local/images/sfx_button.gif b/web/opac/skin/uwin/local/images/sfx_button.gif
new file mode 100644 (file)
index 0000000..80e5ade
Binary files /dev/null and b/web/opac/skin/uwin/local/images/sfx_button.gif differ
index c768730..c1d1ea2 100644 (file)
@@ -7,6 +7,9 @@
        <script language='javascript' type='text/javascript' src='<!--#echo var="OILS_OPAC_JS_HOST"-->/skin/uwin/js/holds.js'></script>
        <script language='javascript' type='text/javascript' src='<!--#echo var="OILS_OPAC_JS_HOST"-->/skin/uwin/js/cn_browse.js'></script>
        <script language='javascript' type='text/javascript' src='<!--#echo var="OILS_OPAC_JS_HOST"-->/skin/uwin/js/container.js'></script>
+       <script language='javascript' type='text/javascript' src='../js/rdetail_custom.js'></script>
+       <script language='javascript' type='text/javascript' src='../js/xregexp.js'></script>
+       <script language='javascript' type='text/javascript' src='../js/check_sfx.js'></script>
        <script src='http://www.google.com/jsapi' type='text/javascript' language='javascript'></script>
        <script type='text/javascript' src='http://books.google.com/books/api.js?key=notsupplied&amp;v=0&amp;callback=google.loader.callbacks.books'></script>
 
index 372d3bf..1b83640 100644 (file)
@@ -20,7 +20,9 @@
                             class='classic_link' id='rdetail.jacket_attrib_link'>&vendor.name;</a></div>\r
                     </div>\r
                                </td>\r
-                               <td nowrap='nowrap' class='rdetail_desc'>&common.title;</td>            \r
+                               <td nowrap='nowrap' class='rdetail_desc'>\r
+                                       <a style='float:right; white-space:nowrap;' class='hide_me' id='rdetail_sfx_check' title='Check All Holdings'>\r
+                                       <img border='0' target='_top' src='../local/images/sfx_button.gif' /></a>&common.title;</td>            \r
                 <!-- *** Example of how to use the openils.BibTemplate infrastructure to augment the stock\r
                      *** summary screen with more and/or different information.  In this case, the raw MARC 245. -->\r
                 <td type='opac/slot-data' query='datafield[tag=245]' class='rdetail_item' id='rdetail_title'> </td>\r
                                </td>\r
                        </tr>\r
 \r
-                       <tr>\r
+                       <tr class='hide_me' id='rdetail_isbn_row'>\r
                                <td nowrap='nowrap' class='rdetail_desc'>&common.isbn;</td>                     \r
                                <td class='rdetail_item' id='rdetail_isbn'> </td>\r
                        </tr>\r
 \r
                        <tr>\r
+                               <td nowrap='nowrap' class='rdetail_desc'>&common.issn;</td>                     \r
+                               <td class='rdetail_item' id='rdetail_issn'><MARC datafield="022"></MARC></td>\r
+                       </tr>\r
+\r
+                       <tr>\r
                                <td nowrap='nowrap' class='rdetail_desc'>&common.edition;</td>          \r
                                <td class='rdetail_item' id='rdetail_edition'> </td>\r
                        </tr>\r
 \r
                        <tr>\r
-                               <td nowrap='nowrap' class='rdetail_desc'>&common.pubdate;</td>          \r
-                               <td class='rdetail_item' id='rdetail_pubdate'> </td>\r
+                               <td nowrap='nowrap' class='rdetail_desc'>Place of Publication</td>                      \r
+                               <td class='rdetail_item' id='rdetail_place_of_pub'><MARC datafield="260" subfield='a'></MARC></td>\r
                        </tr>\r
 \r
                        <tr>\r
-                               <td nowrap='nowrap' class='rdetail_desc'>&common.publisher;</td>                \r
-                               <td type='opac/slot-data' query='datafield[tag=260]' class='rdetail_item' id='rdetail_publisher'> </td>\r
+                               <td nowrap='nowrap' class='rdetail_desc'>&common.publisher;</td>                        \r
+                               <td class='rdetail_item' id='rdetail_publisher'><MARC datafield="260" subfield='b'></MARC></td>\r
+                       </tr>\r
+                       \r
+                       <tr>\r
+                               <td nowrap='nowrap' class='rdetail_desc'>&common.pubdate;</td>          \r
+                               <td class='rdetail_item' id='rdetail_pubdate'> </td>\r
                        </tr>\r
-\r
 \r
                        <tr>\r
                                <td nowrap='nowrap' class='rdetail_desc'>&common.physical;</td>         \r