1/* 2 * The contents of this file are subject to the license and copyright 3 * detailed in the LICENSE and NOTICE files at the root of the source 4 * tree and available online at 5 * 6 * http://www.dspace.org/license/ 7 */ 8// Client-side scripting to support DSpace Choice Control 9 10// IMPORTANT NOTE: 11// This code depends on a *MODIFIED* version of the 12// script.aculo.us controls v1.8.2, fixed to avoid a bug that 13// affects autocomplete in Firefox 3. This code is included in DSpace. 14 15// Entry points: 16// 1. DSpaceAutocomplete -- add autocomplete (suggest) to an input field 17// 18// 2. DSpaceChoiceLookup -- create popup window with authority choices 19// 20// @Author: Larry Stone <[email protected]> 21// $Revision $ 22 23// -------------------- support for Autocomplete (Suggest) 24 25// Autocomplete utility: 26// Arguments: 27// formID -- ID attribute of form tag 28// args properties: 29// metadataField -- metadata field e.g. dc_contributor_author 30// inputName -- input field name for text input, or base of "Name" pair 31// authorityName -- input field name in which to set authority 32// containerID -- ID attribute of DIV to hold the menu objects 33// indicatorID -- ID attribute of element to use as a "loading" indicator 34// confidenceIndicatorID -- ID of element on which to set confidence 35// confidenceName - NAME of confidence input (not ID) 36// contextPath -- URL path prefix (i.e. webapp contextPath) for DSpace. 37// collection -- db ID of dspace collection to serve as context 38// isClosed -- true if authority value is required, false = non-auth allowed 39// XXX Can't really enforce "isClosed=true" with autocomplete, user can type anything 40// 41// NOTE: Successful autocomplete always sets confidence to 'accepted' since 42// authority value (if any) *was* chosen interactively by a human. 43function DSpaceSetupAutocomplete(formID, args) 44{ 45 if (args.authorityName == null) 46 args.authorityName = dspace_makeFieldInput(args.inputName,'_authority'); 47 48 var authInput = jQuery('#'+formID + ' input[name=\''+args.authorityName+'\']'); 49 var input = jQuery('#'+formID + ' input[name=\''+args.inputName+'\']'); 50 input.parent('td').attr('style','white-space:nowrap;'); 51 // AJAX menu source, can add &query=TEXT 52 var choiceURL = args.contextPath+"/choices/"+args.metadataField; 53 var collID = args.collection == null ? -1 : args.collection; 54 if (authInput != null) 55 { 56 input.data('previousData', {authority: authInput.val(), value: input.val()}); 57 } 58 var options = { 59 lenght: 2, 60 search: function(event, ui) { 61 jQuery('#'+args.indicatorID).show(); 62 }, 63 source: function( request, response ) { 64 jQuery.ajax({ 65 url: choiceURL, 66 dataType: "xml", 67 data: { 68 query: request.term, 69 collection: collID, 70 format: 'ul' 71 }, 72 success: function( data ) { 73 jQuery('#'+args.indicatorID).hide(); 74 75 response(jQuery("li", data ).map(function() { 76 return { 77 authority : jQuery(this).attr('authority'), 78 label : jQuery('span.label',this).text(), 79 value : jQuery('span.value',this).text() 80 }; 81 })); 82 } 83 }); 84 }, 85 select: function(event, ui){ 86 input.data('previousData', ui.item); 87 var authority = ui.item.authority; 88 var authValue = authority; 89 if (authInput != null) 90 { 91 authInput.val(authValue); 92 // update confidence input's value too if available. 93 if (args.confidenceName != null) 94 { 95 var confInput = jQuery('#'+formID + ' input[name=\''+args.confidenceName+'\']'); 96 if (confInput != null) 97 { 98 if (authority != '') 99 { 100 confInput.val('accepted'); 101 } 102 else 103 { 104 confInput.val(''); 105 } 106 } 107 } 108 } 109 // make indicator blank if no authority value 110 DSpaceUpdateConfidence(document, args.confidenceIndicatorID, 111 authValue == null || authValue == '' ? 'blank' :'accepted'); 112 } 113 }; 114 input.autocomplete(options).change(function(){ 115 var lastSel = input.data('previousData'); 116 var newauth = ''; 117 var newval = input.val(); 118 if (authInput != null) 119 { 120 newauth = authInput.val(); 121 } 122 if (newauth != lastSel.authority || newval != lastSel.value) 123 { 124 if (authInput != null) 125 { 126 authInput.val(' '); 127 DSpaceUpdateConfidence(document, args.confidenceIndicatorID, 'blank'); 128 } 129 } 130 }); 131} 132
133// -------------------- support for Lookup Popup 134 135// Create popup window with authority choices for value of an input field. 136// This is intended to be called by onClick of a "Lookup" or "Add" button. 137function DSpaceChoiceLookup(url, field, formID, valueInput, authInput, 138 confIndicatorID, collectionID, isName, isRepeating) 139{ 140 // fill in URL 141 url += '?field='+field+'&formID='+formID+'&valueInput='+valueInput+ 142 '&authorityInput='+authInput+'&collection='+collectionID+ 143 '&isName='+isName+'&isRepeating='+isRepeating+'&confIndicatorID='+confIndicatorID; 144 145 // primary input field - for positioning popup. 146 var inputFieldName = isName ? dspace_makeFieldInput(valueInput,'_last') : valueInput; 147 var inputField = document.getElementById(formID).elements[inputFieldName]; 148 // scriptactulous magic to figure out true offset: 149 var cOffset = 0; 150 if (inputField != null) 151 cOffset = $(inputField).cumulativeOffset(); 152 var width = 600; // XXX guesses! these should be params, or configured.. 153 var height = 470; 154 var left; var top; 155 if (window.screenX == null) { 156 left = window.screenLeft + cOffset.left - (width/2); 157 top = window.screenTop + cOffset.top - (height/2); 158 } else { 159 left = window.screenX + cOffset.left - (width/2); 160 top = window.screenY + cOffset.top - (height/2); 161 } 162 if (left < 0) left = 0; 163 if (top < 0) top = 0; 164 var pw = window.open(url, 'ignoreme', 165 'width='+width+',height='+height+',left='+left+',top='+top+ 166 ',toolbar=no,menubar=no,location=no,status=no,resizable'); 167 if (window.focus) pw.focus(); 168 return false; 169} 170 171// Run this as the Lookup page is loaded to initialize DOM objects, load choices 172function DSpaceChoicesSetup(form) 173{ 174 // find the "LEGEND" in fieldset, which acts as page title, 175 // and save it as a bogus form element, e.g. elements['statline'] 176 var fieldset = document.getElementById('aspect_general_ChoiceLookupTransformer_list_choicesList'); 177 for (i = 0; i < fieldset.childNodes.length; ++i) 178 { 179 if (fieldset.childNodes[i].nodeName == 'LEGEND') 180 { 181 form.statline = fieldset.childNodes[i]; 182 form.statline_template = fieldset.childNodes[i].innerHTML; 183 fieldset.childNodes[i].innerHTML = "Loading..."; 184 break; 185 } 186 } 187 DSpaceChoicesLoad(form); 188} 189 190 191// populate the "select" with options from ajax request 192// stash some parameters as properties of the "select" so we can add to 193// the last start index to query for next set of results. 194function DSpaceChoicesLoad(form) 195{ 196 var field = form.elements['paramField'].value; 197 var value = form.elements['paramValue'].value; 198 var start = form.elements['paramStart'].value; 199 var limit = form.elements['paramLimit'].value; 200 var formID = form.elements['paramFormID'].value; 201 var collID = form.elements['paramCollection'].value; 202 var isName = form.elements['paramIsName'].value == 'true'; 203 var isRepeating = form.elements['paramIsRepeating'].value == 'true'; 204 var isClosed = form.elements['paramIsClosed'].value == 'true'; 205 var contextPath = form.elements['contextPath'].value; 206 var fail = form.elements['paramFail'].value; 207 var valueInput = form.elements['paramValueInput'].value; 208 var nonAuthority = ""; 209 if (form.elements['paramNonAuthority'] != null) 210 nonAuthority = form.elements['paramNonAuthority'].value; 211 212 // get value from form inputs in opener if not explicitly supplied 213 if (value.length == 0) 214 { 215 var of = window.opener.document.getElementById(formID); 216 if (isName) 217 value = makePersonName(of.elements[dspace_makeFieldInput(valueInput,'_last')].value, 218 of.elements[dspace_makeFieldInput(valueInput,'_first')].value); 219 else 220 value = of.elements[valueInput].value; 221 222 // if this is a repeating input, clear the source value so that e.g. 223 // clicking "Next" on a submit-describe page will not *add* the proposed 224 // lookup text as a metadata value: 225 if (isRepeating) 226 { 227 if (isName) 228 { 229 of.elements[dspace_makeFieldInput(valueInput,'_last')].value = null; 230 of.elements[dspace_makeFieldInput(valueInput,'_first')].value = null; 231 } 232 else 233 of.elements[valueInput].value = null; 234 } 235 } 236 237 // start spinner 238 var indicator = document.getElementById('lookup_indicator_id'); 239 if (indicator != null) 240 indicator.style.display = "inline"; 241 242 new Ajax.Request(contextPath+"/choices/"+field, 243 { 244 method: "get", 245 parameters: {query: value, format: 'select', collection: collID, 246 start: start, limit: limit}, 247 // triggered by any exception, even in success 248 onException: function(req, e) { 249 window.alert(fail+" Exception="+e); 250 if (indicator != null) indicator.style.display = "none"; 251 }, 252 // when http load of choices fails 253 onFailure: function() { 254 window.alert(fail+" HTTP error resonse"); 255 if (indicator != null) indicator.style.display = "none"; 256 }, 257 // format is <select><option authority="key" value="val">label</option>... 258 onSuccess: function(transport) { 259 var ul = transport.responseXML.documentElement; 260 var err = ul.getAttributeNode('error'); 261 if (err != null && err.value == 'true') 262 window.alert(fail+" Server indicates error in response."); 263 var opts = ul.getElementsByTagName('option'); 264 265 // update range message and update 'more' button 266 var oldStart = 1 * ul.getAttributeNode('start').value; 267 var nextStart = oldStart + opts.length; 268 var lastTotal = ul.getAttributeNode('total').value; 269 var resultMore = ul.getAttributeNode('more'); 270 form.elements['more'].disabled = !(resultMore != null && resultMore.value == 'true'); 271 form.elements['paramStart'].value = nextStart; 272 273 // clear select first 274 var select = form.elements['chooser']; 275 for (var i = select.length-1; i >= 0; --i) 276 select.remove(i); 277 278 // load select and look for default selection 279 var selectedByValue = -1; // select by value match 280 var selectedByChoices = -1; // Choice says its selected 281 for (var i = 0; i < opts.length; ++i) 282 { 283 var opt = opts.item(i); 284 var olabel = ""; 285 for (var j = 0; j < opt.childNodes.length; ++j) 286 { 287 var node = opt.childNodes[j]; 288 if (node.nodeName == "#text")
289 olabel += node.data; 290 } 291 var ovalue = opt.getAttributeNode('value').value; 292 var option = new Option(olabel, ovalue); 293 option.authority = opt.getAttributeNode('authority').value; 294 select.add(option, null); 295 if (value == ovalue) 296 selectedByValue = select.options.length - 1; 297 if (opt.getAttributeNode('selected') != null) 298 selectedByChoices = select.options.length - 1; 299 } 300 // add non-authority option if needed. 301 if (!isClosed) 302 { 303 select.add(new Option(dspace_formatMessage(nonAuthority, value), value), null); 304 } 305 var defaultSelected = -1; 306 if (selectedByChoices >= 0) 307 defaultSelected = selectedByChoices; 308 else if (selectedByValue >= 0) 309 defaultSelected = selectedByValue; 310 else if (select.options.length == 1) 311 defaultSelected = 0; 312 313 // load default-selected value 314 if (defaultSelected >= 0) 315 { 316 select.options[defaultSelected].defaultSelected = true; 317 var so = select.options[defaultSelected]; 318 if (isName) 319 { 320 form.elements['text1'].value = lastNameOf(so.value); 321 form.elements['text2'].value = firstNameOf(so.value); 322 } 323 else 324 form.elements['text1'].value = so.value; 325 } 326 327 // turn off spinner 328 if (indicator != null) 329 indicator.style.display = "none"; 330 331 // "results" status line 332 var statLast = nextStart + (isClosed ? 2 : 1); 333 334 form.statline.innerHTML = 335 dspace_formatMessage(form.statline_template, 336 oldStart+1, statLast, Math.max(lastTotal,statLast), value); 337 } 338 }); 339} 340 341// handler for change event on choice selector - load new values 342function DSpaceChoicesSelectOnChange () 343{ 344 // "this" is the window, 345 var form = document.getElementById('aspect_general_ChoiceLookupTransformer_div_lookup'); 346 var select = form.elements['chooser']; 347 var so = select.options[select.selectedIndex]; 348 var isName = form.elements['paramIsName'].value == 'true'; 349 350 if (isName) 351 { 352 form.elements['text1'].value = lastNameOf(so.value); 353 form.elements['text2'].value = firstNameOf(so.value); 354 } 355 else 356 form.elements['text1'].value = so.value; 357} 358 359// handler for lookup popup's accept (or add) button 360// stuff values back to calling page, force an add if necessary, and close. 361function DSpaceChoicesAcceptOnClick () 362{ 363 var select = this.form.elements['chooser']; 364 var isName = this.form.elements['paramIsName'].value == 'true'; 365 var isRepeating = this.form.elements['paramIsRepeating'].value == 'true'; 366 var valueInput = this.form.elements['paramValueInput'].value; 367 var authorityInput = this.form.elements['paramAuthorityInput'].value; 368 var formID = this.form.elements['paramFormID'].value; 369 var confIndicatorID = this.form.elements['paramConfIndicatorID'] == null?null:this.form.elements['paramConfIndicatorID'].value; 370 371 // default the authority input if not supplied.
372 if (authorityInput.length == 0) 373 authorityInput = dspace_makeFieldInput(valueInput,'_authority'); 374 375 // always stuff text fields back into caller's value input(s) 376 if (valueInput.length > 0) 377 { 378 var of = window.opener.document.getElementById(formID); 379 if (isName) 380 { 381 of.elements[dspace_makeFieldInput(valueInput,'_last')].value = this.form.elements['text1'].value; 382 of.elements[dspace_makeFieldInput(valueInput,'_first')].value = this.form.elements['text2'].value; 383 } 384 else 385 of.elements[valueInput].value = this.form.elements['text1'].value; 386 387 if (authorityInput.length > 0 && of.elements[authorityInput] != null) 388 { 389 // conf input is auth input, substitute '_confidence' for '_authority' 390 // if conf fieldname is FIELD_confidence_NUMBER, then '_authority_' => '_confidence_' 391 var confInput = ""; 392 393 var ci = authorityInput.lastIndexOf("_authority_"); 394 if (ci < 0) 395 confInput = authorityInput.substring(0, authorityInput.length-10)+'_confidence'; 396 else 397 confInput = authorityInput.substring(0, ci)+"_confidence_"+authorityInput.substring(ci+11); 398 // DEBUG: 399 // window.alert('Setting fields auth="'+authorityInput+'", conf="'+confInput+'"'); 400 401 var authValue = null; 402 if (select.selectedIndex >= 0 && select.options[select.selectedIndex].authority != null) 403 { 404 authValue = select.options[select.selectedIndex].authority; 405 of.elements[authorityInput].value = authValue; 406 } 407 if (of.elements[confInput] != null) 408 of.elements[confInput].value = 'accepted'; 409 // make indicator blank if no authority value 410 DSpaceUpdateConfidence(window.opener.document, confIndicatorID, 411 authValue == null || authValue == '' ? 'blank' :'accepted'); 412 } 413 414 // force the submit button -- if there is an "add" 415 if (isRepeating) 416 { 417 var add = of.elements["submit_"+valueInput+"_add"]; 418 if (add != null) 419 add.click(); 420 else 421 alert('Sanity check: Cannot find button named "submit_'+valueInput+'_add"'); 422 } 423 } 424 window.close(); 425 return false; 426} 427 428// handler for lookup popup's more button 429function DSpaceChoicesMoreOnClick () 430{ 431 DSpaceChoicesLoad(this.form); 432} 433 434// handler for lookup popup's cancel button 435function DSpaceChoicesCancelOnClick () 436{ 437 window.close(); 438 return false; 439} 440 441// -------------------- Utilities 442 443// DSpace person-name conventions, see DCPersonName 444function makePersonName(lastName, firstName) 445{ 446 return (firstName == null || firstName.length == 0) ? lastName : 447 lastName+", "+firstName; 448} 449 450// DSpace person-name conventions, see DCPersonName 451function firstNameOf(personName) 452{ 453 var comma = personName.indexOf(","); 454 return (comma < 0) ? "" : stringTrim(personName.substring(comma+1)); 455} 456 457// DSpace person-name conventions, see DCPersonName 458function lastNameOf(personName) 459{ 460 var comma = personName.indexOf(","); 461 return stringTrim((comma < 0) ? personName : personName.substring(0, comma)); 462} 463 464// replicate java String.trim() 465function stringTrim(str) 466{ 467 var start = 0; 468 var end = str.length; 469 for (; str.charAt(start) == ' '&& start < end; ++start) ; 470 for (; end > start && str.charAt(end-1) == ' '; --end) ; 471 return str.slice(start, end); 472} 473 474// format utility - replace @1@, @2@ etc with args 1, 2, 3.. 475// NOTE params MUST be monotonically increasing 476// NOTE we can't use "{1}" like the i18n catalog because it elides them!! 477// ...UNLESS maybe it were to be fixed not to when no params... 478function dspace_formatMessage() 479{ 480 var template = dspace_formatMessage.arguments[0]; 481 var i; 482 for (i = 1; i < arguments.length; ++i) 483 { 484 var pattern = '@'+i+'@'; 485 if (template.search(pattern) >= 0) 486 template = template.replace(pattern, dspace_formatMessage.arguments[i]); 487 } 488 return template; 489} 490 491// utility to make sub-field name of input field, e.g. _last, _first, _auth.. 492// if name ends with _1, _2 etc, put sub-name BEFORE the number 493function dspace_makeFieldInput(name, sub) 494{ 495 var i = name.search("_[0-9]+$"); 496 if (i < 0) 497 return name+sub; 498 else 499 return name.substr(0, i)+sub+name.substr(i); 500} 501 502// update the class value of confidence-indicating element
503function DSpaceUpdateConfidence(doc, confIndicatorID, newValue) 504{ 505 // sanity checks - need valid ID and a real DOM object 506 if (confIndicatorID == null || confIndicatorID == "") 507 return; 508 var confElt = doc.getElementById(confIndicatorID); 509 if (confElt == null) 510 return; 511 512 // add or update CSS class with new confidence value, e.g. "cf-accepted". 513 if (confElt.className == null) 514 confElt.className = "cf-"+newValue; 515 else 516 { 517 var classes = confElt.className.split(" "); 518 var newClasses = ""; 519 var found = false; 520 for (var i = 0; i < classes.length; ++i) 521 { 522 if (classes[i].match('^cf-[a-zA-Z0-9]+$')) 523 { 524 newClasses += "cf-"+newValue+" "; 525 found = true; 526 } 527 else 528 newClasses += classes[i]+" "; 529 } 530 if (!found) 531 newClasses += "cf-"+newValue+" "; 532 confElt.className = newClasses; 533 } 534} 535 536// respond to "onchanged" event on authority input field 537// set confidence to 'accepted' if authority was changed by user. 538function DSpaceAuthorityOnChange(self, confValueID, confIndicatorID) 539{ 540 var confidence = 'accepted'; 541 if (confValueID != null && confValueID != '') 542 { 543 var confValueField = document.getElementById(confValueID); 544 if (confValueField != null) 545 confValueField.value = confidence; 546 } 547 DSpaceUpdateConfidence(document, confIndicatorID, confidence) 548 return false; 549} 550 551// respond to click on the authority-value lock button in Edit Item Metadata: 552// "button" is bound to the image input for the lock button, "this" 553function DSpaceToggleAuthorityLock(button, authInputID) 554{ 555 // sanity checks - need valid ID and a real DOM object 556 if (authInputID == null || authInputID == '') 557 return false; 558 var authInput = document.getElementById(authInputID); 559 if (authInput == null) 560 return false; 561 562 // look for is-locked or is-unlocked in class list: 563 var classes = button.className.split(' '); 564 var newClass = ''; 565 var newLocked = false; 566 var found = false; 567 for (var i = 0; i < classes.length; ++i) 568 { 569 if (classes[i] == 'is-locked') 570 { 571 newLocked = false; 572 found = true; 573 } 574 else if (classes[i] == 'is-unlocked') 575 { 576 newLocked = true; 577 found = true; 578 } 579 else 580 newClass += classes[i]+' '; 581 } 582 if (!found) 583 return false; 584 // toggle the image, and set readability 585 button.className = newClass + (newLocked ? 'is-locked' : 'is-unlocked') + ' '; 586 authInput.readOnly = newLocked; 587 return false; 588}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.