autocomplete-lhc.js 310 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559
  1. /******/ (function(modules) { // webpackBootstrap
  2. /******/ // The module cache
  3. /******/ var installedModules = {};
  4. /******/
  5. /******/ // The require function
  6. /******/ function __webpack_require__(moduleId) {
  7. /******/
  8. /******/ // Check if module is in cache
  9. /******/ if(installedModules[moduleId]) {
  10. /******/ return installedModules[moduleId].exports;
  11. /******/ }
  12. /******/ // Create a new module (and put it into the cache)
  13. /******/ var module = installedModules[moduleId] = {
  14. /******/ i: moduleId,
  15. /******/ l: false,
  16. /******/ exports: {}
  17. /******/ };
  18. /******/
  19. /******/ // Execute the module function
  20. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  21. /******/
  22. /******/ // Flag the module as loaded
  23. /******/ module.l = true;
  24. /******/
  25. /******/ // Return the exports of the module
  26. /******/ return module.exports;
  27. /******/ }
  28. /******/
  29. /******/
  30. /******/ // expose the modules object (__webpack_modules__)
  31. /******/ __webpack_require__.m = modules;
  32. /******/
  33. /******/ // expose the module cache
  34. /******/ __webpack_require__.c = installedModules;
  35. /******/
  36. /******/ // define getter function for harmony exports
  37. /******/ __webpack_require__.d = function(exports, name, getter) {
  38. /******/ if(!__webpack_require__.o(exports, name)) {
  39. /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
  40. /******/ }
  41. /******/ };
  42. /******/
  43. /******/ // define __esModule on exports
  44. /******/ __webpack_require__.r = function(exports) {
  45. /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  46. /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  47. /******/ }
  48. /******/ Object.defineProperty(exports, '__esModule', { value: true });
  49. /******/ };
  50. /******/
  51. /******/ // create a fake namespace object
  52. /******/ // mode & 1: value is a module id, require it
  53. /******/ // mode & 2: merge all properties of value into the ns
  54. /******/ // mode & 4: return value when already ns object
  55. /******/ // mode & 8|1: behave like require
  56. /******/ __webpack_require__.t = function(value, mode) {
  57. /******/ if(mode & 1) value = __webpack_require__(value);
  58. /******/ if(mode & 8) return value;
  59. /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
  60. /******/ var ns = Object.create(null);
  61. /******/ __webpack_require__.r(ns);
  62. /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
  63. /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
  64. /******/ return ns;
  65. /******/ };
  66. /******/
  67. /******/ // getDefaultExport function for compatibility with non-harmony modules
  68. /******/ __webpack_require__.n = function(module) {
  69. /******/ var getter = module && module.__esModule ?
  70. /******/ function getDefault() { return module['default']; } :
  71. /******/ function getModuleExports() { return module; };
  72. /******/ __webpack_require__.d(getter, 'a', getter);
  73. /******/ return getter;
  74. /******/ };
  75. /******/
  76. /******/ // Object.prototype.hasOwnProperty.call
  77. /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  78. /******/
  79. /******/ // __webpack_public_path__
  80. /******/ __webpack_require__.p = "";
  81. /******/
  82. /******/
  83. /******/ // Load entry module and return exports
  84. /******/ return __webpack_require__(__webpack_require__.s = 0);
  85. /******/ })
  86. /************************************************************************/
  87. /******/ ([
  88. /* 0 */
  89. /***/ (function(module, exports, __webpack_require__) {
  90. Def = {};
  91. Def.PrototypeAPI = __webpack_require__(1);
  92. __webpack_require__(2);
  93. __webpack_require__(3)(Def);
  94. __webpack_require__(4)(Def.PrototypeAPI.$, jQuery, Def);
  95. __webpack_require__(5)(Def.PrototypeAPI.$, Def.Effect);
  96. __webpack_require__(6)(Def.PrototypeAPI.$, Def);
  97. __webpack_require__(7)(Def);
  98. __webpack_require__(8)(Def);
  99. __webpack_require__(9)(Def.PrototypeAPI.$, jQuery, Def);
  100. __webpack_require__(10)(Def.PrototypeAPI.$, jQuery, Def);
  101. __webpack_require__(11)(Def);
  102. __webpack_require__(12)(Def.PrototypeAPI.$, jQuery, Def);
  103. __webpack_require__(13)(Def.PrototypeAPI.$, jQuery, Def);
  104. __webpack_require__(14)(Def.PrototypeAPI.$, jQuery, Def);
  105. __webpack_require__(15)(Def.PrototypeAPI.$, jQuery, Def);
  106. __webpack_require__(16)(Def);
  107. /***/ }),
  108. /* 1 */
  109. /***/ (function(module, exports, __webpack_require__) {
  110. // Contains the subset of PrototypeJS APIs needed by this package, reimplemented
  111. // using jQuery.
  112. // Mostly copied (and modified) from the original PrototypeJS at
  113. // http://prototypejs.org/
  114. if (typeof Def === 'undefined') window.Def = {};
  115. Def.PrototypeAPI = function () {
  116. "use strict";
  117. var $break = {};
  118. /**
  119. * Constructs and returns an array from the given iterable.
  120. */
  121. function $A(iterable) {
  122. if (!iterable) return [];
  123. if ('toArray' in Object(iterable)) return iterable.toArray();
  124. var length = iterable.length || 0,
  125. results = new Array(length);
  126. while (length--) {
  127. results[length] = iterable[length];
  128. }
  129. return results;
  130. }
  131. /**
  132. * Returns the element with the given ID.
  133. * @param id the ID of the element.
  134. */
  135. function $(id) {
  136. var rtn = id; // "id" might be an element
  137. if (Def.PrototypeAPI.isString(id)) rtn = document.getElementById(id);
  138. return rtn;
  139. }
  140. var _toString = Object.prototype.toString;
  141. var Browser = function () {
  142. var ua = navigator.userAgent;
  143. var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
  144. return {
  145. IE: !!window.attachEvent && !isOpera,
  146. Opera: isOpera,
  147. WebKit: ua.indexOf('AppleWebKit/') > -1,
  148. Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
  149. MobileSafari: /Apple.*Mobile/.test(ua)
  150. };
  151. }();
  152. /**
  153. * Returns true if the given object is a function.
  154. */
  155. function isFunction(obj) {
  156. return _toString.call(obj) === '[object Function]';
  157. }
  158. /**
  159. * Returns true if the given object is a string.
  160. */
  161. function isString(object) {
  162. return _toString.call(object) === '[object String]';
  163. }
  164. /**
  165. * Returns true if the given object is an array.
  166. */
  167. function isArray(object) {
  168. return _toString.call(object) === '[object Array]';
  169. }
  170. /**
  171. * Returns the argument names of the given function.
  172. * @param f the function
  173. */
  174. function functionArgumentNames(f) {
  175. var names = f.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1].replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '').replace(/\s+/g, '').split(',');
  176. return names.length == 1 && !names[0] ? [] : names;
  177. }
  178. /**
  179. * Copies properties of source into destination.
  180. * @return destination
  181. */
  182. function extend(destination, source) {
  183. for (var property in source) {
  184. destination[property] = source[property];
  185. }
  186. return destination;
  187. }
  188. /**
  189. * An identity function
  190. * @return the object given.
  191. */
  192. function K(a) {
  193. return a;
  194. }
  195. /**
  196. * A function for constructing a class.
  197. */
  198. var Class = function () {
  199. var IS_DONTENUM_BUGGY = function () {
  200. for (var p in {
  201. toString: 1
  202. }) {
  203. if (p === 'toString') return false;
  204. }
  205. return true;
  206. }();
  207. function subclass() {}
  208. ;
  209. function create() {
  210. var parent = null,
  211. properties = $A(arguments);
  212. if (isFunction(properties[0])) parent = properties.shift();
  213. function klass() {
  214. this.initialize.apply(this, arguments);
  215. }
  216. extend(klass, Class.Methods);
  217. klass.superclass = parent;
  218. klass.subclasses = [];
  219. if (parent) {
  220. subclass.prototype = parent.prototype;
  221. klass.prototype = new subclass();
  222. parent.subclasses.push(klass);
  223. }
  224. for (var i = 0, length = properties.length; i < length; i++) {
  225. klass.addMethods(properties[i]);
  226. }
  227. if (!klass.prototype.initialize) klass.prototype.initialize = function () {}; // empty function
  228. klass.prototype.constructor = klass;
  229. return klass;
  230. }
  231. function addMethods(source) {
  232. var ancestor = this.superclass && this.superclass.prototype,
  233. properties = Object.keys(source);
  234. if (IS_DONTENUM_BUGGY) {
  235. if (source.toString != Object.prototype.toString) properties.push("toString");
  236. if (source.valueOf != Object.prototype.valueOf) properties.push("valueOf");
  237. }
  238. for (var i = 0, length = properties.length; i < length; i++) {
  239. var property = properties[i],
  240. value = source[property];
  241. if (ancestor && isFunction(value) && functionArgumentNames(value)[0] == "$super") {
  242. var method = value;
  243. value = function (m) {
  244. return function () {
  245. return ancestor[m].apply(this, arguments);
  246. };
  247. }(property).wrap(method);
  248. value.valueOf = function (method) {
  249. return function () {
  250. return method.valueOf.call(method);
  251. };
  252. }(method);
  253. value.toString = function (method) {
  254. return function () {
  255. return method.toString.call(method);
  256. };
  257. }(method);
  258. }
  259. this.prototype[property] = value;
  260. }
  261. return this;
  262. }
  263. return {
  264. create: create,
  265. Methods: {
  266. addMethods: addMethods
  267. }
  268. };
  269. }(); // end of Class
  270. var Enumerable = function () {
  271. function each(iterator, context) {
  272. try {
  273. this._each(iterator, context);
  274. } catch (e) {
  275. if (e != $break) throw e;
  276. }
  277. return this;
  278. }
  279. function eachSlice(number, iterator, context) {
  280. var index = -number,
  281. slices = [],
  282. array = this.toArray();
  283. if (number < 1) return array;
  284. while ((index += number) < array.length) {
  285. slices.push(array.slice(index, index + number));
  286. }
  287. return slices.collect(iterator, context);
  288. }
  289. function all(iterator, context) {
  290. iterator = iterator || K;
  291. var result = true;
  292. this.each(function (value, index) {
  293. result = result && !!iterator.call(context, value, index, this);
  294. if (!result) throw $break;
  295. }, this);
  296. return result;
  297. }
  298. function any(iterator, context) {
  299. iterator = iterator || K;
  300. var result = false;
  301. this.each(function (value, index) {
  302. if (result = !!iterator.call(context, value, index, this)) throw $break;
  303. }, this);
  304. return result;
  305. }
  306. function collect(iterator, context) {
  307. iterator = iterator || K;
  308. var results = [];
  309. this.each(function (value, index) {
  310. results.push(iterator.call(context, value, index, this));
  311. }, this);
  312. return results;
  313. }
  314. function detect(iterator, context) {
  315. var result;
  316. this.each(function (value, index) {
  317. if (iterator.call(context, value, index, this)) {
  318. result = value;
  319. throw $break;
  320. }
  321. }, this);
  322. return result;
  323. }
  324. function findAll(iterator, context) {
  325. var results = [];
  326. this.each(function (value, index) {
  327. if (iterator.call(context, value, index, this)) results.push(value);
  328. }, this);
  329. return results;
  330. }
  331. function grep(filter, iterator, context) {
  332. iterator = iterator || K;
  333. var results = [];
  334. if (Def.PrototypeAPI.isString(filter)) filter = new RegExp(RegExp.escape(filter));
  335. this.each(function (value, index) {
  336. if (filter.match(value)) results.push(iterator.call(context, value, index, this));
  337. }, this);
  338. return results;
  339. }
  340. function include(object) {
  341. if (isFunction(this.indexOf) && this.indexOf(object) != -1) return true;
  342. var found = false;
  343. this.each(function (value) {
  344. if (value == object) {
  345. found = true;
  346. throw $break;
  347. }
  348. });
  349. return found;
  350. }
  351. function inGroupsOf(number, fillWith) {
  352. fillWith = Object.isUndefined(fillWith) ? null : fillWith;
  353. return this.eachSlice(number, function (slice) {
  354. while (slice.length < number) {
  355. slice.push(fillWith);
  356. }
  357. return slice;
  358. });
  359. }
  360. function inject(memo, iterator, context) {
  361. this.each(function (value, index) {
  362. memo = iterator.call(context, memo, value, index, this);
  363. }, this);
  364. return memo;
  365. }
  366. function invoke(method) {
  367. var args = $A(arguments).slice(1);
  368. return this.map(function (value) {
  369. return value[method].apply(value, args);
  370. });
  371. }
  372. function max(iterator, context) {
  373. iterator = iterator || K;
  374. var result;
  375. this.each(function (value, index) {
  376. value = iterator.call(context, value, index, this);
  377. if (result == null || value >= result) result = value;
  378. }, this);
  379. return result;
  380. }
  381. function min(iterator, context) {
  382. iterator = iterator || K;
  383. var result;
  384. this.each(function (value, index) {
  385. value = iterator.call(context, value, index, this);
  386. if (result == null || value < result) result = value;
  387. }, this);
  388. return result;
  389. }
  390. function partition(iterator, context) {
  391. iterator = iterator || K;
  392. var trues = [],
  393. falses = [];
  394. this.each(function (value, index) {
  395. (iterator.call(context, value, index, this) ? trues : falses).push(value);
  396. }, this);
  397. return [trues, falses];
  398. }
  399. function pluck(property) {
  400. var results = [];
  401. this.each(function (value) {
  402. results.push(value[property]);
  403. });
  404. return results;
  405. }
  406. function reject(iterator, context) {
  407. var results = [];
  408. this.each(function (value, index) {
  409. if (!iterator.call(context, value, index, this)) results.push(value);
  410. }, this);
  411. return results;
  412. }
  413. function sortBy(iterator, context) {
  414. return this.map(function (value, index) {
  415. return {
  416. value: value,
  417. criteria: iterator.call(context, value, index, this)
  418. };
  419. }, this).sort(function (left, right) {
  420. var a = left.criteria,
  421. b = right.criteria;
  422. return a < b ? -1 : a > b ? 1 : 0;
  423. }).pluck('value');
  424. }
  425. function toArray() {
  426. return this.map();
  427. }
  428. function zip() {
  429. var args = $A(arguments);
  430. var collections = [this].concat(args).map($A);
  431. return this.map(function (value, index) {
  432. var rtn = [];
  433. for (var i = 0, len = collections.length; i < len; ++i) {
  434. rtn.push(collections[i][index]);
  435. }
  436. return rtn;
  437. });
  438. }
  439. function size() {
  440. return this.toArray().length;
  441. }
  442. function inspect() {
  443. return '#<Enumerable:' + this.toArray().inspect() + '>';
  444. }
  445. return {
  446. each: each,
  447. eachSlice: eachSlice,
  448. all: all,
  449. every: all,
  450. any: any,
  451. some: any,
  452. collect: collect,
  453. map: collect,
  454. detect: detect,
  455. findAll: findAll,
  456. select: findAll,
  457. filter: findAll,
  458. grep: grep,
  459. include: include,
  460. member: include,
  461. inGroupsOf: inGroupsOf,
  462. inject: inject,
  463. invoke: invoke,
  464. max: max,
  465. min: min,
  466. partition: partition,
  467. pluck: pluck,
  468. reject: reject,
  469. sortBy: sortBy,
  470. toArray: toArray,
  471. entries: toArray,
  472. zip: zip,
  473. size: size,
  474. inspect: inspect,
  475. find: detect
  476. };
  477. }(); // End of Enumerable
  478. /**
  479. * A modified toQueryParams that takes the search part of a URL and returns a
  480. * hash of the parameters.
  481. */
  482. function toQueryParams(search) {
  483. var separator = '&';
  484. var match = search.trim().match(/([^?#]*)(#.*)?$/);
  485. if (!match) return {};
  486. var keyValPairs = match[1].split(separator || '&');
  487. var rtn = {};
  488. for (var i = 0, len = keyValPairs.length; i < len; ++i) {
  489. var pair = keyValPairs[i];
  490. if ((pair = pair.split('='))[0]) {
  491. var key = decodeURIComponent(pair.shift()),
  492. value = pair.length > 1 ? pair.join('=') : pair[0];
  493. if (value != undefined) {
  494. value = value.gsub('+', ' ');
  495. value = decodeURIComponent(value);
  496. }
  497. if (key in hash) {
  498. if (!this.isArray(hash[key])) hash[key] = [hash[key]];
  499. hash[key].push(value);
  500. } else hash[key] = value;
  501. }
  502. }
  503. return rtn;
  504. }
  505. /**
  506. * Escapes a string for safe use as an HTML attribute.
  507. * @param val the string to be escaped
  508. * @return the escaped version of val
  509. */
  510. function escapeAttribute(val) {
  511. // Note: PrototypeJS' escapeHTML does not escape quotes, and for
  512. // attributes quotes need to be escaped.
  513. // JQuery does not provide an API for this at all.
  514. // (See: http://bugs.jquery.com/ticket/11773)
  515. // Various implementations are benchmarked here:
  516. // http://jsperf.com/htmlencoderegex
  517. // This one is the fastest (at least in Chrome).
  518. return val.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  519. }
  520. /* A namespace for the style-related functions */
  521. var Styles = {
  522. /* See PrototypeJS API */
  523. setOpacity: function setOpacity(element, value) {
  524. element = $(element);
  525. if (value == 1 || value === '') value = '';else if (value < 0.00001) value = 0;
  526. element.style.opacity = value;
  527. return element;
  528. },
  529. /* See PrototypeJS API */
  530. setStyle: function setStyle(element, styles) {
  531. element = $(element);
  532. var elementStyle = element.style,
  533. match;
  534. if (Def.PrototypeAPI.isString(styles)) {
  535. elementStyle.cssText += ';' + styles;
  536. if (styles.include('opacity')) {
  537. var opacity = styles.match(/opacity:\s*(\d?\.?\d*)/)[1];
  538. Def.PrototypeAPI.setOpacity(element, opacity);
  539. }
  540. return element;
  541. }
  542. for (var property in styles) {
  543. if (property === 'opacity') {
  544. Def.PrototypeAPI.setOpacity(element, styles[property]);
  545. } else {
  546. var value = styles[property];
  547. if (property === 'float' || property === 'cssFloat') {
  548. property = elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat';
  549. }
  550. elementStyle[property] = value;
  551. }
  552. }
  553. return element;
  554. },
  555. /* See PrototypeJS API */
  556. getStyle: function getStyle(element, style) {
  557. element = $(element); // style = normalizeStyleName(style);
  558. var value = element.style[style];
  559. if (!value || value === 'auto') {
  560. var css = document.defaultView.getComputedStyle(element, null);
  561. value = css ? css[style] : null;
  562. }
  563. if (style === 'opacity') return value ? parseFloat(value) : 1.0;
  564. return value === 'auto' ? null : value;
  565. },
  566. /**
  567. * Stores data about an element
  568. /* See PrototypeJS API */
  569. makePositioned: function makePositioned(element) {
  570. element = $(element);
  571. var position = Def.PrototypeAPI.getStyle(element, 'position'),
  572. styles = {};
  573. if (position === 'static' || !position) {
  574. styles.position = 'relative';
  575. if (Def.PrototypeAPI.Browser.Opera) {
  576. styles.top = 0;
  577. styles.left = 0;
  578. }
  579. Def.PrototypeAPI.setStyle(element, styles);
  580. jQuery(element).data('prototype_made_positioned', true);
  581. }
  582. return element;
  583. },
  584. /* See PrototypeJS API */
  585. undoPositioned: function undoPositioned(element) {
  586. element = $(element);
  587. var jqElem = jQuery(element);
  588. var madePositioned = jqElem.data('prototype_made_positioned');
  589. if (madePositioned) {
  590. jqElem.removeData('prototype_made_positioned');
  591. Def.PrototypeAPI.setStyle(element, {
  592. position: '',
  593. top: '',
  594. bottom: '',
  595. left: '',
  596. right: ''
  597. });
  598. }
  599. return element;
  600. }
  601. }; // Styles
  602. return {
  603. $: $,
  604. Class: Class,
  605. Enumerable: Enumerable,
  606. isString: isString,
  607. isArray: isArray,
  608. Browser: Browser,
  609. parseQuery: toQueryParams,
  610. escapeHTML: escapeAttribute,
  611. escapeAttribute: escapeAttribute,
  612. getStyle: Styles.getStyle,
  613. setStyle: Styles.setStyle,
  614. makePositioned: Styles.makePositioned,
  615. undoPositioned: Styles.undoPositioned,
  616. $A: $A
  617. };
  618. }();
  619. if (true) module.exports = Def.PrototypeAPI;
  620. /***/ }),
  621. /* 2 */
  622. /***/ (function(module, exports) {
  623. // Needed polyfills.
  624. // Object.assign
  625. // From
  626. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
  627. if (typeof Object.assign != 'function') {
  628. (function () {
  629. Object.assign = function (target) {
  630. 'use strict'; // We must check against these specific cases.
  631. if (target === undefined || target === null) {
  632. throw new TypeError('Cannot convert undefined or null to object');
  633. }
  634. var output = Object(target);
  635. for (var index = 1; index < arguments.length; index++) {
  636. var source = arguments[index];
  637. if (source !== undefined && source !== null) {
  638. for (var nextKey in source) {
  639. if (source.hasOwnProperty(nextKey)) {
  640. output[nextKey] = source[nextKey];
  641. }
  642. }
  643. }
  644. }
  645. return output;
  646. };
  647. })();
  648. } // String.trimLeft
  649. // There is no standard yet for trimLeft, so to ensure consistent behavior, I am
  650. // ignoring the trimLeft implemented in Chrome and Firefox (which could behave
  651. // differently).
  652. String.prototype.trimLeft = function () {
  653. // From: http://stackoverflow.com/a/1593909/360782
  654. var start = -1;
  655. while (this.charCodeAt(++start) < 33) {
  656. ;
  657. }
  658. return this.slice(start, this.length);
  659. };
  660. /***/ }),
  661. /* 3 */
  662. /***/ (function(module, exports, __webpack_require__) {
  663. // A replacement for the parts of jQuery (right now just jQuery UI) needed by
  664. // the autocomplete-lhc package. Parts of what is below may be borrowed from
  665. // jQuery, which is under the MIT license.
  666. (function () {
  667. function initJqueryLite(Def) {
  668. Def.jqueryLite = function () {
  669. "use strict";
  670. return {
  671. ui: {
  672. keyCode: {
  673. BACKSPACE: 8,
  674. COMMA: 188,
  675. DELETE: 46,
  676. DOWN: 40,
  677. END: 35,
  678. ENTER: 13,
  679. ESCAPE: 27,
  680. HOME: 36,
  681. LEFT: 37,
  682. PAGE_DOWN: 34,
  683. PAGE_UP: 33,
  684. PERIOD: 190,
  685. RIGHT: 39,
  686. SPACE: 32,
  687. TAB: 9,
  688. UP: 38
  689. }
  690. }
  691. };
  692. }(); // Eventually, but not yet, we'll try to replace jQuery entirely. For now, just copy in the above.
  693. Object.assign(jQuery, Def.jqueryLite);
  694. }
  695. if (true) module.exports = initJqueryLite;else {}
  696. })();
  697. /***/ }),
  698. /* 4 */
  699. /***/ (function(module, exports, __webpack_require__) {
  700. // A subset of Scriptaculous' effects.js code needed by this package, with
  701. // modifications.
  702. // See http://script.aculo.us/ for Scriptaculous, whose license is the following
  703. // MIT-style license:
  704. //
  705. // Copyright © 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  706. //
  707. // Permission is hereby granted, free of charge, to any person obtaining
  708. // a copy of this software and associated documentation files (the
  709. // “Software”), to deal in the Software without restriction, including
  710. // without limitation the rights to use, copy, modify, merge, publish,
  711. // distribute, sublicense, and/or sell copies of the Software, and to
  712. // permit persons to whom the Software is furnished to do so, subject to
  713. // the following conditions:
  714. //
  715. // The above copyright notice and this permission notice shall be
  716. // included in all copies or substantial portions of the Software.
  717. //
  718. // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
  719. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  720. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  721. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  722. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  723. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  724. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  725. if (typeof Def === 'undefined') window.Def = {};
  726. (function () {
  727. function initEffects($, jQuery, Def) {
  728. "use strict";
  729. var Class = Def.PrototypeAPI.Class;
  730. var Enumerable = Def.PrototypeAPI.Enumerable;
  731. var $A = Def.PrototypeAPI.$A;
  732. var isString = Def.PrototypeAPI.isString;
  733. var Effect = {
  734. _elementDoesNotExistError: {
  735. name: 'ElementDoesNotExistError',
  736. message: 'The specified DOM element does not exist, but is required for this effect to operate'
  737. },
  738. Transitions: {
  739. linear: function linear(a) {
  740. return a;
  741. },
  742. // identity function
  743. sinoidal: function sinoidal(pos) {
  744. return -Math.cos(pos * Math.PI) / 2 + .5;
  745. },
  746. reverse: function reverse(pos) {
  747. return 1 - pos;
  748. },
  749. flicker: function flicker(pos) {
  750. var pos = -Math.cos(pos * Math.PI) / 4 + .75 + Math.random() / 4;
  751. return pos > 1 ? 1 : pos;
  752. },
  753. wobble: function wobble(pos) {
  754. return -Math.cos(pos * Math.PI * (9 * pos)) / 2 + .5;
  755. },
  756. pulse: function pulse(pos, pulses) {
  757. return -Math.cos(pos * ((pulses || 5) - .5) * 2 * Math.PI) / 2 + .5;
  758. },
  759. spring: function spring(pos) {
  760. return 1 - Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6);
  761. },
  762. none: function none(pos) {
  763. return 0;
  764. },
  765. full: function full(pos) {
  766. return 1;
  767. }
  768. },
  769. DefaultOptions: {
  770. duration: 1.0,
  771. // seconds
  772. fps: 100,
  773. // 100= assume 66fps max.
  774. sync: false,
  775. // true for combining
  776. from: 0.0,
  777. to: 1.0,
  778. delay: 0.0,
  779. queue: 'parallel'
  780. }
  781. };
  782. Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; // --- Queues ---
  783. Effect.ScopedQueue = Class.create(Enumerable, {
  784. initialize: function initialize() {
  785. this.effects = [];
  786. this.interval = null;
  787. },
  788. _each: function _each(iterator) {
  789. this.effects._each(iterator);
  790. },
  791. add: function add(effect) {
  792. var timestamp = new Date().getTime();
  793. var position = isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position;
  794. switch (position) {
  795. case 'front':
  796. // move unstarted effects after this effect
  797. this.effects.findAll(function (e) {
  798. return e.state == 'idle';
  799. }).each(function (e) {
  800. e.startOn += effect.finishOn;
  801. e.finishOn += effect.finishOn;
  802. });
  803. break;
  804. case 'with-last':
  805. timestamp = this.effects.pluck('startOn').max() || timestamp;
  806. break;
  807. case 'end':
  808. // start effect after last queued effect has finished
  809. timestamp = this.effects.pluck('finishOn').max() || timestamp;
  810. break;
  811. }
  812. effect.startOn += timestamp;
  813. effect.finishOn += timestamp;
  814. if (!effect.options.queue.limit || this.effects.length < effect.options.queue.limit) this.effects.push(effect);
  815. if (!this.interval) this.interval = setInterval(jQuery.proxy(this.loop, this), 15);
  816. },
  817. remove: function remove(effect) {
  818. var i;
  819. while ((i = this.effects.indexOf(effect)) > -1) {
  820. this.effects.splice(i, 1);
  821. }
  822. if (this.effects.length == 0) {
  823. clearInterval(this.interval);
  824. this.interval = null;
  825. }
  826. },
  827. loop: function loop() {
  828. var timePos = new Date().getTime();
  829. for (var i = 0, len = this.effects.length; i < len; i++) {
  830. this.effects[i] && this.effects[i].loop(timePos);
  831. }
  832. }
  833. });
  834. Effect.Queues = {
  835. instances: {},
  836. get: function get(queueName) {
  837. if (!isString(queueName)) return queueName;
  838. return this.instances[queueName] || (this.instances[queueName] = new Effect.ScopedQueue());
  839. }
  840. };
  841. Effect.Queue = Effect.Queues.get('global'); // --- End of code for Queues ---
  842. Effect.Base = Class.create({
  843. position: null,
  844. start: function start(options) {
  845. if (options && options.transition === false) options.transition = Effect.Transitions.linear;
  846. this.options = jQuery.extend(jQuery.extend({}, Effect.DefaultOptions), options || {});
  847. this.currentFrame = 0;
  848. this.state = 'idle';
  849. this.startOn = this.options.delay * 1000;
  850. this.finishOn = this.startOn + this.options.duration * 1000;
  851. this.fromToDelta = this.options.to - this.options.from;
  852. this.totalTime = this.finishOn - this.startOn;
  853. this.totalFrames = this.options.fps * this.options.duration;
  854. this.render = function () {
  855. function dispatch(effect, eventName) {
  856. if (effect.options[eventName + 'Internal']) effect.options[eventName + 'Internal'](effect);
  857. if (effect.options[eventName]) effect.options[eventName](effect);
  858. }
  859. return function (pos) {
  860. if (this.state === "idle") {
  861. this.state = "running";
  862. dispatch(this, 'beforeSetup');
  863. if (this.setup) this.setup();
  864. dispatch(this, 'afterSetup');
  865. }
  866. if (this.state === "running") {
  867. pos = this.options.transition(pos) * this.fromToDelta + this.options.from;
  868. this.position = pos;
  869. dispatch(this, 'beforeUpdate');
  870. if (this.update) this.update(pos);
  871. dispatch(this, 'afterUpdate');
  872. }
  873. };
  874. }();
  875. this.event('beforeStart');
  876. if (!this.options.sync) Effect.Queues.get(isString(this.options.queue) ? 'global' : this.options.queue.scope).add(this);
  877. },
  878. loop: function loop(timePos) {
  879. if (timePos >= this.startOn) {
  880. if (timePos >= this.finishOn) {
  881. this.render(1.0);
  882. this.cancel();
  883. this.event('beforeFinish');
  884. if (this.finish) this.finish();
  885. this.event('afterFinish');
  886. return;
  887. }
  888. var pos = (timePos - this.startOn) / this.totalTime,
  889. frame = Math.round(pos * this.totalFrames);
  890. if (frame > this.currentFrame) {
  891. this.render(pos);
  892. this.currentFrame = frame;
  893. }
  894. }
  895. },
  896. cancel: function cancel() {
  897. if (!this.options.sync) Effect.Queues.get(isString(this.options.queue) ? 'global' : this.options.queue.scope).remove(this);
  898. this.state = 'finished';
  899. },
  900. event: function event(eventName) {
  901. if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
  902. if (this.options[eventName]) this.options[eventName](this);
  903. },
  904. inspect: function inspect() {
  905. var data = $H();
  906. for (property in this) {
  907. if (!Object.isFunction(this[property])) data.set(property, this[property]);
  908. }
  909. return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  910. }
  911. });
  912. Effect.Move = Class.create(Effect.Base, {
  913. initialize: function initialize(element) {
  914. this.element = $(element);
  915. if (!this.element) throw Effect._elementDoesNotExistError;
  916. var options = jQuery.extend({
  917. x: 0,
  918. y: 0,
  919. mode: 'relative'
  920. }, arguments[1] || {});
  921. this.start(options);
  922. },
  923. setup: function setup() {
  924. Def.PrototypeAPI.makePositioned(this.element);
  925. var dpapi = Def.PrototypeAPI;
  926. this.originalLeft = parseFloat(dpapi.getStyle(this.element, 'left') || '0');
  927. this.originalTop = parseFloat(dpapi.getStyle(this.element, 'top') || '0');
  928. if (this.options.mode == 'absolute') {
  929. this.options.x = this.options.x - this.originalLeft;
  930. this.options.y = this.options.y - this.originalTop;
  931. }
  932. },
  933. update: function update(position) {
  934. Def.PrototypeAPI.setStyle(this.element, {
  935. left: Math.round(this.options.x * position + this.originalLeft) + 'px',
  936. top: Math.round(this.options.y * position + this.originalTop) + 'px'
  937. });
  938. }
  939. });
  940. Effect.Shake = function (element) {
  941. element = $(element);
  942. var options = jQuery.extend({
  943. distance: 20,
  944. duration: 0.5
  945. }, arguments[1] || {});
  946. var distance = parseFloat(options.distance);
  947. var split = parseFloat(options.duration) / 10.0;
  948. var offset = jQuery(element).offset();
  949. var dpapi = Def.PrototypeAPI;
  950. var oldStyle = {
  951. top: offset.top,
  952. left: offset.left
  953. };
  954. return new Effect.Move(element, {
  955. x: distance,
  956. y: 0,
  957. duration: split,
  958. afterFinishInternal: function afterFinishInternal(effect) {
  959. new Effect.Move(effect.element, {
  960. x: -distance * 2,
  961. y: 0,
  962. duration: split * 2,
  963. afterFinishInternal: function afterFinishInternal(effect) {
  964. new Effect.Move(effect.element, {
  965. x: distance * 2,
  966. y: 0,
  967. duration: split * 2,
  968. afterFinishInternal: function afterFinishInternal(effect) {
  969. new Effect.Move(effect.element, {
  970. x: -distance * 2,
  971. y: 0,
  972. duration: split * 2,
  973. afterFinishInternal: function afterFinishInternal(effect) {
  974. new Effect.Move(effect.element, {
  975. x: distance * 2,
  976. y: 0,
  977. duration: split * 2,
  978. afterFinishInternal: function afterFinishInternal(effect) {
  979. new Effect.Move(effect.element, {
  980. x: -distance,
  981. y: 0,
  982. duration: split,
  983. afterFinishInternal: function afterFinishInternal(effect) {
  984. dpapi.setStyle(dpapi.undoPositioned(effect.element), oldStyle);
  985. }
  986. });
  987. }
  988. });
  989. }
  990. });
  991. }
  992. });
  993. }
  994. });
  995. }
  996. });
  997. };
  998. Def.Effect = Effect;
  999. }
  1000. if (true) module.exports = initEffects;else {}
  1001. })();
  1002. /***/ }),
  1003. /* 5 */
  1004. /***/ (function(module, exports, __webpack_require__) {
  1005. // These two methods are based on code from the web page (but don't use this URL, as it seems to have been
  1006. // taken over by some advertising company):
  1007. // http://www.garyharan.com/index.php/2007/11/26/how-to-unobtrusively-scroll-a-div-with-prototype-scriptaculous/
  1008. // They introduce a scrolling effect that can scroll to x and y coordinates
  1009. // instead of an element, and can scroll a div as well as a window.
  1010. // Wrap the definitions in a function to protect our version of global variables
  1011. (function () {
  1012. function initEffectScroll($, Effect) {
  1013. "use strict";
  1014. var Class = Def.PrototypeAPI.Class;
  1015. /**
  1016. * Sets the scroll position within an element.
  1017. * @param element the element whose contents are being scrolled.
  1018. * @param left the new horizontal scroll position
  1019. * @param top the new vertical scroll position
  1020. */
  1021. function scrollTo(element, left, top) {
  1022. var element = $(element);
  1023. if (arguments.length == 1) {
  1024. var pos = element.cumulativeOffset();
  1025. window.scrollTo(pos[0], pos[1]);
  1026. } else {
  1027. element.scrollLeft = left;
  1028. element.scrollTop = top;
  1029. }
  1030. return element;
  1031. }
  1032. ;
  1033. Effect.Scroll = Class.create();
  1034. jQuery.extend(jQuery.extend(Effect.Scroll.prototype, Effect.Base.prototype), {
  1035. /**
  1036. * Returns the current scroll position of element.
  1037. */
  1038. currentScrollPos: function currentScrollPos(element) {
  1039. // Store the current scroll position. This used to be done in setup, but
  1040. // in Chrome, the scroll position sometimes shifts (when a field is getting
  1041. // focused) between initialize and setup.
  1042. var scrollOffsets;
  1043. if (this.element === window) scrollOffsets = document.viewport.getScrollOffsets();else {
  1044. // Work around bug in Chrome (see comments in update).
  1045. if (this.element === document.documentElement && document.documentElement.scrollTop === 0 && document.documentElement.scrollLeft === 0) {
  1046. scrollOffsets = {
  1047. left: document.body.scrollLeft,
  1048. top: document.body.scrollTop
  1049. };
  1050. } else scrollOffsets = {
  1051. left: this.element.scrollLeft,
  1052. top: this.element.scrollTop
  1053. };
  1054. }
  1055. return scrollOffsets;
  1056. },
  1057. initialize: function initialize(element) {
  1058. this.element = $(element);
  1059. if (!this.element) throw Effect._elementDoesNotExistError; // Capture the target location.
  1060. var originalScrollPos = this.currentScrollPos(element);
  1061. var shift = jQuery.extend({
  1062. x: 0,
  1063. y: 0
  1064. }, arguments[1] || {});
  1065. var targetPos = {
  1066. x: originalScrollPos.left + shift.x,
  1067. y: originalScrollPos.top + shift.y
  1068. };
  1069. this.start(targetPos);
  1070. },
  1071. setup: function setup() {},
  1072. update: function update(pos) {
  1073. // Recalcaute the current scroll position in case it has changed. (The
  1074. // browser also tries to scroll to fields on focus events.)
  1075. var current = this.currentScrollPos(this.element);
  1076. var left = Math.round((this.options.x - current.left) * pos + current.left);
  1077. var top = Math.round((this.options.y - current.top) * pos + current.top);
  1078. scrollTo(this.element, left, top); // Work around a bug in Chrome. For chrome, if document.documentElement is
  1079. // being scrolled, we need instead to set the scroll position on
  1080. // document.body.
  1081. // See https://code.google.com/p/chromium/issues/detail?id=157855
  1082. // https://code.google.com/p/chromium/issues/detail?id=345592
  1083. // https://code.google.com/p/chromium/issues/detail?id=342307
  1084. if (this.element === document.documentElement) scrollTo(document.body, left, top);
  1085. }
  1086. });
  1087. }
  1088. if (true) module.exports = initEffectScroll;else {}
  1089. })();
  1090. /***/ }),
  1091. /* 6 */
  1092. /***/ (function(module, exports, __webpack_require__) {
  1093. // Based on: https://github.com/kangax/protolicious/blob/5b56fdafcd7d7662c9d648534225039b2e78e371/event.simulate.js
  1094. // (MIT License)
  1095. /**
  1096. * Event.simulate(@element, eventName[, options]) -> Element
  1097. *
  1098. * - @element: element to fire event on
  1099. * - eventName: name of event to fire (only MouseEvents and HTMLEvents interfaces are supported)
  1100. * - options: optional object to fine-tune event properties - pointerX, pointerY, ctrlKey, etc.
  1101. *
  1102. * $('foo').simulate('click'); // => fires "click" event on an element with id=foo
  1103. *
  1104. **/
  1105. (function () {
  1106. function initSimulate($, Def) {
  1107. "use strict";
  1108. var eventMatchers = {
  1109. 'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
  1110. 'MouseEvents': /^(?:click|mouse(?:down|up|over|move|out))$/
  1111. };
  1112. var defaultOptions = {
  1113. pointerX: 0,
  1114. pointerY: 0,
  1115. button: 0,
  1116. ctrlKey: false,
  1117. altKey: false,
  1118. shiftKey: false,
  1119. metaKey: false,
  1120. bubbles: true,
  1121. cancelable: true
  1122. };
  1123. Def.Event = {};
  1124. Def.Event.simulate = function (element, eventName) {
  1125. var options = jQuery.extend(defaultOptions, arguments[2] || {});
  1126. var oEvent,
  1127. eventType = null;
  1128. element = $(element);
  1129. for (var name in eventMatchers) {
  1130. if (eventMatchers[name].test(eventName)) {
  1131. eventType = name;
  1132. break;
  1133. }
  1134. }
  1135. if (!eventType) throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
  1136. if (document.createEvent) {
  1137. oEvent = document.createEvent(eventType);
  1138. if (eventType == 'HTMLEvents') {
  1139. oEvent.initEvent(eventName, options.bubbles, options.cancelable);
  1140. } else {
  1141. oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView, options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
  1142. }
  1143. element.dispatchEvent(oEvent);
  1144. } else {
  1145. options.clientX = options.pointerX;
  1146. options.clientY = options.pointerY;
  1147. oEvent = jQuery.extend(document.createEventObject(), options);
  1148. element.fireEvent('on' + eventName, oEvent);
  1149. }
  1150. return element;
  1151. };
  1152. }
  1153. if (true) module.exports = initSimulate;else {}
  1154. })();
  1155. /***/ }),
  1156. /* 7 */
  1157. /***/ (function(module, exports, __webpack_require__) {
  1158. if (typeof Def === 'undefined') Def = {};
  1159. (function () {
  1160. function initObservable(Def) {
  1161. "use strict";
  1162. /*
  1163. * This is a mix-in for objects/classes that want to provide hooks for
  1164. * custom events. See Def.Autocompleter.Event for an example of the usage.
  1165. * The methods here are not meant to be called directly by code that wants
  1166. * to register a callback. Instead, the class that extends this one can
  1167. * provide observe[Event Name] methods that call storeCallback.
  1168. */
  1169. Def.Observable = {
  1170. /**
  1171. * Storage of callback functions. Null means there are no callbacks
  1172. * registered.
  1173. */
  1174. callbacks_: null,
  1175. /**
  1176. * Runs the callbacks for the given event. (This is meant for internal
  1177. * use by the autocompleter code; other code should not call it.)
  1178. * The callbacks are run in a timeout so that normal operation of the
  1179. * autocompleter can continue.
  1180. * @param field the field on which the event occurred. This
  1181. * can be null for certain types of events (for which storeCallback
  1182. * was called with null).
  1183. * @param eventType the type of event (e.g. 'LIST_EXP' for list expansions)
  1184. * @param data a hash containing an event-specific data structure. See the
  1185. * relevant "observe..." method for the details of what callbacks can expect.
  1186. */
  1187. notifyObservers: function notifyObservers(field, eventType, data) {
  1188. if (this.callbacks_ !== null) {
  1189. data['field_id'] = field ? field.id : null;
  1190. setTimeout(function () {
  1191. var eventCallbacks = this.callbacks_[eventType];
  1192. if (eventCallbacks !== undefined) {
  1193. if (field !== null) {
  1194. var key = this.lookupKey(field);
  1195. var fieldEventCallbacks = eventCallbacks[key];
  1196. } // Also get the callbacks that apply to all fields
  1197. var allFieldEventCallbacks = eventCallbacks[null];
  1198. var allCallbacks = [fieldEventCallbacks, allFieldEventCallbacks];
  1199. for (var j = 0, maxJ = allCallbacks.length; j < maxJ; ++j) {
  1200. var callbackArray = allCallbacks[j];
  1201. if (callbackArray !== undefined) {
  1202. for (var i = 0, c = callbackArray.length; i < c; ++i) {
  1203. callbackArray[i].call(this, data);
  1204. }
  1205. }
  1206. }
  1207. }
  1208. }.bind(this));
  1209. }
  1210. },
  1211. /**
  1212. * Returns a lookup key for finding callbacks for the given field.
  1213. * This is overridable. By default it returns the field's name (or if
  1214. * that is not present, the ID, but it
  1215. * could be something more general, e.g. a part of a field's ID that is shared
  1216. * by several fields so that an event observer can be easily registered on a
  1217. * set of similar fields.
  1218. * @param field the field for which the lookup key is needed.
  1219. */
  1220. lookupKey: function lookupKey(field) {
  1221. return field.id || field.name; // STAG: look for "id" first
  1222. },
  1223. /**
  1224. * Stores a callback function. (This is meant for internal
  1225. * use by the classes that extend Observable; other code should not call it.)
  1226. * @param fieldLookupKey the lookup key for the field which the callback is
  1227. * registered. This could be the output of the lookupKey function. If null
  1228. * is passed, the callback will be called anytime the event occurs on any
  1229. * field.
  1230. * @param eventType The type of event for which the callback is to be called
  1231. * @param callback the callback function
  1232. */
  1233. storeCallback: function storeCallback(fieldLookupKey, eventType, callback) {
  1234. if (this.callbacks_ === null) this.callbacks_ = {};
  1235. var listExpCallbacks = this.callbacks_[eventType];
  1236. if (listExpCallbacks === undefined) {
  1237. listExpCallbacks = {};
  1238. this.callbacks_[eventType] = listExpCallbacks;
  1239. }
  1240. var fieldListExpCallbacks = listExpCallbacks[fieldLookupKey];
  1241. if (fieldListExpCallbacks === undefined) {
  1242. fieldListExpCallbacks = [];
  1243. listExpCallbacks[fieldLookupKey] = fieldListExpCallbacks;
  1244. }
  1245. fieldListExpCallbacks.push(callback);
  1246. },
  1247. /**
  1248. * Removes a callback function that was previously registered.
  1249. * @param fieldLookupKey the lookup key for the field which the callback is
  1250. * registered. This could be the output of the lookupKey function.
  1251. * @param eventType The type of event for which the callback is to be called
  1252. * @param callback the callback function to be removed
  1253. */
  1254. removeCallback: function removeCallback(fieldLookupKey, eventType, callback) {
  1255. if (this.callbacks_ !== null) {
  1256. var typeCallbacks = this.callbacks_[eventType];
  1257. if (typeCallbacks !== undefined) {
  1258. var fieldCallbacks = typeCallbacks[fieldLookupKey];
  1259. if (fieldCallbacks !== undefined) {
  1260. var callbackIndex = fieldCallbacks.indexOf(callback);
  1261. if (callbackIndex > -1) {
  1262. fieldCallbacks.splice(callbackIndex, 1);
  1263. }
  1264. }
  1265. }
  1266. }
  1267. }
  1268. };
  1269. }
  1270. ;
  1271. if (true) module.exports = initObservable;else {}
  1272. })();
  1273. /***/ }),
  1274. /* 8 */
  1275. /***/ (function(module, exports, __webpack_require__) {
  1276. (function () {
  1277. function defineSCR(Def) {
  1278. "use strict";
  1279. /**
  1280. * This manages a log meant to be used in assisting users with screen readers.
  1281. * For backwards compatibility, in addition to the constructor,
  1282. * Def.ScreenReaderLog.add(msg) will log msg to a DOM element with id
  1283. * "reader_log". However, that usage is deprecated.
  1284. * Current usage: var myLog = new Def.ScreenReaderLog(); myLog.add(msg);
  1285. * @param logID (optional) the ID of the DOM element to use for the log
  1286. */
  1287. Def.ScreenReaderLog = function (logID) {
  1288. if (logID === undefined) {
  1289. // Create a new log element
  1290. var baseID = 'reader_log';
  1291. var logID = baseID;
  1292. var counter = 1;
  1293. while (document.getElementById(logID)) {
  1294. logID = baseID + ++counter;
  1295. }
  1296. this.logElement_ = document.createElement('div');
  1297. this.logElement_.setAttribute('id', logID);
  1298. $('body').append($(this.logElement_)); // STAG: do not assume document.body
  1299. } else this.logElement_ = document.getElementById(logID);
  1300. this.logElement_.setAttribute('aria-live', 'assertive');
  1301. this.logElement_.setAttribute('aria-relevant', 'additions');
  1302. this.logElement_.setAttribute('role', 'log');
  1303. this.logElement_.setAttribute('class', 'screen_reader_only');
  1304. };
  1305. Def.ScreenReaderLog.prototype = {
  1306. /**
  1307. * Adds some text to the log to be read by the screen reader.
  1308. * @param text the text to be read (hopefully immediately). Note that at
  1309. * least with JAWS, sometimes the text isn't read if other things are
  1310. * happening at the same time.
  1311. */
  1312. add: function add(text) {
  1313. // In Firefox, we can just append the text as a text node. In IE 9, if
  1314. // you do that, it reads the log text from the beginning with each add.
  1315. // Putting each entry in p tags solves that, and still works okay in
  1316. // Firefox.
  1317. var p = document.createElement('p');
  1318. p.appendChild(document.createTextNode(text));
  1319. this.logElement_.appendChild(p);
  1320. }
  1321. }; // For backwards compatibility, include a static method
  1322. /**
  1323. * Adds some text to the log to be read by the screen reader.
  1324. * @param text the text to be read (hopefully immediately). Note that at
  1325. * least with JAWS, sometimes the text isn't read if other things are
  1326. * happening at the same time.
  1327. */
  1328. Def.ScreenReaderLog.add = function (text) {
  1329. if (!this.log_) this.log_ = new Def.ScreenReaderLog('reader_log');
  1330. this.log_.add(text);
  1331. };
  1332. }
  1333. if (true) module.exports = defineSCR;else {}
  1334. })();
  1335. /***/ }),
  1336. /* 9 */
  1337. /***/ (function(module, exports, __webpack_require__) {
  1338. if (typeof Def === 'undefined') window.Def = {};
  1339. (function () {
  1340. // Wrap the definitions in a function to protect our version of global variables
  1341. function defineRDR($, jQuery, Def) {
  1342. "use strict";
  1343. var Class = Def.PrototypeAPI.Class;
  1344. /**
  1345. * This class handles data requests that some fields (just autocompleting
  1346. * fields, at present) make to retrieve additional information about a record
  1347. * specified by the field's value (perhaps in combination with other field
  1348. * values). The class relies on the data_req_input parameter for a field
  1349. * to know which fields need to be sent along with value of the field
  1350. * making the request. It also relies on the data_req_output parameter
  1351. * to know what fields are populated from a request's return data.
  1352. */
  1353. Def.RecordDataRequester = Class.create();
  1354. var tmp = {
  1355. /**
  1356. * The HTML DOM form field element for which this instance
  1357. * will be retrieving additional data.
  1358. */
  1359. formField_: null,
  1360. /**
  1361. * The code field (if present) associated with formField_
  1362. */
  1363. codeField_: null,
  1364. /**
  1365. * The URL for getting additional data.
  1366. */
  1367. dataURL_: null,
  1368. /**
  1369. * This is an array of target field names (e.g. patient_name) of fields whose
  1370. * values should be sent along with the formField's value when sending the
  1371. * request for additional data. They are sent in the URL created for the
  1372. * ajax request, in the form fieldname=fieldvalue.
  1373. */
  1374. dataReqInput_: null,
  1375. /**
  1376. * This is an array of target field names (e.g. patient_name) of fields whose
  1377. * values will be filled in following a data request.
  1378. */
  1379. dataReqOutput_: null,
  1380. /**
  1381. * A hash of dataReqInput_ values (target field names) to
  1382. * the corresponding field objects.
  1383. */
  1384. inputFieldsHash_: null,
  1385. /**
  1386. * A hash of dataReqOutput_ values (target field names) to
  1387. * arrays of the corresponding field objects.
  1388. *
  1389. * Note that there can be more than one output field per target field
  1390. * name, as in the fetch rule form. This would usually be when writing
  1391. * a value for a field in a horizontal table, where multiple lines exist and
  1392. * the field in each line must be updated.
  1393. */
  1394. outputFieldsHash_: null,
  1395. /**
  1396. * It is important for the assignListData method to know whether this
  1397. * RecordDataRequester (RDR) has been previously used to fetch data.
  1398. * This keeps track of that.
  1399. */
  1400. noPriorDataReq_: true,
  1401. /**
  1402. * The latest pending Ajax request (if any).
  1403. */
  1404. latestPendingAjaxRequest_: null,
  1405. /**
  1406. * The field value at the time of the last data request.
  1407. */
  1408. lastFieldVal_: null,
  1409. /**
  1410. * The hash of data returned in response to the last data request
  1411. * (made by this RecordDataRequester).
  1412. */
  1413. lastDataHash_: null,
  1414. /**
  1415. * true if the output fields all in the same group as formField. In this
  1416. * case, we can cache the output fields.
  1417. */
  1418. outputToSameGroup_: null,
  1419. /*
  1420. * a hash of hashes that represent the fields that should be checked
  1421. * for list updating when the value of the field changes. Each hash
  1422. * key represents an output field - which should be one of the field
  1423. * names in the outputFieldsHash_. The value of each hash element should
  1424. * be a hash whose key is a condition string and whose value is the
  1425. * autocompleter whose list should be updated if the condition is met.
  1426. *
  1427. * autoCompUpdateList_{outputFieldA: {update_condition_a: [ac1,ac2,ac3,ac4],
  1428. * update_condition_b: [ac5,ac6]},
  1429. * outputFieldB: {update_condition_c: [ac10],
  1430. * update_condition_d: [ac20,ac2]} }
  1431. */
  1432. autoCompUpdateList_: null,
  1433. /**
  1434. * The class constructor.
  1435. * @param formField The HTML DOM form field element for which this instance
  1436. * will be retrieving additional data.
  1437. * @param dataURL The URL for getting additional data.
  1438. * @param dataReqInput This is an array of
  1439. * target field names (e.g. patient_name) of fields whose values should be
  1440. * sent along with the formField's value when sending the request
  1441. * for additional data. This may be null.
  1442. * @param dataReqOutput This is an array of
  1443. * target field names (e.g. patient_name) of fields whose values will
  1444. * be filled in following a data request.
  1445. * @param outputToSameGroup true if the output fields all in the same
  1446. * group as formField. In this case, we can cache the output
  1447. * fields.
  1448. */
  1449. initialize: function initialize(formField, dataURL, dataReqInput, dataReqOutput, outputToSameGroup) {
  1450. this.formField_ = formField;
  1451. this.dataURL_ = dataURL;
  1452. this.dataReqInput_ = dataReqInput;
  1453. this.dataReqOutput_ = dataReqOutput;
  1454. this.outputToSameGroup_ = outputToSameGroup;
  1455. this.setOutputNamesToRDRNames(formField, dataReqOutput);
  1456. },
  1457. /**
  1458. * This sets the mapping between the field that "owns" this
  1459. * RecordDataRequester and the field(s) that should get the
  1460. * output from it. The mapping is maintained in the
  1461. * Def.RecordDataRequester.outputFieldNameToRDRFieldName_ hash.
  1462. *
  1463. * This was originally part of the initialize function. However, we
  1464. * have a case (and will probably have more) where a field's list can
  1465. * come from more than one field's RDR. So this was broken out from
  1466. * the initialize function and is called there AND is also called
  1467. * when a list is passed to an autocompleter. That way the field that
  1468. * gets its list from multiple fields will get the latest list created.
  1469. *
  1470. * @param formField the form field that owns this RDR
  1471. * @param dataReqOutput the dataReqOutput list to use to get the
  1472. * output fields. These will always be target field names - without
  1473. * the prefix and suffix.
  1474. */
  1475. setOutputNamesToRDRNames: function setOutputNamesToRDRNames(formField, dataReqOutput) {
  1476. // Initialize this RDR's entries in outputFieldNameToRDRFieldName_.
  1477. // (See the declaration of this hash map below.)
  1478. var rdrTargetField = Def.Autocompleter.getFieldLookupKey(formField);
  1479. var map = Def.RecordDataRequester.outputFieldNameToRDRFieldName_;
  1480. for (var i = 0, max = dataReqOutput.length; i < max; ++i) {
  1481. map[dataReqOutput[i]] = rdrTargetField;
  1482. } // end do for each output field
  1483. },
  1484. // end setOutputNamesToRDRNames
  1485. /**
  1486. * A copy constructor, for a new field (e.g. another field in a new row
  1487. * of a table).
  1488. * @param fieldID the ID of the field for which the new RecordDataRequester
  1489. * is being constructed.
  1490. * @return a new RecordDataRequester for field field ID
  1491. */
  1492. dupForField: function dupForField(fieldID) {
  1493. return new Def.RecordDataRequester($(fieldID), this.dataURL_, this.dataReqInput_, this.dataReqOutput_, this.outputToSameGroup_);
  1494. },
  1495. /**
  1496. * Starts a request for the additional data needed for the field. When
  1497. * the request completes, a callback function in this class
  1498. * (onDataReqComplete) will be called to put the retrieved data into the
  1499. * fields specified by the dataReqOutput parameter given in the constructor.
  1500. * (However, the callback code relies in the server's copy of dataReqOutput.)
  1501. * If the field's value is blank, this will just call clearDataOutputFields()
  1502. * instead of making the AJAX request.
  1503. * @param listDataOnly (optional, default=false) Whether only data for
  1504. * autocompleter lists should be assigned. If this is true, any other
  1505. * data (including values for the autocompleter fields) will be ignored.
  1506. * (The "true" value is used by assignListData()).
  1507. */
  1508. requestData: function requestData(listDataOnly) {
  1509. this.noPriorDataReq_ = false;
  1510. if (!this.inputFieldsHash_) this.initFieldsHash(); // Start an Ajax request
  1511. if (!this.dataRequestOptions_) this.dataRequestOptions_ = {}; // Clear the output fields, which might have now have invalid data
  1512. // from a previous data request. The drug duplicate warning code
  1513. // in appSpecific.js waits until the output fields are filled in,
  1514. // which it can only do if the fields are cleared before the request
  1515. // is sent.
  1516. // Don't do this if listDataOnly is true, which happens when we are
  1517. // retrieving the list for a field on a saved form. We don't need to do
  1518. // the duplicate check then, because presumably it has already been done,
  1519. // and we don't want to wipe out the entered values.
  1520. if (!listDataOnly) this.clearDataOutputFields(); // We can no longer cache the assignment of onComplete, which now
  1521. // depends on the input parameter. (We could cache the bound versions
  1522. // of the functions, but I am not sure if it is worth it.)
  1523. this.dataRequestOptions_.complete = jQuery.proxy(listDataOnly ? this.onDataReqCompleteForListData : this.onDataReqComplete, this);
  1524. this.dataRequestOptions_.data = this.buildParameters();
  1525. this.latestPendingAjaxRequest_ = jQuery.ajax(this.dataURL_, this.dataRequestOptions_);
  1526. this.lastFieldVal_ = Def.Autocompleter.getFieldVal(this.formField_);
  1527. },
  1528. // end requestData
  1529. /**
  1530. * Under certain conditions, this method will set the lists of any
  1531. * output fields that have prefetched autocompleters. The use case
  1532. * for this method is where a prefetched autocompleter for some field,
  1533. * field B, is initially
  1534. * defined without a list, because its list is based on another field,
  1535. * field A,
  1536. * that has a RecordDataRequester that assigns the list for field B after
  1537. * a change to field A. However, for
  1538. * previously saved forms that are being edited, the value of the field A
  1539. * will sometimes be filled in, and no change event is issued,
  1540. * so field B doesn't get its list that way. Instead, we wait for
  1541. * field B to get a focus event, and then it uses this method (on
  1542. * field A's RecordDataRequester) to request that the list data for
  1543. * any output fields that have lists be filled in. Now, it is possible
  1544. * that the focus event on field B may be occuring just after a change
  1545. * event from field A. To avoid sending an unnecessary request, we check
  1546. * to see whether this RecordDataRequester has already requested data, and
  1547. * if so this method does nothing. (Also, if the field does not have a
  1548. * value, we don't do anything because we have nothing about which to request
  1549. * data.)
  1550. */
  1551. assignListData: function assignListData() {
  1552. if (this.noPriorDataReq_ && Def.Autocompleter.getFieldVal(this.formField_) !== '') this.requestData(true);
  1553. },
  1554. /**
  1555. * Returns the data retrieved for the given field on the last data
  1556. * request this RecordDataRequester made.
  1557. * @param targetField the target field name of the output field for which
  1558. * data is needed.
  1559. * @return the data, or null if there is no data for the given target field
  1560. * or if no data request has yet been run or if the RecordDataRequester's
  1561. * form field's value has changed since the last data request (in which case
  1562. * a new one is probably in progress).
  1563. */
  1564. getFieldData: function getFieldData(targetField) {
  1565. var rtn = null;
  1566. if (this.lastDataHash_ && Def.Autocompleter.getFieldVal(this.formField_) === this.lastFieldVal_) {
  1567. rtn = this.lastDataHash_[targetField];
  1568. }
  1569. return rtn;
  1570. },
  1571. /**
  1572. * This gets called when the data request comes back (after the user
  1573. * has made a selection from the list).
  1574. * @param response the AJAX response object
  1575. */
  1576. onDataReqComplete: function onDataReqComplete(response) {
  1577. // Do nothing if this is not the most recent request.
  1578. // There is a small chance (which becomes larger with network delays)
  1579. // that two requests from in the same field could be issued and return
  1580. // in the order A, B, A returns, B returns, or in the order
  1581. // A, B, B returns, A returns. This check keeps the output fields
  1582. // in a consistent state with the triggering field.
  1583. if (this.latestPendingAjaxRequest_ === response) {
  1584. // Do nothing if the field value has changed since this
  1585. this.lastFieldVal_ = Def.Autocompleter.getFieldVal(this.formField_); // The response text should be a JSON object for a data hash map.
  1586. var dataHash = response.responseJSON || JSON.parse(response.responseText);
  1587. this.lastDataHash_ = dataHash;
  1588. this.assignDataToFields(dataHash);
  1589. this.processUpdateList(dataHash);
  1590. this.latestPendingAjaxRequest_ = null;
  1591. }
  1592. },
  1593. /**
  1594. * This gets called when the data request comes back (after the user
  1595. * has made a selection from the list).
  1596. * @param response the AJAX response object
  1597. */
  1598. onDataReqCompleteForListData: function onDataReqCompleteForListData(response) {
  1599. // Do nothing if this is not the most recent request.
  1600. // (See onDataReqComplete.)
  1601. if (this.latestPendingAjaxRequest_ === response) {
  1602. // The response text should be a JSON object for a data hash map.
  1603. var dataHash = response.responseJSON || JSON.parse(response.responseText);
  1604. this.lastDataHash_ = dataHash;
  1605. this.assignDataToFields(dataHash, true);
  1606. this.processUpdateList(dataHash);
  1607. this.latestPendingAjaxRequest_ = null;
  1608. }
  1609. },
  1610. /**
  1611. * Clears the fields specified as output fields at construction. If the
  1612. * field has an associated prefetched list, the list will be cleared as
  1613. * well. I'm not sure yet if that is what we will always want to happen.
  1614. * At the moment, when a list field gets assigned a value from a
  1615. * data request, the value is the list's list, not the field value.
  1616. */
  1617. clearDataOutputFields: function clearDataOutputFields() {
  1618. if (!this.inputFieldsHash_) this.initFieldsHash();
  1619. var updatedFields = [];
  1620. var outputFieldsHash = this.getOutputFieldsHash();
  1621. for (var i = 0, max = this.dataReqOutput_.length; i < max; ++i) {
  1622. var fields = outputFieldsHash[this.dataReqOutput_[i]];
  1623. if (fields !== undefined) {
  1624. for (var j = 0, maxJ = fields.length; j < maxJ; ++j) {
  1625. var field = fields[j]; // Look for an autocompleter for the field. For now,
  1626. // we assume a prefetched list autocompleter.
  1627. if (field.autocomp && field.autocomp.setListAndField) {
  1628. // If we call setListAndField, that will take care of propagating
  1629. // the change in field value. For this reason, we don't add
  1630. // the field to the updatedFields list.
  1631. field.autocomp.setListAndField([]);
  1632. } else {
  1633. Def.Autocompleter.setFieldVal(field, '', false);
  1634. updatedFields.push(field);
  1635. }
  1636. }
  1637. }
  1638. }
  1639. Def.Autocompleter.Event.notifyObservers(null, 'RDR_CLEARING', updatedFields);
  1640. },
  1641. // end clearDataOutputFields
  1642. /**
  1643. * This function adds fields to the list of fields whose autocompleter
  1644. * lists need to be updated on a change to the field to which this rdr
  1645. * is attached.
  1646. *
  1647. * Use case: On the fetch rule form we have a field that might contain
  1648. * a drug name. (The field is a combo field, so it might be used for
  1649. * drug names or for other things). If a drug name is specified, the
  1650. * name of the drug drives the values in a list of valid strengths for
  1651. * the drug. Another field may need that strength list - IF yet ANOTHER
  1652. * field is set to a certain value.
  1653. *
  1654. * So, fieldA = the first field - the one that contains a drug name.
  1655. * fieldB = the second field - the one that needs the strength list.
  1656. * fieldC = the third field - the one that is set to a value that makes
  1657. * fieldB need the strength list.
  1658. *
  1659. * Other code works fine to allow fieldB to obtain the strength list
  1660. * from fieldA's record data requester when fieldB is first created.
  1661. * But suppose fieldB has already been created and filled in with a
  1662. * strength that is valid for the drug named in fieldA. Then the user
  1663. * CHANGES the value of fieldA, invalidating the value in fieldB. The
  1664. * value in fieldB needs to be removed and the autocompleter on fieldB
  1665. * needs a new list of valid strength values. That's where this list
  1666. * comes in. If fieldA has values in the autoCompUpdateList_ that belongs
  1667. * to its recordDataRequester object, and fieldA is changed, this list
  1668. * is used to find fields (via fieldC's value) whose autocompleter lists
  1669. * need to be updated. See the processUpdateList function for a
  1670. * description of how that happens.
  1671. *
  1672. * This function is in charge of adding fields to the autoCompUpdateList_.
  1673. * This is called when fieldC is set to a value that indicates fieldB
  1674. * needs the list. Using the above example, when fieldC is set to
  1675. * 'Strength', fieldB needs the strength list that is based on the
  1676. * drug name in fieldA. So when fieldB (another combo field) is 'created',
  1677. * it calls this to add its name to fieldA's autoCompUpdateList_,
  1678. * specifying that the update should be done only for instances of fieldC
  1679. * that are set to 'Strength'. (Naturally, all of these fields are in
  1680. * horizontal tables and so may have multiple instances).
  1681. *
  1682. * Hope this helps. lm, 10/2009.
  1683. *
  1684. * @param origOutputField the name (without the prefix or suffix) of the
  1685. * original output field for the list. This will correspond to the
  1686. * name this recordDataRequester uses in the dataHash it creates for
  1687. * the requested data. For example, for the strength list described
  1688. * above, this will be drug_strength_form.
  1689. *
  1690. * @param ac the autocompleter object for the field that needs the
  1691. * drug_strength_form list. In the example given above, this would
  1692. * be the autocompleter for fieldB.
  1693. *
  1694. * @param update_condition an array expressing the condition used to
  1695. * tell which versions of the output field (fieldC) need to be updated.
  1696. * In the example given above, this would contain the field name (WITH
  1697. * prefix and suffix) - fe_fieldA_x_x ... , the operator to be used
  1698. * (must be 'EQ' or 'NE') - in this case 'EQ', and the value that fieldA
  1699. * must contain - in this case 'Strength'.
  1700. */
  1701. addFieldsToUpdateList: function addFieldsToUpdateList(origOutputField, ac, update_condition) {
  1702. if (this.autoCompUpdateList_ === null) {
  1703. this.autoCompUpdateList_ = {};
  1704. }
  1705. if (this.autoCompUpdateList_[origOutputField] === null) {
  1706. this.autoCompUpdateList_[origOutputField] = {};
  1707. }
  1708. if (this.autoCompUpdateList_[origOutputField][update_condition] === null) {
  1709. this.autoCompUpdateList_[origOutputField][update_condition] = [ac];
  1710. } else {
  1711. this.autoCompUpdateList_[origOutputField][update_condition].push(ac);
  1712. }
  1713. },
  1714. // end addFieldsToUpdateList
  1715. /**
  1716. * This function processes the autoCompUpdateList_, if there is one for
  1717. * this recordDataRequester, against the dataHash passed in. If a key
  1718. * in the dataHash matches a key in the autoCompUpdateList_, and if
  1719. * the condition specified for that key in the autoCompUpdateList_
  1720. * evaluates to true, the selections list in the autocompleter(s)
  1721. * specified for that key in the autoCompUpdateList_ is updated with
  1722. * the list from the dataHash.
  1723. *
  1724. * See the addFieldsToUpdateList function for a description of the use case
  1725. * that drives this process.
  1726. *
  1727. * @param dataHash a hash whose keys are the names of fields (without a
  1728. * prefix or suffix) that are to receive the data obtained by this
  1729. * recordDataRequester, and whose values are the values to be placed in
  1730. * the field or given to the field's autocompleter.
  1731. */
  1732. processUpdateList: function processUpdateList(dataHash) {
  1733. if (this.autoCompUpdateList_ !== null) {
  1734. for (var key in dataHash) {
  1735. var val = dataHash[key];
  1736. var isList = val instanceof Array && val.length > 0;
  1737. if (this.autoCompUpdateList_[key] !== null) {
  1738. for (var cond in this.autoCompUpdateList_[key]) {
  1739. var condition = cond.split(',');
  1740. var trig_field = $(condition[0]);
  1741. var trig_val = Def.Autocompleter.getFieldVal(trig_field);
  1742. if (condition[1] === 'EQ' && trig_val === condition[2] || condition[1] === 'NE' && trig_val !== condition[2]) {
  1743. var acList = this.autoCompUpdateList_[key][cond];
  1744. for (var a = 0, max = acList.length; a < max; a++) {
  1745. // make sure that this is not a zombie autocompleter that
  1746. // is too much trouble to fully destroy.
  1747. if (acList[a].element) {
  1748. if (isList) {
  1749. if (val[0] instanceof Array) acList[a].setListAndField(val[0], val[1]);else acList[a].setListAndField(val);
  1750. } // Could be a null return for an autocompleter field
  1751. // that had a list and now does not.
  1752. else if (val === null) {
  1753. acList[a].setListAndField([], []);
  1754. }
  1755. }
  1756. } // end do for each autocompleter or field to be updated
  1757. } // end if the condition is true
  1758. } // end do for each condition
  1759. } // end if the list has entries for this key
  1760. } // end do for each key in the hash
  1761. } // end if this rdr has a list
  1762. },
  1763. // end processUpdateList
  1764. /* ***********************************************************************
  1765. * Functions below this line are not intended to be called directly (except by
  1766. * test code.)
  1767. */
  1768. /**
  1769. * Initializes input/outputFieldsHash_.
  1770. */
  1771. initFieldsHash: function initFieldsHash() {
  1772. this.inputFieldsHash_ = {};
  1773. var autospace = Def.Autocompleter;
  1774. if (this.dataReqInput_) {
  1775. var targetFields = this.dataReqInput_;
  1776. for (var i = 0, max = targetFields.length; i < max; ++i) {
  1777. var targetFieldName = targetFields[i];
  1778. var fields = autospace.findRelatedFields(this.formField_, targetFieldName);
  1779. if (fields.length === 1) {
  1780. // We found the field. Store it for future use.
  1781. this.inputFieldsHash_[targetFieldName] = fields[0];
  1782. }
  1783. }
  1784. } // If the output fields are for the same row, cache them.
  1785. if (this.outputToSameGroup_) this.outputFieldsHash_ = this.constructOutputFieldsHash();
  1786. },
  1787. /**
  1788. * Returns a hash of output target field names to arrays of matching
  1789. * fields. Uses a cached version if available.
  1790. */
  1791. getOutputFieldsHash: function getOutputFieldsHash() {
  1792. return this.outputToSameGroup_ ? this.outputFieldsHash_ : this.constructOutputFieldsHash();
  1793. },
  1794. /**
  1795. * Constructs a hash of output target field names to arrays of matching
  1796. * fields.
  1797. */
  1798. constructOutputFieldsHash: function constructOutputFieldsHash() {
  1799. var outputFH = {};
  1800. var targetFields = this.dataReqOutput_;
  1801. var autospace = Def.Autocompleter;
  1802. for (var i = 0, max = targetFields.length; i < max; ++i) {
  1803. var targetFieldName = targetFields[i];
  1804. var fields = autospace.findRelatedFields(this.formField_, targetFieldName); // There could be more than one field per targetFieldName (e.g. a
  1805. // field in a repeating line table, whose source list is determined by
  1806. // the value of another field outside the table.) So, we no longer
  1807. // restrict this to just one field.
  1808. if (fields.length > 0) {
  1809. // We found the field. Store it for future use.
  1810. outputFH[targetFieldName] = fields;
  1811. }
  1812. }
  1813. return outputFH;
  1814. },
  1815. /**
  1816. * Finds fields matching the target names in the given data hash's keys,
  1817. * and tries to assign to them (in some appropriate way) the values.
  1818. * @param dataHash the data hash
  1819. * @param listDataOnly (optional, default=false) Whether only data for
  1820. * autocompleter lists should be assigned. If this is true, any other
  1821. * data (including values for the autocompleter fields) will be ignored.
  1822. * (The "true" value is used by assignListData()). No field values
  1823. * are changed when this is true. The purpose is just to update lists.
  1824. */
  1825. assignDataToFields: function assignDataToFields(dataHash, listDataOnly) {
  1826. if (!this.inputFieldsHash_) this.initFieldsHash(); // For each key in the hash, look for a field to give the value to.
  1827. var updatedFields = [];
  1828. var updatedFieldIDToVal = {}; // hash from fields to field values
  1829. var outputFieldsHash = this.getOutputFieldsHash();
  1830. var lookupCache = Def.Autocompleter;
  1831. for (var key in dataHash) {
  1832. var fields = outputFieldsHash[key];
  1833. if (fields !== undefined) {
  1834. for (var i = 0, max = fields.length; i < max; ++i) {
  1835. var field = fields[i]; // We found the field.
  1836. var val = dataHash[key];
  1837. if (val instanceof Array) {
  1838. // Look for an autocompleter for the field. For now,
  1839. // we assume a prefetched list autocompleter.
  1840. if (field.autocomp !== null) {
  1841. // Now that lists carry codes with them, when setting the value
  1842. // for a list, it should normally have both a list of codes
  1843. // and a list of values. However, we support the case where
  1844. // there is just a list of values.
  1845. var targetField = lookupCache.getFieldLookupKey(field);
  1846. this.setOutputNamesToRDRNames(this.formField_, [targetField]); //this.setOutputNamesToRDRNames(this.formField_,
  1847. // splitFullFieldID(field.id)[1]) ;
  1848. // Note: Calling setListAndField takes care of propagating the
  1849. // change to the field, so we don't need to add it to the
  1850. // updatedFields array.
  1851. if (val.length > 0 && val[0] instanceof Array) {
  1852. // if there's an option
  1853. if (val[2]) {
  1854. field.autocomp.initHeadings(val[2]);
  1855. }
  1856. if (listDataOnly) // In this case, don't update the field.
  1857. field.autocomp.setList(val[0], val[1]); // list, codes
  1858. else field.autocomp.setListAndField(val[0], val[1]); // list, codes
  1859. } else {
  1860. if (listDataOnly) field.autocomp.setList(val); // just a list
  1861. else field.autocomp.setListAndField(val); // just a list
  1862. }
  1863. if (this.autoCompUpdateList_ !== null && this.autoCompUpdateList_[targetField] !== null) {
  1864. var fieldValHash = {};
  1865. fieldValHash[targetField] = val;
  1866. this.processUpdateList(fieldValHash);
  1867. }
  1868. } // end if we found an autocompleter for the field
  1869. } // end if the value is an array
  1870. else if (!listDataOnly) {
  1871. if (field.comboField !== undefined) {
  1872. // if the field is a "combo field"
  1873. field.comboField.mimicField(val, this.formField_.id);
  1874. } else {
  1875. // Assume a string
  1876. Def.Autocompleter.setFieldVal(field, val, false);
  1877. updatedFields.push(field);
  1878. updatedFieldIDToVal[field.id] = val;
  1879. }
  1880. } // end if the value is an array or listDataOnly is false
  1881. } // end do for each field
  1882. } // end if there are fields
  1883. } // end do for each key in the dataHash
  1884. Def.Autocompleter.Event.notifyObservers(null, 'RDR_ASSIGNMENT', {
  1885. updatedFields: updatedFields,
  1886. updatedFieldIDToVal: updatedFieldIDToVal,
  1887. listField: this.formField_
  1888. });
  1889. },
  1890. // end assignDataToFields
  1891. /**
  1892. * Constructs and returns the CGI parameter string for the URL used
  1893. * to make the data request.
  1894. */
  1895. buildParameters: function buildParameters() {
  1896. var data = {};
  1897. if (!this.inputFieldsHash_) this.initFieldsHash(); // Include the code field's value if there is a code field and if it
  1898. // has a value. If there isn't a code field value, include formField_'s
  1899. // value.
  1900. // Get the code value, assuming there is at most one (i.e. a non-multiselect
  1901. // list, which is the use case for RecordDataRequester).
  1902. var codeVal = this.formField_.autocomp.getSelectedCodes()[0];
  1903. if (codeVal !== null && codeVal !== undefined) data.code_val = codeVal;else data.field_val = Def.Autocompleter.getFieldVal(this.formField_);
  1904. if (this.dataReqInput_) {
  1905. for (var i = 0, max = this.dataReqInput_.length; i < max; ++i) {
  1906. var inputTargetFieldName = this.dataReqInput_[i];
  1907. var inputField = this.inputFieldsHash_[inputTargetFieldName];
  1908. if (inputField === undefined || inputField === null) throw 'Could not find field for ' + inputTargetFieldName;
  1909. data[inputTargetFieldName] = Def.Autocompleter.getFieldVal(inputField);
  1910. }
  1911. } // Lastly add authenticity_token for csrf protection
  1912. data.authenticity_token = window._token || '';
  1913. return data;
  1914. }
  1915. };
  1916. jQuery.extend(Def.RecordDataRequester.prototype, tmp);
  1917. tmp = null; // Additional class-level data members and methods.
  1918. jQuery.extend(Def.RecordDataRequester, {
  1919. /**
  1920. * A hash map from data request output field target field names to the
  1921. * target field names of the field whose data requester sends them data.
  1922. *
  1923. * So if fieldA has an autocompleter and a recordDataRequester that
  1924. * provides data for fieldB, this hash would contain an entry with a
  1925. * key of fieldB whose value is fieldA.
  1926. *
  1927. * This hash is created as the RecordDataRequesters are created.
  1928. */
  1929. outputFieldNameToRDRFieldName_: {},
  1930. /**
  1931. * Returns the RDR for the given field ID, or null if none can be located.
  1932. * @param outputFieldID the field ID of the output field for the
  1933. * RecordDataRequester that is to be returned.
  1934. */
  1935. getOutputFieldRDR: function getOutputFieldRDR(outputFieldID) {
  1936. var rtn = null;
  1937. var outputField = $(outputFieldID);
  1938. var outputFieldKey = Def.Autocompleter.getFieldLookupKey(outputField);
  1939. var rdrFieldName = this.outputFieldNameToRDRFieldName_[outputFieldKey];
  1940. var rdrFields = Def.Autocompleter.findRelatedFields(outputField, rdrFieldName);
  1941. if (rdrFields.length === 1) {
  1942. var rdrField = rdrFields[0];
  1943. var autocomp = rdrField.autocomp;
  1944. if (autocomp) rtn = autocomp.recDataRequester_;
  1945. }
  1946. return rtn;
  1947. }
  1948. });
  1949. }
  1950. if (true) module.exports = defineRDR;else {}
  1951. })();
  1952. /***/ }),
  1953. /* 10 */
  1954. /***/ (function(module, exports, __webpack_require__) {
  1955. if (typeof Def === 'undefined') window.Def = {};
  1956. (function () {
  1957. // Wrap the definitions in a function to protect our version of global variables
  1958. function defineAlarms($, jQuery, Def) {
  1959. "use strict";
  1960. Def.FieldAlarms = {
  1961. /**
  1962. * Sets off an alarm with few shakes and a "bonk" sound
  1963. *
  1964. * @param field a field which should be shaked when the alarm is setoff
  1965. **/
  1966. setOffAlarm: function setOffAlarm(field) {
  1967. if (this.bonk === undefined) this.bonk = new Audio(this.soundData_); // see bonk.js for soundData_
  1968. // Reset the play position back the the beginning, if the sound has
  1969. // been loaded sufficiently.
  1970. if (this.bonk.readyState >= 2) {
  1971. this.bonk.currentTime = 0; // now the sound can be replayed
  1972. if (this.bonk.currentTime !== 0) {
  1973. // Work around Chrome bug. However, this bug really
  1974. // has to do with the server not setting the Accept-Ranges
  1975. // and Content-Range headers on the response. The drawback
  1976. // of this workaround is that it will trigger a reload
  1977. // of the sound file (so it is better to adjust your server's
  1978. // configuration if you are having that problem).
  1979. this.bonk.src = this.bonk.src; // should reset the time to 0
  1980. }
  1981. }
  1982. this.bonk.play();
  1983. Def.Effect.Shake(field.id, 5);
  1984. },
  1985. // end of setOffAlarm
  1986. /**
  1987. * Cancels the alarm.
  1988. */
  1989. cancelAlarm: function cancelAlarm(field) {
  1990. field.shakeCanceled = true;
  1991. this.bonk.pause();
  1992. this.bonk.currentTime = 0;
  1993. }
  1994. };
  1995. }
  1996. if (true) module.exports = defineAlarms;else {}
  1997. })();
  1998. /***/ }),
  1999. /* 11 */
  2000. /***/ (function(module, exports, __webpack_require__) {
  2001. // This is a base64-encoded version of bonk.mp3. Using this version avoids
  2002. // issues finding the sound file when the JavaScript is packed together by a
  2003. // tool like grunt.
  2004. (function () {
  2005. function initSound(Def) {
  2006. Def.FieldAlarms.soundData_ = 'data:audio/mp3;base64,/+OAxAAAAAAAAAAAAEluZm8AAAAPAAAABwAADQ4AJCQkJCQkJCQkJCQkJCRJSUlJSUlJSUlJSUlJSW1tbW1tbW1tbW1tbW1tkpKSkpKSkpKSkpKSkpKStra2tra2tra2tra2trbb29vb29vb29vb29vb2///////////////////AAAAWkxBTUUzLjkyIAHDAAAAAAAAAAACQCQF2SEAAAAAAA0OXWa08wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+OAxABa1IIMF5uwACCDBAGAB/86aEEGTrP/hZNdJTyqr/pn4OCRDARfjtIEHqAZfjEAZzj+AZkhOeMiITA2IE0A1rkeA6cdIA9GMcA66Ro+MmOYSZqT4G0RQAGcoYgGMcNoGAQPQGJ4KH4wyIEXNjUiYGUkgoGMMRYGEkBIGFkLYGG8P4GGMDv8tmpPn0J4DEOL0DGmI8AYFQGEkHIGDUHIGDEBYGD4F3/NicKiaCSJfLYGBoAQr4GFIIgGDoGwGCYAgGCsC4WhAFAQAw7hT//PE+ZmREB5IkLgGAO8ghUAwXgbAaAuUwMAIHQMFYDwMQAngMUwZgIAJAx5CmAwpAQFrAwcAs//6d97vTdngYJAPAFAkAGAeK2DlAGABluASAMDAaAsL3gYBwBgYAwDgNAHDU///////AwBgDDfxkBjxS4aoGQWIIF0nxWgYoFgJoAwBABwFBBQMiCAH//4GAUAIGAMA4DQBwAgDhc2MmBgWAgDY6AcA9UJAILy11W1dqwrXViy2OQCw1TFUqEku6WxLaggJZ4skWaLxKbQemiY/+OCxCha9IJFQ9jwABJw+YDYFRgagfGCiDUYQ4UhhvilGSSRkauQAZguA5mCgBuYC4DRgKgBBcBJxU5SzpgBADmAMAWYBQC5gFgGGAaAUYBIAxgDgBGAGAAWaQeV070dgBymHLDLDKmVMoEw1xZajTDtnsNKVBQAEwCgITBSAeMBcAgFABJFLucp3ozGYzLYzGcq0qpYzGYy5L+w67LszVqmv3dVYzLY1Gniac/0PRp/mlJiNeh6TyyNRqNQ1GYZf2GX9jLOXVgatTQ0/0PQWYEALbAkTlAVSs5a6zl/Xdh2My6mtayzq1aXKmlVymtU2WPa0n3n+tZY1X2TgjVNTU1al3j+5mUfg0WUQn9ZWvyxu473zH/5r/5jzHX/zHDn719rLLn3bO+5VuxaGm/ZbLbUqtU1rLP48+l/Hm5bD1NTUtLZpsfwxxuZU2SPPcqaaQAhh+FryH0uXjKpcoDewl+bSaWw/SLjbQLj8agKecVQYFATAIARMA0CgwBgSzAVCgMFAdkxD47Tt0DxMGgRAwHwVTAtAHBwVhgyiBBgOSTMH//jgsRRWgyCFULXtuQ5FkPVXXrENw3ZinyWZDgGE5YWqR/J+P0gNEVBZdGEjl1wlmZgAkb+ljIIyOMR2tdf7s3Fbzds47dprvwFI701C0yHDlMW5VhyelLWaO8MgTbROzjZc4MF3gn26qtiU/vstkOdu+6bR0TXks1pmVLtAosYDlE1+mY4CbaNMqlEbpb2oKk8kty2tauSKXyZusW+ih2GakYp2DVpRlRUUnmnjVAX3jVmJ87EpXEdSBuqVOMVFA1JhGCC4zBkqv3MotL5fRw1HpdnGpJD8odr56cgJyb0jm3ihiHJuIu9KqODsvkUzhS1uSqWx0EAa/YQ/N+jcF8ZqGIgoEJBNBSV5hbC9sZqDr8MqW/L6sVl2MM2pIYKQJncuwwqmR50hLvF3Zl0dUbAs7ViSSi7QSKIS6Hc9Z27ECMHRnIgBDAGAaMAkFEwJwFzEzJPMjjX89/l5TFqBeMCUC0wEQATAoA6MEcGZLtXk5SdWTA29vrU1POVTvBQwfEZbbtQ9KZRD4oA2JAExp9XBhyXxJK8wCALmttYq1rcamv/44LEfVscgggNWvAAViw7L6zk9Vh+eytw9KmttugmdZokWo5RJ9Yu1LMGBV5fOP9uEEwAMlpIJbWahqdmqlWgn6Wwy59WI3ZbHZA4bymB6BIDAFm5xtUiw1q/GXrsw5uVPLDVSnhFR+FtwKuxe0odCHpdTY3aTeFXka5L9KrAYBKV0UruS7KR3rlM6Rf7O8IgAXsQVijiSx5VrLCt86jEXVWbYclwoBj7IlH2ssGpJG6NiIPhGnLaWuZe7uxJ/YTMu03Z2WbQA3GSqbLqaS4sOupDcvf2rF4csW3Rrqyr2lcMUuT1ONYu3KGWwqgqOjCIy+2FuUhYAeBcoRTVBCSCRkof4QBfxYWZQMRCQAd1kQCzHgTXLwA6pYCCAFTAtBRMD8A8wOAcTBoAvMFUQ6GoZRCMKEQcwQJFzHkCyMMINwwiBNTHuK1MwwuswSAgVbn3Y0wIwvARi7Jg+AoAIDkwFwCzCwDVMKwEcwNAbzCAB1MDADCD4KhrN4jAwAOAoD5gNgDoCTAnAZDAMzA6BpMFYCcwBQVjAmA/MBIAQwMQUIat/+OCxKV/zGIABZrwAMBRK/NGBWBKQgDquMCEBtC9SwwGQATAMAVEIERgWgPBcAwwIwKgwCswCwJq1/H8pmqVAAAUATDQIAGFgCAAAIYBAACxTASAEMCUBQqgRmAiBIPARGAYBWYAYBRYALMBgB198JmryrV5vY8ABPGAMAAvFWNPARgImBAAOhPLVs3MAMAMwCAFwwCEEAHmAsAmCAADACAXAQEIhAUAgAqNhgFgJNdxrZbmq3a1butJ1v0YBIA8aZIgPTQXQ+4QAAtRUiDiXKNgNAIAwCyRxgBgCISwSAUBgEiAAkEgCK/AAB6gSMoCAFZj3HHUzVwmavKtXn+zR/ggAxmKQb8l91L0w3HTDLfoL0KAeLqELfTsYKAABU4AsACBgBUdAYAGmaFwBkEq+ACAFOoZJ9JpFtmHoCkNkdca2W61btat3Wu739XH6qokhHEYI6j8M4aRRKZP2lS1p8UxbTope7WqTEFNRTMuOTKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv/jgsQ6AAADSAHAAACqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpMQU1FMy45Mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr/44LE/wAAA0gAAAAAqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuOTKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
  2007. }
  2008. if (true) module.exports = initSound;else {}
  2009. })();
  2010. /***/ }),
  2011. /* 12 */
  2012. /***/ (function(module, exports, __webpack_require__) {
  2013. // These autocompleters are based on the Autocompleter.Base class defined
  2014. // in the Script.aculo.us controls.js file. Most of the controls.js code has
  2015. // been overridden, and the part that hasn't has been included in this file.
  2016. //
  2017. // See http://script.aculo.us/ for Scriptaculous, whose license is the following
  2018. // MIT-style license:
  2019. //
  2020. // Copyright © 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  2021. //
  2022. // Permission is hereby granted, free of charge, to any person obtaining
  2023. // a copy of this software and associated documentation files (the
  2024. // “Software”), to deal in the Software without restriction, including
  2025. // without limitation the rights to use, copy, modify, merge, publish,
  2026. // distribute, sublicense, and/or sell copies of the Software, and to
  2027. // permit persons to whom the Software is furnished to do so, subject to
  2028. // the following conditions:
  2029. //
  2030. // The above copyright notice and this permission notice shall be
  2031. // included in all copies or substantial portions of the Software.
  2032. //
  2033. // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
  2034. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2035. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2036. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2037. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2038. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2039. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2040. if (typeof Def === 'undefined') window.Def = {};
  2041. (function () {
  2042. // Wrap the definitions in a function to protect our version of global variables
  2043. function initializeBase($, jQuery, Def) {
  2044. "use strict"; // A test for IE, borrowed from PrototypeJS -- and modified.
  2045. var Browser = Def.PrototypeAPI.Browser;
  2046. var isIE = !!window.attachEvent && !Browser.isOpera || navigator.userAgent.indexOf('Trident') >= 0;
  2047. Def.Autocompleter = {
  2048. // Namespace for DEF autocompletion stuff
  2049. // Variables related to autocompleters but independent of any particular
  2050. // autocompleter go here.
  2051. /**
  2052. * True if the browser is IE.
  2053. */
  2054. isIE: isIE,
  2055. /**
  2056. * A variable to keep track of which autocomplete text field is using the
  2057. * shared autocompletion area.
  2058. */
  2059. currentAutoCompField_: -1,
  2060. /**
  2061. * The suggestion mode constant that means rely on the statistics for
  2062. * the field's master table. See the suggestionMode option in
  2063. * defAutocompleterBaseInit.
  2064. */
  2065. USE_STATISTICS: 2,
  2066. /**
  2067. * The suggestion mode constant that means do not recommend one item from
  2068. * the returned list over the others. See the suggestionMode option in
  2069. * defAutocompleterBaseInit.
  2070. */
  2071. NO_COMPLETION_SUGGESTIONS: 0,
  2072. /**
  2073. * The suggestion mode constant that means the shortest match should
  2074. * recommended over other returned items. See the suggestionMode option in
  2075. * defAutocompleterBaseInit.
  2076. */
  2077. SUGGEST_SHORTEST: 1,
  2078. /**
  2079. * If the list items consist of multiple
  2080. * strings (fields) each, this is the string used to join together each list
  2081. * item's fields to produce the list item string the user sees in the list.
  2082. */
  2083. LIST_ITEM_FIELD_SEP: ' - ',
  2084. /**
  2085. * The screen reader log used by the autocompleter.
  2086. */
  2087. screenReaderLog_: new Def.ScreenReaderLog(),
  2088. /**
  2089. * Sets global options for customizing behavior of all autocompleters.
  2090. * Currently, what is supported is the overriding (or supplying) of the
  2091. * functions found in this object.
  2092. * @param options - a hash from one or more of this object's function names
  2093. * to a replacement function.
  2094. */
  2095. setOptions: function setOptions(options) {
  2096. jQuery.extend(this, options);
  2097. },
  2098. /**
  2099. * Returns value of the given form field element. (This may be overridden for
  2100. * special handling of values.)
  2101. * @param field the form field from which the value is needed.
  2102. */
  2103. getFieldVal: function getFieldVal(field) {
  2104. return field.value;
  2105. },
  2106. /**
  2107. * Sets the given form element's value to the given value. (This may be
  2108. * overridden for special handling of values.)
  2109. * @param field the DOM field element.
  2110. * @param val the new value, which should only be a string.
  2111. * @param runChangeEventObservers (default true) whether the change
  2112. * event observers for the field (which includes the update for the data
  2113. * model and the running of rules) should be run after the value is set.
  2114. */
  2115. setFieldVal: function setFieldVal(field, val, runChangeEventObservers) {
  2116. if (field.autocomp) field.autocomp.setFieldVal(val, runChangeEventObservers);else {
  2117. if (typeof runChangeEventObservers === 'undefined') runChangeEventObservers = true; // default
  2118. var fieldVal;
  2119. if (runChangeEventObservers) fieldVal = this.getFieldVal(field);
  2120. field.value = val;
  2121. if (runChangeEventObservers && fieldVal !== val) {
  2122. Def.Event.simulate(field, 'change');
  2123. }
  2124. }
  2125. },
  2126. /**
  2127. * Returns the field lookup key for the given field. Lookup keys are used
  2128. * to store information about a particular field (or maybe a column of
  2129. * identical fields) and are also used to store/retrieve the associated
  2130. * fields themselves. In systems where every field is unique, this can
  2131. * be the field's name or ID attribute, but it can also be a key shared by fields
  2132. * that have the same supporting list. If this is overridden, be sure to
  2133. * also override lookupFields.
  2134. * @param field a DOM field element
  2135. */
  2136. getFieldLookupKey: Def.Observable.lookupKey,
  2137. // default implementation
  2138. /**
  2139. * Returns the fields matching the given lookup key. (See getFieldLookupKey).
  2140. * If there is no match, an empty array will be returned.
  2141. * This should be overridden to match getFieldLookupKey if that is overridden.
  2142. * @param lookupKey a key for finding matching elements.
  2143. */
  2144. lookupFields: function lookupFields(lookupKey) {
  2145. var rtn = [];
  2146. for (var i = 0, numForms = document.forms.length; i < numForms; ++i) {
  2147. var match = document.forms[i].elements[lookupKey];
  2148. if (match !== undefined) rtn.push(match);
  2149. }
  2150. return rtn;
  2151. },
  2152. /**
  2153. * Returns the fields matching otherFieldLookupKey (see getFieldLookupKey)
  2154. * which are associated in some way with "field". This default implementation
  2155. * just returns all the fields matching otherFieldLookupKey.
  2156. * @param field the field for which related fields are needed
  2157. * @param otherFieldLookupKey the key for finding fields related to "field".
  2158. * (See getFieldLookupKey.)
  2159. * @returns an array of matching fields. The array will be empty if there
  2160. * are no matching fields.
  2161. */
  2162. findRelatedFields: function findRelatedFields(field, otherFieldLookupKey) {
  2163. return this.lookupFields(otherFieldLookupKey);
  2164. },
  2165. /**
  2166. * Returns the label text of the field with the given ID, or null if there
  2167. * isn't a label. This default implementation just returns null.
  2168. * @param fieldID the ID of the field for which the label is needed.
  2169. */
  2170. getFieldLabel: function getFieldLabel(fieldID) {
  2171. return null;
  2172. },
  2173. /**
  2174. * Returns the DOM node immediately containing the list item elements. This
  2175. * could either be a tbody or a ul, depending on options.tableFormat.
  2176. * If there is no list, the return value may be null.
  2177. */
  2178. listItemElementContainer: function listItemElementContainer() {
  2179. var rtn = jQuery("#completionOptions")[0].firstChild;
  2180. if (rtn && rtn.tagName === "TABLE") rtn = rtn.tBodies[0]; // tbody
  2181. return rtn;
  2182. },
  2183. /**
  2184. * Returns the list items elements, which will be either
  2185. * tr elements or li elements depending on options.tableFormat.
  2186. * If there is no list, the return value may be null.
  2187. */
  2188. listItemElements: function listItemElements() {
  2189. var rtn = null;
  2190. var itemContainer = this.listItemElementContainer();
  2191. if (itemContainer) rtn = itemContainer.childNodes;
  2192. return rtn;
  2193. },
  2194. /**
  2195. * Sets off an alarm when a field is in an invalid state.
  2196. * @param field the field that is invalid
  2197. */
  2198. setOffAlarm: function setOffAlarm(field) {
  2199. Def.FieldAlarms.setOffAlarm(field);
  2200. },
  2201. /**
  2202. * Cancels the alarm started by setOffAlarm.
  2203. */
  2204. cancelAlarm: function cancelAlarm(field) {
  2205. Def.FieldAlarms.cancelAlarm(field);
  2206. },
  2207. /**
  2208. * Stops further event handlers from runing on the element and prevents the
  2209. * default action.
  2210. * @param event a jQuery Event object
  2211. */
  2212. stopEvent: function stopEvent(event) {
  2213. event.stopImmediatePropagation();
  2214. event.preventDefault();
  2215. },
  2216. /**
  2217. * Logs a message for a screen reader to read. By default, this
  2218. * uses an instance of Def.ScreenReaderLog.
  2219. * @param msg the message to log
  2220. */
  2221. screenReaderLog: function screenReaderLog(msg) {
  2222. Def.Autocompleter.screenReaderLog_.add(msg);
  2223. },
  2224. /**
  2225. * Creates a cache for storing DOM values.
  2226. * @param directProps a hash of properties that should be directly defined
  2227. * on the hash. These properties should not include "data", "get",
  2228. * "invalidate", or "refresh".
  2229. * @param jitProps a hash of properties to functions that will be called
  2230. * (just in time) to initialize the properties. These properties will be
  2231. * accessible via the "get" function on the cache, and can be cleared with
  2232. * the "invalidate" function.
  2233. * @return the cache object
  2234. */
  2235. createDOMCache: function createDOMCache(directProps, jitProps) {
  2236. var rtn = {
  2237. data: {},
  2238. get: function get(item) {
  2239. var rtn = this.data[item];
  2240. if (rtn === undefined) rtn = this.data[item] = this.refresh[item].apply(this);
  2241. return rtn;
  2242. },
  2243. set: function set(item, value) {
  2244. this.data[item] = value;
  2245. },
  2246. // Drops the current value for "item"
  2247. invalidate: function invalidate(item) {
  2248. if (item) delete this.data[item];else this.data = {};
  2249. },
  2250. // A hash of functions to get a new property value
  2251. refresh: {// populated with jitProps
  2252. }
  2253. };
  2254. Object.assign(rtn, directProps);
  2255. Object.assign(rtn.refresh, jitProps);
  2256. return rtn;
  2257. }
  2258. };
  2259. /**
  2260. * A base class for our Ajax and local autocompleters.
  2261. */
  2262. Def.Autocompleter.Base = function () {}; // Base class object
  2263. /**
  2264. * Class-level stuff for Def.Autocompleter.Base.
  2265. */
  2266. jQuery.extend(Def.Autocompleter.Base, {
  2267. /**
  2268. * The maximum number of items to show below a field if the user has not
  2269. * used the "see more" feature.
  2270. */
  2271. MAX_ITEMS_BELOW_FIELD: 7,
  2272. /**
  2273. * Whether classInit() has been called.
  2274. */
  2275. classInit_: false,
  2276. /**
  2277. * Does one-time initialization needed by all autocompleters on the page.
  2278. */
  2279. classInit: function classInit() {
  2280. if (!this.classInit_) {
  2281. jQuery(document.body).append('<div id="searchResults" class="form_auto_complete"> \
  2282. <div id="completionOptionsScroller">\
  2283. <span class="auto_complete" id="completionOptions"></span> \
  2284. </div> \
  2285. <div id="moreResults">See more items (Ctl Ret)</div> \
  2286. <div id="searchCount">Search Results<!-- place holder for result count, \
  2287. needed for height calculation--></div> \
  2288. <div id="searchHint">Search Hint<!--place holder--></div> \
  2289. </div>');
  2290. jQuery('#moreResults').mousedown(function (event) {
  2291. var field = $(Def.Autocompleter.currentAutoCompField_);
  2292. field.autocomp.handleSeeMoreItems(event);
  2293. Def.Autocompleter.Event.notifyObservers(field, 'LIST_EXP', {
  2294. list_expansion_method: 'clicked'
  2295. });
  2296. });
  2297. jQuery('#completionOptionsScroller').mousedown(jQuery.proxy(function (event) {
  2298. // Here is a work-around for an IE-only issue in which if you use the scrollbar
  2299. // on the list, the field gets a blur event (and maybe a change event
  2300. // as well.) For IE, we set things to refocus the field and to ignore
  2301. // the change, blur, and focus events.
  2302. if (Def.Autocompleter.isIE && event.target.id === 'completionOptionsScroller') {
  2303. Def.Autocompleter.stopEvent(event);
  2304. Def.Autocompleter.completionOptionsScrollerClicked_ = true;
  2305. if ($(Def.Autocompleter.currentAutoCompField_) != -1) {
  2306. var field = $(Def.Autocompleter.currentAutoCompField_);
  2307. setTimeout(function () {
  2308. field.focus();
  2309. });
  2310. }
  2311. }
  2312. }, this));
  2313. this.classInit_ = true;
  2314. }
  2315. },
  2316. /**
  2317. * Provides a way to do a case-insensitive sort on a javascript array.
  2318. * Simply specify this function as the parameter to the sort function,
  2319. * as in myArray.sort(noCaseSort)
  2320. */
  2321. noCaseSort: function noCaseSort(a, b) {
  2322. var al = a.toLowerCase();
  2323. var bl = b.toLowerCase();
  2324. if (al > bl) return 1;else if (al < bl) return -1;else return 0;
  2325. },
  2326. /**
  2327. * Escapes a string for safe use as an HTML attribute.
  2328. * @param val the string to be escaped
  2329. * @return the escaped version of val
  2330. */
  2331. escapeAttribute: Def.PrototypeAPI.escapeAttribute,
  2332. /**
  2333. * Reverses escapeAttribute.
  2334. * @param escapedVal the string to be unescaped
  2335. * @return the unescaped version of escapedVal
  2336. */
  2337. unescapeAttribute: function unescapeAttribute(escapedVal) {
  2338. return escapedVal.replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, '\'').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
  2339. }
  2340. });
  2341. /**
  2342. * A cache of DOM values shared by all autocompleters on the page.
  2343. */
  2344. Def.Autocompleter.sharedDOMCache = Def.Autocompleter.createDOMCache({}, {
  2345. spacerDiv: function spacerDiv() {
  2346. var spacerDiv = $('spacer');
  2347. if (!spacerDiv) {
  2348. spacerDiv = document.createElement('div');
  2349. spacerDiv.setAttribute('id', 'spacer');
  2350. document.body.appendChild(spacerDiv);
  2351. }
  2352. return spacerDiv;
  2353. },
  2354. listContainer: function listContainer() {
  2355. return $('searchResults');
  2356. },
  2357. firstEntryWidth: function firstEntryWidth() {
  2358. return Def.Autocompleter.listItemElements()[0].offsetWidth;
  2359. },
  2360. listBoundingRect: function listBoundingRect() {
  2361. return this.get('listContainer').getBoundingClientRect();
  2362. },
  2363. viewPortWidth: function viewPortWidth() {
  2364. return document.documentElement.clientWidth;
  2365. },
  2366. spacerCoords: function spacerCoords() {
  2367. return this.get('spacerDiv').getBoundingClientRect();
  2368. }
  2369. }); // This is the definition for the Base instance methods. We define it in
  2370. // a temporary object to help NetBeans see it.
  2371. var tmp = {
  2372. /**
  2373. * The array of options passed to the constructor.
  2374. */
  2375. constructorOpts_: null,
  2376. /**
  2377. * The HTML DOM input element holding the score field for this list.
  2378. */
  2379. scoreField_: null,
  2380. /**
  2381. * Whether scoreField_ has been initialized.
  2382. */
  2383. scoreFieldInitialized_: false,
  2384. /**
  2385. * A hash between list values and the original unsorted list index,
  2386. * so that we can match up list values with arrays for codes and other
  2387. * data.
  2388. */
  2389. itemToDataIndex_: null,
  2390. /**
  2391. * The codes of the currently selected items, stored as values on a hash,
  2392. * where the keys are the display strings.
  2393. */
  2394. selectedCodes_: null,
  2395. /**
  2396. * The currently selected items' display strings, stored as keys on a hash.
  2397. * Some might not have codes, and so there might be more entries here than in
  2398. * selectedCodes_.
  2399. */
  2400. selectedItems_: null,
  2401. /**
  2402. * The currently selected items' completed data, as an array of hashes for
  2403. * each item.
  2404. */
  2405. selectedItemData_: null,
  2406. /**
  2407. * Whether the field value is required to be one from the list.
  2408. */
  2409. matchListValue_: null,
  2410. /**
  2411. * Whether the field is invalid.
  2412. */
  2413. invalidStatus_: false,
  2414. /**
  2415. * Whether the current field's value matches (even partially) one or more of
  2416. * the items in the list. If the user types the first few leters of an
  2417. * item, it's matchStatus_ is true, even though the field value does
  2418. * not equal a complete list item value.
  2419. */
  2420. matchStatus_: true,
  2421. /**
  2422. * Whether the field is responding to a focus event.
  2423. */
  2424. focusInProgress_: false,
  2425. /**
  2426. * Whether the field is losing focus but will be refocused after a short
  2427. * delay.
  2428. */
  2429. refocusInProgress_: false,
  2430. /**
  2431. * Whether or not the list will be shown below the field.
  2432. */
  2433. listBelowField_: true,
  2434. /**
  2435. * The element that holds the selection list and search hit count
  2436. * information.
  2437. */
  2438. listContainer: null,
  2439. /**
  2440. * A RecordDataRequester instance that will get used after list entry
  2441. * selection to pull back additional data.
  2442. */
  2443. recDataRequester_: null,
  2444. /**
  2445. * Whether the autocompleter is enabled. For example, this is false when
  2446. * there is no list assigned to the field.
  2447. */
  2448. enabled_: true,
  2449. /**
  2450. * The value of the list's field before it was filled in by changing the
  2451. * default list selection (e.g. by arrowing into the list).
  2452. */
  2453. preFieldFillVal_: null,
  2454. /**
  2455. * This is true when the field value is a list value. This is initially null,
  2456. * which means we do not know anything about the current field value. (A value
  2457. * of false means we know it is not a list value.)
  2458. */
  2459. fieldValIsListVal_: null,
  2460. /**
  2461. * A hash from item indexes to heading levels, for the full list.
  2462. * A level of 0 means the item is not a heading, level 1 means the item is a top-level
  2463. * heading, and level 2 means a sub-heading.
  2464. */
  2465. indexToHeadingLevel_: {},
  2466. /**
  2467. * An integer specifying what type of suggestion should
  2468. * be offered based on what the user has typed. For allowed values,
  2469. * see the suggestionMode option in defAutocompleterBaseInit.
  2470. */
  2471. suggestionMode_: Def.Autocompleter.SUGGEST_SHORTEST,
  2472. /**
  2473. * A reference to the last scroll effect (used in positioning).
  2474. */
  2475. lastScrollEffect_: null,
  2476. /**
  2477. * Whether or not multiple items can be selected from the list.
  2478. */
  2479. multiSelect_: false,
  2480. /**
  2481. * The hash of "extra data" for the current list. (This might only apply
  2482. * to search lists.)
  2483. */
  2484. listExtraData_: null,
  2485. /**
  2486. * The last value we tried to handle as a data entry, valid or not.
  2487. */
  2488. processedFieldVal_: null,
  2489. /**
  2490. * An initialization method for the base Def.Autocompleter class.
  2491. * @param field the ID or the DOM element of the field for which the
  2492. * list is displayed. If an element is provided, it must contain an ID
  2493. * attribute, or one will be assigned.
  2494. * @param options A hash of optional parameters. For the allowed keys see the
  2495. * subclasses. The base class uses the following keys:
  2496. * <ul>
  2497. * <li>matchListValue - whether the field should validate its value
  2498. * against the list (default: false)</li>
  2499. * <li>dataRequester - A DataRecordRequester for getting additional data
  2500. * after the user makes a selection from the completion list. This may be
  2501. * null, in which case no request for additional data is made.</li>
  2502. * <li>suggestionMode - an integer specifying what type of suggestion
  2503. * should be offered based on what the user has typed. If this is not
  2504. * specified, the default is [Def.Autocompleter.]SUGGEST_SHORTEST, which
  2505. * means "pick the shortest match." A value of
  2506. * NO_COMPLETION_SUGGESTIONS means no suggestions, and a value of
  2507. * USE_STATISTICS means that the suggestion is based on statistics, and
  2508. * that we will rely on the server to return the best item as the first
  2509. * item in the list.</li>
  2510. * <li>maxSelect - (default 1) The maximum number of items that can be
  2511. * selected. Use '*' for unlimited.</li>
  2512. * <li>wordBoundaryChars - (default none) For autocompleting based on part of the
  2513. * field's value, this should be an array of the characters that are
  2514. * considered "word" boundaries (e.g. a space, but could be something
  2515. * else). When this option is used, maxSelect is ignored.
  2516. * <li>scrolledContainer - the element that should be scrolled to bring
  2517. * the list into view if it would otherwise extend below the edge of the
  2518. * window. The default is document.documentElement (i.e. the whole
  2519. * window). This may be null if no scrolling is desired (e.g. if the
  2520. * list field is in a fixed position on the window), but in that
  2521. * case the list element might be unusually short.
  2522. * Note: At present the only tested cases of this parameter are the
  2523. * default value and null.</li>
  2524. * <li>nonMatchSuggestions - (default: false) Whether the user should be
  2525. * given a list of suggestions if they enter a non-matching value.
  2526. * This only applies when matchListValue is false. Also, the option is
  2527. * only presently supported by search autocompleters.</li>
  2528. * <li>headerBar - If the page has a fixed-position element at the top of
  2529. * the page (e.g. a top navigation bar), the autocompleter needs to know
  2530. * that so that when scrolling to show the list it doesn't scroll the current
  2531. * field under the header bar. This is the element ID for such a header
  2532. * bar.</li>
  2533. * <li>twoColumnFlow - (default: true) Whether to allow long lists to
  2534. * flow into two columns to show more of the list on the page.</li>
  2535. * </ul>
  2536. */
  2537. defAutocompleterBaseInit: function defAutocompleterBaseInit(field, options) {
  2538. if (!options) options = {}; // Rename the wordBoundaryChars option back to "tokens", the original
  2539. // name from Scriptaculous, which seemed to confuse tokens with token
  2540. // delimiters. Also allow the older "tokens" option name to be used
  2541. // for backward compatibility.
  2542. if (options.wordBoundaryChars) options.tokens = options.wordBoundaryChars;
  2543. if (options['suggestionMode'] !== undefined) this.suggestionMode_ = options['suggestionMode'];
  2544. this.twoColumnFlow_ = options.twoColumnFlow;
  2545. if (this.twoColumnFlow_ === undefined) this.twoColumnFlow_ = true;
  2546. if (options.tokens || options.maxSelect === undefined) options.maxSelect = 1;else if (options.maxSelect === '*') options.maxSelect = Infinity;
  2547. this.multiSelect_ = options.maxSelect !== 1;
  2548. if (options.scrolledContainer !== undefined) // allow null
  2549. this.scrolledContainer_ = options.scrolledContainer;else this.scrolledContainer_ = document.documentElement;
  2550. if ((this.nonMatchSuggestions_ = options['nonMatchSuggestions']) === undefined) this.nonMatchSuggestions_ = false; // default
  2551. this.constructorOpts_ = options;
  2552. this.selectedCodes_ = {};
  2553. this.selectedItems_ = {};
  2554. this.selectedItemData_ = [];
  2555. var dataRequester = options.dataRequester;
  2556. if (!Def.Autocompleter.Base.classInit_) Def.Autocompleter.Base.classInit();
  2557. this.matchListValue_ = options['matchListValue'] || false;
  2558. this.recDataRequester_ = dataRequester;
  2559. this.update = $('completionOptions');
  2560. this.options = options;
  2561. this.options.frequency = this.options.frequency || 0.01;
  2562. this.options.minChars = this.options.minChars || 1;
  2563. this.element = typeof field === 'string' ? $(field) : field;
  2564. this.ensureNeededAttrs(); // --- start of section copied from controls.js baseInitialize ---
  2565. this.hasFocus = false;
  2566. this.changed = false;
  2567. this.active = false;
  2568. this.index = 0;
  2569. this.entryCount = 0;
  2570. this.observer = null;
  2571. this.element.setAttribute('autocomplete', 'off'); // --- end of section copied from controls.js baseInitialize ---
  2572. jQuery(this.update).hide();
  2573. var jqElem = jQuery(this.element);
  2574. jqElem.blur(jQuery.proxy(this.onBlur, this));
  2575. jqElem.keydown(jQuery.proxy(this.onKeyPress, this)); // On clicks, reset the token bounds relative to the point of the click
  2576. if (this.options.tokens) {
  2577. jqElem.click(function () {
  2578. this.tokenBounds = null;
  2579. this.getTokenBounds(this.element.selectionStart);
  2580. }.bind(this));
  2581. } // If this is a multiselect list, put the field into a span.
  2582. if (options.maxSelect > 1) {
  2583. var fieldDiv = jQuery('<span class="autocomp_selected"><ul></ul></span>')[0];
  2584. var fieldParent = this.element.parentNode;
  2585. fieldParent.replaceChild(fieldDiv, this.element);
  2586. fieldDiv.appendChild(this.element);
  2587. this.selectedList = fieldDiv.firstChild;
  2588. } // ARIA markup for screen readers
  2589. // See http://test.cita.illinois.edu/aria/combobox/combobox2.php
  2590. // for an example that works with JAWS + Firefox. (It behaves
  2591. // like a regular combobox, according to a JAWS user.)
  2592. this.element.setAttribute('role', 'combobox'); // For aria-expanded, I am following the example at:
  2593. // http://www.w3.org/TR/wai-aria/roles#combobox
  2594. this.element.setAttribute('aria-expanded', 'false'); // Set up event handler functions.
  2595. this.onMouseDownListener = jQuery.proxy(this.onMouseDown, this);
  2596. jQuery(this.element).change(jQuery.proxy(this.onChange, this));
  2597. jQuery(this.element).keypress(jQuery.proxy(this.changeToFieldByKeys, this));
  2598. var fieldChanged = jQuery.proxy(function () {
  2599. this.typedSinceLastFocus_ = true;
  2600. }, this);
  2601. jQuery(this.element).bind('paste cut', fieldChanged); // Store a reference to the element that should be positioned in order
  2602. // to align the list with the field.
  2603. this.listContainer = Def.Autocompleter.sharedDOMCache.get('listContainer'); // Make the this.showList and this.hideList available to onShow and onHide
  2604. this.options.showList = jQuery.proxy(this.showList, this);
  2605. this.options.hideList = jQuery.proxy(this.hideList, this);
  2606. this.options.posAnsList = jQuery.proxy(this.posAnsList, this); // Undo the base class' hiding of the update element. (We're hiding
  2607. // the listContainer instead.)
  2608. this.update.style.display = "block"; // Store a reference to the autocompleter in the field object, for
  2609. // ease of accessing the autocompleter given the field.
  2610. this.element.autocomp = this; // Set the active list item index to -1, instead of 0 as in controls.js,
  2611. // because there might not be any list items.
  2612. this.index = -1;
  2613. this.initDOMCache();
  2614. this.oldElementValue = this.domCache.get('elemVal');
  2615. },
  2616. /**
  2617. * Sets the autocompleter's form element's value to the given value.
  2618. * Differs from Def.Autocompleter.setFieldVal in that it uses and manages
  2619. * the domCache values.
  2620. * @param val the new value, which should only be a string.
  2621. * @param runChangeEventObservers (default true) whether the change
  2622. * event observers for the field (which includes the update for the data
  2623. * model and the running of rules) should be run after the value is set.
  2624. */
  2625. setFieldVal: function setFieldVal(val, runChangeEventObservers) {
  2626. if (typeof runChangeEventObservers === 'undefined') runChangeEventObservers = true; // default
  2627. var fieldVal;
  2628. if (runChangeEventObservers) fieldVal = this.domCache.get('elemVal');
  2629. this.domCache.set('elemVal', this.element.value = this.oldElementValue = val);
  2630. this.tokenBounds = null;
  2631. if (runChangeEventObservers && fieldVal !== val) {
  2632. Def.Event.simulate(this.element, 'change');
  2633. }
  2634. },
  2635. /**
  2636. * Ensures there is an ID on the list's element, creating one if necessary.
  2637. */
  2638. ensureNeededAttrs: function ensureNeededAttrs() {
  2639. // The autocompleter uses the ID attribute of the element. If pElem
  2640. // does not have an ID, give it one.
  2641. var pElem = this.element;
  2642. if (pElem.id === '') {
  2643. // In this case just make up an ID.
  2644. if (!Def.Autocompleter.lastGeneratedID_) Def.Autocompleter.lastGeneratedID_ = 0;
  2645. pElem.id = 'ac' + ++Def.Autocompleter.lastGeneratedID_;
  2646. }
  2647. },
  2648. /**
  2649. * Used by the dupForField methods (defined in the subclasses) to
  2650. * duplicate the RecordDataRequester.
  2651. * @param fieldID the ID of the field being assigned to the new RecordDataRequester
  2652. * this method creates.
  2653. * @return the RecordDataRequester for the new autocompleter being
  2654. * constructed. (The return value will be null if this autocompleter
  2655. * doesn't have a RecordDataRequester.)
  2656. */
  2657. dupDataReqForField: function dupDataReqForField(fieldID) {
  2658. var dataReq = null;
  2659. if (this.recDataRequester_) dataReq = this.recDataRequester_.dupForField(fieldID);
  2660. return dataReq;
  2661. },
  2662. /**
  2663. * Returns the codes for the currently selected items or an empty array if there are none.
  2664. * If some of the selected items do not have a code, there will be null in
  2665. * that place in the returned array.
  2666. */
  2667. getSelectedCodes: function getSelectedCodes() {
  2668. var keys = this.getSelectedItems();
  2669. var rtn = [];
  2670. for (var i = 0, len = keys.length; i < len; ++i) {
  2671. rtn.push(this.selectedCodes_[keys[i]]);
  2672. }
  2673. return rtn;
  2674. },
  2675. /**
  2676. * Returns the display strings for the currently selected items or an empty array if there are none.
  2677. */
  2678. getSelectedItems: function getSelectedItems() {
  2679. return Object.keys(this.selectedItems_);
  2680. },
  2681. /**
  2682. * Returns all information about the currently selected list items.
  2683. * @return an array of hashes, each with at least a "text" property for the
  2684. * item's display text. The hashes may also contain (if the data was
  2685. * provided) properties "code", "code_system", and "data" (which for search
  2686. * lists contains the "extra data" fields for that item). The return value
  2687. * will be null if there are no selected items.
  2688. */
  2689. getSelectedItemData: function getSelectedItemData() {
  2690. return this.selectedItemData_.length > 0 ? this.selectedItemData_ : null;
  2691. },
  2692. /**
  2693. * Adds the code for the current item in the field to the list of selected
  2694. * codes, and does the same for the item text. If this is not a multi-select
  2695. * list, the newly selected code will replace the others. The text and
  2696. * code values can be provided to set the currently stored value. This
  2697. * does not broadcast any events.
  2698. * @param itemText (optional) if provided, this will be the selected text rather
  2699. * than the current item in the field. When this is provided, it is
  2700. * assumed that "code" is provided too.
  2701. * @param code (optional) if provided, this will be the code for the
  2702. * selected text rather that then code for the item currently in the field.
  2703. * If this is provided, itemText must be provided too.
  2704. */
  2705. storeSelectedItem: function storeSelectedItem(itemText, code) {
  2706. if (itemText === undefined) {
  2707. itemText = this.domCache.get('elemVal');
  2708. code = this.getItemCode(itemText);
  2709. }
  2710. if (!this.multiSelect_) {
  2711. this.selectedCodes_ = {};
  2712. this.selectedItems_ = {};
  2713. this.selectedItemData_ = [];
  2714. }
  2715. if (itemText) {
  2716. var hasCode = code !== null && code !== undefined;
  2717. if (hasCode) this.selectedCodes_[itemText] = code;
  2718. this.selectedItems_[itemText] = 1;
  2719. var itemData;
  2720. if (this.getItemData) itemData = this.getItemData(itemText);else {
  2721. itemData = {
  2722. text: itemText
  2723. };
  2724. if (hasCode) itemData.code = code;
  2725. }
  2726. this.selectedItemData_.push(itemData);
  2727. }
  2728. },
  2729. /**
  2730. * Returns the code for the given item text, or null if there isn't one.
  2731. */
  2732. getItemCode: function getItemCode(itemText) {
  2733. if (!this.itemToDataIndex_) this.initItemToDataIndex();
  2734. var dataIndex = this.itemToDataIndex_[itemText];
  2735. var newCode = null;
  2736. if (dataIndex !== undefined && this.itemCodes_) newCode = this.itemCodes_[dataIndex];
  2737. return newCode;
  2738. },
  2739. /**
  2740. * Appends the given string (presumably a list item, but possibly off the
  2741. * list) to the selected area. This is only for multi-select lists. Do
  2742. * not call it for single-select lists, or you will get an error.
  2743. * @param text the text to be added to the list of selected items.
  2744. * @return an HTML-escaped version of the "text"
  2745. */
  2746. addToSelectedArea: function addToSelectedArea(text) {
  2747. var escapedVal = Def.Autocompleter.Base.escapeAttribute(text);
  2748. var li = jQuery('<li><button type="button" alt="' + escapedVal + '"><span aria-hidden="true">&times;</span></button>' + escapedVal + '</li>')[0];
  2749. this.selectedList.appendChild(li);
  2750. var span = li.childNodes[0];
  2751. jQuery(span).click(jQuery.proxy(this.removeSelection, this));
  2752. return escapedVal;
  2753. },
  2754. /**
  2755. * Moves the current field string to the selected area (for multi-select
  2756. * lists). After this, the field will be blank.
  2757. */
  2758. moveEntryToSelectedArea: function moveEntryToSelectedArea() {
  2759. var escapedVal = this.addToSelectedArea(this.domCache.get('elemVal'));
  2760. this.setFieldVal(this.processedFieldVal_ = '', false);
  2761. Def.Autocompleter.screenReaderLog('Selected ' + escapedVal);
  2762. if (this.index >= 0) {
  2763. // i.e. if it is a list item
  2764. // Delete selected item
  2765. var itemContainer = Def.Autocompleter.listItemElementContainer();
  2766. itemContainer.removeChild(this.getCurrentEntry()); // Having deleted that item, we now need to update the the remaining ones
  2767. --this.entryCount;
  2768. var itemNodes = itemContainer.childNodes;
  2769. for (var i = this.index, len = itemNodes.length; i < len; ++i) {
  2770. itemNodes[i].autocompleteIndex = i;
  2771. }
  2772. if (this.index == this.entryCount) --this.index;
  2773. if (this.numHeadings_) {
  2774. // Move index forward until there is a non-heading entry. If there
  2775. // isn't one forward, try backward.
  2776. var startPos = this.index;
  2777. while (this.index < this.entryCount && this.liIsHeading(this.getCurrentEntry())) {
  2778. ++this.index;
  2779. }
  2780. if (this.index == this.entryCount) {
  2781. // no non-heading found
  2782. this.index = startPos - 1;
  2783. while (this.index > 0 && this.liIsHeading(this.getCurrentEntry())) {
  2784. --this.index;
  2785. }
  2786. }
  2787. } // Mark the new "current" item as selected
  2788. this.render();
  2789. } // Make the list "active" again (functional) and reposition
  2790. this.active = true;
  2791. this.hasFocus = true;
  2792. this.posAnsList();
  2793. },
  2794. /**
  2795. * For a multi-select list, this is an event handler that removes an item
  2796. * from the selected area.
  2797. * @param event the click event on the item to be removed.
  2798. */
  2799. removeSelection: function removeSelection(event) {
  2800. var li = event.target.parentNode;
  2801. if (event.target.tagName === 'SPAN') // the span within the button
  2802. li = li.parentNode;
  2803. li.parentNode.removeChild(li);
  2804. var itemText = li.childNodes[1].textContent;
  2805. delete this.selectedCodes_[itemText];
  2806. delete this.selectedItems_[itemText];
  2807. for (var i = 0, len = this.selectedItemData_.length; i < len; ++i) {
  2808. if (this.selectedItemData_[i].text === itemText) {
  2809. this.selectedItemData_.splice(i, 1);
  2810. break;
  2811. }
  2812. }
  2813. this.listSelectionNotification(itemText, true, true);
  2814. Def.Autocompleter.screenReaderLog('Unselected ' + itemText);
  2815. },
  2816. /**
  2817. * Returns true if the given text is one of the list items that
  2818. * has already been selected (for multi-select lists).
  2819. */
  2820. isSelected: function isSelected(itemText) {
  2821. return this.selectedItems_ && this.selectedItems_[itemText] !== undefined;
  2822. },
  2823. /**
  2824. * Returns the score field for this list, or null if there isn't one
  2825. */
  2826. getScoreField: function getScoreField() {
  2827. if (!this.scoreFieldInitialized_) {
  2828. this.scoreField_ = Def.Autocompleter.getScoreField(this.element);
  2829. if (this.scoreField_) this.scoreFieldInitialized_ = true;
  2830. }
  2831. return this.scoreField_;
  2832. },
  2833. /**
  2834. * Listens to keypress events to determine if the user has typed into
  2835. * the field.
  2836. * @param evt the key event
  2837. */
  2838. changeToFieldByKeys: function changeToFieldByKeys(evt) {
  2839. // Only continue if we haven't already seen such an event.
  2840. if (!this.typedSinceLastFocus_) {
  2841. // Based on code from:
  2842. // http://stackoverflow.com/a/4180715/360782
  2843. var change = false;
  2844. if (typeof evt.which === "undefined") {
  2845. // This is IE, which only fires keypress events for printable keys
  2846. change = true;
  2847. } else if (typeof evt.which === "number" && evt.which > 0) {
  2848. // In other browsers except old versions of WebKit, evt.which is
  2849. // only greater than zero if the keypress is a printable key.
  2850. // We need to filter out backspace and ctrl/alt/meta key combinations
  2851. change = !evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.which !== 8;
  2852. }
  2853. this.typedSinceLastFocus_ = change;
  2854. }
  2855. },
  2856. /**
  2857. * Sets up event listeners for the list elements.
  2858. * @param element a list item DOM element.
  2859. */
  2860. addObservers: function addObservers(element) {
  2861. // Listen for mousedown events (which arrive more quickly than
  2862. // click events, presumably because click events probably have
  2863. // to be distinguished from double-clicks.)
  2864. jQuery(element).mousedown(this.onMouseDownListener);
  2865. },
  2866. /**
  2867. * Returns the value of a list item (minus any sequence number and
  2868. * separator.)
  2869. * @param itemElem a list item DOM element.
  2870. */
  2871. listItemValue: function listItemValue(itemElem) {
  2872. var rtn;
  2873. if (this.options.tableFormat) rtn = itemElem.getAttribute('data-fieldval');else rtn = itemElem.textContent; // decodes escaped HTML elements
  2874. return rtn;
  2875. },
  2876. /**
  2877. * Override the Scriptaculous version so we do *not* call scrollIntoView().
  2878. * This does not work well on our page, so we have to do the scrolling
  2879. * ourselves.
  2880. */
  2881. markPrevious: function markPrevious() {
  2882. if (this.preFieldFillVal_ === null) // save the value in case of ESC
  2883. this.preFieldFillVal_ = this.domCache.get('elemVal'); // Move the index back and keep doing so until we're not on a heading (unless we
  2884. // get back to where we started).
  2885. var stopIndex = this.index;
  2886. if (stopIndex === -1) stopIndex = this.entryCount - 1;
  2887. var highlightedLITag;
  2888. do {
  2889. if (this.index > 0) this.index--;else this.index = this.entryCount - 1;
  2890. highlightedLITag = this.getCurrentEntry(); // depends on this.index
  2891. var itemText = this.listItemValue(highlightedLITag);
  2892. if (this.itemTextIsHeading(itemText)) {
  2893. Def.Autocompleter.screenReaderLog('Above list heading: ' + itemText);
  2894. highlightedLITag = null;
  2895. }
  2896. } while (!highlightedLITag && this.index !== stopIndex);
  2897. if (highlightedLITag) {
  2898. this.scrollToShow(highlightedLITag, this.update.parentNode);
  2899. this.updateElementAfterMarking(highlightedLITag);
  2900. }
  2901. },
  2902. /**
  2903. * Override the Scriptaculous version so we do *not* call scrollIntoView().
  2904. * This does not work well on our page, so we have to do the scrolling
  2905. * ourselves.
  2906. */
  2907. markNext: function markNext() {
  2908. if (this.preFieldFillVal_ === null) // save the value in case of ESC
  2909. this.preFieldFillVal_ = this.domCache.get('elemVal'); // Move the index forward and keep doing so until we're not on a heading (unless we
  2910. // get back to where we started).
  2911. var stopIndex = this.index;
  2912. if (stopIndex === -1) stopIndex = this.entryCount - 1;
  2913. var highlightedLITag;
  2914. do {
  2915. if (this.index < this.entryCount - 1) this.index++;else this.index = 0;
  2916. highlightedLITag = this.getCurrentEntry(); // depends on this.index
  2917. var itemText = this.listItemValue(highlightedLITag);
  2918. if (this.itemTextIsHeading(itemText)) {
  2919. Def.Autocompleter.screenReaderLog('Under list heading: ' + itemText);
  2920. highlightedLITag = null;
  2921. }
  2922. } while (!highlightedLITag && this.index !== stopIndex);
  2923. if (highlightedLITag) {
  2924. this.scrollToShow(highlightedLITag, this.update.parentNode);
  2925. this.updateElementAfterMarking(highlightedLITag);
  2926. }
  2927. },
  2928. /**
  2929. * Updates the field after an element has been highlighted in the list
  2930. * (e.g. via arrow keys).
  2931. * @param listElement the DOM element that has been highlighted
  2932. */
  2933. updateElementAfterMarking: function updateElementAfterMarking(listElement) {
  2934. // Also put the value into the field, but don't run the change event yet,
  2935. // because the user has not really selected it.
  2936. var oldTokenBounds = this.tokenBounds;
  2937. this.updateElement(listElement); // clears this.tokenBounds
  2938. if (this.options.tokens) {
  2939. // Recompute token bounds, because we've inserted a list value
  2940. this.getTokenBounds(oldTokenBounds && oldTokenBounds[0]);
  2941. this.element.setSelectionRange(this.tokenBounds[0], this.tokenBounds[1]);
  2942. } else this.element.select(); // At least under some circumstances, JAWS reads the field value (perhaps
  2943. // because of the "select" above). However, if this is a table-format
  2944. // autocompleter, we need to read the row.
  2945. if (this.options.tableFormat) {
  2946. var logEntry = [];
  2947. var cells = jQuery(listElement).children('td'); // Only read the row if there is more than one cell, because the screen
  2948. // reader will read what gets put in the field.
  2949. if (cells.length > 1) {
  2950. for (var i = 0, len = cells.length; i < len; ++i) {
  2951. logEntry.push(cells[i].innerText);
  2952. }
  2953. Def.Autocompleter.screenReaderLog(logEntry.join('; '));
  2954. }
  2955. }
  2956. },
  2957. /**
  2958. * Hides the list container.
  2959. */
  2960. hideList: function hideList() {
  2961. if (Def.Autocompleter.currentAutoCompField_ === this.element.id) {
  2962. // Check whether the list is hidden. By default (via CSS) it is hidden,
  2963. // so if style.visibility is blank, it is hidden.
  2964. var hidden = this.listContainer.style.visibility !== 'visible';
  2965. if (!hidden) {
  2966. this.listContainer.style.visibility = 'hidden';
  2967. this.listShowing = false;
  2968. this.listContainer.setAttribute('aria-hidden', 'true');
  2969. this.element.setAttribute('aria-expanded', 'false');
  2970. }
  2971. }
  2972. },
  2973. /**
  2974. * Shows the list container.
  2975. */
  2976. showList: function showList() {
  2977. var previouslyHidden = this.listContainer.style.visibility !== 'visible';
  2978. this.listContainer.style.visibility = 'visible';
  2979. this.listShowing = true;
  2980. this.listContainer.setAttribute('aria-hidden', 'false');
  2981. this.element.setAttribute('aria-expanded', 'true');
  2982. if (previouslyHidden && !this.temporaryHide_ && this.entryCount > 0) {
  2983. Def.Autocompleter.screenReaderLog('A list has appeared below the ' + this.getFieldName() + '.');
  2984. if (this.options.tableFormat && this.options.colHeaders) {
  2985. Def.Autocompleter.screenReaderLog('The column headers on the ' + 'multi-column list are ' + this.options.colHeaders.join('; '));
  2986. }
  2987. }
  2988. },
  2989. /**
  2990. * Returns a field "name" like 'field "Drug Use Status"' for labeled fields,
  2991. * or just 'field' if there is no field label.
  2992. */
  2993. getFieldName: function getFieldName() {
  2994. if (this.fieldName_ === undefined) {
  2995. var fieldLabel = Def.Autocompleter.getFieldLabel(this.element.id);
  2996. this.fieldName_ = fieldLabel === null ? 'field' : 'field "' + fieldLabel + '"';
  2997. }
  2998. return this.fieldName_;
  2999. },
  3000. /**
  3001. * Scrolls the given item into view within its container.
  3002. * @param item the item to scroll into view
  3003. * @param container the scrollable container that has the item
  3004. */
  3005. scrollToShow: function scrollToShow(item, container) {
  3006. if (item.offsetTop < container.scrollTop) {
  3007. container.scrollTop = item.offsetTop;
  3008. } else {
  3009. var itemHeight = item.clientHeight; // Get the height of the container, less border and scroll bar pixels
  3010. var containerHeight = container.clientHeight;
  3011. if (item.offsetTop + itemHeight - container.scrollTop > containerHeight) {
  3012. container.scrollTop = item.offsetTop + itemHeight - containerHeight;
  3013. }
  3014. }
  3015. },
  3016. /**
  3017. * Pages the choice list (or table) up or down.
  3018. * @param pageUp - true if it should try to page up, or false if it should
  3019. * try to page down.
  3020. */
  3021. pageOptionsUpOrDown: function pageOptionsUpOrDown(pageUp) {
  3022. // Get the height of the search results, which might be constrained by
  3023. // span tag (id completionOptions).
  3024. var compOpts = jQuery('#completionOptionsScroller')[0];
  3025. var compOptHeight = compOpts.clientHeight; // the inner height, minus border
  3026. var newScrollTop;
  3027. if (pageUp) {
  3028. if (compOpts.scrollTop > 0) {
  3029. newScrollTop = compOpts.scrollTop - compOptHeight;
  3030. if (newScrollTop < 0) newScrollTop = 0;
  3031. compOpts.scrollTop = newScrollTop;
  3032. }
  3033. } else {
  3034. // PAGE DOWN
  3035. var fullListHeight = jQuery('#completionOptions')[0].clientHeight;
  3036. var maxScrollTop = fullListHeight - compOptHeight;
  3037. if (maxScrollTop < 0) maxScrollTop = 0;
  3038. if (compOpts.scrollTop < maxScrollTop) {
  3039. newScrollTop = compOpts.scrollTop + compOptHeight;
  3040. if (newScrollTop > maxScrollTop) newScrollTop = maxScrollTop;
  3041. compOpts.scrollTop = newScrollTop;
  3042. }
  3043. }
  3044. },
  3045. /**
  3046. * Returns true if the given key event is a search request.
  3047. */
  3048. isSearchKey: function isSearchKey(event) {
  3049. return event.ctrlKey && event.keyCode === jQuery.ui.keyCode.ENTER;
  3050. },
  3051. /**
  3052. * Handles key down events in the field (in spite of the name).
  3053. * @param event the event object from the keypress event
  3054. */
  3055. onKeyPress: function onKeyPress(event) {
  3056. // Do nothing if the autocompleter widget is not enabled_.
  3057. if (this.enabled_) {
  3058. // Note: Normal (i.e. not search or navigation) key strokes are handled
  3059. // by Scriptaculous, which defers processing until a short time later
  3060. // (specified by 'frequency'). This is important, because we are
  3061. // catching a keyDown event, at which time the element's value has not
  3062. // yet been updated.
  3063. var charCode = event.keyCode;
  3064. var keyHandled = true;
  3065. if (this.fieldEventIsBigList(event)) {
  3066. event.stopImmediatePropagation(); // If the user had arrowed down into the list, reset the field
  3067. // value to what the user actually typed before running the search.
  3068. if (this.preFieldFillVal_) this.setFieldVal(this.preFieldFillVal_, false);
  3069. this.handleSeeMoreItems(event); // implemented in sub-classes
  3070. // Currently we don't have separate events for different reasons to
  3071. // show the big list (e.g. search vs. list expansion), so just send
  3072. // the list expansion event.
  3073. Def.Autocompleter.Event.notifyObservers(this.element, 'LIST_EXP', {
  3074. list_expansion_method: 'CtrlRet'
  3075. });
  3076. } else {
  3077. var keys = jQuery.ui.keyCode;
  3078. switch (charCode) {
  3079. case keys.ENTER:
  3080. // Step the event for multiselect lists so the focus stays in the
  3081. // field. The user might be trying to select more than one item
  3082. // by hitting return more than once.
  3083. if (this.multiSelect_) Def.Autocompleter.stopEvent(event);
  3084. this.handleDataEntry(event);
  3085. break;
  3086. case keys.TAB:
  3087. // For a tab, only try to select a value if there is something in
  3088. // the field. An item might be highlighted from a return-key
  3089. // selection (in a multi-select list), but if the field is empty we
  3090. // will ignore that because the user might just be trying to leave
  3091. // the field.
  3092. if (this.domCache.get('elemVal') !== '') this.handleDataEntry(event);
  3093. break;
  3094. case keys.ESCAPE:
  3095. if (this.preFieldFillVal_ !== null) {
  3096. // Restore the field value
  3097. this.setFieldVal(this.preFieldFillVal_, false);
  3098. Def.Autocompleter.Event.notifyObservers(this.element, 'CANCEL', {
  3099. restored_value: this.preFieldFillVal_
  3100. });
  3101. }
  3102. if (this.active) {
  3103. this.index = -1;
  3104. this.hide();
  3105. this.active = false;
  3106. }
  3107. break;
  3108. default:
  3109. if (this.active) {
  3110. switch (charCode) {
  3111. case keys.PAGE_UP:
  3112. this.pageOptionsUpOrDown(true);
  3113. break;
  3114. case keys.PAGE_DOWN:
  3115. this.pageOptionsUpOrDown(false);
  3116. break;
  3117. default:
  3118. if (!event.ctrlKey) {
  3119. switch (charCode) {
  3120. case keys.DOWN:
  3121. case keys.UP:
  3122. charCode === keys.UP ? this.markPrevious() : this.markNext();
  3123. this.render();
  3124. Def.Autocompleter.stopEvent(event);
  3125. break;
  3126. case keys.LEFT:
  3127. case keys.RIGHT:
  3128. if (this.options.tokens) {
  3129. this.tokenBounds = null; // selection point may have moved
  3130. this.getTokenBounds(); // selection point may have moved
  3131. }
  3132. if (!event.ctrlKey && this.index >= 0 && jQuery(this.update).hasClass('multi_col')) {
  3133. this.moveToOtherColumn(event);
  3134. }
  3135. break;
  3136. default:
  3137. keyHandled = false;
  3138. }
  3139. } else keyHandled = false;
  3140. } // switch
  3141. } // if this.active
  3142. else keyHandled = false;
  3143. } // switch
  3144. }
  3145. if (!keyHandled) {
  3146. // Ignore events that are only a shift or control key. If we allow a
  3147. // shift key to get processed (and e.g. show the list) then shift-tab
  3148. // to a previous field can have trouble, because the autocompleter will
  3149. // still be scrolling the page to show the list.
  3150. // charCode being 0 is a case Scriptaculous excluded for WebKit
  3151. // browsers. (I'm not sure when that happens.)
  3152. // 16 & 17 = shift & control key codes
  3153. // Also ignore control key combination events except for control+v.
  3154. // We also handle control+enter, which is taken care of above (see the
  3155. // call to fieldEventIsBigList).
  3156. if ((!event.ctrlKey || charCode === 86) && // 86 = V (control+v sends V)
  3157. charCode !== 16 && charCode !== 17 && charCode !== 0) {
  3158. this.preFieldFillVal_ = null; // reset on key strokes in field
  3159. this.changed = true;
  3160. this.hasFocus = true;
  3161. this.matchListItemsToField_ = true;
  3162. if (this.observer) clearTimeout(this.observer);
  3163. this.observer = setTimeout(jQuery.proxy(this.onObserverEvent, this), this.options.frequency * 1000);
  3164. }
  3165. }
  3166. }
  3167. },
  3168. /**
  3169. * Sets the indicator to let the user know the whether the field value
  3170. * (if present) matches a value in the field's list.
  3171. * @param matchStatus the match status. This should be true if the field
  3172. * value either matches a list item or is blank, and false otherwise.
  3173. */
  3174. setMatchStatusIndicator: function setMatchStatusIndicator(matchStatus) {
  3175. if (matchStatus !== this.matchStatus_) {
  3176. if (matchStatus) {
  3177. if (jQuery(this.element).hasClass('no_match')) {
  3178. jQuery(this.element).removeClass('no_match');
  3179. Def.Autocompleter.screenReaderLog('The field no longer contains a non-matching value.');
  3180. }
  3181. } else {
  3182. jQuery(this.element).addClass('no_match');
  3183. Def.Autocompleter.screenReaderLog('The field\'s value does not match any items in the list.');
  3184. }
  3185. this.matchStatus_ = matchStatus;
  3186. }
  3187. },
  3188. /**
  3189. * Sets the indicator that marks a field as having an invalid value. If
  3190. * the "invalid" parameter is set to false, the visual and permanent
  3191. * indicator an invalid value will be removed, but if animation and sound
  3192. * was in progress, that will run until completion. (To interrupt that,
  3193. * use cancelInvalidValIndicator).
  3194. * @param invalid true if the field is invalid. (This is the reverse of
  3195. * the parameter to setMatchStatusIndicator, mostly because of the names
  3196. * of the two methods.)
  3197. */
  3198. setInvalidValIndicator: function setInvalidValIndicator(invalid) {
  3199. if (invalid) {
  3200. Def.Autocompleter.setOffAlarm(this.element);
  3201. if (!this.invalidStatus_) {
  3202. jQuery(this.element).addClass('invalid');
  3203. this.element.setAttribute('invalid', true);
  3204. }
  3205. } else {
  3206. if (this.invalidStatus_) {
  3207. jQuery(this.element).removeClass('invalid');
  3208. this.element.setAttribute('invalid', false);
  3209. }
  3210. }
  3211. this.invalidStatus_ = invalid;
  3212. },
  3213. /**
  3214. * Halts any animation and sound associated with the invalid field value
  3215. * indicator. This does not clear the permanent visual indicator. To clear
  3216. * that, use setInvalidValIndicator(false).
  3217. */
  3218. cancelInvalidValIndicator: function cancelInvalidValIndicator() {
  3219. Def.Autocompleter.cancelAlarm(this.element);
  3220. },
  3221. /**
  3222. * This is called to update the completion list area with new search results.
  3223. * We override this to change the default selection.
  3224. * @param choices the HTML for a ul list. It should not contain whitespace
  3225. * text between tags.
  3226. * @param pickedByNum whether the user is picking by number
  3227. */
  3228. updateChoices: function updateChoices(choices, pickedByNum) {
  3229. // We no longer call controls.js' updateChoices because the autocompleteIndex
  3230. // settings need to be made after we move the default selection. However,
  3231. // a good bit of this code is copied from there.
  3232. this.index = -1;
  3233. if (!this.changed && this.hasFocus) {
  3234. this.update.innerHTML = choices; // If the HTML has a header row, disable clicks on that row
  3235. var fc = this.update.firstChild;
  3236. if (fc && fc.tHead) {
  3237. jQuery(fc.tHead).mousedown(function (e) {
  3238. Def.Autocompleter.stopEvent(e);
  3239. });
  3240. }
  3241. var domItems = Def.Autocompleter.listItemElements();
  3242. if (domItems) {
  3243. this.entryCount = domItems.length;
  3244. var i;
  3245. if (this.suggestionMode_ !== Def.Autocompleter.NO_COMPLETION_SUGGESTIONS) {
  3246. if (this.entryCount > 0 && !this.focusInProgress_ && pickedByNum) {
  3247. // Use the first non-heading entry (whose number should match
  3248. // what was typed) as the default
  3249. for (i = 0; this.liIsHeading(domItems[i]) && i < this.entryCount; ++i) {
  3250. ;
  3251. }
  3252. this.index = i;
  3253. }
  3254. } // If we are making a suggestion
  3255. for (i = 0; i < this.entryCount; i++) {
  3256. var entry = this.getEntry(i);
  3257. entry.autocompleteIndex = i;
  3258. this.addObservers(entry);
  3259. }
  3260. } else {
  3261. this.entryCount = 0;
  3262. }
  3263. if (this.entryCount === 1 && this.options.autoSelect) {
  3264. this.selectEntry();
  3265. this.hide();
  3266. } else {
  3267. this.render();
  3268. } // don't change the match indicator on a focus event. (Prefetch
  3269. // autocompleters show the whole list, no matter what is in the field.)
  3270. if (!this.focusInProgress_) {
  3271. // The field is in a non-matching state if the value is not empty
  3272. // and there are no items in the list.
  3273. this.setMatchStatusIndicator(this.entryCount > 0 || this.trimmedElemVal === '');
  3274. }
  3275. }
  3276. },
  3277. /**
  3278. * Returns true if the user seems to be picking a list item by number.
  3279. */
  3280. pickedByNumber: function pickedByNumber() {
  3281. return this.add_seqnum && this.trimmedElemVal.match(/^\d+$/);
  3282. },
  3283. /**
  3284. * Returns the index of the item in the given list
  3285. * which should be offered as best match.
  3286. * @param listItems an array of the items in the list
  3287. * @return the index of the item, or -1 if no item should be highlighted.
  3288. */
  3289. pickBestMatch: function pickBestMatch(listItems) {
  3290. // If there is something in the field, pick:
  3291. // 1) the shortest choice with the field value at the beginning, or
  3292. // 2) the shortest choice with the field value somewhere, or
  3293. // 3) the shortest choice
  3294. var elemValue = this.trimmedElemVal.toLowerCase();
  3295. var numItems = listItems.length;
  3296. var rtn = -1;
  3297. if (elemValue.length > 0 && numItems > 0) {
  3298. var minLengthIndex = -1;
  3299. var minLength = Infinity;
  3300. var beginMatchMinLengthIndex = -1;
  3301. var beginMatchMinLength = minLength;
  3302. var innerMatchMinLengthIndex = -1;
  3303. var innerMatchMinLength = minLength;
  3304. for (var i = 0; i < numItems; ++i) {
  3305. // Make sure the entry is not a heading before considering it
  3306. var itemText = listItems[i];
  3307. if (!this.itemTextIsHeading(itemText)) {
  3308. var itemTextLC = itemText.toLowerCase(); // Also remove non-word characters from the start of the string.
  3309. itemTextLC = itemTextLC.replace(/^\W+/, '');
  3310. var matchIndex = itemTextLC.indexOf(elemValue);
  3311. var itemTextLength = itemText.length;
  3312. if (matchIndex === 0) {
  3313. // if searching by list item #, then ignore length and highlight
  3314. // first element
  3315. if (/(^\d+$)/.test(elemValue)) {
  3316. beginMatchMinLengthIndex = 0;
  3317. beginMatchMinLength = 0;
  3318. } else if (itemTextLength < beginMatchMinLength) {
  3319. beginMatchMinLengthIndex = i;
  3320. beginMatchMinLength = itemTextLength;
  3321. }
  3322. } else if (beginMatchMinLengthIndex === -1) {
  3323. // no begin match found yet
  3324. if (matchIndex > 0) {
  3325. if (itemTextLength < innerMatchMinLength) {
  3326. innerMatchMinLengthIndex = i;
  3327. innerMatchMinLength = itemTextLength;
  3328. }
  3329. } else if (innerMatchMinLengthIndex === -1 && // no inner match yet
  3330. itemTextLength < minLength) {
  3331. minLength = itemTextLength;
  3332. minLengthIndex = i;
  3333. }
  3334. }
  3335. }
  3336. }
  3337. if (beginMatchMinLengthIndex > -1) rtn = beginMatchMinLengthIndex;else if (innerMatchMinLengthIndex > -1) rtn = innerMatchMinLengthIndex;else rtn = minLengthIndex;
  3338. } // if we have some entries
  3339. return rtn;
  3340. },
  3341. /**
  3342. * Positions the answer list.
  3343. */
  3344. posAnsList: function posAnsList() {
  3345. this.posListBelowFieldInMultiCol(); // If the list was already showing, made sure the currently selected item
  3346. // is still in view after the repositioning (which sets the scrollTop
  3347. // of the container back to 0.)
  3348. if (this.index > 0) this.scrollToShow(this.getCurrentEntry(), $('completionOptionsScroller'));
  3349. },
  3350. /**
  3351. * Positions the list below the field, using a multicolumn format if
  3352. * necessary and scrolling the document up to show the multicolumn list if
  3353. * necessary. This is like the old "posListInMultiCol", but the list is
  3354. * always below the field.
  3355. */
  3356. posListBelowFieldInMultiCol: function posListBelowFieldInMultiCol() {
  3357. var sharedDOMCache = Def.Autocompleter.sharedDOMCache;
  3358. var element = this.domCache.element;
  3359. var update = this.update; // Clear previous settings
  3360. this.domCache.invalidate('elemPos');
  3361. sharedDOMCache.invalidate('firstEntryWidth');
  3362. sharedDOMCache.invalidate('listBoundingRect');
  3363. sharedDOMCache.invalidate('viewPortWidth');
  3364. if (update.style.height) update.style.height = ''; // Turn off height setting, if any
  3365. this.setListWrap(false);
  3366. update.style.width = 'auto';
  3367. $('completionOptionsScroller').style.height = '';
  3368. this.listContainer.style.width = '';
  3369. this.listHeight = undefined; // Positioning strategies (in order of attempt) to show all of the list
  3370. // element within the viewport.
  3371. // 1) list below field as a single column list, with no constraint on
  3372. // height. If that fits in the viewport's height, adjust left position as
  3373. // necessary.
  3374. // 2) list below field as a two column wrapped list. If that fits in the
  3375. // viewports height, and the wider form can fit within the viewport width,
  3376. // adjust the left position as necessary. If the new width is too wide
  3377. // for the viewport, revert to the single column list, and adjust the left
  3378. // position as needed.
  3379. // 3) scroll page up to make room for the list below field
  3380. // 4) constrain the list height. If the addition of a scrollbar on the
  3381. // list makes a two-column list too wide for the viewport, revert to a
  3382. // single column list. Adjust the left position as necessary.
  3383. // 5) If we can't constrain the list height (because it would be too
  3384. // short), then just adjust the left position.
  3385. // First put the list below the field as a single column list.
  3386. // Moving the list can result in the window scrollbar either appearing or
  3387. // disappearing, which can change the position of the field. So, first
  3388. // hide the list to determine the element position. Unfortunately this
  3389. // introduces an additional 1ms of positioning time, but I don't see a
  3390. // good way to avoid that.
  3391. var positionedElement = this.listContainer;
  3392. positionedElement.style.display = 'none';
  3393. var elemPos = this.domCache.get('elemPos');
  3394. positionedElement.style.display = '';
  3395. positionedElement.style.top = elemPos.top + element.offsetHeight + 'px';
  3396. var scrolledContainer = this.scrolledContainer_;
  3397. var viewPortHeight = document.documentElement.clientHeight;
  3398. var maxListContainerBottom = viewPortHeight; // bottom edge of viewport
  3399. var posElVPCoords = sharedDOMCache.get('listBoundingRect');
  3400. var bottomOfListContainer = posElVPCoords.bottom;
  3401. if (bottomOfListContainer <= maxListContainerBottom) {
  3402. this.setListLeft(); // We're done positioning the list
  3403. } else {
  3404. // If this list is not completely on the page, try making it a multi-column
  3405. // list (unless it is a table format list, which already has columns).
  3406. var tryMultiColumn = this.twoColumnFlow_ && !this.options.tableFormat && this.entryCount > 4; // otherwise it's too short
  3407. if (tryMultiColumn) {
  3408. tryMultiColumn = this.setListWrap(true);
  3409. if (tryMultiColumn) {
  3410. // We wrapped the list, so update the bottom position
  3411. bottomOfListContainer = sharedDOMCache.get('listBoundingRect').bottom;
  3412. }
  3413. }
  3414. if (tryMultiColumn && bottomOfListContainer <= maxListContainerBottom) {
  3415. this.setListLeft(); // We're done positioning the list
  3416. } else {
  3417. // The multi-column list is still not on the page, try scrolling the
  3418. // page down (making the list go up).
  3419. var elementBoundingRect = element.getBoundingClientRect();
  3420. var heightConstraint = undefined;
  3421. if (!scrolledContainer) {
  3422. heightConstraint = window.innerHeight - elementBoundingRect.bottom;
  3423. } else {
  3424. // Cancel any active scroll effect
  3425. if (this.lastScrollEffect_) this.lastScrollEffect_.cancel();
  3426. var scrollDownAmount = bottomOfListContainer - maxListContainerBottom;
  3427. var elementTop = elementBoundingRect.top;
  3428. var topNavBarHeight = 0;
  3429. var headerBarID = this.constructorOpts_.headerBar;
  3430. if (headerBarID) {
  3431. var headerBar = document.getElementById(headerBarID);
  3432. if (headerBar) topNavBarHeight = headerBar.offsetHeight;
  3433. }
  3434. var maxScroll;
  3435. var scrolledContainerViewportTop = scrolledContainer.getBoundingClientRect().top;
  3436. if (scrolledContainerViewportTop > topNavBarHeight) maxScroll = elementTop - scrolledContainerViewportTop;else maxScroll = elementTop - topNavBarHeight; // Make sure we don't scroll the field out of view.
  3437. if (scrollDownAmount > maxScroll) {
  3438. scrollDownAmount = maxScroll; // Also constrain the height of the list, so the bottom is on the page
  3439. // The maximum allowable space is the viewport height minus the field
  3440. // height minus the top nav bar height minus the part of the list
  3441. // container that is not for list items (e.g. "See more results")).
  3442. heightConstraint = viewPortHeight - elementBoundingRect.height - topNavBarHeight;
  3443. }
  3444. bottomOfListContainer = heightConstraint === undefined ? sharedDOMCache.get('listBoundingRect').bottom : sharedDOMCache.get('listBoundingRect').top + heightConstraint; // If the list is extending beyond the bottom of the page's normal
  3445. // limits, increasing the page's length, extend the spacer div to make
  3446. // sure the size does not diminish. This should prevent the "bouncing"
  3447. // effect we were getting when typing into the field, where the page
  3448. // would first scroll up to accomodate a large list, and then as more
  3449. // keystrokes were enterd the list got smaller, so the page scrolled
  3450. // back down. (The browser does that automatically when the page
  3451. // shrinks.)
  3452. var spacerCoords = sharedDOMCache.get('spacerCoords');
  3453. if (bottomOfListContainer > spacerCoords.bottom) {
  3454. var spacerDiv = sharedDOMCache.get('spacerDiv');
  3455. spacerDiv.style.height = bottomOfListContainer - spacerCoords.top + 'px';
  3456. sharedDOMCache.invalidate('spacerCoords');
  3457. }
  3458. this.lastScrollEffect_ = new Def.Effect.Scroll(scrolledContainer, {
  3459. y: scrollDownAmount,
  3460. duration: 0.4
  3461. });
  3462. }
  3463. if (heightConstraint !== undefined) {
  3464. // If we can't scroll the list into view, just constrain the height so
  3465. // the list is visible.
  3466. var elementRect = this.setListHeight(heightConstraint); // Setting this list height likely introduced a scrollbar on the list.
  3467. var viewPortWidth = sharedDOMCache.get('viewPortWidth');
  3468. var posElVPCoords = sharedDOMCache.get('listBoundingRect');
  3469. if (sharedDOMCache.listWrap && posElVPCoords.width > viewPortWidth) {
  3470. // The list is too wide, so remove the wrap
  3471. this.setListWrap(false);
  3472. }
  3473. }
  3474. this.setListLeft();
  3475. }
  3476. }
  3477. },
  3478. /**
  3479. * Constructs a cache of DOM values for use during list positioning.
  3480. * Unliked the sharedDOMCache, each autocompleter has its own one of these.
  3481. */
  3482. initDOMCache: function initDOMCache() {
  3483. var acInstance = this;
  3484. var ac = Def.Autocompleter;
  3485. this.domCache = ac.createDOMCache({
  3486. // element is the positioned element, which might be acInstance.element,
  3487. // or might be a span wrapping it.
  3488. element: acInstance.listPositioningElem()
  3489. }, {
  3490. // elemPos is the offset of "element" as defined above.
  3491. elemPos: function elemPos() {
  3492. return jQuery(this.element).offset();
  3493. },
  3494. // The field value
  3495. elemVal: function elemVal() {
  3496. return ac.getFieldVal(acInstance.element);
  3497. }
  3498. });
  3499. },
  3500. /**
  3501. * Returns the element used for positioning the answer list.
  3502. */
  3503. listPositioningElem: function listPositioningElem() {
  3504. // Set "element" to the container of the element and the selected list
  3505. // when this is a multi-select list, so that when the list is scrolled
  3506. // into view, the selected items remain visible.
  3507. return this.multiSelect_ ? this.element.parentNode : this.element;
  3508. },
  3509. /**
  3510. * Sets whether the list is wrapped to two columns or not. If there is not
  3511. * enough space for two columns, then there will be no effect when "wrap"
  3512. * is true.
  3513. * @param wrap if true, the list will be set to flow into two columns; if
  3514. * false, it will be set to be just one column.
  3515. * otherwise.
  3516. * @return true if the list is wrapped
  3517. */
  3518. setListWrap: function setListWrap(wrap) {
  3519. var sharedDOMCache = Def.Autocompleter.sharedDOMCache;
  3520. if (wrap !== sharedDOMCache.listWrap) {
  3521. if (wrap) {
  3522. // For Chrome, but not Firefox, we need to set the width of the
  3523. // list container; otherwise it will not adjust when the multiple
  3524. // columns are turned on.
  3525. // We set it to be twice the width of a list item plus 4 pixels for
  3526. // the border.
  3527. // There might also be a scrollbar on the list, but we won't know that
  3528. // until we set the height.
  3529. var newListWidth = sharedDOMCache.get('firstEntryWidth') * 2 + 4; // Make sure the new width will fit horizontally
  3530. var viewPortWidth = sharedDOMCache.get('viewPortWidth');
  3531. if (newListWidth <= viewPortWidth) {
  3532. this.listContainer.style.width = newListWidth + 'px';
  3533. jQuery(this.update).addClass('multi_col');
  3534. sharedDOMCache.listWrap = true;
  3535. }
  3536. } else {
  3537. jQuery(this.update).removeClass('multi_col');
  3538. this.listContainer.style.width = ''; // reset it
  3539. sharedDOMCache.listWrap = false; // There could now be a vertical scrollbar on the window, reducing
  3540. // horizontal viewport space.
  3541. sharedDOMCache.invalidate('viewPortWidth');
  3542. }
  3543. sharedDOMCache.invalidate('listBoundingRect'); // The window vertical scrollbar might have appeared/disappeared,
  3544. // causing the field's horizontal position to change
  3545. this.domCache.invalidate('elemPos');
  3546. }
  3547. return sharedDOMCache.listWrap;
  3548. },
  3549. /**
  3550. * Sets the list's left position to bring it as close as possible to the
  3551. * left edge of the field and to show as much of the list as possible.
  3552. */
  3553. setListLeft: function setListLeft() {
  3554. // The window's scrollbar might be showing, and which might or might not
  3555. // be due to the placement of the list. We could potentially reclaim that
  3556. // space if we move the list left so the scrollbar isn't needed, but that might
  3557. // take time, so don't.
  3558. var positionedElement = this.listContainer;
  3559. var sharedDOMCache = Def.Autocompleter.sharedDOMCache;
  3560. var viewPortWidth = sharedDOMCache.get('viewPortWidth');
  3561. var posElVPCoords = sharedDOMCache.get('listBoundingRect');
  3562. var elemPos = this.domCache.get('elemPos');
  3563. var leftShift = posElVPCoords.width - (viewPortWidth - elemPos.left);
  3564. if (leftShift < 0) // no need to shift
  3565. leftShift = 0;
  3566. var newLeftPos = elemPos.left - leftShift;
  3567. if (newLeftPos < 0) newLeftPos = 0; // don't move the list past the left edge of the page
  3568. var cache = Def.Autocompleter.sharedDOMCache;
  3569. if (cache.listPosLeft !== newLeftPos) {
  3570. positionedElement.style.left = newLeftPos + 'px';
  3571. cache.listPosLeft = newLeftPos;
  3572. }
  3573. },
  3574. /**
  3575. * Constrains the height of the completion options list.
  3576. * @param height the height for entire list, including the options, the "see
  3577. * more" link, and the hit count. This should be an integer number of
  3578. * pixels.
  3579. */
  3580. setListHeight: function setListHeight(height) {
  3581. // Subtract from the height the height of the "see more" and hit count
  3582. // divs. We do this before increasing the width below, because that can
  3583. // change update.height.
  3584. var sharedDOMCache = Def.Autocompleter.sharedDOMCache;
  3585. var posElVPCoords = sharedDOMCache.get('listBoundingRect');
  3586. var height = height - posElVPCoords.height + // listContainer = everything
  3587. this.update.offsetHeight; // update = list items only
  3588. // This will usually be called when the list needs to scroll.
  3589. // First make the list wider to allow room for the scrollbar (which will
  3590. // mostly likely appear) and to avoid squeezing and wrapping the list items.
  3591. this.listContainer.style.width = posElVPCoords.width + 20 + 'px'; // Multi-column lists typical scroll/overflow to the right, so we have put
  3592. // $('completionOptions') in a container, $('completionOptionsScroller')
  3593. // and set the height on that instead. This allows the list to be
  3594. // scrolled vertically instead of horizontally (with lots of short
  3595. // columns).
  3596. // Require at least 20 px of height, or give up
  3597. if (height >= 20) {
  3598. $('completionOptionsScroller').style.height = height + 'px';
  3599. sharedDOMCache.invalidate('listBoundingRect');
  3600. }
  3601. },
  3602. /**
  3603. * Returns the part of the field value (maybe the full field value) that
  3604. * should be used as the basis for autocompletion.
  3605. */
  3606. getToken: function getToken() {
  3607. var rtn = this.domCache.get('elemVal');
  3608. if (this.options.tokens) {
  3609. var bounds = this.getTokenBounds();
  3610. rtn = rtn.substring(bounds[0], bounds[1]);
  3611. }
  3612. return rtn;
  3613. },
  3614. /**
  3615. * Returns the indices of the most recently changed part of the element's
  3616. * value whose boundaries are the closest token characters. Use when
  3617. * autocompleting based on just part of the field's value. Note that the
  3618. * value is cached. If you want an updated value, clear this.tokenBounds.
  3619. * @param pos (optional) a position in the string around which to extract
  3620. * the token. Used when the changed part of the string is not known, or
  3621. * when there is no changed part but the user has clicked on a token.
  3622. */
  3623. getTokenBounds: function () {
  3624. /*
  3625. This function was used in Scriptaculous, but we are not basing the
  3626. concept of current tokens on what has changed, but on where the
  3627. cursor is in the field. Retaining for referrence in case we need it.
  3628. function getFirstDifferencePos(newS, oldS) {
  3629. var boundary = Math.min(newS.length, oldS.length);
  3630. for (var index = 0; index < boundary; ++index)
  3631. if (newS[index] != oldS[index])
  3632. return index;
  3633. return boundary;
  3634. };
  3635. */
  3636. return function (pos) {
  3637. if (null != this.tokenBounds) return this.tokenBounds;
  3638. var value = this.domCache.get('elemVal');
  3639. if (value.trim() === '') return [-1, 0]; // diff = position around which a token will be found.
  3640. var diff = pos !== undefined ? pos : this.element.selectionStart; // var diff = pos !== undefined ? pos :
  3641. // getFirstDifferencePos(value, this.oldElementValue);
  3642. var offset = diff == this.oldElementValue.length ? 1 : 0;
  3643. var prevTokenPos = -1,
  3644. nextTokenPos = value.length;
  3645. var tp;
  3646. for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
  3647. tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
  3648. if (tp > prevTokenPos) prevTokenPos = tp;
  3649. tp = value.indexOf(this.options.tokens[index], diff + offset);
  3650. if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
  3651. }
  3652. return this.tokenBounds = [prevTokenPos + 1, nextTokenPos];
  3653. };
  3654. }(),
  3655. /**
  3656. * A copy constructor, for a new field (e.g. another field in a new row
  3657. * of a table). This method must be overridden by subclasses.
  3658. * @param fieldID the ID of the field being assigned to the new autocompleter
  3659. * this method creates.
  3660. * @return a new autocompleter for field field ID
  3661. */
  3662. dupForField: function dupForField(fieldID) {
  3663. throw 'dupForField must be overridden by autocompleter subclasses.';
  3664. },
  3665. /**
  3666. * Initializes the itemToDataIndex_ map. This should be overridden by
  3667. * subclasses.
  3668. */
  3669. initItemToDataIndex: function initItemToDataIndex() {
  3670. throw 'initItemToDataIndex must be overridden by autocompleter classes that ' + 'need it';
  3671. },
  3672. /**
  3673. * Runs the stuff that needs to be run when the field changes. (This assumes
  3674. * that the field has changed.)
  3675. * @param matchStatus (optional) Set this to false if this should assume the
  3676. * field value does not match the list. If not provided, this.matchStatus_
  3677. * will be used.
  3678. */
  3679. propagateFieldChanges: function propagateFieldChanges(matchStatus) {
  3680. if (matchStatus === undefined) matchStatus = this.matchStatus_; // If this autocompleter has a record data requester, run it or clear
  3681. // the output fields. This will make sure the output fields are clear
  3682. // before the change event observers run for this field, in case one of
  3683. // the change observers wants to use the data model's copy of the output
  3684. // fields. (If it does, it can wait for the record data requester's
  3685. // latestPendingAjaxRequest_ variable to be null.)
  3686. if (this.recDataRequester_) {
  3687. if (matchStatus && this.domCache.get('elemVal').trim() !== '') this.recDataRequester_.requestData();else // no data, or no data from list
  3688. this.recDataRequester_.clearDataOutputFields();
  3689. }
  3690. },
  3691. /*
  3692. * Returns the value the user actually typed in the field (which might
  3693. * have just been the first few characters of the final list value
  3694. * following a selection).
  3695. */
  3696. getValTyped: function getValTyped() {
  3697. return this.preFieldFillVal_ === null ? this.domCache.get('elemVal') : this.preFieldFillVal_;
  3698. },
  3699. /**
  3700. * Notifies event observers of an attempted list selection (which might
  3701. * actually have just been the user typing a value rather than picking it
  3702. * from the list).
  3703. * @param valTyped The value the user actually typed in the field (which might
  3704. * have just been the first few characters of the final list value).
  3705. * @param onList whether the final value was on the list
  3706. * @param removed For multi-select lists, this indicates whether the
  3707. * selection was actual an unselection, removing the named item from the
  3708. * list of selected items. When true, valTyped is the removed value.
  3709. * (Optional; default false)
  3710. */
  3711. listSelectionNotification: function listSelectionNotification(valTyped, onList, removed) {
  3712. var finalVal;
  3713. if (removed === undefined) removed = false;else if (removed) {
  3714. // For this case, we are passing in the removed value via valTyped
  3715. finalVal = valTyped;
  3716. valTyped = '';
  3717. }
  3718. if (finalVal === undefined) finalVal = this.domCache.get('elemVal');
  3719. var inputMethod = this.clickSelectionInProgress_ ? 'clicked' : this.preFieldFillVal_ === null ? 'typed' : 'arrows';
  3720. var usedList = inputMethod !== 'typed' && onList;
  3721. var newCode = this.getItemCode(finalVal);
  3722. Def.Autocompleter.Event.notifyObservers(this.element, 'LIST_SEL', {
  3723. input_method: inputMethod,
  3724. val_typed_in: valTyped,
  3725. final_val: finalVal,
  3726. used_list: usedList,
  3727. list: this.rawList_,
  3728. on_list: onList,
  3729. item_code: newCode,
  3730. removed: removed
  3731. });
  3732. },
  3733. /**
  3734. * Attempts to select an item from the list, if possible. If successful,
  3735. * this will take care of updating the code field, and running rules.
  3736. * @return true if an item was successfully selected (i.e. the list was active
  3737. * and the item was on the list), and false if not.
  3738. */
  3739. attemptSelection: function attemptSelection() {
  3740. var canSelect = false;
  3741. var valTyped = this.getValTyped();
  3742. if (this.active) {
  3743. if (this.index === -1) {
  3744. var elemVal = this.domCache.get('elemVal').trim();
  3745. var lcElemVal = elemVal.toLowerCase();
  3746. var caseSensitiveMatchIndex = -1;
  3747. var matchIndex = -1; // Allow the selection if what the user typed exactly matches an item
  3748. // in the list, except for case, but prefer a case-sensitive match.
  3749. for (var i = 0; i < this.entryCount && caseSensitiveMatchIndex < 0; ++i) {
  3750. var li = this.getEntry(i);
  3751. var liVal = this.listItemValue(li);
  3752. if (!this.liIsHeading(li)) {
  3753. if (elemVal === liVal) caseSensitiveMatchIndex = i;else if (matchIndex < 0 && lcElemVal === liVal.toLowerCase()) matchIndex = i;
  3754. }
  3755. }
  3756. if (caseSensitiveMatchIndex >= 0) {
  3757. this.index = caseSensitiveMatchIndex;
  3758. canSelect = true;
  3759. } else if (matchIndex >= 0) {
  3760. this.index = matchIndex;
  3761. canSelect = true;
  3762. }
  3763. } else canSelect = this.entryCount > 0 && !this.liIsHeading(this.getCurrentEntry());
  3764. this.fieldValIsListVal_ = canSelect;
  3765. if (canSelect) {
  3766. this.active = false;
  3767. this.updateElement(this.getCurrentEntry());
  3768. this.storeSelectedItem(); // Queue the list selection event before doing further processing,
  3769. // which might trigger other events (i.e. the duplication warning event.)
  3770. if (Def.Autocompleter.Event.callbacks_ !== null) this.listSelectionNotification(valTyped, true); // Now continue with the processing of the selection.
  3771. this.processedFieldVal_ = Def.Autocompleter.getFieldVal(this.element);
  3772. this.setMatchStatusIndicator(true);
  3773. this.setInvalidValIndicator(false);
  3774. this.propagateFieldChanges();
  3775. if (this.multiSelect_) this.moveEntryToSelectedArea();
  3776. } // Don't hide the list if this is a multi-select list.
  3777. if (!this.multiSelect_) {
  3778. this.active = false;
  3779. this.hide();
  3780. }
  3781. }
  3782. return canSelect;
  3783. },
  3784. /**
  3785. * Overrides the base selectEntry to handle the updating of the code field,
  3786. * etc. This function assumes that the caller knows there is something
  3787. * to select.
  3788. */
  3789. selectEntry: function selectEntry() {
  3790. this.attemptSelection(); // should always succeed (per pre-conditions).
  3791. },
  3792. /**
  3793. * Takes appropriate action when the user enters something in the field
  3794. * that is not a list item.
  3795. */
  3796. handleNonListEntry: function handleNonListEntry() {
  3797. this.propagateFieldChanges(false); // For a single selection list, clear the stored selection
  3798. if (!this.multiSelect_) {
  3799. this.selectedCodes_ = {};
  3800. this.selectedItems_ = {};
  3801. } // Blank values should not look different than values that haven't been
  3802. // filled in. They are okay-- at least until a submit, at which point
  3803. // blank required fields will be brought to the user's attention.
  3804. var fieldVal = Def.Autocompleter.getFieldVal(this.element);
  3805. if (Def.Autocompleter.getFieldVal(this.element) === '') {
  3806. this.setMatchStatusIndicator(true);
  3807. this.setInvalidValIndicator(false);
  3808. this.storeSelectedItem(''); // Send a list selection event for this case.
  3809. if (Def.Autocompleter.Event.callbacks_ !== null) this.listSelectionNotification('', false);
  3810. this.processedFieldVal_ = fieldVal;
  3811. } else {
  3812. if (this.enabled_) // i.e. if there is a list that should be matched
  3813. this.setMatchStatusIndicator(false); // Send a list selection notification for non-matching values too, even
  3814. // if non-matching values aren't allowed (in which case the AngularJS
  3815. // directive listener needs to clean up the model value).
  3816. if (Def.Autocompleter.Event.callbacks_ !== null) this.listSelectionNotification(this.getValTyped(), false);
  3817. if (this.matchListValue_) {
  3818. Def.Autocompleter.screenReaderLog('For this field your entry must match an item from the suggestion list.'); // If the element is not blank, and if a match is required, we set the
  3819. // invalid value indicator.
  3820. this.setInvalidValIndicator(true); // Refocus the field. We have to wait until after the pending
  3821. // focus event (for whatever element might be getting the focus) is
  3822. // processed. Waiting the smallest amount of time should be sufficient
  3823. // to push this after the pending events.
  3824. this.refocusInProgress_ = true;
  3825. this.processedFieldVal_ = fieldVal;
  3826. setTimeout(jQuery.proxy(function () {
  3827. this.element.focus();
  3828. this.element.select(); // select the text
  3829. // Clear refocusInProgress_, which onFocus also clears, because
  3830. // onFocus isn't called if the field is still focused when focus()
  3831. // is called above. That happens when you hit return to select an
  3832. // invalid value.
  3833. this.refocusInProgress_ = false;
  3834. }, this));
  3835. } else {
  3836. this.storeSelectedItem();
  3837. if (this.multiSelect_) this.moveEntryToSelectedArea(); // resets processedFieldVal_
  3838. else this.processedFieldVal_ = fieldVal; // See if we can find some suggestions for what the user typed.
  3839. // For now, we do not support suggestions for multiselect lists.
  3840. if (this.findSuggestions && this.nonMatchSuggestions_ && !this.multiSelect_) {
  3841. // Use a timeout to let the event that triggered this call finish,
  3842. // before we notify suggestion listeners which might bring up a
  3843. // dialog box and change the focus state and interfere
  3844. // with subsequent event handlers after this one.
  3845. // (This was to fix issue 4569, in which the drug use status field's
  3846. // list showed up on top of the dialog box, even though the field
  3847. // had lost focus. What happened there is that the showing of the
  3848. // dialog box came before the navigation code's attempt to focus
  3849. // the status field, and then when focus() was called the dialog
  3850. // somehow called blur() on the field (perhaps using event capturing)
  3851. // before the autocompleter's focus event handler ran.)
  3852. setTimeout(jQuery.proxy(function () {
  3853. this.findSuggestions();
  3854. }, this));
  3855. }
  3856. }
  3857. }
  3858. },
  3859. /**
  3860. * An event function for when the field changes.
  3861. * @param event the DOM event object for the change event
  3862. */
  3863. onChange: function onChange(event) {
  3864. this.domCache.invalidate('elemVal');
  3865. if (!Def.Autocompleter.completionOptionsScrollerClicked_) {
  3866. // We used to only process the change if this.enabled_ was true. However,
  3867. // if the list field is changed by a RecordDataRequester, it will not
  3868. // be active and might have an empty list.
  3869. this.handleDataEntry(event);
  3870. }
  3871. },
  3872. /**
  3873. * An event function for when the field loses focus.
  3874. * @param event the DOM event object for the blur event
  3875. */
  3876. onBlur: function onBlur(event) {
  3877. // Ignore blur events on the completionOptionsScroller.
  3878. if (!Def.Autocompleter.completionOptionsScrollerClicked_) {
  3879. // Cancel any active scroll effect
  3880. if (this.lastScrollEffect_) this.lastScrollEffect_.cancel(); // If the user did not type in the field but the value is different from the
  3881. // value when the field was focused (such as via down arrow or a click)
  3882. // we need to simulate the change event.
  3883. var elemVal = Def.Autocompleter.getFieldVal(this.element);
  3884. if (elemVal !== this.processedFieldVal_) Def.Event.simulate(this.element, 'change');
  3885. if (this.enabled_ && !this.refocusInProgress_) {
  3886. // The scriptaculous autocompleter uses click events on the list,
  3887. // and so has to do its hide() call via a timeout. We're using
  3888. // mousedown events, which means the field never loses focus when a list
  3889. // item is clicked, so we can just make the call directly. For this
  3890. // reason, we don't call the base onBlur.
  3891. // Autocompleter.Base.prototype.onBlur.apply(this, [event]);
  3892. this.hide();
  3893. this.hasFocus = false;
  3894. this.active = false; // If the field is invalid and not being refocused (as it would be if the
  3895. // user changed the field value to something invalid) clear the field
  3896. // value.
  3897. // Since the empty field is not an invalid field, we need to set the
  3898. // invalid indicator to false
  3899. if (this.invalidStatus_) this.clearInvalidFieldVal();else {
  3900. // If the user retyped a non-list value that was in the field, and that
  3901. // value that matches part of an entry but not completely, and the field
  3902. // allows non-list values, then the no-match indicator will have been
  3903. // turned off and no change event will get fired. We turn it back on
  3904. // here.
  3905. // However, another case is where the user makes a saved row editable, clicks
  3906. // in the new prefetched field (e.g. the strength field) and clicks out again
  3907. // leaving the old value there. In that case, we do not know whether the field
  3908. // value is in the list or not, because the user has not changed the value. We
  3909. // could check each item in the list for prefetched lists but not for search lists;
  3910. // however it seems okay to leave the match status indicator alone in this case. In
  3911. // this case fieldValIsListVal_ will be null (neither true nor false).
  3912. //
  3913. // A third case: If the user types an invalid value into a field,
  3914. // then erases it and leaves the field, the field is now empty and
  3915. // should have the no-match indicator removed. In all cases where
  3916. // the field is blank, the no-match indicator should be removed.
  3917. if (Def.Autocompleter.getFieldVal(this.element) === '') this.setMatchStatusIndicator(true);else if (this.fieldValIsListVal_ === false) this.setMatchStatusIndicator(false);
  3918. }
  3919. }
  3920. }
  3921. },
  3922. /**
  3923. * Clears an (assumed) invalid value from the list field, and resets the
  3924. * invalid indicator.
  3925. */
  3926. clearInvalidFieldVal: function clearInvalidFieldVal() {
  3927. this.setFieldVal('', false);
  3928. this.setInvalidValIndicator(false); // Also clear the match status flag, because a blank value is okay
  3929. // (except for required fields when the form submits).
  3930. this.setMatchStatusIndicator(true);
  3931. this.listSelectionNotification('', false);
  3932. this.processedFieldVal_ = '';
  3933. },
  3934. /**
  3935. * A method that gets called when the field gains the focus.
  3936. * @param event the DOM event object for the focus event
  3937. */
  3938. onFocus: function onFocus(event) {
  3939. Def.Autocompleter.currentAutoCompField_ = this.element.id; // Don't update processedFieldVal_ if we are refocusing due to an invalid
  3940. // value. processedFieldVal_ should retain the last non-invalid value in
  3941. // the field.
  3942. if (!this.refocusInProgress_) this.processedFieldVal_ = Def.Autocompleter.getFieldVal(this.element);
  3943. this.refocusInProgress_ = false;
  3944. this.preFieldFillVal_ = null;
  3945. Def.Autocompleter.Event.notifyObservers(this.element, 'FOCUS', {
  3946. start_val: this.processedFieldVal_
  3947. }); // If this is a multi-select list, announce any items in the selected
  3948. // area.
  3949. if (this.multiSelect_) {
  3950. var selectedItems = Object.getOwnPropertyNames(this.selectedItems_);
  3951. var numSelected = selectedItems.length;
  3952. if (numSelected > 0) {
  3953. var msg = 'Above this multi-select field are deselection buttons for ' + 'each selected item. Currently selected:' + selectedItems.join(', ');
  3954. Def.Autocompleter.screenReaderLog(msg);
  3955. }
  3956. }
  3957. },
  3958. /**
  3959. * Handles click events on the option list.
  3960. * @param event the DOM event object for the mouse event
  3961. */
  3962. onMouseDown: function onMouseDown(event) {
  3963. // Only process the event if the item is not a heading, but in all cases
  3964. // stop the event so that the list stays open and the field retains focus.
  3965. Def.Autocompleter.stopEvent(event);
  3966. var itemElem = event.target;
  3967. while (itemElem && itemElem.autocompleteIndex === undefined) {
  3968. itemElem = itemElem.parentNode;
  3969. }
  3970. if (itemElem && !this.liIsHeading(itemElem)) {
  3971. this.clickSelectionInProgress_ = true;
  3972. this.index = itemElem.autocompleteIndex;
  3973. this.selectEntry();
  3974. this.hide();
  3975. this.clickSelectionInProgress_ = false; // Reshow the list if this is a multi-select list.
  3976. if (this.multiSelect_) this.showList();
  3977. }
  3978. this.tokenBounds = null; // selection point may have moved
  3979. },
  3980. /**
  3981. * Handles entry of an item.
  3982. * @param event the DOM event signaling the data entry
  3983. */
  3984. handleDataEntry: function handleDataEntry(event) {
  3985. if (this.invalidStatus_ && this.processedFieldVal_ === this.domCache.get('elemVal')) this.clearInvalidFieldVal();else {
  3986. // If there was a pending autocompletion event (key event) clear it so we
  3987. // don't reshow a list right after this selection.
  3988. if (this.observer) clearTimeout(this.observer);
  3989. var elemVal = Def.Autocompleter.getFieldVal(this.element); // If the user has changed the value since the last entry/selection,
  3990. // try to use the value to select an item from the list.
  3991. // Don't attempt to make a selection if the user has cleared the field,
  3992. // unless this is a multiselect list, in which case the field will be
  3993. // cleared if another item was selected before this one.
  3994. // Also, note that for multiselect lists the value in the field might
  3995. // not have changed. It can remain blank while the enter is pressed
  3996. // repeatedly.
  3997. var selectionSucceeded = false;
  3998. if (this.processedFieldVal_ !== elemVal && elemVal !== '') selectionSucceeded = this.attemptSelection();else if (this.multiSelect_ && elemVal === '' && this.index >= 0) selectionSucceeded = this.attemptSelection(); // If the value changed but we couldn't select it from the list, treat
  3999. // it as a non-list entry.
  4000. if (this.processedFieldVal_ !== elemVal && !selectionSucceeded) {
  4001. if (elemVal === "") this.fieldValIsListVal_ = false;
  4002. this.handleNonListEntry();
  4003. }
  4004. if (!this.multiSelect_) {
  4005. this.hide();
  4006. this.active = false;
  4007. } // Stop the event if the field is in an invalid state (to avoid form
  4008. // submission.)
  4009. if (!event.stopped && this.matchListValue_ && this.invalidStatus_) Def.Autocompleter.stopEvent(event);
  4010. }
  4011. },
  4012. /**
  4013. * Returns true if the given list item is a list heading rather than a
  4014. * list item.
  4015. * @param itemText the text of the item from the list
  4016. */
  4017. itemTextIsHeading: function itemTextIsHeading(itemText) {
  4018. var rtn = !!this.numHeadings_; // true if headings exist
  4019. if (rtn) {
  4020. // if there are headings
  4021. if (!this.itemToDataIndex_) this.initItemToDataIndex();
  4022. var listDataIndex = this.itemToDataIndex_[itemText]; // heading level 0 means not a heading
  4023. rtn = listDataIndex !== undefined && !!this.indexToHeadingLevel_[listDataIndex];
  4024. }
  4025. return rtn;
  4026. },
  4027. /**
  4028. * Returns true if the given LI element is a list heading rather than a
  4029. * list item.
  4030. * @param li the LI DOM element from the list
  4031. */
  4032. liIsHeading: function liIsHeading(li) {
  4033. var rtn = !!this.numHeadings_; // true if headings exist
  4034. if (rtn) {
  4035. // if there are headings
  4036. rtn = this.itemTextIsHeading(this.listItemValue(li));
  4037. }
  4038. return rtn;
  4039. },
  4040. /**
  4041. * Gets called when the list needs to be shown.
  4042. * @param element the autocompleter's field
  4043. * @param update the DOM element that gets updated with the list
  4044. */
  4045. onShow: function onShow(element, update) {
  4046. element.autocomp.showList();
  4047. },
  4048. /**
  4049. * Gets called when the list needs to be hidden.
  4050. * @param element the autocompleter's field
  4051. * @param update the DOM element that gets updated with the list
  4052. */
  4053. onHide: function onHide(element, update) {
  4054. element.autocomp.hideList();
  4055. },
  4056. /**
  4057. * Moves the selected item to the other column, if there are two columns
  4058. * in the list. (This is called when the user hits the right or left arrow.)
  4059. * This method assumes that the list is active and there is a selected item
  4060. * in the list (i.e., that the user has arrowed down into the list).
  4061. * @param event the event that triggered this. If moving to the other
  4062. * column is possible, the event will be stopped.
  4063. */
  4064. moveToOtherColumn: function moveToOtherColumn(event) {
  4065. // This is designed to work whether the number of items is odd or even.
  4066. // If the number of items is odd and the current index is the middle
  4067. // value, then there is no item in the other column so we don't move it.
  4068. // Note that the index starts at zero (so 0 to 6 for 7 items).
  4069. var numItems = Def.Autocompleter.listItemElements().length;
  4070. var half = Math.floor(numItems / 2); // e.g. 3 if numItems == 6 or 7
  4071. var shift = Math.ceil(numItems / 2.0); // e.g. 4 if numItems == 7
  4072. var newIndex = this.index;
  4073. if (this.index < half) // e.g. 0, 1, or 2 if numItems == 6 or 7
  4074. newIndex = this.index + shift;else if (this.index >= shift) // e.g. >= 4 if numItems == 7
  4075. newIndex = this.index - shift;
  4076. if (newIndex !== this.index) {
  4077. // Make sure the new index is not a header item. If so, don't move.
  4078. var newItem = this.getEntry(newIndex);
  4079. if (!this.liIsHeading(newItem)) {
  4080. // Put the value into the field, but don't run the change event yet,
  4081. // because the user has not really selected it.
  4082. this.index = newIndex;
  4083. this.setFieldVal(this.listItemValue(newItem), false);
  4084. this.element.select();
  4085. this.render();
  4086. Def.Autocompleter.stopEvent(event);
  4087. }
  4088. }
  4089. },
  4090. /**
  4091. * This gets called when the "See more items" link is clicked. It should
  4092. * be overridden by subclasses as appropriate. This default implementation
  4093. * does nothing.
  4094. * @param event the click event on the link
  4095. */
  4096. handleSeeMoreItems: function handleSeeMoreItems(event) {},
  4097. /**
  4098. * "Reads" the searchCount and moreResults divs via the ScreenReaderLog.
  4099. */
  4100. readSearchCount: function readSearchCount() {
  4101. var rtn = false;
  4102. if ($('searchCount').style.display !== 'none') {
  4103. Def.Autocompleter.screenReaderLog('Showing ' + $('searchCount').innerHTML + '.');
  4104. if ($('moreResults').style.display !== 'none') {
  4105. Def.Autocompleter.screenReaderLog('Pressing control+return will expand the list.');
  4106. }
  4107. rtn = true;
  4108. }
  4109. return rtn;
  4110. },
  4111. /**
  4112. * This can be called when an autocompleter is no longer needed.
  4113. * It performs any needed cleanup of field references and event listeners.
  4114. * Most sub-classes should not override this directly, but override
  4115. * stopObservingEvents and detachFromDOM instead.
  4116. */
  4117. destroy: function destroy() {
  4118. //Def.Logger.logMessage(['in autoCompBase.destroy, this.element.id = ',
  4119. // this.element.id]) ;
  4120. this.stopObservingEvents();
  4121. this.detachFromDOM();
  4122. },
  4123. /**
  4124. * This can be called to detach an autocompleter's event listeners.
  4125. */
  4126. stopObservingEvents: function stopObservingEvents() {
  4127. jQuery(this.element).unbind();
  4128. },
  4129. /**
  4130. * Frees any references this autocompleter has to DOM objects.
  4131. */
  4132. detachFromDOM: function detachFromDOM() {
  4133. this.element.autocomp = null;
  4134. this.element = null;
  4135. this.update = null;
  4136. this.listContainer = null;
  4137. this.recDataRequester_ = null; // has DOM references
  4138. },
  4139. /**
  4140. * Updates the field with the selected list item value.
  4141. * @param selectedElement the DOM list element (LI or TR) the user selected.
  4142. */
  4143. updateElement: function updateElement(selectedElement) {
  4144. var selectedVal = this.listItemValue(selectedElement);
  4145. var newFieldVal = selectedVal;
  4146. if (this.options.tokens) {
  4147. // We're autocompleting on paritial field values
  4148. var bounds = this.getTokenBounds();
  4149. if (bounds[0] != -1) {
  4150. var currentVal = this.domCache.get('elemVal');
  4151. var newValue = currentVal.substr(0, bounds[0]);
  4152. var whitespace = currentVal.substr(bounds[0]).match(/^\s+/);
  4153. if (whitespace) newValue += whitespace[0];
  4154. newFieldVal = newValue + selectedVal + currentVal.substr(bounds[1]);
  4155. }
  4156. }
  4157. this.setFieldVal(newFieldVal, false); // The "false" argument above means do not run change observers. After
  4158. // this gets called, propagateFieldChanges is called, and that takes care
  4159. // of running change event handlers.
  4160. if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement);
  4161. },
  4162. /**
  4163. * Shows the list.
  4164. */
  4165. show: function show() {
  4166. if (jQuery(this.update).css('display') == 'none') this.options.onShow(this.element, this.update);
  4167. if (!this.iefix && Browser.IE && jQuery(this.update).css('position') == 'absolute') {
  4168. new Insertion.After(this.update, '<iframe id="' + this.update.id + '_iefix" ' + 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
  4169. this.iefix = $(this.update.id + '_iefix');
  4170. }
  4171. if (this.iefix) setTimeout(jQuery.proxy(this.fixIEOverlapping, this), 50);
  4172. },
  4173. // This originally came from controls.js in Scriptaculous. It seems to be working
  4174. // around some IE bug. (Rewritten to use jQuery.)
  4175. fixIEOverlapping: function fixIEOverlapping() {
  4176. var updatePos = this.update.offset();
  4177. this.iefix.style.left = updatePos.left;
  4178. if (!this.update.style.height) this.update.style.top = updatePos.top;
  4179. this.iefix.style.zIndex = 1;
  4180. this.update.style.zIndex = 2;
  4181. jQuery(this.iefix).show();
  4182. },
  4183. /**
  4184. * Hides the list.
  4185. */
  4186. hide: function hide() {
  4187. if (jQuery(this.update).css('display') != 'none') this.options.onHide(this.element, this.update);
  4188. if (this.iefix) jQuery(this.iefix).hide();
  4189. },
  4190. /**
  4191. * Determines the state of the list and its items and shows/hides it as
  4192. * appropriate.
  4193. */
  4194. render: function render() {
  4195. if (this.entryCount > 0) {
  4196. for (var i = 0; i < this.entryCount; i++) {
  4197. this.index == i ? jQuery(this.getEntry(i)).addClass("selected") : jQuery(this.getEntry(i)).removeClass("selected");
  4198. }
  4199. if (this.hasFocus) {
  4200. this.show();
  4201. this.active = true;
  4202. }
  4203. } else {
  4204. this.active = false;
  4205. this.hide();
  4206. }
  4207. },
  4208. /**
  4209. * Returns the DOM node corresponding to the list item at the given index.
  4210. * @param index the zero-based index of the list item to retrieve.
  4211. */
  4212. getEntry: function getEntry(index) {
  4213. return Def.Autocompleter.listItemElements()[index];
  4214. },
  4215. // Copied as-is from controls.js (remove this comment if you modify it).
  4216. getCurrentEntry: function getCurrentEntry() {
  4217. return this.getEntry(this.index);
  4218. },
  4219. onObserverEvent: function onObserverEvent() {
  4220. this.domCache.invalidate('elemVal'); // presumably the field value changed
  4221. this.changed = false;
  4222. this.tokenBounds = null;
  4223. if (this.getToken().length >= this.options.minChars) {
  4224. this.getUpdatedChoices();
  4225. } else {
  4226. this.active = false;
  4227. this.hide();
  4228. }
  4229. this.oldElementValue = this.domCache.get('elemVal');
  4230. }
  4231. }; // end Def.Autocompleter.Base class
  4232. jQuery.extend(Def.Autocompleter.Base.prototype, tmp);
  4233. tmp = null;
  4234. }
  4235. if (true) module.exports = initializeBase;else {}
  4236. })();
  4237. /***/ }),
  4238. /* 13 */
  4239. /***/ (function(module, exports, __webpack_require__) {
  4240. // This file contains auto-completer code for the Data Entry Framework project.
  4241. // These autocompleters are based on the Autocompleter.Base class defined
  4242. // in the Script.aculo.us controls.js file.
  4243. (function () {
  4244. function definePrefetch($, jQuery, Def) {
  4245. "use strict";
  4246. var Class = Def.PrototypeAPI.Class;
  4247. var Browser = Def.PrototypeAPI.Browser;
  4248. /**
  4249. * A prefetched list autocompleter. This is extended from the Scriptaculous
  4250. * local autocompleter, and then from our autocompleter base class (so
  4251. * our settings override those of the Scriptaculous autocompleter).
  4252. */
  4253. Def.Autocompleter.Prefetch = Class.create();
  4254. Def.Autocompleter.Prefetch.constructor = Def.Autocompleter.Prefetch;
  4255. jQuery.extend(Def.Autocompleter.Prefetch.prototype, Def.Autocompleter.Base.prototype);
  4256. Def.Autocompleter.Prefetch.prototype.className = 'Def.Autocompleter.Prefetch'; // Define a temporary object for extending the Prefetch.prototype, which we
  4257. // will do below. This helps NetBeans find the methods and constants.
  4258. var tmp = {
  4259. /**
  4260. * The HTML that goes before the sequence number (if used).
  4261. */
  4262. SEQ_NUM_PREFIX: '<span class="listNum">',
  4263. /**
  4264. * The separator between the sequence number (if used) and the list item.
  4265. * (Note: The </span> matches an opening <span> before the sequence number.
  4266. */
  4267. SEQ_NUM_SEPARATOR: ':</span>&nbsp; ',
  4268. /**
  4269. * Whether the field failed validation the last time validation was
  4270. * run.
  4271. */
  4272. validationFailed_: false,
  4273. /**
  4274. * Whether or not the list has been changed since construction.
  4275. */
  4276. listIsOriginal_: true,
  4277. /**
  4278. * The list of options before the addition of item numbers. Items in this
  4279. * list will match the value of the field after an item is selected.
  4280. */
  4281. rawList_: null,
  4282. /**
  4283. * An array of the codes for the items in the list.
  4284. */
  4285. itemCodes_: null,
  4286. /**
  4287. * Keeps track of whether the autocompleter has made an attempt to
  4288. * retrieve its list using a RecordDataRequester.
  4289. */
  4290. listLoadAttempted_: false,
  4291. /**
  4292. * This is set to true when the user clicks on the "see more items" link.
  4293. */
  4294. seeMoreItemsClicked_: false,
  4295. /**
  4296. * Whether the list shown to the user should be based on matches with
  4297. * the current field value.
  4298. */
  4299. matchListItemsToField_: false,
  4300. /**
  4301. * The default selection index for this list when the field is empty.
  4302. */
  4303. defaultSelectionIndex_: null,
  4304. /**
  4305. * If true, the field will be filled in with
  4306. * the list's value if there is just one item in the list.
  4307. */
  4308. autoFill_: true,
  4309. /**
  4310. * The constructor. (See Prototype's Class.create method.)
  4311. *
  4312. * @param field the ID or the DOM element of the field for which the
  4313. * list is displayed. If an element is provided, it must contain an ID
  4314. * attribute, or one will be assigned.
  4315. * @param listItems the array of completion options (list values). If not
  4316. * specified here, the list will be supplied later by a call to the
  4317. * setListAndField function.
  4318. * @param options A hash of optional parameters. The allowed keys and their
  4319. * values are:
  4320. * <ul>
  4321. * <li>addSeqNum - whether sequence numbers should be added to the items
  4322. * in the prefetched answer list (default true).</li>
  4323. * <li>codes - the array of codes for the list values in "listItems"</li>
  4324. * <li>dataRequester - A DataRecordRequester for getting additional data
  4325. * after the user makes a selection from the completion list. This may be
  4326. * null, in which case no request for additional data is made.</li>
  4327. * <li>matchListValue - whether the field should validate its value
  4328. * against the list (default: false)</li>
  4329. * <li>autoFill - If true, the field will be filled in with
  4330. * the list's value if there is just one item in the list.</li>
  4331. * <li>suggestionMode - an integer specifying what type of suggestion
  4332. * should be offered based on what the user has typed. For values, see
  4333. * defAutocompleterBaseInit in autoCompBase.js.
  4334. * <li>itemToHeading - a hash of item codes to codes of item headings,
  4335. * where both items and headings appear in the listItems array. This
  4336. * parameter requires that the codes parameter also be supplied.</li>
  4337. * <li>defaultValue - Either the code or the item text of the default value
  4338. * for this list's field.</li>
  4339. * <li>maxSelect - (default 1) The maximum number of items that can be
  4340. * selected. Use '*' for unlimited.</li>
  4341. * <li>scrolledContainer - the element that should be scrolled to bring
  4342. * the list into view if it would otherwise extend below the edge of the
  4343. * window. The default is document.documentElement (i.e. the whole
  4344. * window). This may be null if no scrolling is desired (e.g. if the
  4345. * list field is in a fixed position on the window), but in that
  4346. * case the list element might be unusually short.
  4347. * Note: At present the only tested cases of this parameter are the
  4348. * default value and null.</li>
  4349. * <li>headerBar - If the page has a fixed-position element at the top of
  4350. * the page (e.g. a top navigation bar), the autocompleter needs to know
  4351. * that so that when scrolling to show the list it doesn't scroll the current
  4352. * field under the header bar. This is the element ID for such a header
  4353. * bar.</li>
  4354. * </ul>
  4355. */
  4356. initialize: function initialize(id, listItems, options) {
  4357. // Add Scriptaculous defaults, modified
  4358. options = jQuery.extend({
  4359. ignoreCase: true,
  4360. fullSearch: false,
  4361. selector: this.selector,
  4362. onShow: this.onShow,
  4363. onHide: this.onHide
  4364. }, options || {});
  4365. var addSeqNum = options['addSeqNum'];
  4366. this.add_seqnum = addSeqNum === undefined ? true : addSeqNum;
  4367. var autoFill = options['autoFill'];
  4368. if (autoFill !== undefined) this.autoFill_ = autoFill; // Call the base class' initialize method. We do this via the "apply"
  4369. // function, which lets us specify the "this" object plus an array of
  4370. // arguments to pass in to the method.
  4371. if (!Def.Autocompleter.Base.classInit_) Def.Autocompleter.Base.classInit();
  4372. this.initHeadings(options);
  4373. this.defAutocompleterBaseInit(id, options); // Set up event observers.
  4374. jQuery(this.element).focus(jQuery.proxy(this.onFocus, this));
  4375. jQuery(this.element).click(jQuery.proxy(this.onFieldClick, this)); // The base class sets up one for a "blur" event.
  4376. var codes = options['codes'];
  4377. this.setList(listItems, codes);
  4378. this.listIsOriginal_ = true; // reset this after calling setList
  4379. this.originalCodes_ = codes;
  4380. this.options.minChars = 0; // do autocompletion even if the field is blank
  4381. this.splitAutocomp_ = false;
  4382. jQuery(this.element).addClass('ansList');
  4383. },
  4384. /**
  4385. * Populates the list based on the field content.
  4386. */
  4387. getUpdatedChoices: function getUpdatedChoices() {
  4388. this.trimmedElemVal = this.domCache.get('elemVal').trim();
  4389. this.updateChoices(this.options.selector(this), this.pickedByNumber());
  4390. },
  4391. /**
  4392. * Used by dupForField to duplicate the item to indices map when creating a
  4393. * copy of this autocompleter for another field. The map will not be
  4394. * copied if this autocompleter is not using its original list. (The
  4395. * idea is that for fields that are given a list to begin with it makes
  4396. * sense to copy the map when duplicating, but for fields that
  4397. * are assigned lists from other actions, it does not make sense to copy
  4398. * the map.)
  4399. * @param dupAutoComp the duplciate autocompleter instance.
  4400. */
  4401. dupItemToDataIndex: function dupItemToDataIndex(dupAutoComp) {
  4402. if (this.listIsOriginal_) {
  4403. // Give the copy our hashmap of list items to codes.
  4404. if (!this.itemToDataIndex_) this.initItemToDataIndex(); // so each copy doesn't have to do it
  4405. dupAutoComp.itemToDataIndex_ = this.itemToDataIndex_;
  4406. }
  4407. },
  4408. /**
  4409. * A copy constructor, for a new field (e.g. another field in a new row
  4410. * of a table).
  4411. * @param fieldID the ID of the field being assigned to the new autocompleter
  4412. * this method creates.
  4413. * @return a new autocompleter for field field ID
  4414. */
  4415. dupForField: function dupForField(fieldID) {
  4416. var dataReq = this.dupDataReqForField(fieldID);
  4417. var opts = {};
  4418. jQuery.extend(true, opts, this.constructorOpts_);
  4419. opts['dataRequester'] = dataReq;
  4420. var rtn = new Def.Autocompleter.Prefetch(fieldID, this.rawList_, opts);
  4421. this.dupItemToDataIndex(rtn);
  4422. return rtn;
  4423. },
  4424. /**
  4425. * Initializes data structures needed to support headings.
  4426. * @param options the options parameter passed into the constructor.
  4427. */
  4428. initHeadings: function initHeadings(options) {
  4429. var codes = options['codes'];
  4430. var itemToHeading = options['itemToHeading'];
  4431. if (itemToHeading) {
  4432. // Remove this from the options so we don't re-do this part if we
  4433. // duplicate the field.
  4434. options['itemToHeading'] = null; // Initialize indexToHeadingLevel_
  4435. var headingCodeLevels = {};
  4436. var indexToHeadingLevel = {};
  4437. for (var i = 0, max = codes.length; i < max; ++i) {
  4438. var itemCode = codes[i];
  4439. var headingCode = itemToHeading[itemCode];
  4440. if (headingCode) {
  4441. // else this item has no heading
  4442. var hcLevel = headingCodeLevels[headingCode];
  4443. if (!hcLevel) {
  4444. // See if this heading has a parent heading, and make this heading's
  4445. // level one more than the parent's level.
  4446. // Assume the parent heading would have been processed ealier,
  4447. // which it would have been if the list items were in order.
  4448. var phCode = itemToHeading[headingCode];
  4449. hcLevel = phCode ? headingCodeLevels[phCode] + 1 : 1;
  4450. headingCodeLevels[headingCode] = hcLevel;
  4451. }
  4452. }
  4453. }
  4454. for (i = 0, max = codes.length; i < max; ++i) {
  4455. hcLevel = headingCodeLevels[codes[i]];
  4456. indexToHeadingLevel[i] = hcLevel ? hcLevel : 0;
  4457. }
  4458. this.indexToHeadingLevel_ = indexToHeadingLevel;
  4459. options['indexToHeadingLevel'] = indexToHeadingLevel;
  4460. this.numHeadings_ = Object.keys(headingCodeLevels).length;
  4461. options['numHeadings'] = this.numHeadings_;
  4462. } else if (options['indexToHeadingLevel']) {
  4463. this.indexToHeadingLevel_ = options['indexToHeadingLevel'];
  4464. this.numHeadings_ = options['numHeadings'];
  4465. }
  4466. },
  4467. /**
  4468. * Initializes itemToDataIndex_, based on the current value of this.rawList_.
  4469. */
  4470. initItemToDataIndex: function initItemToDataIndex() {
  4471. this.itemToDataIndex_ = {};
  4472. if (this.rawList_) {
  4473. for (var i = 0, max = this.rawList_.length; i < max; ++i) {
  4474. this.itemToDataIndex_[this.rawList_[i]] = i;
  4475. }
  4476. }
  4477. },
  4478. /**
  4479. * Generates the list of items that match the user's input. This was
  4480. * copied from the Scriptaculous controls.js Autocompleter.Local.prototype
  4481. * and modified (initially to allow matches at word boundaries).
  4482. * For focus events that are not due to a mouse click selection, we show
  4483. * the full list.
  4484. * @param instance the autocompleter instance
  4485. *
  4486. * @return the HTML for the list.
  4487. */
  4488. selector: function selector(instance) {
  4489. var ret = []; // Beginning matches
  4490. var partial = []; // Inside matches
  4491. var entry = instance.getToken();
  4492. var totalCount = 0;
  4493. var suggestionIndex = null;
  4494. var useFullList = !instance.matchListItemsToField_ || instance.domCache.get('elemVal').trim() === ''; // If the user selected "See More Items", find all the matches.
  4495. // Otherwise, limit the find to the maximum number of items we
  4496. // show in the regular list (*2 because we allow two columns).
  4497. var maxReturn = instance.seeMoreItemsClicked_ ? Infinity : Def.Autocompleter.Base.MAX_ITEMS_BELOW_FIELD * 2;
  4498. var maxItemsPerHeading = useFullList && !instance.seeMoreItemsClicked_ ? Math.floor(maxReturn / instance.numHeadings_) : Infinity;
  4499. if (maxItemsPerHeading < 1) maxItemsPerHeading = 1;
  4500. var countForLastHeading = 0; // number of items for the last header
  4501. var itemsInList = [];
  4502. var itemToHTMLData = {};
  4503. var lastHeading = null;
  4504. var foundItemForLastHeading = false;
  4505. var headerCount = 0;
  4506. var headingsShown = 0;
  4507. var skippedSelected = 0; // items already selected that are left out of the list
  4508. var escapeHTML = Def.Autocompleter.Base.escapeAttribute;
  4509. if (instance.options.ignoreCase) entry = entry.toLowerCase();
  4510. for (var i = 0, max = instance.rawList_.length; i < max; ++i) {
  4511. var tmp = instance.indexToHeadingLevel_[i];
  4512. var isSelectedByNumber = false;
  4513. if (tmp) {
  4514. lastHeading = instance.rawList_[i];
  4515. foundItemForLastHeading = false;
  4516. ++headerCount;
  4517. } else {
  4518. var itemText = null; // Find all of the matches, even though we don't return them all,
  4519. // so we can give the user a count.
  4520. // This part does not yet support multi-level headings
  4521. var rawItemText = instance.rawList_[i];
  4522. if (useFullList) {
  4523. ++totalCount;
  4524. itemText = escapeHTML(rawItemText);
  4525. } // We need to be careful not to match the HTML we've put around the
  4526. // list numbers.
  4527. // See if the entry matches a number.
  4528. var itemNumStr = null;
  4529. var matchesItemNum = false; // exact match
  4530. var matchInItemNum = false; // partial match
  4531. if (instance.add_seqnum) {
  4532. itemNumStr = i + 1 - headerCount + '';
  4533. var isSelectedByNumber = itemNumStr === entry;
  4534. if (!useFullList && (isSelectedByNumber || itemNumStr.indexOf(entry) === 0)) {
  4535. ++totalCount;
  4536. matchInItemNum = true;
  4537. if (isSelectedByNumber || totalCount <= maxReturn) {
  4538. itemNumStr = '<strong>' + itemNumStr.substr(0, entry.length) + '</strong>' + itemNumStr.substr(entry.length);
  4539. matchesItemNum = true;
  4540. itemText = instance.SEQ_NUM_PREFIX + itemNumStr + instance.SEQ_NUM_SEPARATOR + escapeHTML(rawItemText);
  4541. }
  4542. }
  4543. } // if we're adding sequence numbers to this list
  4544. if (!matchInItemNum && !useFullList) {
  4545. // See if it matches the item at the beginning
  4546. var foundMatch = false;
  4547. var elemComp = rawItemText;
  4548. if (instance.options.ignoreCase) elemComp = rawItemText.toLowerCase();
  4549. var foundPos = elemComp.indexOf(entry);
  4550. while (!foundMatch && foundPos !== -1) {
  4551. if (foundPos === 0) {
  4552. ++totalCount;
  4553. foundMatch = true;
  4554. if (totalCount <= maxReturn) {
  4555. itemText = '<strong>' + escapeHTML(rawItemText.substr(0, entry.length)) + '</strong>' + escapeHTML(rawItemText.substr(entry.length));
  4556. }
  4557. } else {
  4558. // foundPos > 0
  4559. // See if the match is at a word boundary
  4560. if (instance.options.fullSearch || /(.\b|_)./.test(elemComp.substr(foundPos - 1, 2))) {
  4561. ++totalCount;
  4562. foundMatch = true;
  4563. if (totalCount <= maxReturn) {
  4564. var prefix = escapeHTML(rawItemText.substr(0, foundPos));
  4565. itemText = prefix + '<strong>' + escapeHTML(rawItemText.substr(foundPos, entry.length)) + '</strong>' + escapeHTML(rawItemText.substr(foundPos + entry.length));
  4566. }
  4567. }
  4568. }
  4569. if (!foundMatch) foundPos = elemComp.indexOf(entry, foundPos + 1);
  4570. } // while we haven't found a match at a word boundary
  4571. } // if it didn't match the item number
  4572. var alreadySelected = false;
  4573. if (instance.multiSelect_) {
  4574. alreadySelected = instance.isSelected(rawItemText);
  4575. if (alreadySelected) ++skippedSelected;
  4576. } // Make sure that if the item's number is an exact match for what was
  4577. // typed, it gets into the list (unless already selected).
  4578. // For multi-select lists, filter out currently selected items.
  4579. // Then, only add it if we haven't exceeded the limit.
  4580. if (!alreadySelected && itemText && (isSelectedByNumber || totalCount <= maxReturn || instance.numHeadings_ > 0 && useFullList)) {
  4581. if (lastHeading && !foundItemForLastHeading) {
  4582. foundItemForLastHeading = true;
  4583. itemsInList.push(lastHeading);
  4584. ++headingsShown;
  4585. itemToHTMLData[lastHeading] = [escapeHTML(lastHeading), 'heading'];
  4586. countForLastHeading = 0;
  4587. }
  4588. if (!useFullList || !instance.numHeadings_ || countForLastHeading < maxItemsPerHeading || isSelectedByNumber) {
  4589. if (!matchesItemNum && instance.add_seqnum) {
  4590. itemText = instance.SEQ_NUM_PREFIX + itemNumStr + instance.SEQ_NUM_SEPARATOR + itemText;
  4591. }
  4592. itemsInList.push(rawItemText);
  4593. if (isSelectedByNumber) suggestionIndex = itemsInList.length - 1;
  4594. itemToHTMLData[rawItemText] = [itemText];
  4595. if (useFullList) ++countForLastHeading;
  4596. }
  4597. }
  4598. } // else this is not a heading
  4599. } // for each item
  4600. var itemsShownCount = itemsInList.length - headingsShown;
  4601. if (totalCount > itemsShownCount + skippedSelected) {
  4602. $('searchCount').innerHTML = itemsShownCount + ' of ' + totalCount + ' items total';
  4603. $('moreResults').style.display = 'block';
  4604. $('searchCount').style.display = 'block';
  4605. } else {
  4606. $('moreResults').style.display = 'none';
  4607. $('searchCount').style.display = 'none';
  4608. }
  4609. return instance.buildHTML(itemsInList, itemToHTMLData, suggestionIndex);
  4610. },
  4611. /**
  4612. * Constructs the HTML for the list.
  4613. * @param itemsInList an array of the raw item text for the items to shown
  4614. * @param itemToHTMLData a hash from raw item texts to an array of data for
  4615. * the HTML output. The first item should be the item text with any needed
  4616. * HTML markup. The second item, if present, should be a class to apply to
  4617. * the item's row in the list.
  4618. * @param suggestionIndex the index of the item found for the suggested
  4619. * item, or null if one is not known yet.
  4620. */
  4621. buildHTML: function buildHTML(itemsInList, itemToHTMLData, suggestionIndex) {
  4622. // Don't use suggestions if there are headings, or if we are showing the
  4623. // full list.
  4624. var topItemIndex = -1;
  4625. var i, topItem;
  4626. var haveSug = suggestionIndex !== null;
  4627. if (!this.numHeadings_ && this.matchListItemsToField_ && (haveSug || this.suggestionMode_ === Def.Autocompleter.SUGGEST_SHORTEST)) {
  4628. var topItemIndex = haveSug ? suggestionIndex : this.pickBestMatch(itemsInList);
  4629. if (topItemIndex >= 0) {
  4630. // Move that item to the start of the list
  4631. var topItem = itemsInList[topItemIndex];
  4632. for (i = topItemIndex; i > 0; --i) {
  4633. itemsInList[i] = itemsInList[i - 1];
  4634. }
  4635. itemsInList[0] = topItem;
  4636. }
  4637. }
  4638. var rtn = '<ul>'; // Process the first item separately, because it might be a suggestion.
  4639. i = 0;
  4640. if (topItemIndex >= 0) {
  4641. rtn += '<li class="suggestion">' + itemToHTMLData[topItem][0] + '</li>';
  4642. ++i;
  4643. }
  4644. for (var len = itemsInList.length; i < len; ++i) {
  4645. var itemData = itemToHTMLData[itemsInList[i]];
  4646. var cls = itemData[1];
  4647. if (cls) rtn += '<li class="' + cls + '">' + itemData[0] + '</li>';else rtn += '<li>' + itemData[0] + '</li>';
  4648. }
  4649. rtn += '</ul>';
  4650. return rtn;
  4651. },
  4652. /**
  4653. * Sets the list of items.
  4654. * @param listItems an array of strings to use for the list
  4655. * @param itemCodes an array of codes corresponding to the items in listItems
  4656. */
  4657. setList: function setList(listItems, itemCodes) {
  4658. //Def.Logger.logMessage(['in setList, listItems = [', listItems.join(', '),
  4659. // '] and itemCodes = [', itemCodes.join(', '), ']'])
  4660. //Def.Logger.logMessage(['this.element.id = ',
  4661. // this.element.id])
  4662. // Copy the list of options for future reference, and also make a hash
  4663. // of the values for checking whether the field's value matches the list.
  4664. // Also trim the list items before using them.
  4665. // Some values (e.g. the strengths in the drug strength and form field)
  4666. // have padding in front to help with the sorting. However, once we are
  4667. // putting them into the list, we don't need to sort them further.
  4668. this.listIsOriginal_ = false;
  4669. var numItems = listItems.length;
  4670. this.rawList_ = new Array(numItems);
  4671. for (var r = 0, max = listItems.length; r < max; ++r) {
  4672. this.rawList_[r] = listItems[r].trim();
  4673. }
  4674. var displayList = new Array(numItems);
  4675. var escapeHTML = Def.Autocompleter.Base.escapeAttribute;
  4676. for (var i = 0; i < numItems; ++i) {
  4677. displayList[i] = escapeHTML(this.rawList_[i]); // preprocess option list to add a serial number in the beginning of the
  4678. // each item in the list, except for list headers
  4679. if (this.add_seqnum === true && !this.indexToHeadingLevel_[i]) {
  4680. displayList[i] = this.SEQ_NUM_PREFIX + (i + 1) + this.SEQ_NUM_SEPARATOR + displayList[i];
  4681. }
  4682. }
  4683. this.options.array = displayList;
  4684. this.itemCodes_ = itemCodes;
  4685. this.itemToDataIndex_ = null; // to be built later
  4686. // Turn off autocomplete listeners when we don't have a list
  4687. this.enabled_ = listItems.length > 0; // Add a class to the field if there is more than 1 item in the list
  4688. // (so that CSS can add a small arrow-shaped background image).
  4689. if (listItems.length > 1) jQuery(this.element).addClass('ac_multiple');else jQuery(this.element).removeClass('ac_multiple'); // If the field has focus, call onFocus to re-render and decide what
  4690. // to do about displaying the list.
  4691. if (this.hasFocus || document.activeElement === this.element) this.onFocus();
  4692. },
  4693. /**
  4694. * Sets the field value to a known list value. No checking is done
  4695. * on the value; it is assumed that the caller knows it is a valid
  4696. * list value.
  4697. */
  4698. setFieldToListValue: function setFieldToListValue(newVal) {
  4699. this.setFieldVal(newVal, false);
  4700. this.fieldValIsListVal_ = true;
  4701. this.storeSelectedItem(); // Set this value as the "processed value", so that when we send a change
  4702. // event below, the autocompleter does not try to select the value from the
  4703. // list (which can fail if the list is not active, e.g. when the value
  4704. // is being set programattically, as in via selectByCode()).
  4705. this.processedFieldVal_ = newVal; // Queue the list selection event before doing further processing,
  4706. // which might trigger other events (i.e. the duplication warning event.)
  4707. this.listSelectionNotification('', true);
  4708. this.setMatchStatusIndicator(true);
  4709. this.setInvalidValIndicator(false);
  4710. this.propagateFieldChanges();
  4711. },
  4712. /**
  4713. * Sets the list of items. If there is just one value in the list, the
  4714. * field value is set to that value too. If there is more than one value
  4715. * in the list, the field value is set to blank, because the user should
  4716. * select a new value if the field now has a new list.
  4717. *
  4718. * This is invoked when the list is populated based on the value
  4719. * specified in a different field. For example, and specifically, the
  4720. * PHR form has a drug field (e.g. aspirin), and has a field for the
  4721. * combined strength and form (e.g. 325 MG Tabs). The list of applicable
  4722. * strength and form values is not built until the user specifies the
  4723. * drug. When that happens, the autocompleter for the drug field
  4724. * gets and passes the strength and form list to this TablePrefetch
  4725. * autocompleter. It's called from the assignDataToFields function -
  4726. * see Def.Autcompleter.Base.
  4727. *
  4728. * @param listItems an array of strings to use for the list
  4729. * @param itemCodes an array of codes corresponding to the items in listItems
  4730. * @param fieldAlreadySet an optional flag that indicates whether or not
  4731. * the caller has taken care of updating the field value and its
  4732. * associated code field. (Default is FALSE, in which case the field
  4733. * and code will be updated by this method.)
  4734. * @param pickFirstItem an optional flag that indicates whether or not to set
  4735. * the field value with the first item in the list, even if the list is
  4736. * longer than 1.
  4737. */
  4738. setListAndField: function setListAndField(listItems, itemCodes, fieldAlreadySet, pickFirstItem) {
  4739. if (fieldAlreadySet === undefined) fieldAlreadySet = false;
  4740. if (pickFirstItem === undefined) pickFirstItem = false;
  4741. this.setList(listItems, itemCodes);
  4742. Def.Autocompleter.Event.notifyObservers(this.element, 'LIST_ASSIGNMENT', {}); // Reset the contents of the field unless the fieldAlreadySet flag
  4743. // is set to false
  4744. var oldValue = this.domCache.get('elemVal');
  4745. var lenList = listItems.length;
  4746. var newVal;
  4747. if (fieldAlreadySet === false) {
  4748. if (this.autoFill_ && (lenList === 1 || lenList > 1 && pickFirstItem)) newVal = this.assembleValue(listItems[0]);else newVal = ''; // Set the field value, but leave the running of change event observers
  4749. // until later.
  4750. this.setFieldVal(newVal, false);
  4751. this.fieldValIsListVal_ = true;
  4752. } // If the value changed, update stuff that needs updating.
  4753. if (!fieldAlreadySet && oldValue !== newVal) {
  4754. this.setFieldToListValue(newVal);
  4755. } // Clear the no_match and invalid indicators, if they are set.
  4756. // (Presumably, we have corrected the problem by setting the field value.)
  4757. this.setInvalidValIndicator(false);
  4758. this.setMatchStatusIndicator(true);
  4759. if (this.options.afterUpdateElement) this.options.afterUpdateElement();
  4760. },
  4761. /**
  4762. * Used by setListAndField to construct an element value from one of the
  4763. * list items passed in.
  4764. * @param listItem One of the list items given to setListAndField (or in
  4765. * the same format.
  4766. * @return the value for the field.
  4767. */
  4768. assembleValue: function assembleValue(listItem) {
  4769. return listItem.trim();
  4770. },
  4771. /**
  4772. * Override the observer event function (called after a small delay following
  4773. * a key stroke) so that the list position gets updated.
  4774. */
  4775. onObserverEvent: function onObserverEvent() {
  4776. // First, hide the list so we don't see the list update and then move
  4777. this.temporaryHide_ = true;
  4778. this.hideList(); // $('searchCount').style.display = 'none';
  4779. // $('searchHint').style.display = 'none';
  4780. // Now call the base class' onObserverEvent
  4781. Def.Autocompleter.Base.prototype.onObserverEvent.apply(this, []);
  4782. this.posAnsList();
  4783. this.showList(); // Dan Clark of Freedom Scientific reported that the search count made
  4784. // the output for JAWS to verbose, so I am commenting out this call.
  4785. // this.readSearchCount();
  4786. this.temporaryHide_ = false;
  4787. },
  4788. /**
  4789. * Attempts to find a RecordDataRequester that it can use to retrieve
  4790. * the list for this autocompleter. (This method assumes this autocompleter
  4791. * does not already have a list.)
  4792. *
  4793. * @param outputFieldID - optional parameter that provides the name of
  4794. * the field that is to receive the list. The default is the current
  4795. * element's id, which is used when this is not supplied. This is used
  4796. * in those cases where the output field did/does not exist on the form
  4797. * and we are getting the list after the field has been created. The
  4798. * use case for this is combo fields whose lists are dependent on values
  4799. * chosen from other (possibly combo) field lists. An example is the
  4800. * drug_strength_form "field" on the fetch rule form, whose contents are
  4801. * dependent on a name_and_route value previously chosen.
  4802. *
  4803. * @param triggerFieldID - the (also) optional parameter that provides
  4804. * the name of a field whose current contents are set up as a condition
  4805. * that later determines whether or not the autocompleter for the current
  4806. * or output field should be updated when the list being loaded changes.
  4807. * I know. That's really convoluted. I don't know yet how to simplify
  4808. * it. lm, 10/2009.
  4809. */
  4810. loadList: function loadList(outputFieldID, triggerFieldID) {
  4811. // Def.Logger.logMessage(['in pac.loadList, this.element.id = ' ,
  4812. // this.element.id, '; outputFieldID = ' ,
  4813. // outputFieldID, '; triggerFieldID = ' ,
  4814. // triggerFieldID]) ;
  4815. // Set the targetField based on whether or not an outputFieldID was
  4816. // specified
  4817. if (outputFieldID === undefined) outputFieldID = this.element.id;
  4818. var targetField = Def.Autocompleter.getFieldLookupKey(this.element); // Now try to get the RecordDataRequester for the output field
  4819. this.listLoadAttempted_ = true;
  4820. var listRdr = Def.RecordDataRequester.getOutputFieldRDR(outputFieldID); // If we got a RecordDataRequester, try to re-use the a prior data
  4821. // request's data. If we don't get anything for that, try to issue a new
  4822. // request.
  4823. if (listRdr) {
  4824. var listData = listRdr.getFieldData(targetField);
  4825. if (listData) {
  4826. // Use setListAndField, because that takes care of running rules, etc.
  4827. this.setListAndField(listData[0], listData[1], true, false);
  4828. } else {
  4829. // maybe the RecordDataRequester hasn't run yet
  4830. listRdr.assignListData();
  4831. } // If we passed in an outputFieldID, it means that we're loading a list
  4832. // into an autocompleter whose list comes from the RecordDataRequester
  4833. // of another autocompleter. Add this field to that
  4834. // RecordDataRequester's list of fields to be updated if the value
  4835. // chosen from its list changes. That's because if the value chosen
  4836. // from its list changes, it will change the list that the current
  4837. // autocompleter should be using.
  4838. if (outputFieldID !== this.element.id) {
  4839. var triggerField = $(triggerFieldID);
  4840. var update_condition = [triggerFieldID, 'EQ', Def.Autocompleter.getFieldVal(triggerField)];
  4841. listRdr.addFieldsToUpdateList(outputFieldID, this, update_condition);
  4842. }
  4843. } // end if we got an rdr
  4844. },
  4845. // end loadList
  4846. /**
  4847. * Returns true if the list is empty. (May be overridden by a subclass.)
  4848. */
  4849. listIsEmpty: function listIsEmpty() {
  4850. return this.options.array.length === 0;
  4851. },
  4852. /**
  4853. * Returns the initial selection index for the list for when it is
  4854. * first shown (e.g. on a focus event). This currently only works
  4855. * for cases where the number of items in the list does not exceed
  4856. * MAX_ITEMS_BELOW_FIELD. (Otherwise, it just returns -1.)
  4857. */
  4858. getInitialSelectionIndex: function getInitialSelectionIndex() {
  4859. // Set the selection index to -1, so that initially nothing is selected,
  4860. // unless there is a field default and the field is blank, in which case
  4861. // we use the index of the default.
  4862. var index = -1; // default (no selection)
  4863. if (this.domCache.get('elemVal') == '') {
  4864. if (!this.defaultSelectionIndex_) {
  4865. // Find the default index
  4866. var defaultVal = this.constructorOpts_.defaultValue;
  4867. if (defaultVal !== undefined) {
  4868. if (this.itemCodes_) {
  4869. // the default value should be a code
  4870. for (var i = 0, max = this.itemCodes_.length; i < max; ++i) {
  4871. if (this.itemCodes_[i] === defaultVal) index = i;
  4872. }
  4873. }
  4874. if (index === -1) {
  4875. // Look for the value in the list itself.
  4876. for (var r = 0, maxlen = this.rawList_.length; r < maxlen; ++r) {
  4877. if (this.rawList_[r] === defaultVal) index = r;
  4878. }
  4879. }
  4880. } // If the index is less than the number of items we show below
  4881. // the field, use it; otherwise we won't use the default, because we
  4882. // can't show it selected in the list if it isn't visible.
  4883. if (index >= Def.Autocompleter.Base.MAX_ITEMS_BELOW_FIELD * 2) index = -1;
  4884. this.defaultSelectionIndex_ = index;
  4885. } else index = this.defaultSelectionIndex_;
  4886. }
  4887. return index;
  4888. },
  4889. /**
  4890. * A method that gets called when the field gains the focus.
  4891. */
  4892. onFocus: function onFocus() {
  4893. // Ignore blur events on the completionOptionsScroller.
  4894. if (Def.Autocompleter.completionOptionsScrollerClicked_ === true) {
  4895. Def.Autocompleter.completionOptionsScrollerClicked_ = false;
  4896. } else {
  4897. this.matchListItemsToField_ = false; // See if we should try to load the list. Do not try if this is a combo
  4898. // field, because in that case the field does not get its list from a
  4899. // record data requester (it gets its type changed by it).
  4900. if (!this.listLoadAttempted_ && this.listIsEmpty() && !this.element.comboField) {
  4901. this.loadList();
  4902. }
  4903. if (this.enabled_) {
  4904. this.listBelowField_ = true;
  4905. this.focusInProgress_ = true;
  4906. this.hideList(); // while we reposition
  4907. Def.Autocompleter.Base.prototype.onFocus.apply(this);
  4908. this.element.shakeCanceled = false;
  4909. this.maybeShowList();
  4910. this.index = this.getInitialSelectionIndex(); // for field defaults
  4911. // Also put the value at "index" in the field
  4912. if (this.index >= 0) {
  4913. this.setFieldToListValue(this.listItemValue(this.getCurrentEntry())); // this.selectEntry();
  4914. this.element.select(); // select the text
  4915. // selectEntry above will send a list selection event, so there is
  4916. // no need to do that here.
  4917. // selectEntry hides the list. Call render to highlight the default
  4918. // item and show the list.
  4919. this.render(); // to highlight the entered item
  4920. }
  4921. this.focusInProgress_ = false;
  4922. } // if enabled
  4923. }
  4924. },
  4925. /**
  4926. * Decides whether the list should be shown, and if so, positions and shows
  4927. * it. This used to be a part of onFocus.
  4928. */
  4929. maybeShowList: function maybeShowList() {
  4930. this.activate(); // determines what is in the list (and resets the index)
  4931. this.render(); // marks which item is selected
  4932. //show the list based on following rules.
  4933. var blnShowList = false;
  4934. if (this.add_seqnum == false) {
  4935. //show list if number of choices > 0 (when no sequence number was added)
  4936. blnShowList = this.entryCount > 0;
  4937. } else {
  4938. //show list if number of choices > 1 and sequence number added
  4939. if (this.entryCount > 1) {
  4940. blnShowList = true;
  4941. } //check if the list item value matches field value
  4942. else if (this.entryCount == 1) {
  4943. var value = this.listItemValue(Def.Autocompleter.listItemElements()[0]);
  4944. blnShowList = value != this.processedFieldVal_;
  4945. }
  4946. }
  4947. if (blnShowList == true) {
  4948. // This sets the top for the initial list displayed
  4949. // when the field first gets focus
  4950. this.posAnsList();
  4951. this.showList();
  4952. this.readSearchCount();
  4953. }
  4954. },
  4955. /**
  4956. * Handles clicks on the field.
  4957. */
  4958. onFieldClick: function onFieldClick() {
  4959. if (this.enabled_ && // i.e. has list items
  4960. this.element.id === Def.Autocompleter.currentAutoCompField_ && (!this.listShowing || this.matchListItemsToField_)) {
  4961. //not already showing the full list
  4962. this.matchListItemsToField_ = false; // Temporarily disable list suggestions so we just show the whole list
  4963. // in order.
  4964. var oldSug = this.suggestionMode_;
  4965. this.suggestionMode_ = Def.Autocompleter.NO_COMPLETION_SUGGESTIONS;
  4966. this.maybeShowList();
  4967. this.suggestionMode_ = oldSug;
  4968. }
  4969. },
  4970. /**
  4971. * Puts the focus into the field.
  4972. */
  4973. focusField: function focusField() {
  4974. this.element.focus();
  4975. },
  4976. /**
  4977. * Returns the value of a list item (minus any sequence number an
  4978. * separator.)
  4979. * @param li the list item DOM element.
  4980. */
  4981. listItemValue: function listItemValue(li) {
  4982. var value = li.innerHTML;
  4983. if (this.add_seqnum) {
  4984. // Check to see if browser is IE.
  4985. // All versions of IE convert lower case tag names to upper case - anantha (11/17/09)
  4986. if (Browser.IE) value = value.replace("SPAN", "span");
  4987. var index = value.indexOf(this.SEQ_NUM_SEPARATOR);
  4988. if (index >= 0) // headings won't have the list number
  4989. value = value.substring(index + this.SEQ_NUM_SEPARATOR.length); // Strip out any remaining tags and unescape the HTML
  4990. value = value.replace(/(<([^>]+)>)/ig, "");
  4991. value = Def.Autocompleter.Base.unescapeAttribute(value);
  4992. } else value = li.textContent;
  4993. return value;
  4994. },
  4995. /**
  4996. * Returns true if the given key event (from the input field) is a request
  4997. * for seeing the full list. We are borrowing the key syntax for running
  4998. * a search in the search autocompleter, and reusing code (and hence the
  4999. * function name).
  5000. */
  5001. fieldEventIsBigList: function fieldEventIsBigList(event) {
  5002. return event.ctrlKey && event.keyCode === jQuery.ui.keyCode.ENTER;
  5003. },
  5004. /**
  5005. * This gets called when the "See more items" link is clicked (or when
  5006. * control + return is pressed, which is another way of making the same
  5007. * request).
  5008. * @param event the click event on the link
  5009. */
  5010. handleSeeMoreItems: function handleSeeMoreItems(event) {
  5011. this.seeMoreItemsClicked_ = true;
  5012. $('searchHint').style.display = 'none';
  5013. this.listBelowField_ = false;
  5014. this.getUpdatedChoices();
  5015. this.posAnsList();
  5016. this.seeMoreItemsClicked_ = false;
  5017. this.splitAutocomp_ = false;
  5018. Def.Autocompleter.stopEvent(event);
  5019. },
  5020. /**
  5021. * Returns the index of the item matching the given code.
  5022. */
  5023. findItemIndexByCode: function findItemIndexByCode(code) {
  5024. // Find the index of the code in the list.
  5025. var codeIndex = null;
  5026. for (var i = 0, max = this.itemCodes_.length; i < max && !codeIndex; ++i) {
  5027. if (code == this.itemCodes_[i]) codeIndex = i;
  5028. }
  5029. return codeIndex;
  5030. },
  5031. /**
  5032. * Selects an item from the list by its code value. Both the list field and
  5033. * the code field are set as a result.
  5034. * @param code the code value for the list item
  5035. */
  5036. selectByCode: function selectByCode(code) {
  5037. var codeIndex = this.findItemIndexByCode(code);
  5038. if (codeIndex != null) {
  5039. this.setFieldToListValue(this.rawList_[codeIndex]);
  5040. }
  5041. },
  5042. /**
  5043. * "Reads" the searchCount and moreResults divs via the ScreenReaderLog.
  5044. * @return true if the search count was read.
  5045. */
  5046. readSearchCount: function readSearchCount() {
  5047. var rtn = Def.Autocompleter.Base.prototype.readSearchCount.apply(this);
  5048. if (!rtn && this.entryCount > 0) {
  5049. Def.Autocompleter.screenReaderLog('Showing ' + this.entryCount + ' of ' + this.rawList_.length + ' items.');
  5050. rtn = true;
  5051. }
  5052. return rtn;
  5053. },
  5054. // Copied as-is from controls.js (remove this comment if you modify it).
  5055. activate: function activate() {
  5056. this.changed = false;
  5057. this.hasFocus = true;
  5058. this.getUpdatedChoices();
  5059. }
  5060. }; // end Def.Autocompleter.Prefetch class
  5061. jQuery.extend(Def.Autocompleter.Prefetch.prototype, tmp);
  5062. tmp = null; // prevent other code here from accidentally using it
  5063. }
  5064. if (true) module.exports = definePrefetch;else {}
  5065. })();
  5066. /***/ }),
  5067. /* 14 */
  5068. /***/ (function(module, exports, __webpack_require__) {
  5069. // This file defines the "search" (AJAX) autocompleter.
  5070. // These autocompleters are based on the Autocompleter.Base class defined
  5071. // in the Script.aculo.us controls.js file.
  5072. (function () {
  5073. function defineSearch($, jQuery, Def) {
  5074. "use strict";
  5075. var Class = Def.PrototypeAPI.Class;
  5076. /**
  5077. * An autocompleter that retrieves list options via AJAX calls.
  5078. */
  5079. Def.Autocompleter.Search = Class.create(); // This is the definition for the Search class methods. We define it in
  5080. // a temporary object to help NetBeans see it.
  5081. var ctmp = {
  5082. /**
  5083. * A cache for search result objects. The key is the search
  5084. * autocompleter's base URL, and the value is a cache for queries sent to
  5085. * that URL. (In a repeating line table, the cache gets shared across rows.)
  5086. */
  5087. urlToCache_: {},
  5088. /**
  5089. * The index into the resultCache_ (an instance variable) for the part
  5090. * of the cache used to store autocompletion results (which are generally
  5091. * fewer than the search results, which can be up to 500.)
  5092. */
  5093. RESULT_CACHE_AUTOCOMP_RESULTS: 1,
  5094. /**
  5095. * The index into the resultCache_ (an instance variable) for the part
  5096. * of the cache used to store search results (which generally have many
  5097. * more returned hits than the autcompletions results.)
  5098. */
  5099. RESULT_CACHE_SEARCH_RESULTS: 0,
  5100. /**
  5101. * The maximum number of characters in the field for which we will send
  5102. * an autocompletion request. If the field value is longer than this,
  5103. * we will truncate it when sending the request.
  5104. */
  5105. MAX_VALUE_SIZE_FOR_AUTOCOMP: 25,
  5106. /**
  5107. * The constructor function.
  5108. */
  5109. constructor: Def.Autocompleter.Search,
  5110. /**
  5111. * The superclass.
  5112. */
  5113. superclass: Def.Autocompleter.Base.prototype
  5114. };
  5115. jQuery.extend(Def.Autocompleter.Search, ctmp);
  5116. ctmp = null;
  5117. jQuery.extend(Def.Autocompleter.Search.prototype, Def.Autocompleter.Base.prototype);
  5118. Def.Autocompleter.Search.prototype.className = 'Def.Autocompleter.Search'; // This is the definition for the Search instance methods. We define it in
  5119. // a temporary object to help NetBeans see it.
  5120. var tmp = {
  5121. /**
  5122. * The pending Ajax request (if any).
  5123. */
  5124. lastAjaxRequest_: null,
  5125. /**
  5126. * A reference to the search result cache for this autocompleter. The
  5127. * results cache is an array of two hashes, where the index is the value of
  5128. * the "autocomp" parameter in the AJAX request, i.e, the 0th hash is
  5129. * the hash for the non-autocomp request (e.g. control+return to see
  5130. * an expanded results list) and the hash at index 1 is the hash for
  5131. * autocompletion results. Each hash is a hash from the search string
  5132. * the autocompletion results for the string 'pro'.)
  5133. */
  5134. resultCache_: null,
  5135. /**
  5136. * Whether we are using search result caches in this autocompleter.
  5137. * It might not be a good idea for all fields, but for now the default
  5138. * is to use it.
  5139. */
  5140. useResultCache_: true,
  5141. /**
  5142. * The data for the suggestion list that appears when the user leaves a
  5143. * non-matching field value in a field for which matching values are not
  5144. * required. (This could also be used for suggestions when a matching value
  5145. * is required, but we would need to change the message the user sees to
  5146. * handle that case.)
  5147. */
  5148. suggestionList_: null,
  5149. /**
  5150. * The constructor. (See Prototype's Class.create method.)
  5151. * @param field the ID or the DOM element of the field for which the
  5152. * list is displayed. If an element is provided, it must contain an ID
  5153. * attribute, or one will be assigned.
  5154. * @param url for getting the completion list. The website answering the
  5155. * URL is expected to understand the following parameters:
  5156. * <ul>
  5157. * <li>terms - the text from the field. This should be used to find
  5158. * matching list items.</li>
  5159. * <li>maxList - if present, this signifies that this is a request
  5160. * for a large list of search results (e.g. by using the "see more" link
  5161. * on the list). If maxList is not present, that means this is an autocompletion
  5162. * request and the server should return a short list (e.g. 7 items) as
  5163. * quickly as possible.</li>
  5164. * <li>authenticity_token - (optional) This is an anti-CSRF parameter.
  5165. * If the page has a value in window._token, it will get sent in this
  5166. * parameter.</li>
  5167. * <li>suggest - (optional) User input that does not match a list value
  5168. * will trigger a request for suggestions that are close to what the
  5169. * user typed. A "suggest" parameter value of "1" means the request
  5170. * is for suggestions.</li>
  5171. * <li>field_val - When "suggest"==1, this contains the value the user
  5172. * typed.</li>
  5173. * </ul>
  5174. * The URL's response should be an array. For a non-suggestion request
  5175. * (suggest != '1'), it should have the following elements:
  5176. * <ul>
  5177. * <li>position 0 - the total search result count (including the ones not
  5178. * returned, if autocomp==1).</li>
  5179. * <li>position 1 - the list of codes for the list items (if the items are
  5180. * coded)</li>
  5181. * <li>position 2 - A hash of extra data about the list items (e.g.
  5182. * an extra set of codes), or null if there is none.
  5183. * The keys in the hash should be names for the
  5184. * data elements, and the values should be an array of values, one for
  5185. * each returned item. Configuration for what gets returned here is out
  5186. * of scope of this class; this search autocompleter just sends the
  5187. * parameters above. The extra data for the selected item (when the
  5188. * user makes a selection) can get be retrieved with
  5189. * getItemExtraData(itemText).</li>
  5190. * <li>position 3 - the list item data; each item is an array of display
  5191. * string fields which will be joined together. (At a mimimum, each item
  5192. * should be an array of one string.) These display strings can contain
  5193. * span tags for styling sub-strings (e.g. matches to the user's input)
  5194. * but other HTML tags will be escaped.</li>
  5195. * <li>position 4 - if present, this is an array of code system names
  5196. * identifying the code system for each of the codes in the code array in
  5197. * position 1. This is useful for lists that contain entries from
  5198. * different code systems.</li>
  5199. * </ul>
  5200. * For a "suggest" request, the response should have the following elements:
  5201. * <ul>
  5202. * <li>position 0 - the list of codes for the suggested items (if the
  5203. * items have codes)</li>
  5204. * <li>position 1 - the list of display strings (an array of strings, not
  5205. * of arrays) for the suggested items.</li>
  5206. * <li>position 2 - A hash of extra data about the list items (the same
  5207. * as position 2 for the non-suggestion request above.)
  5208. * <li>position 3 - if present, this is an array of code system names
  5209. * identifying the code system for each of the codes in the code array in
  5210. * position 0. This is useful for lists that contain entries from
  5211. * different code systems.</li>
  5212. * </ul>
  5213. * @param options A hash of optional parameters. The allowed keys and their
  5214. * values are:
  5215. * <ul>
  5216. * <li>matchListValue - Whether the field value is required to be one from
  5217. * the list (default: false).</li>
  5218. * <li>sort - Whether or not values should be sorted after being
  5219. * retrieved from the server. (Default: true). Note that if you do not
  5220. * want sorting, you might also want set the suggestionMode parameter to
  5221. * Def.Autocompleter.NO_COMPLETION_SUGGESTIONS so that a suggestion is
  5222. * not moved to the top of the list.</li>
  5223. * <li>suggestionMode - an integer specifying what type of suggestion
  5224. * should be offered based on what the user has typed. For values, see
  5225. * defAutocompleterBaseInit in autoCompBase.js.
  5226. * <li>useResultCache - (default: true) Whether or not the results
  5227. * should be cached. The same cache is used for all fields that share
  5228. * the same url parameter value.</li>
  5229. * <li>maxSelect - (default 1) The maximum number of items that can be
  5230. * selected. Use '*' for unlimited.</li>
  5231. * <li>minChars - (default 1) The minimum number of characters that must
  5232. * be in the field before autocompletion will start.</li>
  5233. * <li>scrolledContainer - the element that should be scrolled to bring
  5234. * the list into view if it would otherwise extend below the edge of the
  5235. * window. The default is document.documentElement (i.e. the whole
  5236. * window). This may be null if no scrolling is desired (e.g. if the
  5237. * list field is in a fixed position on the window), but in that
  5238. * case the list element might be unusually short.
  5239. * Note: At present the only tested cases of this parameter are the
  5240. * default value and null.</li>
  5241. * <li>nonMatchSuggestions - (default: false, as of version 10)
  5242. * Whether a list of suggestions should be generated if the user
  5243. * enters a non-matching value. To receive the list of suggestions,
  5244. * the program should register a callback function via
  5245. * Def.Autocompleter.Event.observeSuggestions, and if the user selects
  5246. * a suggestion, the function acceptSuggestion should be called on the
  5247. * autocompleter instance with the index of the selected suggestion.
  5248. * See section on Notifications in the documentation. This only
  5249. * applies when matchListValue is false.
  5250. * <li>headerBar - If the page has a fixed-position element at the top of
  5251. * the page (e.g. a top navigation bar), the autocompleter needs to know
  5252. * that so that when scrolling to show the list it doesn't scroll the current
  5253. * field under the header bar. This is the element ID for such a header
  5254. * bar.</li>
  5255. * <li>tableFormat - If true, then if the list's items contain
  5256. * multiple fields, the list will be formatted in a table instead of just
  5257. * concatenating the fields together for each list item.</li>
  5258. * <li>valueCols - Used when tableFormat is true to indicate
  5259. * which columns in the table should be combined to form the field value
  5260. * when the row is selected. This should be an array of column indices
  5261. * (starting with 0). If absent, all columns will be combined for the
  5262. * value. Note that the specification here must result in unique field
  5263. * values for each table row.</li>
  5264. * <li>colHeaders - Used when tableFormat is true, this is an array of
  5265. * column headers for the columns in the table. If this is not supplied, no header
  5266. * row will be created.</li>
  5267. * <ul>Somewhat obsolete, but not yet deprecated, parameters:
  5268. * <li>buttonID - the ID of the button (if there is one) which activates
  5269. * a search. If you use this option, do not set matchListValue.</li>
  5270. * <li>autocomp - a boolean that controls whether the field should
  5271. * also autocomplete as the user types. When this is false, the user
  5272. * won't see an autocompletion list until they hit return. (Default: true)</li>
  5273. * <li>dataRequester - A RecordDataRequester for getting additional data
  5274. * after the user makes a selection from the completion list. This may be
  5275. * null, in which case no request for additional data is made.</li>
  5276. * </ul>
  5277. * </ul>
  5278. */
  5279. initialize: function initialize(fieldID, url, options) {
  5280. options = jQuery.extend({
  5281. partialChars: 2,
  5282. onHide: jQuery.proxy(function (element, update) {
  5283. $('searchCount').style.display = 'none';
  5284. $('moreResults').style.display = 'none';
  5285. Def.Autocompleter.Base.prototype.hideList.apply(this);
  5286. }, this),
  5287. onShow: jQuery.proxy(function (element, update) {
  5288. // Make the search count display before adjusting the list position.
  5289. $('searchCount').style.display = 'block';
  5290. $('moreResults').style.display = 'block';
  5291. Def.Autocompleter.Base.prototype.showList.apply(this);
  5292. }, this),
  5293. onComplete: jQuery.proxy(this.onComplete, this)
  5294. }, options || {});
  5295. if (!Def.Autocompleter.Base.classInit_) Def.Autocompleter.Base.classInit();
  5296. this.url = url;
  5297. this.defAutocompleterBaseInit(fieldID, options);
  5298. this.autocomp = options['autocomp'];
  5299. if (this.autocomp === undefined) this.autocomp = true; // default
  5300. else if (!this.autocomp) {
  5301. // Disable autocompletion by setting it to run once every year.
  5302. // Note: This used to be 1000 years, but the Linux version of Firefox
  5303. // was treating such a large timeout value as zero.
  5304. this.options.frequency = 365 * 86400; // seconds
  5305. }
  5306. if (options.sort === undefined) options.sort = true; // default
  5307. if (options['useResultCache'] !== null && options['useResultCache'] === false) this.useResultCache_ = false; // default is true-- see declaration
  5308. // Do not use the synchronous request option. On Windows and Firefox,
  5309. // if you use synchronous, and hit control+enter to run a search, the
  5310. // Firefox Downloads window opens. I don't know why. See my post
  5311. // (Paul Lynch) to the Prototype & Scriptaculous Google group, dated
  5312. // 2008/2/5 for a test case.
  5313. // Also, the Prototype library recommends not to use synchronous requests.
  5314. // this.options.asynchronous = false;
  5315. // Set up event observers.
  5316. jQuery(this.element).focus(jQuery.proxy(this.onFocus, this)); // The base class sets up one for a "blur" event.
  5317. var buttonID = options['buttonID'];
  5318. this.buttonID = buttonID; // buttonID might be "null", see line 3 of _search_field_autocomp.rhtml.
  5319. if (buttonID && buttonID !== 'null') {
  5320. // We need to use mousedown for the button. We cannot wait for a
  5321. // mouseup or click event because we have no idea how long that might
  5322. // take, and we need to handle the blur event (which could be the result
  5323. // or a click or of something else.) Handling the mousedown event
  5324. // also has the nice side-effect of preventing the blur from ever
  5325. // occuring -- though I don't understand why. (If I comment out the
  5326. // Ajax.Request, the blur event occurs, but if I uncomment that and
  5327. // comment out the onComplete code, it does not.)
  5328. var button = jQuery(document.getElementById(buttonID));
  5329. button.mousedown(jQuery.proxy(this.buttonClick, this));
  5330. button.keypress(jQuery.proxy(this.buttonKeyPress, this));
  5331. }
  5332. jQuery(this.element).addClass('search_field');
  5333. if (options.colHeaders) {
  5334. this.colHeaderHTML = '<table><thead><th>' + options.colHeaders.join('</th><th>') + '</th></thead><tbody>';
  5335. }
  5336. },
  5337. /**
  5338. * Initializes the itemToDataIndex_ map.
  5339. */
  5340. initItemToDataIndex: function initItemToDataIndex() {
  5341. // For the search list, itemToDataIndex_ gets populated when we get an
  5342. // autocompletion list. However, it needs to have a non-null value for
  5343. // cases where lookups are done for non-matching field values which did
  5344. // not bring back any list (or single-character values when did not
  5345. // trigger an autocompletion event).
  5346. this.itemToDataIndex_ = {};
  5347. },
  5348. /**
  5349. * A copy constructor, for a new field (e.g. another field in a new row
  5350. * of a table).
  5351. * @param fieldID the ID of the field being assigned to the new autocompleter
  5352. * this method creates.
  5353. * @return a new autocompleter for field field ID
  5354. */
  5355. dupForField: function dupForField(fieldID) {
  5356. var dataReq = this.dupDataReqForField(fieldID);
  5357. var opts = Object.clone(this.constructorOpts_);
  5358. opts['dataRequester'] = dataReq;
  5359. return new Def.Autocompleter.Search(fieldID, this.url, opts);
  5360. },
  5361. /**
  5362. * Returns the field value (or partial value, if the tokens option was
  5363. * specified) with any field separator strings replaced by
  5364. * spaces, so it is ready to use as a search string.
  5365. * @param fieldVal (optional) the field value if already obtained from this.element
  5366. */
  5367. getSearchStr: function getSearchStr(fieldVal) {
  5368. // Use a cached version of the regular expression so we don't need to
  5369. // create one for every autocompletion request.
  5370. var ac = Def.Autocompleter;
  5371. if (!ac.LIST_ITEM_FIELD_SEP_REGEX) ac.LIST_ITEM_FIELD_SEP_REGEX = new RegExp(ac.LIST_ITEM_FIELD_SEP, 'g');
  5372. if (!fieldVal) fieldVal = this.getToken();
  5373. return fieldVal.replace(ac.LIST_ITEM_FIELD_SEP_REGEX, ' ').trimLeft();
  5374. },
  5375. /**
  5376. * Runs the search (asynchronously). This gets called when the search
  5377. * button is clicked. When the search completes, onComplete
  5378. * will be called to update the choice list.
  5379. */
  5380. runSearch: function runSearch() {
  5381. // Cancel the previous search/AJAX request, if there is one pending.
  5382. // This might free up a thread for the browser, but it does not help
  5383. // the server any.
  5384. if (this.lastAjaxRequest_ && this.lastAjaxRequest_.transport) this.lastAjaxRequest_.abort();
  5385. this.searchInProgress = true;
  5386. this.searchStartTime = new Date().getTime(); // See if the search has been run before.
  5387. var searchStr = this.getSearchStr();
  5388. var results = null;
  5389. if (this.useResultCache_) {
  5390. results = this.getCachedResults(searchStr, Def.Autocompleter.Search.RESULT_CACHE_SEARCH_RESULTS);
  5391. if (results) this.onComplete(results, null, true);
  5392. }
  5393. if (!results) {
  5394. // i.e. if it wasn't cached
  5395. // Run the search
  5396. var paramData = {
  5397. authenticity_token: window._token || '',
  5398. maxList: null,
  5399. // no value
  5400. terms: searchStr
  5401. };
  5402. var options = {
  5403. data: paramData,
  5404. complete: this.options.onComplete
  5405. };
  5406. this.changed = false;
  5407. this.hasFocus = true;
  5408. this.lastAjaxRequest_ = jQuery.ajax(this.url, options);
  5409. this.lastAjaxRequest_.requestParamData_ = paramData;
  5410. }
  5411. },
  5412. /**
  5413. * Initializes this.resultCache_.
  5414. */
  5415. initResultCache: function initResultCache() {
  5416. this.resultCache_ = Def.Autocompleter.Search.urlToCache_[this.url];
  5417. if (!this.resultCache_) {
  5418. this.resultCache_ = [{}, {}];
  5419. Def.Autocompleter.Search.urlToCache_[this.url] = this.resultCache_;
  5420. }
  5421. },
  5422. /**
  5423. * Returns the cached search results (in the form of an AJAX response object
  5424. * for a request initiated by runSearch or getUpdatedChoices)
  5425. * for the given search string, or null if there are no cached results.
  5426. * @param str the search string
  5427. * @param autocomp RESULT_CACHE_AUTOCOMP_RESULTS if the results were for an
  5428. * autocompletion request (as opposed to a search request, which returns a
  5429. * much longer list of results), and RESULT_CACHE_SEARCH_RESULTS if they were
  5430. * for a search request.
  5431. */
  5432. getCachedResults: function getCachedResults(str, autocomp) {
  5433. if (!this.resultCache_) this.initResultCache();
  5434. return this.resultCache_[autocomp][str];
  5435. },
  5436. /**
  5437. * Stores search results for the given search string, for later re-use
  5438. * via getCachedResults.
  5439. * @param str the search string
  5440. * @param autocomp RESULT_CACHE_AUTOCOMP_RESULTS if the results were for an
  5441. * autocompletion request (as opposed to a search request, which returns a
  5442. * much longer list of results), and RESULT_CACHE_SEARCH_RESULTS if they were
  5443. * for a search request.
  5444. * @param results the AJAX response object for a search initiated by
  5445. * runSearch or getUpdatedChoices.
  5446. */
  5447. storeCachedResults: function storeCachedResults(str, autocomp, results) {
  5448. if (!this.resultCache_) this.initResultCache();
  5449. this.resultCache_[autocomp][str] = results;
  5450. },
  5451. /**
  5452. * Forgets previously cached results.
  5453. */
  5454. clearCachedResults: function clearCachedResults() {
  5455. this.resultCache_ = [{}, {}];
  5456. Def.Autocompleter.Search.urlToCache_[this.url] = this.resultCache_;
  5457. },
  5458. /**
  5459. * Changes the autocompleter's URL to the given URL, and updates the cache.
  5460. * @param url The new url for getting the completion list. See the "url"
  5461. * parameter in the constructor.
  5462. */
  5463. setURL: function setURL(url) {
  5464. this.url = url;
  5465. this.initResultCache();
  5466. },
  5467. /**
  5468. * Returns true if the given key event (from the input field) is a request
  5469. * for showing the expanded list.
  5470. * @param event the key event
  5471. */
  5472. fieldEventIsBigList: function fieldEventIsBigList(event) {
  5473. return event.keyCode === jQuery.ui.keyCode.ENTER && (event.ctrlKey || !this.autocomp && this.domCache.get('elemVal') !== this.processedFieldVal_ && this.domCache.get('elemVal').trim() !== '');
  5474. },
  5475. /**
  5476. * This gets called when the user presses a key on the search button.
  5477. * @param event the key event
  5478. */
  5479. buttonKeyPress: function buttonKeyPress(event) {
  5480. if (event.keyCode === jQuery.ui.keyCode.ENTER) {
  5481. this.runSearch();
  5482. }
  5483. },
  5484. /**
  5485. * Processes a returned set of choices in preparation for building
  5486. * the HTML for the update (choices) area. This filters out selected
  5487. * items, sorts the items, and picks the default item.
  5488. * @param fieldValToItemFields a hash from field value version of the list
  5489. * items to the list item arrays received from the AJAX call
  5490. * @return an array of two elements, an array of field value strings from
  5491. * fieldValToItemFields ordered in the way the items should appear in the
  5492. * list, and a boolean indicating whether the
  5493. * topmost item is placed as a suggested item.
  5494. */
  5495. processChoices: function processChoices(fieldValToItemFields) {
  5496. // Filter out already selected items for multi-select lists
  5497. var filteredItems = [];
  5498. var fieldVals = Object.keys(fieldValToItemFields);
  5499. for (var i = 0, len = fieldVals.length; i < len; ++i) {
  5500. var item = fieldVals[i];
  5501. if (!this.multiSelect_ || !this.isSelected(item)) filteredItems.push(item);
  5502. }
  5503. if (filteredItems.length > 0 && !this.numHeadings_) {
  5504. // Sort items, but first see if there is a best match we want to move to
  5505. // the top.
  5506. var useStats = this.suggestionMode_ === Def.Autocompleter.USE_STATISTICS;
  5507. var topItem = null;
  5508. var topItemIndex = -1;
  5509. if (useStats) {
  5510. // For this kind of suggestion mode, we want to rely on the statistical
  5511. // ordering of results returned by the server, which provides the
  5512. // statistically best option at the top, so we work to keep this
  5513. // item at the top of the list when sorting.
  5514. topItemIndex = 0;
  5515. } else if (this.suggestionMode_ === Def.Autocompleter.SUGGEST_SHORTEST) {
  5516. topItemIndex = this.pickBestMatch(filteredItems);
  5517. }
  5518. if (this.options.sort) {
  5519. if (topItemIndex > -1) {
  5520. topItem = filteredItems[topItemIndex]; // Set the top item to '', so it will sort to the top of the list.
  5521. // That way, after the sort, we don't have to push it into the top
  5522. // of the list. (It should be faster this way.)
  5523. filteredItems[topItemIndex] = '';
  5524. }
  5525. filteredItems = filteredItems.sort(Def.Autocompleter.Base.noCaseSort);
  5526. if (topItemIndex > -1) filteredItems[0] = topItem;
  5527. } else if (topItemIndex > 0) {
  5528. // no sorting, but still want suggestion at top
  5529. var temp = filteredItems[0];
  5530. filteredItems[0] = filteredItems[topItemIndex];
  5531. filteredItems[topItemIndex] = temp;
  5532. }
  5533. }
  5534. return [filteredItems, topItemIndex > -1];
  5535. },
  5536. /**
  5537. * HTML-escapes a string of text for display in a search list.
  5538. * Allows <span> tags to pass through.
  5539. * @param text the string to escape
  5540. * @return the escaped string
  5541. */
  5542. escapeHTML: function escapeHTML(text) {
  5543. var f = Def.Autocompleter.Base.escapeAttribute(text); // Allow (unescape) span tags to mark matches.
  5544. return f.replace(/&lt;(\/)?span&gt;/g, '<$1span>');
  5545. },
  5546. /**
  5547. * Builds and returns the HTML for the selection area.
  5548. * @param listFieldVals the array of field values for the items to be shown in the list.
  5549. * @param bestMatchFound whether a best match was found as a recommenation
  5550. * @param fieldValToItemFields a hash from field value version of the list
  5551. * items to the list item arrays received from the AJAX call
  5552. */
  5553. buildUpdateHTML: function buildUpdateHTML(listFieldVals, bestMatchFound, fieldValToItemFields) {
  5554. var rtn, htmlStart, htmlEnd, rowStartOpen, rowStartClose, fieldSep, rowEnd;
  5555. var tableFormat = this.options.tableFormat;
  5556. if (tableFormat) {
  5557. htmlStart = this.colHeaderHTML || '<table><tbody>';
  5558. htmlEnd = '</tbody></table>';
  5559. rowStartOpen = '<tr';
  5560. rowStartClose = '><td>';
  5561. fieldSep = '</td><td>';
  5562. rowEnd = '</td></tr>';
  5563. } else {
  5564. htmlStart = '<ul>';
  5565. htmlEnd = '</ul>';
  5566. rowStartOpen = '<li';
  5567. rowStartClose = '>';
  5568. fieldSep = Def.Autocompleter.LIST_ITEM_FIELD_SEP;
  5569. rowEnd = '</li>';
  5570. }
  5571. rtn = htmlStart;
  5572. for (var i = 0, len = listFieldVals.length; i < len; ++i) {
  5573. var itemText = listFieldVals[i];
  5574. var itemFields = fieldValToItemFields[itemText];
  5575. var escapedFields = [];
  5576. for (var c = 0, flen = itemFields.length; c < flen; ++c) {
  5577. escapedFields[c] = this.escapeHTML(itemFields[c]);
  5578. }
  5579. rtn += rowStartOpen;
  5580. if (i === 0 && bestMatchFound) rtn += ' class="suggestion"';
  5581. if (tableFormat) rtn += ' data-fieldval="' + this.escapeHTML(itemText) + '"';
  5582. rtn += rowStartClose;
  5583. rtn += escapedFields.join(fieldSep);
  5584. rtn += rowEnd;
  5585. }
  5586. rtn += htmlEnd;
  5587. return rtn;
  5588. },
  5589. /**
  5590. * Updates the contents of the search count div below the list, if
  5591. * there were any results.
  5592. * @param totalCount the total hits found on the server (possibly more than
  5593. * returned.)
  5594. * @param shownCount the number of hits to be shown in the list
  5595. * @param responseLength the number of characters in the returned data
  5596. */
  5597. setSearchCountDiv: function setSearchCountDiv(totalCount, shownCount, responseLength) {
  5598. var searchCountElem = $('searchCount');
  5599. var searchCountStr = '';
  5600. if (totalCount > 0) {
  5601. searchCountStr = shownCount + ' of ' + totalCount + ' total'; // Dan Clark of Freedom Scientific reported that the search count made
  5602. // the output for JAWS too verbose, so I am commenting out this call.
  5603. // this.readSearchCount();
  5604. // Now display the counts and the elapsed time
  5605. var timestamp = new Date(); // In computing the elapsed time, add the delay from the last keystroke,
  5606. // so the user gets the total time from that point.
  5607. var elapsedTime = timestamp.getTime() - this.searchStartTime + this.options.frequency * 1000 + ''; // bytes count of the total response data
  5608. var bytes = responseLength + ''; // Add some padding so the string stays roughly the same size
  5609. if (bytes.length < 3) bytes += '&nbsp;';
  5610. var resultInfo = '; ' + bytes + ' bytes in ' + elapsedTime + ' ms';
  5611. if (elapsedTime.length < 3) resultInfo += '&nbsp;';
  5612. searchCountStr += resultInfo;
  5613. searchCountElem.innerHTML = searchCountStr;
  5614. }
  5615. },
  5616. /**
  5617. * Returns a hash from the values that get placed into the form field when
  5618. * an item is selected to the array of item field values shown in the
  5619. * autocompletion list. While doing this it also initializes
  5620. * itemToDataIndex_.
  5621. * @param itemFieldArrays the array of item field arrays (one array per
  5622. * item
  5623. */
  5624. createFieldVals: function createFieldVals(itemFieldArrays) {
  5625. var rtn = {};
  5626. var valCols = this.options.valueCols;
  5627. var joinSep = Def.Autocompleter.LIST_ITEM_FIELD_SEP;
  5628. this.itemToDataIndex_ = {};
  5629. if (valCols) var numValCols = valCols.length;
  5630. for (var i = 0, len = itemFieldArrays.length; i < len; ++i) {
  5631. var itemFields = itemFieldArrays[i];
  5632. var selectedFields;
  5633. if (valCols) {
  5634. selectedFields = [];
  5635. for (var c = 0; c < numValCols; ++c) {
  5636. selectedFields[c] = itemFields[valCols[c]];
  5637. }
  5638. } else selectedFields = itemFields;
  5639. var fieldVal = selectedFields.join(joinSep); // Remove any <span> tags added for highlighting
  5640. fieldVal = fieldVal.replace(/<\/?span>/g, '');
  5641. this.itemToDataIndex_[fieldVal] = i;
  5642. rtn[fieldVal] = itemFields;
  5643. }
  5644. return rtn;
  5645. },
  5646. /**
  5647. * This gets called when an Ajax request returns. (See Prototype's
  5648. * Ajax.Request and callback sections.)
  5649. * @param xhrObj A jQuery-extended XMLHttpRequest object
  5650. * @param textStatus A jQuery text version of the status of the request
  5651. * (e.g. "success")
  5652. * @param fromCache whether "response" is from the cache (optional).
  5653. */
  5654. onComplete: function onComplete(xhrObj, textStatus, fromCache) {
  5655. var untrimmedFieldVal = this.getToken();
  5656. this.trimmedElemVal = untrimmedFieldVal.trim(); // used in autoCompBase
  5657. if (this.lastAjaxRequest_ === xhrObj) {
  5658. this.lastAjaxRequest_ = null;
  5659. }
  5660. if (xhrObj.status === 200) {
  5661. // 200 is the "OK" status
  5662. var reqParams = xhrObj.requestParamData_;
  5663. var searchStr = reqParams['terms'];
  5664. var autocomp = reqParams['maxList'] === undefined;
  5665. var searchAC = Def.Autocompleter.Search;
  5666. if (!fromCache && this.useResultCache_) {
  5667. var resultCacheIndex = autocomp ? searchAC.RESULT_CACHE_AUTOCOMP_RESULTS : searchAC.RESULT_CACHE_SEARCH_RESULTS;
  5668. this.storeCachedResults(searchStr, resultCacheIndex, xhrObj);
  5669. } // The search string is a truncated version of the field value for
  5670. // autocompletion requests. Compute what the search string would be
  5671. // if it were sent for the current field value.
  5672. var searchStrForFieldVal = this.getSearchStr(untrimmedFieldVal);
  5673. if (autocomp) {
  5674. searchStrForFieldVal = searchStrForFieldVal.substr(0, searchAC.MAX_VALUE_SIZE_FOR_AUTOCOMP);
  5675. } // If the user is not in the field, don't try to display the returned
  5676. // results. (Note: Refocusing does not work well, because it
  5677. // confuses the field validation code which happens on change.)
  5678. // Also, if this response is not for the text that is currently in the
  5679. // field, don't do anything with it.
  5680. if ((this.hasFocus || this.refocusInProgress_) && searchStrForFieldVal === searchStr) {
  5681. // Retrieve the response data, which is in JSON format.
  5682. var responseData = xhrObj.responseJSON || JSON.parse(xhrObj.responseText);
  5683. var totalCount = responseData[0];
  5684. this.itemCodes_ = responseData[1];
  5685. this.listExtraData_ = responseData[2];
  5686. this.itemCodeSystems_ = responseData[4];
  5687. this.rawList_ = responseData[3]; // rawList_ is used in list selection events
  5688. var fieldValToItemFields = this.createFieldVals(this.rawList_);
  5689. var data = this.processChoices(fieldValToItemFields);
  5690. var listFieldVals = data[0],
  5691. bestMatchFound = data[1];
  5692. var listHTML = this.buildUpdateHTML(listFieldVals, bestMatchFound, fieldValToItemFields);
  5693. this.updateChoices(listHTML, false);
  5694. var shownCount = listFieldVals.length;
  5695. this.setSearchCountDiv(totalCount, shownCount, xhrObj.responseText.length); // Show "see more" link depending on whether this was an autocompletion
  5696. // event and whether there are more items to see.
  5697. if (shownCount < totalCount && autocomp) $('moreResults').style.display = 'block';else {
  5698. $('moreResults').style.display = 'none';
  5699. }
  5700. this.searchInProgress = false; // If the number of list items is too large, use the split area, otherwise
  5701. // put the list below the field.
  5702. this.listBelowField_ = this.entryCount <= Def.Autocompleter.Base.MAX_ITEMS_BELOW_FIELD; // Now position the answer list. We would like to do that before, so we
  5703. // could include the position time in the above time measurement, but the
  5704. // time and byte count string can affect the position.
  5705. this.posAnsList();
  5706. }
  5707. }
  5708. },
  5709. /**
  5710. * Returns a hash of extra data (returned with AJAX autocompletion request)
  5711. * for a selected list item.
  5712. * Currently, this assumes that itemText was present in the last list shown
  5713. * for this field; if subsequent autocompletion requests take place in
  5714. * which itemText is not present, the return value will be empty.
  5715. * @param itemText the display string of the selected item.
  5716. */
  5717. getItemExtraData: function getItemExtraData(itemText) {
  5718. var itemData = {};
  5719. if (this.listExtraData_) {
  5720. var dataIndex = this.itemToDataIndex_[itemText];
  5721. if (dataIndex != null) {
  5722. // if it is on the list
  5723. var keys = Object.keys(this.listExtraData_);
  5724. for (var k = 0, numKeys = keys.length; k < numKeys; ++k) {
  5725. var key = keys[k];
  5726. itemData[key] = this.listExtraData_[key][dataIndex];
  5727. }
  5728. }
  5729. }
  5730. return itemData;
  5731. },
  5732. /**
  5733. * Returns a hash of all data about the item whose value is currently in the
  5734. * field, unless itemText is provided, in which case it will return data
  5735. * for that item. This should only be used just after a selection has been made.
  5736. * @param itemText (optional) the display text of an list item. If the text
  5737. * is not in the list, then the returned hash will only contain the "text"
  5738. * property.
  5739. *
  5740. * @return a hash with "code" and "text" properties for the selected item,
  5741. * and if there is any extra data for the item, that will be under a
  5742. * "data" sub-hash. If the items came with code system data, there will
  5743. * also be a "code_system" property with the code system corresponding to
  5744. * "code". Properties for which there are no values will not be present,
  5745. * except for the "text" property.
  5746. */
  5747. getItemData: function getItemData(itemText) {
  5748. if (!itemText) itemText = this.domCache.get('elemVal');
  5749. var rtn = {
  5750. text: itemText
  5751. };
  5752. if (itemText != '' && this.itemToDataIndex_) {
  5753. var code = this.getItemCode(itemText);
  5754. if (code !== undefined && code !== null) {
  5755. rtn.code = code;
  5756. if (this.itemCodeSystems_) {
  5757. var itemIndex = this.itemToDataIndex_[itemText];
  5758. var codeSys = this.itemCodeSystems_[itemIndex];
  5759. if (codeSys) rtn.code_system = codeSys;
  5760. }
  5761. }
  5762. var data = this.getItemExtraData(itemText);
  5763. if (Object.keys(data).length > 0) rtn.data = data;
  5764. }
  5765. return rtn;
  5766. },
  5767. /**
  5768. * This gets called to show the list.
  5769. */
  5770. show: function show() {
  5771. // The base class' show only calls onShow if the "update" element
  5772. // has "display: none" set. Since we are hiding the list container
  5773. // instead, we need to explicitly call onShow here.
  5774. // Only do this if the list is not already being shown. For some reason,
  5775. // in addition to checking whether the list container's visibility style is
  5776. // "hidden", we also need to check for no value, because (at least in
  5777. // Firefox) it doesn't have a value initially.
  5778. if (this.listContainer.style.visibility === 'hidden' || this.listContainer.style.visibility === '') {
  5779. this.options.onShow(this.element, this.update);
  5780. }
  5781. },
  5782. /**
  5783. * This to hide the list. (e.g. after a selection).
  5784. */
  5785. hide: function hide() {
  5786. if (!this.searchInProgress) {
  5787. Def.Autocompleter.Search.superclass.hide.apply(this);
  5788. }
  5789. },
  5790. /**
  5791. * Handles the click on the search button.
  5792. * @param event the event object
  5793. */
  5794. buttonClick: function buttonClick(event) {
  5795. // If there is a timeout from a key event, clear it. (The user might have
  5796. // hit one character, and then hit the search button, and if we don't clear
  5797. // it, the timeout will hide the list because the input length is less
  5798. // than the minimum number of characters.
  5799. if (this.observer) clearTimeout(this.observer); // This runs on mouse down, and we stop the event so the focus never
  5800. // leaves the field.
  5801. this.searchInProgress = true;
  5802. this.runSearch();
  5803. Def.Autocompleter.stopEvent(event);
  5804. },
  5805. /**
  5806. * This gets called when the "See more items" link is clicked.
  5807. * @param event the click event on the link
  5808. */
  5809. handleSeeMoreItems: function handleSeeMoreItems(event) {
  5810. // For multiselect lists, after selecting an item the field is empty, so
  5811. // if we have a preFieldFillVal_, we reset the field value back to that
  5812. // before running the search. At present, the only case where we don't
  5813. // have preFieldFillVal_ is when the user has clicked on a list item,
  5814. // after which (kind of by accident) the "see more items" link is hidden,
  5815. // so we don't need to worry about that case for now.
  5816. if (this.multiSelect_ && this.domCache.get('elemVal') === '' && this.preFieldFillVal_) {
  5817. this.setFieldVal(this.preFieldFillVal_, false);
  5818. }
  5819. this.buttonClick(event);
  5820. },
  5821. /**
  5822. * A method that gets called when the field gains the focus.
  5823. */
  5824. onFocus: function onFocus() {
  5825. // Ignore blur events on the completionOptionsScroller.
  5826. if (Def.Autocompleter.completionOptionsScrollerClicked_ === true) {
  5827. Def.Autocompleter.completionOptionsScrollerClicked_ = false;
  5828. } else {
  5829. if (!this.refocusInProgress_) {
  5830. Def.Autocompleter.screenReaderLog('Type to show matching list values.'); // Hide the list, which might be showing from another autocompleter.
  5831. // (On blur events, autocompleters set a timeout for hiding the list
  5832. // so click events will work, but if the autocompleter isn't the current
  5833. // one when the timeout runs, it doesn't know whether it should really
  5834. // hide the list, so it doesn't.)
  5835. this.hide(); // Reset rawList_, which might have data from a prior use of the field,
  5836. // and which is used by attemptSelection for list selection observers.
  5837. this.rawList_ = [];
  5838. } // The base onFocus resets refocusInProgress_, so we call it after the above
  5839. // check.
  5840. Def.Autocompleter.Base.prototype.onFocus.apply(this);
  5841. this.hasFocus = true;
  5842. }
  5843. },
  5844. /**
  5845. * This gets called when the field loses focus.
  5846. * @param event the DOM event object
  5847. */
  5848. onBlur: function onBlur(event) {
  5849. // Do nothing if we're refocusing the field.
  5850. if (!this.refocusInProgress_ && !Def.Autocompleter.completionOptionsScrollerClicked_) {
  5851. Def.Autocompleter.Base.prototype.onBlur.apply(this, [event]);
  5852. if (!this.searchInProgress) {
  5853. this.active = false;
  5854. }
  5855. }
  5856. },
  5857. /**
  5858. * Overrides the method in the Scriptaculous superclass to change the
  5859. * parameters that are posted.
  5860. */
  5861. getUpdatedChoices: function getUpdatedChoices() {
  5862. if (this.lastAjaxRequest_ && this.lastAjaxRequest_.transport) this.lastAjaxRequest_.abort();
  5863. this.searchStartTime = new Date().getTime();
  5864. var results = null;
  5865. var autocompSearch = Def.Autocompleter.Search;
  5866. var fieldVal = this.getSearchStr(); // Truncate fieldVal to some maximum length so we limit the number of
  5867. // autocompletion requests that get generated if a user sets a book on the
  5868. // keyboard.
  5869. if (fieldVal.length > autocompSearch.MAX_VALUE_SIZE_FOR_AUTOCOMP) fieldVal = fieldVal.substr(0, autocompSearch.MAX_VALUE_SIZE_FOR_AUTOCOMP);
  5870. if (this.useResultCache_) {
  5871. // See if the search has been run before.
  5872. results = this.getCachedResults(fieldVal, autocompSearch.RESULT_CACHE_AUTOCOMP_RESULTS);
  5873. if (results) this.onComplete(results, null, true);
  5874. }
  5875. if (!results) {
  5876. // Run the search
  5877. var paramData = {
  5878. authenticity_token: window._token || '',
  5879. terms: fieldVal
  5880. };
  5881. var options = {
  5882. data: paramData,
  5883. dataType: 'json',
  5884. complete: this.options.onComplete
  5885. };
  5886. this.lastAjaxRequest_ = jQuery.ajax(this.url, options);
  5887. this.lastAjaxRequest_.requestParamData_ = paramData;
  5888. }
  5889. },
  5890. /**
  5891. * Starts an AJAX call to find suggestions for a field value that does
  5892. * not match the list.
  5893. */
  5894. findSuggestions: function findSuggestions() {
  5895. var fieldVal = this.getSearchStr();
  5896. var paramData = {
  5897. authenticity_token: window._token || '',
  5898. field_val: fieldVal,
  5899. suggest: 1
  5900. };
  5901. var options = {
  5902. data: paramData,
  5903. complete: jQuery.proxy(this.onFindSuggestionComplete, this)
  5904. };
  5905. jQuery.ajax(this.url, options);
  5906. },
  5907. /**
  5908. * Handles the return of the AJAX call started in findSuggestions.
  5909. * (See Prototype's Ajax.Request and callback sections for a description
  5910. * of the parameter and how this works.)
  5911. * @param response the jQuery-extended XMLHttpRequest object
  5912. */
  5913. onFindSuggestionComplete: function onFindSuggestionComplete(response) {
  5914. if (response.status === 200) {
  5915. // 200 is the "OK" status
  5916. // Retrieve the response data, which is in JSON format.
  5917. var responseData = response.responseJSON || JSON.parse(response.responseText);
  5918. var codes = responseData[0];
  5919. var eventData = [];
  5920. var foundMatch = false;
  5921. if (codes.length > 0) {
  5922. // See if one of the suggestions matches what was typed (in which case we just accept
  5923. // that item as the selection).
  5924. var listItems = responseData[1];
  5925. this.suggestionList_ = responseData;
  5926. var lowerCaseFieldVal = this.domCache.get('elemVal').trim().toLowerCase();
  5927. var fieldSep = Def.Autocompleter.LIST_ITEM_FIELD_SEP;
  5928. for (var i = 0, max = listItems.length; !foundMatch && i < max; ++i) {
  5929. // The suggestion comes as an array (for the different fields that
  5930. // might be displayed). Fix that, and store it in hopes of
  5931. // helping acceptSuggstion.
  5932. listItems[i] = listItems[i].join(fieldSep);
  5933. if (listItems[i].toLowerCase() === lowerCaseFieldVal) {
  5934. foundMatch = true;
  5935. if (this.observer) clearTimeout(this.observer); // stop the autocompletion
  5936. this.acceptSuggestion(i);
  5937. }
  5938. }
  5939. if (!foundMatch) eventData = listItems;
  5940. } // Do not notify if we found a match and are not providing
  5941. // suggestions.
  5942. if (!foundMatch) {
  5943. Def.Autocompleter.Event.notifyObservers(this.element, 'SUGGESTIONS', {
  5944. suggestion_list: eventData
  5945. });
  5946. }
  5947. }
  5948. },
  5949. /**
  5950. * Handles the user's request to accept a suggestion as a replacement for
  5951. * the field value.
  5952. * @param index the index (in the suggestionList_ codes and values)
  5953. * of the suggestion that was accepted.
  5954. */
  5955. acceptSuggestion: function acceptSuggestion(index) {
  5956. // We stored the last suggestion list data in suggestionList_. Look
  5957. // for "code".
  5958. var codes = this.suggestionList_[0];
  5959. var listItems = this.suggestionList_[1];
  5960. var usedSuggestion = listItems[index];
  5961. var valTyped = this.domCache.get('elemVal');
  5962. var newVal = listItems[index];
  5963. this.setFieldVal(this.processedFieldVal_ = usedSuggestion, false); // Mark the field as having a valid value, and reset processedFieldVal_.
  5964. this.setMatchStatusIndicator(true);
  5965. this.fieldValIsListVal_ = true;
  5966. this.propagateFieldChanges();
  5967. Def.Autocompleter.Event.notifyObservers(this.element, 'SUGGESTION_USED', {
  5968. suggestion_used: usedSuggestion
  5969. }); // Also send a list selection notification (so that that event can be
  5970. // used as a change event for the field). Also, the suggestion was from
  5971. // the list.
  5972. this.itemCodes_ = codes; // used by listSelectionNotification
  5973. this.itemToDataIndex_ = {};
  5974. this.itemToDataIndex_[listItems[index]] = index;
  5975. this.listExtraData_ = this.suggestionList_[2];
  5976. this.itemCodeSystems_ = this.suggestionList_[3];
  5977. this.listSelectionNotification(valTyped, true); // not typed, on list
  5978. // Put the focus back into the field we just updated.
  5979. this.element.focus();
  5980. }
  5981. };
  5982. jQuery.extend(Def.Autocompleter.Search.prototype, tmp);
  5983. tmp = null;
  5984. }
  5985. if (true) module.exports = defineSearch;else {}
  5986. })();
  5987. /***/ }),
  5988. /* 15 */
  5989. /***/ (function(module, exports, __webpack_require__) {
  5990. (function () {
  5991. // Wrap the definitions in a function to protect our version of global variables
  5992. function defineEvent($, jQuery, Def) {
  5993. "use strict";
  5994. /*
  5995. * This contains code for the custom "events" the autocompleter generates.
  5996. * Other code can use one of the "observe" methods to register to be notified
  5997. * when a certain type of event occurs.
  5998. */
  5999. Def.Autocompleter.Event = {
  6000. /**
  6001. * Registers a callback for when the list is expanded.
  6002. * @param fieldKey the lookup key from Def.Observable.lookupKey(field) for which
  6003. * the given callback will be called. Fields whose lookupKey value matches
  6004. * fieldKey will trigger the callback for this event. The
  6005. * idea is that there might be multiple fields (perhaps of an unknown number)
  6006. * that are related for which the callback should receive notifications.
  6007. * This can be null, in which case the function will be called for every
  6008. * event of this kind, regardless of the field for which it occurred.
  6009. * @param callback the function to be called when the event occurs.
  6010. * The function will be called with the argument
  6011. * {list_expansion_method: 'CtrlRet'} if it was expanded with the keyboard,
  6012. * and {list_expansion_method: 'clicked'} if it was expanded with
  6013. * the mouse.
  6014. */
  6015. observeListExpansions: function observeListExpansions(fieldKey, callback) {
  6016. this.storeCallback(fieldKey, 'LIST_EXP', callback);
  6017. },
  6018. /**
  6019. * Registers a callback for when an item is selected from the list, or if
  6020. * the user enters a non-list value (for lists that support that).
  6021. * @param fieldKey the lookup key from Def.Observable.lookupKey(field) for which
  6022. * the given callback will be called. Fields whose lookupKey value matches
  6023. * fieldKey will trigger the callback for this event. The
  6024. * idea is that there might be multiple fields (perhaps of an unknown number)
  6025. * that are related for which the callback should receive notifications.
  6026. * This can be null, in which case the function will be called for every
  6027. * event of this kind, regardless of the field for which it occurred.
  6028. * @param callback the function to be called when the event occurs.
  6029. * The function will be called with a hash argument with the following keys:
  6030. * 1) val_typed_in (what the user actually typed in);
  6031. * 2) final_val (the final value for the field);
  6032. * 3) used_list (boolean indicating whether or not the final was
  6033. * selected from a list, whether by clicking or by arrows);
  6034. * 4) on_list - boolean indicating whether or not the final value was on
  6035. * the list
  6036. * 5) input_method ('clicked', 'arrows', or 'typed')
  6037. * 6) item_code - the code for the selected item, or null if there isn't
  6038. * one.
  6039. * 7) removed - For multi-select lists, this indicates whether the
  6040. * selection was actual an unselection, removing the named item from the
  6041. * list of selected items. When true, final_val is the removed value
  6042. * (although for multi-select fields the field is blank afterward).
  6043. * (Optional; default false)
  6044. * 8) list - the items that were in the list (which is the full list for a
  6045. * prefetched list, or the portion shown to the user for a search list).
  6046. * 9) field_id - the ID of the list field
  6047. */
  6048. observeListSelections: function observeListSelections(fieldKey, callback) {
  6049. this.storeCallback(fieldKey, 'LIST_SEL', callback);
  6050. },
  6051. /**
  6052. * Registers a callback for when a list field receives focus.
  6053. * @param fieldKey the lookup key from Def.Observable.lookupKey(field) for which
  6054. * the given callback will be called. Fields whose lookupKey value matches
  6055. * fieldKey will trigger the callback for this event. The
  6056. * idea is that there might be multiple fields (perhaps of an unknown number)
  6057. * that are related for which the callback should receive notifications.
  6058. * @param callback the function to be called when the event occurs.
  6059. * The function will be called with an the following argument:
  6060. * - start_val (the value already in the field)
  6061. */
  6062. observeFocusEvents: function observeFocusEvents(fieldKey, callback) {
  6063. this.storeCallback(fieldKey, 'FOCUS', callback);
  6064. },
  6065. /**
  6066. * Registers a callback for when users cancel the list (by pressing
  6067. * the escape key). This closes the list and restores the field's value.
  6068. * @param fieldKey the lookup key from Def.Observable.lookupKey(field) for which
  6069. * the given callback will be called. Fields whose lookupKey value matches
  6070. * fieldKey will trigger the callback for this event. The
  6071. * idea is that there might be multiple fields (perhaps of an unknown number)
  6072. * that are related for which the callback should receive notifications.
  6073. * @param callback the function to be called when the event occurs.
  6074. * The function will be called with an the following argument:
  6075. * - restored_value (the value that was restored to the field)
  6076. */
  6077. observeCancelList: function observeCancelList(fieldKey, callback) {
  6078. this.storeCallback(fieldKey, 'CANCEL', callback);
  6079. },
  6080. /**
  6081. * Registers a callback for when suggestions should be shown to the
  6082. * user. If the user selects a suggestion, the function acceptSuggestion
  6083. * should be called with the index of the selected suggestion.
  6084. * @param fieldKey the lookup key from Def.Observable.lookupKey(field) for which
  6085. * the given callback will be called. Fields whose lookupKey value matches
  6086. * fieldKey will trigger the callback for this event. The
  6087. * idea is that there might be multiple fields (perhaps of an unknown number)
  6088. * that are related for which the callback should receive notifications.
  6089. * @param callback the function to be called when the event occurs.
  6090. * The function will be called with an the following argument:
  6091. * - suggestion_list (an array of the values in the list to be shown to the user.
  6092. * or an empty array if no suggestions were found)
  6093. */
  6094. observeSuggestions: function observeSuggestions(fieldKey, callback) {
  6095. this.storeCallback(fieldKey, 'SUGGESTIONS', callback);
  6096. },
  6097. /**
  6098. * Registers a callback for when a user accepts a suggestion.
  6099. * @param fieldKey the lookup key from Def.Observable.lookupKey(field) for which
  6100. * the given callback will be called. Fields whose lookupKey value matches
  6101. * fieldKey will trigger the callback for this event. The
  6102. * idea is that there might be multiple fields (perhaps of an unknown number)
  6103. * that are related for which the callback should receive notifications.
  6104. * @param callback the function to be called when the event occurs.
  6105. */
  6106. observeSuggestionUsed: function observeSuggestionUsed(fieldKey, callback) {
  6107. this.storeCallback(fieldKey, 'SUGGESTION_USED', callback);
  6108. },
  6109. /**
  6110. * For prefetched lists only, this registers a callback for when the
  6111. * list is changed via setListAndField but the field value does NOT change.
  6112. * (If the field value is changed, a change event is sent.)
  6113. * @param fieldKey the lookup key from Def.Observable.lookupKey(field) for which
  6114. * the given callback will be called. Fields whose lookupKey value matches
  6115. * fieldKey will trigger the callback for this event. The
  6116. * idea is that there might be multiple fields (perhaps of an unknown number)
  6117. * that are related for which the callback should receive notifications.
  6118. * @param callback the function to be called when the event occurs.
  6119. */
  6120. observeListAssignments: function observeListAssignments(fieldKey, callback) {
  6121. this.storeCallback(fieldKey, 'LIST_ASSIGNMENT', callback);
  6122. },
  6123. /**
  6124. * Registers a callback for when a record data requester (any one) clears
  6125. * fields.
  6126. * @param callback the function to be called. It will get the following
  6127. * argument:
  6128. * - updatedFields: an array of DOM field elements for the fields that
  6129. * were cleared
  6130. */
  6131. observeRDRClearing: function observeRDRClearing(callback) {
  6132. this.storeCallback(null, 'RDR_CLEARING', callback);
  6133. },
  6134. /**
  6135. * Registers a callback for when a record data requester (any one) assigns
  6136. * values to fields.
  6137. * @param callback the function to be called. It will get a hash containing
  6138. * the following key/value pairs:
  6139. * - updatedFields: an array of DOM field elements for the fields that
  6140. * were cleared
  6141. * - updatedFieldIDToVal: a hash of field IDs to the updated values
  6142. * - listField - the field whose list had the record data requester.
  6143. */
  6144. observeRDRAssignment: function observeRDRAssignment(callback) {
  6145. this.storeCallback(null, 'RDR_ASSIGNMENT', callback);
  6146. }
  6147. };
  6148. jQuery.extend(Def.Autocompleter.Event, Def.Observable);
  6149. }
  6150. if (true) module.exports = defineEvent;else {}
  6151. })();
  6152. /***/ }),
  6153. /* 16 */
  6154. /***/ (function(module, exports, __webpack_require__) {
  6155. function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
  6156. // An AngularJS directive (optional; for use if you are using AngularJS)
  6157. //
  6158. // Example:
  6159. // <input id="myfield" autocomplete-lhc="opts" ng-model="selectedVal">
  6160. //
  6161. // The opts object (which could be a function that returns an object) contains
  6162. // the information needed for specifying the behavior of the autocompleter (e.g.
  6163. // what should be in the list). There are two types of autocompleters. You can
  6164. // either have a "prefetched" list where all the items are given to the autocompleter
  6165. // at construction time, or a "search" list where the list items are discovered
  6166. // as the user types, via AJAX calls. For both types, opts is a hash, but the
  6167. // keys on the hash differ.
  6168. //
  6169. // For "prefetched lists", opts can contain:
  6170. // 1) listItems - (required) This is the list item data. It should be an array,
  6171. // and each element in the array should have a "text" property (or, if the
  6172. // "display" option is set below, property of display's value) which is the
  6173. // display string the user sees. The object containing that text property
  6174. // is what will get stored on the model you have associated via ng-model
  6175. // (selectedVal, in the example above).
  6176. // 2) maxSelect - (default: 1) the maximum number of items that can be selected
  6177. // from the list. If this is '*', an unlimited number can be selected. When
  6178. // more than one item can be selected, selected items are stored in an array
  6179. // on the model (e.g., selectedVal becomes an array).
  6180. // 3) defaultValue - The default value for the field. This setting also exists in
  6181. // the non-directive prefetch lists, but there are two differences here:
  6182. // a) defaultValue can either be one of the list item display strings (the
  6183. // "text" property), or it can be a hash like {code: 'AF-5'}, where "code" is
  6184. // a key on the list item object, and 'AF-5' is the value of that key
  6185. // to select the item as the default. This is to allow the default value
  6186. // to be specified not just by the display string, but by other attributes
  6187. // of the list item object.
  6188. // b) the default list item is loaded into the field when the autocompletion
  6189. // is set up.
  6190. // 4) display - (default "text") the property of the objects in listItems which
  6191. // should be displayed in the list.
  6192. // 5) Any other parameters used by the Def.Autocomp.Prefetch constructor defined in
  6193. // autoCompPrefetch.js. (Look at the options parameter in the initialize method).
  6194. //
  6195. // For "search" lists, opts can contain:
  6196. // 1) url - (required) The URL to which requests for data should be sent. For
  6197. // details about expected parameters and response data, see the comments for the
  6198. // Def.Autocomp.Search constructor (the initialize method in autoCompSearch.js.)
  6199. // 2) Any other parameters used by the Def.Autocomp.Search constructor defiend
  6200. // in autoCompSearch.js. (Look at the options parameter in the initialize
  6201. // method.)
  6202. //
  6203. // For search lists, the model data object (selectedVal in the example above)
  6204. // will become an array of objects if maxSelect is set to '*' to allow multiple
  6205. // selections. (That much is also true for prefetched lists, as noted above.)
  6206. // The object for each selected item will have a "text" property corresponding
  6207. // to the display text for the selected item, and a "code" property as returned
  6208. // by the URL's JSON response. (For details of the return format, see
  6209. // the constructor comments in autoCompSearch.js).
  6210. //
  6211. // Search lists' URLs can respond with a hash of additional field data in the
  6212. // third element of the returned JSON array. These extra data elements
  6213. // will be placed in a sub-object of the model object under the property "data".
  6214. // For example, {text: // display text, code: // code for display text,
  6215. // data: { //extra field data here}}
  6216. //
  6217. // For both types of lists, if the list is configured to allow entry of off-list
  6218. // values, the model data objects for such items will have an additional
  6219. // property, _notOnList, set to true.
  6220. (function () {
  6221. // Wrap the definitions in a function to protect our version of global variables
  6222. function defineDirective(Def) {
  6223. "use strict";
  6224. var AutocompInitializer = Def.PrototypeAPI.Class.create({
  6225. /**
  6226. * Constructor.
  6227. * @param acOptions the options hash passed to the directive
  6228. * for configuring the autocompleter.
  6229. * @param scope the AngularJS scope
  6230. * @param element the jQuery-wrapped element for which an autocompleter is
  6231. * being created.
  6232. * @param controller the AngularJS controller
  6233. */
  6234. initialize: function initialize(acOptions, scope, element, controller) {
  6235. this.displayedProp = acOptions.display || 'text';
  6236. this.scope = scope;
  6237. this.acOptions = acOptions;
  6238. if (controller) {
  6239. // ngModelController, from the "require"
  6240. this.pElem = element[0]; // if there's an autocomp already
  6241. var oldAC = this.pElem.autocomp;
  6242. if (oldAC) {
  6243. // Destroy the existing autocomp
  6244. oldAC.destroy(); // clean up the model data
  6245. scope.modelData = null; // Remove the formatter and parser we defined for the previous
  6246. // autocompleter.
  6247. this.removeAutocompFunction(controller.$formatters);
  6248. this.removeAutocompFunction(controller.$parsers);
  6249. }
  6250. this.ac = acOptions.hasOwnProperty('url') ? this.searchList() : this.prefetchList(); // See if there is an existing model value for the field (which
  6251. // might have been set by the prefetchList call above, if there
  6252. // was a default value for the field).
  6253. var md = scope.modelData;
  6254. var hasPrepoluatedModel = md !== undefined && md !== null; // If there is a already a model value for this field, load it
  6255. // into the autocompleter.
  6256. if (hasPrepoluatedModel) {
  6257. if (this.ac.multiSelect_) {
  6258. // in this case md is an array
  6259. for (var i = 0, len = md.length; i < len; ++i) {
  6260. var dispVal = md[i][this.displayedProp];
  6261. this.ac.storeSelectedItem(dispVal, md[i].code);
  6262. this.ac.addToSelectedArea(dispVal);
  6263. } // Clear the field value for multi-select lists
  6264. this.ac.setFieldVal('', false);
  6265. } else {
  6266. var dispVal = md[this.displayedProp];
  6267. if (typeof dispVal === 'string') {
  6268. this.ac.storeSelectedItem(dispVal, md.code);
  6269. this.ac.setFieldVal(dispVal, false);
  6270. } else // handle the case of an empty object as a model
  6271. this.ac.setFieldVal('', false);
  6272. }
  6273. }
  6274. this.parser = this.parser.bind(this);
  6275. this.parser.fromAutocomp = true;
  6276. controller.$parsers.push(this.parser);
  6277. this.formatter = this.formatter.bind(this);
  6278. this.formatter.fromAutocomp = true;
  6279. controller.$formatters.push(this.formatter);
  6280. } // if controller
  6281. },
  6282. /**
  6283. * A parser to convert from the field value to the object
  6284. * containing the value and (e.g.) code.
  6285. * @param value the field value
  6286. * @return the model object.
  6287. */
  6288. parser: function parser(value) {
  6289. // Just rely on the autocompleter list selection event to manage
  6290. // model updates. Here we will just return the model object, to
  6291. // prevent any change to the model from the parsers.
  6292. var rtn = this.scope.modelData; // Returning "undefined" means the value is invalid and will cause
  6293. // the ng-invalid-parse class to get added. Switch to null.
  6294. if (rtn === undefined) rtn = null;
  6295. return rtn;
  6296. },
  6297. /**
  6298. * A formatter to get the display string if the model is changed.
  6299. * @param value the model object
  6300. * @return the display string for the field value
  6301. */
  6302. formatter: function formatter(value) {
  6303. var rtn = '';
  6304. if (!this.ac.multiSelect_) {
  6305. if (typeof value === 'string') rtn = value;else if (value !== null && _typeof(value) === 'object' && typeof value[this.displayedProp] === "string") {
  6306. rtn = value[this.displayedProp];
  6307. }
  6308. rtn = rtn.trim();
  6309. } else rtn = ''; // If angular is setting the field value, we have to let the
  6310. // autocompleter know.
  6311. this.ac.setFieldVal(rtn, false);
  6312. return rtn;
  6313. },
  6314. /**
  6315. * Returns model data for the field value "finalVal". (Used for Prefetch
  6316. * lists.) If the field is empty, null will be returned.
  6317. * @param finaVal the field value after list selection. This is the
  6318. * trimmed "text" value, which will be in the returned model object.
  6319. * @param itemTextToItem a hash of list values to model data objects
  6320. */
  6321. getPrefetchItemModelData: function getPrefetchItemModelData(finalVal, itemTextToItem) {
  6322. var item = itemTextToItem[finalVal];
  6323. if (!item) {
  6324. if (finalVal != '') {
  6325. item = {
  6326. _notOnList: true
  6327. };
  6328. item[this.displayedProp] = finalVal;
  6329. } else // no value in the field
  6330. item = null;
  6331. }
  6332. return item;
  6333. },
  6334. /**
  6335. * Handles a prefetch list selection event.
  6336. * @param eventData the data about the selection event.
  6337. * @param itemTextToItem a hash from display strings to items
  6338. */
  6339. prefetchListSelHandler: function prefetchListSelHandler(eventData, itemTextToItem) {
  6340. var finalVal = eventData.final_val; // finalVal is a trimmed version of the text. Use that for
  6341. // the model data.
  6342. if (!this.ac.multiSelect_) {
  6343. // Even if the field value is not valid, we need to update the model;
  6344. // clearing the model would clear the field.
  6345. this.scope.modelData = this.getPrefetchItemModelData(finalVal, itemTextToItem);
  6346. } else {
  6347. if (!this.scope.modelData) this.scope.modelData = [];
  6348. var selectedItems = this.scope.modelData;
  6349. if (eventData.removed) {
  6350. // The item was removed
  6351. var removedVal = eventData.final_val;
  6352. for (var i = 0, len = selectedItems.length; i < len; ++i) {
  6353. if (removedVal === selectedItems[i][this.displayedProp]) {
  6354. selectedItems.splice(i, 1);
  6355. break;
  6356. }
  6357. }
  6358. } else if (eventData.on_list || !this.acOptions.matchListValue) {
  6359. // (Add the new model item, but not if it is invalid)
  6360. var newModel = this.getPrefetchItemModelData(finalVal, itemTextToItem);
  6361. if (newModel) // could be null if the field value was empty
  6362. selectedItems.push(newModel);
  6363. }
  6364. }
  6365. },
  6366. /**
  6367. * Sets up a prefetched list on the field.
  6368. */
  6369. prefetchList: function prefetchList() {
  6370. var itemText = [];
  6371. var itemTextToItem = {}; // See if we have a default value, unless the model is already
  6372. // populated.
  6373. var acOptions = this.acOptions;
  6374. var defaultKey = null; // null means not using a default
  6375. var defaultValueSpec = acOptions.defaultValue;
  6376. var defaultKeyVal = null; // the value in defaultValueSpec corresponding to defaultKey
  6377. var displayedProp = this.displayedProp;
  6378. if (defaultValueSpec !== undefined && (this.scope.modelData === undefined || this.scope.modelData === null)) {
  6379. if (typeof defaultValueSpec === 'string') {
  6380. defaultKey = displayedProp;
  6381. defaultKeyVal = defaultValueSpec;
  6382. } else {
  6383. // an object like {code: 'AL-23'}
  6384. defaultKey = Object.keys(defaultValueSpec)[0];
  6385. defaultKeyVal = defaultValueSpec[defaultKey];
  6386. }
  6387. } // "listItems" = list item data.
  6388. var modelDefault = null;
  6389. var oneItemText;
  6390. for (var i = 0, numItems = acOptions.listItems.length; i < numItems; ++i) {
  6391. var item = acOptions.listItems[i];
  6392. oneItemText = item[displayedProp];
  6393. itemText[i] = oneItemText;
  6394. var trimmedText = oneItemText.trim();
  6395. itemTextToItem[trimmedText] = item;
  6396. if (defaultKey && item[defaultKey].trim() === defaultKeyVal) modelDefault = this.getPrefetchItemModelData(trimmedText, itemTextToItem);
  6397. }
  6398. var ac = new Def.Autocompleter.Prefetch(this.pElem, itemText, acOptions);
  6399. this.addNameAttr();
  6400. var self = this;
  6401. this.updateListSelectionHandler(function (eventData) {
  6402. self.scope.$apply(function () {
  6403. self.prefetchListSelHandler(eventData, itemTextToItem);
  6404. });
  6405. }); // If we have a default value, assign it to the model.
  6406. if (modelDefault !== null && !this.scope.modelData) this.scope.modelData = ac.multiSelect_ ? [modelDefault] : modelDefault;
  6407. return ac;
  6408. },
  6409. /**
  6410. * Returns the model data structure for a selected item in a search
  6411. * list. If the field is empty, null will be returned.
  6412. * @param itemText the display string of the selected item
  6413. * @param onList true if the selected item was from the list
  6414. */
  6415. getSearchItemModelData: function getSearchItemModelData(itemText, onList) {
  6416. var rtn;
  6417. if (itemText === '') rtn = null;else {
  6418. rtn = this.ac.getItemData(itemText);
  6419. if (!onList) rtn._notOnList = true;
  6420. }
  6421. return rtn;
  6422. },
  6423. /**
  6424. * Assigns a name to the field if it is missing one.
  6425. * Names are used to register listeners. We don't do this in the
  6426. * autocompleter base class to avoid polluting submitted form data
  6427. * with unintended fields.
  6428. */
  6429. addNameAttr: function addNameAttr() {
  6430. // If the element does not have a name, use the ID. The name
  6431. // to register listeners.
  6432. if (this.pElem.name === '') this.pElem.name = this.pElem.id;
  6433. },
  6434. /**
  6435. * Handles a search list selection event.
  6436. * @param eventData the data about the selection event.
  6437. */
  6438. searchListSelHandler: function searchListSelHandler(eventData) {
  6439. var itemText = eventData.final_val;
  6440. var onList = eventData.on_list;
  6441. if (!this.ac.multiSelect_) {
  6442. // Even if the field value is not valid, we need to update the model;
  6443. // clearing the model would clear the field.
  6444. this.scope.modelData = this.getSearchItemModelData(itemText, onList);
  6445. } else {
  6446. if (!this.scope.modelData) this.scope.modelData = [];
  6447. var selectedItems = this.scope.modelData;
  6448. if (eventData.removed) {
  6449. // The item was removed
  6450. var removedVal = eventData.final_val;
  6451. for (var i = 0, len = selectedItems.length; i < len; ++i) {
  6452. if (removedVal === selectedItems[i].text) {
  6453. selectedItems.splice(i, 1);
  6454. break;
  6455. }
  6456. }
  6457. } else if (eventData.on_list || !this.acOptions.matchListValue) {
  6458. // (Add the new model item, but not if it is invalid)
  6459. var newModel = this.getSearchItemModelData(itemText, onList);
  6460. if (newModel) // could be null if the field value was empty
  6461. selectedItems.push(newModel);
  6462. }
  6463. }
  6464. },
  6465. /**
  6466. * Sets up a search list on the field.
  6467. */
  6468. searchList: function searchList() {
  6469. var ac = new Def.Autocompleter.Search(this.pElem, this.acOptions.url, this.acOptions);
  6470. this.addNameAttr();
  6471. var self = this;
  6472. this.updateListSelectionHandler(function (eventData) {
  6473. self.scope.$apply(function () {
  6474. self.searchListSelHandler(eventData);
  6475. });
  6476. });
  6477. return ac;
  6478. },
  6479. /**
  6480. * Takes an array of functions, and removes the first found that is
  6481. * flagged as being from an autocompleter.
  6482. * @param functionList the array of functions
  6483. */
  6484. removeAutocompFunction: function removeAutocompFunction(functionList) {
  6485. for (var i = 0, len = functionList.length; i < len; ++i) {
  6486. if (functionList[i].fromAutocomp) {
  6487. functionList.splice(i, 1);
  6488. break;
  6489. }
  6490. }
  6491. },
  6492. /**
  6493. * Updates (replaces) the list selection event handler.
  6494. * @param handler the list selection event handler to be assigned
  6495. */
  6496. updateListSelectionHandler: function updateListSelectionHandler(handler) {
  6497. var field = this.pElem;
  6498. var fieldKey = Def.Observable.lookupKey(field);
  6499. var eh = Def.Autocompleter.directiveListEventHandlers;
  6500. var oldHandler = eh[field.id];
  6501. if (oldHandler) {
  6502. Def.Autocompleter.Event.removeCallback(fieldKey, 'LIST_SEL', oldHandler);
  6503. }
  6504. Def.Autocompleter.Event.observeListSelections(fieldKey, handler);
  6505. eh[field.id] = handler;
  6506. }
  6507. }); // class AutocompInitializer
  6508. // Keep track of created list event handlers. This is a hash of field IDs to
  6509. // handler functions.
  6510. Def.Autocompleter.directiveListEventHandlers = {};
  6511. if (typeof angular !== 'undefined') {
  6512. angular.module('autocompleteLhcMod', []).directive('autocompleteLhc', [function () {
  6513. return {
  6514. restrict: 'A',
  6515. require: '?ngModel',
  6516. scope: {
  6517. modelData: '=ngModel',
  6518. // the ng-model attribute on the tag
  6519. acOpts: '=autocompleteLhc'
  6520. },
  6521. link: function link(scope, element, attrs, controller) {
  6522. // Set the update options to 'none' so only the autocompleter code
  6523. // updates the model.
  6524. if (!controller.$options) controller.$options = {};
  6525. controller.$options.updateOn = 'none';
  6526. controller.$options.updateOnDefault = false;
  6527. function initWidget(options) {
  6528. new AutocompInitializer(options, scope, element, controller);
  6529. } // Re-initialize the autocomplete widget whenever the options change
  6530. scope.$watch("acOpts", initWidget, true);
  6531. }
  6532. };
  6533. }]);
  6534. }
  6535. }
  6536. if (true) module.exports = defineDirective;else {}
  6537. })();
  6538. /***/ })
  6539. /******/ ]);