jquery.handsontable.full.js 395 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648
  1. /**
  2. * Handsontable 0.10.0-beta4
  3. * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs
  4. *
  5. * Copyright 2012, Marcin Warpechowski
  6. * Licensed under the MIT license.
  7. * http://handsontable.com/
  8. *
  9. * Date: Wed Nov 27 2013 14:18:10 GMT+0100 (CET)
  10. */
  11. /*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */
  12. var Handsontable = { //class namespace
  13. extension: {}, //extenstion namespace
  14. helper: {} //helper namespace
  15. };
  16. (function ($, window, Handsontable) {
  17. "use strict";
  18. Handsontable.activeGuid = null;
  19. /**
  20. * Handsontable constructor
  21. * @param rootElement The jQuery element in which Handsontable DOM will be inserted
  22. * @param userSettings
  23. * @constructor
  24. */
  25. Handsontable.Core = function (rootElement, userSettings) {
  26. var priv
  27. , datamap
  28. , grid
  29. , selection
  30. , editorManager
  31. , autofill
  32. , instance = this
  33. , GridSettings = function () {};
  34. Handsontable.helper.extend(GridSettings.prototype, DefaultSettings.prototype); //create grid settings as a copy of default settings
  35. Handsontable.helper.extend(GridSettings.prototype, userSettings); //overwrite defaults with user settings
  36. Handsontable.helper.extend(GridSettings.prototype, expandType(userSettings));
  37. this.rootElement = rootElement;
  38. var $document = $(document.documentElement);
  39. var $body = $(document.body);
  40. this.guid = 'ht_' + Handsontable.helper.randomString(); //this is the namespace for global events
  41. if (!this.rootElement[0].id) {
  42. this.rootElement[0].id = this.guid; //if root element does not have an id, assign a random id
  43. }
  44. priv = {
  45. cellSettings: [],
  46. columnSettings: [],
  47. columnsSettingConflicts: ['data', 'width'],
  48. settings: new GridSettings(), // current settings instance
  49. settingsFromDOM: {},
  50. selStart: new Handsontable.SelectionPoint(),
  51. selEnd: new Handsontable.SelectionPoint(),
  52. isPopulated: null,
  53. scrollable: null,
  54. extensions: {},
  55. colToProp: null,
  56. propToCol: null,
  57. dataSchema: null,
  58. dataType: 'array',
  59. firstRun: true
  60. };
  61. datamap = {
  62. recursiveDuckSchema: function (obj) {
  63. var schema;
  64. if ($.isPlainObject(obj)) {
  65. schema = {};
  66. for (var i in obj) {
  67. if (obj.hasOwnProperty(i)) {
  68. if ($.isPlainObject(obj[i])) {
  69. schema[i] = datamap.recursiveDuckSchema(obj[i]);
  70. }
  71. else {
  72. schema[i] = null;
  73. }
  74. }
  75. }
  76. }
  77. else {
  78. schema = [];
  79. }
  80. return schema;
  81. },
  82. recursiveDuckColumns: function (schema, lastCol, parent) {
  83. var prop, i;
  84. if (typeof lastCol === 'undefined') {
  85. lastCol = 0;
  86. parent = '';
  87. }
  88. if ($.isPlainObject(schema)) {
  89. for (i in schema) {
  90. if (schema.hasOwnProperty(i)) {
  91. if (schema[i] === null) {
  92. prop = parent + i;
  93. priv.colToProp.push(prop);
  94. priv.propToCol[prop] = lastCol;
  95. lastCol++;
  96. }
  97. else {
  98. lastCol = datamap.recursiveDuckColumns(schema[i], lastCol, i + '.');
  99. }
  100. }
  101. }
  102. }
  103. return lastCol;
  104. },
  105. createMap: function () {
  106. if (typeof datamap.getSchema() === "undefined") {
  107. throw new Error("trying to create `columns` definition but you didnt' provide `schema` nor `data`");
  108. }
  109. var i, ilen, schema = datamap.getSchema();
  110. priv.colToProp = [];
  111. priv.propToCol = {};
  112. if (priv.settings.columns) {
  113. for (i = 0, ilen = priv.settings.columns.length; i < ilen; i++) {
  114. priv.colToProp[i] = priv.settings.columns[i].data;
  115. priv.propToCol[priv.settings.columns[i].data] = i;
  116. }
  117. }
  118. else {
  119. datamap.recursiveDuckColumns(schema);
  120. }
  121. },
  122. colToProp: function (col) {
  123. col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col);
  124. if (priv.colToProp && typeof priv.colToProp[col] !== 'undefined') {
  125. return priv.colToProp[col];
  126. }
  127. else {
  128. return col;
  129. }
  130. },
  131. propToCol: function (prop) {
  132. var col;
  133. if (typeof priv.propToCol[prop] !== 'undefined') {
  134. col = priv.propToCol[prop];
  135. }
  136. else {
  137. col = prop;
  138. }
  139. col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col);
  140. return col;
  141. },
  142. getSchema: function () {
  143. if (priv.settings.dataSchema) {
  144. if (typeof priv.settings.dataSchema === 'function') {
  145. return priv.settings.dataSchema();
  146. }
  147. return priv.settings.dataSchema;
  148. }
  149. return priv.duckDataSchema;
  150. },
  151. /**
  152. * Creates row at the bottom of the data array
  153. * @param {Number} [index] Optional. Index of the row before which the new row will be inserted
  154. */
  155. createRow: function (index, amount) {
  156. var row
  157. , colCount = instance.countCols()
  158. , numberOfCreatedRows = 0
  159. , currentIndex;
  160. if (!amount) {
  161. amount = 1;
  162. }
  163. if (typeof index !== 'number' || index >= instance.countRows()) {
  164. index = instance.countRows();
  165. }
  166. currentIndex = index;
  167. while (numberOfCreatedRows < amount && instance.countRows() < priv.settings.maxRows) {
  168. if (priv.dataType === 'array') {
  169. row = [];
  170. for (var c = 0; c < colCount; c++) {
  171. row.push(null);
  172. }
  173. }
  174. else if (priv.dataType === 'function') {
  175. row = priv.settings.dataSchema(index);
  176. }
  177. else {
  178. row = $.extend(true, {}, datamap.getSchema());
  179. }
  180. if (index === instance.countRows()) {
  181. GridSettings.prototype.data.push(row);
  182. }
  183. else {
  184. GridSettings.prototype.data.splice(index, 0, row);
  185. }
  186. numberOfCreatedRows++;
  187. currentIndex++;
  188. }
  189. instance.PluginHooks.run('afterCreateRow', index, numberOfCreatedRows);
  190. instance.forceFullRender = true; //used when data was changed
  191. return numberOfCreatedRows;
  192. },
  193. /**
  194. * Creates col at the right of the data array
  195. * @param {Number} [index] Optional. Index of the column before which the new column will be inserted
  196. * * @param {Number} [amount] Optional.
  197. */
  198. createCol: function (index, amount) {
  199. if (priv.dataType === 'object' || priv.settings.columns) {
  200. throw new Error("Cannot create new column. When data source in an object, you can only have as much columns as defined in first data row, data schema or in the 'columns' setting");
  201. }
  202. var rlen = instance.countRows()
  203. , data = GridSettings.prototype.data
  204. , constructor
  205. , numberOfCreatedCols = 0
  206. , currentIndex;
  207. if (!amount) {
  208. amount = 1;
  209. }
  210. currentIndex = index;
  211. while (numberOfCreatedCols < amount && instance.countCols() < priv.settings.maxCols){
  212. constructor = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts);
  213. if (typeof index !== 'number' || index >= instance.countCols()) {
  214. for (var r = 0; r < rlen; r++) {
  215. if (typeof data[r] === 'undefined') {
  216. data[r] = [];
  217. }
  218. data[r].push(null);
  219. }
  220. // Add new column constructor
  221. priv.columnSettings.push(constructor);
  222. }
  223. else {
  224. for (var r = 0 ; r < rlen; r++) {
  225. data[r].splice(currentIndex, 0, null);
  226. }
  227. // Add new column constructor at given index
  228. priv.columnSettings.splice(currentIndex, 0, constructor);
  229. }
  230. numberOfCreatedCols++;
  231. currentIndex++;
  232. }
  233. instance.PluginHooks.run('afterCreateCol', index, numberOfCreatedCols);
  234. instance.forceFullRender = true; //used when data was changed
  235. return numberOfCreatedCols;
  236. },
  237. /**
  238. * Removes row from the data array
  239. * @param {Number} [index] Optional. Index of the row to be removed. If not provided, the last row will be removed
  240. * @param {Number} [amount] Optional. Amount of the rows to be removed. If not provided, one row will be removed
  241. */
  242. removeRow: function (index, amount) {
  243. if (!amount) {
  244. amount = 1;
  245. }
  246. if (typeof index !== 'number') {
  247. index = -amount;
  248. }
  249. index = (instance.countRows() + index) % instance.countRows();
  250. // We have to map the physical row ids to logical and than perform removing with (possibly) new row id
  251. var logicRows = this.physicalRowsToLogical(index, amount);
  252. var actionWasNotCancelled = instance.PluginHooks.execute('beforeRemoveRow', index, amount);
  253. if(actionWasNotCancelled === false){
  254. return;
  255. }
  256. var newData = GridSettings.prototype.data.filter(function (row, index) {
  257. return logicRows.indexOf(index) == -1;
  258. });
  259. GridSettings.prototype.data.length = 0;
  260. Array.prototype.push.apply(GridSettings.prototype.data, newData);
  261. instance.PluginHooks.run('afterRemoveRow', index, amount);
  262. instance.forceFullRender = true; //used when data was changed
  263. },
  264. /**
  265. * Removes column from the data array
  266. * @param {Number} [index] Optional. Index of the column to be removed. If not provided, the last column will be removed
  267. * @param {Number} [amount] Optional. Amount of the columns to be removed. If not provided, one column will be removed
  268. */
  269. removeCol: function (index, amount) {
  270. if (priv.dataType === 'object' || priv.settings.columns) {
  271. throw new Error("cannot remove column with object data source or columns option specified");
  272. }
  273. if (!amount) {
  274. amount = 1;
  275. }
  276. if (typeof index !== 'number') {
  277. index = -amount;
  278. }
  279. index = (instance.countCols() + index) % instance.countCols();
  280. var actionWasNotCancelled = instance.PluginHooks.execute('beforeRemoveCol', index, amount);
  281. if(actionWasNotCancelled === false){
  282. return;
  283. }
  284. var data = GridSettings.prototype.data;
  285. for (var r = 0, rlen = instance.countRows(); r < rlen; r++) {
  286. data[r].splice(index, amount);
  287. }
  288. priv.columnSettings.splice(index, amount);
  289. instance.PluginHooks.run('afterRemoveCol', index, amount);
  290. instance.forceFullRender = true; //used when data was changed
  291. },
  292. /**
  293. * Add / removes data from the column
  294. * @param {Number} col Index of column in which do you want to do splice.
  295. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  296. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  297. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  298. */
  299. spliceCol: function (col, index, amount/*, elements...*/) {
  300. var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
  301. var colData = instance.getDataAtCol(col);
  302. var removed = colData.slice(index, index + amount);
  303. var after = colData.slice(index + amount);
  304. Handsontable.helper.extendArray(elements, after);
  305. var i = 0;
  306. while (i < amount) {
  307. elements.push(null); //add null in place of removed elements
  308. i++;
  309. }
  310. Handsontable.helper.to2dArray(elements);
  311. instance.populateFromArray(index, col, elements, null, null, 'spliceCol');
  312. return removed;
  313. },
  314. /**
  315. * Add / removes data from the row
  316. * @param {Number} row Index of row in which do you want to do splice.
  317. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  318. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  319. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  320. */
  321. spliceRow: function (row, index, amount/*, elements...*/) {
  322. var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
  323. var rowData = instance.getDataAtRow(row);
  324. var removed = rowData.slice(index, index + amount);
  325. var after = rowData.slice(index + amount);
  326. Handsontable.helper.extendArray(elements, after);
  327. var i = 0;
  328. while (i < amount) {
  329. elements.push(null); //add null in place of removed elements
  330. i++;
  331. }
  332. instance.populateFromArray(row, index, [elements], null, null, 'spliceRow');
  333. return removed;
  334. },
  335. /**
  336. * Returns single value from the data array
  337. * @param {Number} row
  338. * @param {Number} prop
  339. */
  340. getVars: {},
  341. get: function (row, prop) {
  342. datamap.getVars.row = row;
  343. datamap.getVars.prop = prop;
  344. instance.PluginHooks.run('beforeGet', datamap.getVars);
  345. if (typeof datamap.getVars.prop === 'string' && datamap.getVars.prop.indexOf('.') > -1) {
  346. var sliced = datamap.getVars.prop.split(".");
  347. var out = priv.settings.data[datamap.getVars.row];
  348. if (!out) {
  349. return null;
  350. }
  351. for (var i = 0, ilen = sliced.length; i < ilen; i++) {
  352. out = out[sliced[i]];
  353. if (typeof out === 'undefined') {
  354. return null;
  355. }
  356. }
  357. return out;
  358. }
  359. else if (typeof datamap.getVars.prop === 'function') {
  360. /**
  361. * allows for interacting with complex structures, for example
  362. * d3/jQuery getter/setter properties:
  363. *
  364. * {columns: [{
  365. * data: function(row, value){
  366. * if(arguments.length === 1){
  367. * return row.property();
  368. * }
  369. * row.property(value);
  370. * }
  371. * }]}
  372. */
  373. return datamap.getVars.prop(priv.settings.data.slice(
  374. datamap.getVars.row,
  375. datamap.getVars.row + 1
  376. )[0]);
  377. }
  378. else {
  379. return priv.settings.data[datamap.getVars.row] ? priv.settings.data[datamap.getVars.row][datamap.getVars.prop] : null;
  380. }
  381. },
  382. /**
  383. * Saves single value to the data array
  384. * @param {Number} row
  385. * @param {Number} prop
  386. * @param {String} value
  387. * @param {String} [source] Optional. Source of hook runner.
  388. */
  389. setVars: {},
  390. set: function (row, prop, value, source) {
  391. datamap.setVars.row = row;
  392. datamap.setVars.prop = prop;
  393. datamap.setVars.value = value;
  394. instance.PluginHooks.run('beforeSet', datamap.setVars, source || "datamapGet");
  395. if (typeof datamap.setVars.prop === 'string' && datamap.setVars.prop.indexOf('.') > -1) {
  396. var sliced = datamap.setVars.prop.split(".");
  397. var out = priv.settings.data[datamap.setVars.row];
  398. for (var i = 0, ilen = sliced.length - 1; i < ilen; i++) {
  399. out = out[sliced[i]];
  400. }
  401. out[sliced[i]] = datamap.setVars.value;
  402. }
  403. else if (typeof datamap.setVars.prop === 'function') {
  404. /* see the `function` handler in `get` */
  405. datamap.setVars.prop(priv.settings.data.slice(
  406. datamap.setVars.row,
  407. datamap.setVars.row + 1
  408. )[0], datamap.setVars.value);
  409. }
  410. else {
  411. priv.settings.data[datamap.setVars.row][datamap.setVars.prop] = datamap.setVars.value;
  412. }
  413. },
  414. /**
  415. * This ridiculous piece of code maps rows Id that are present in table data to those displayed for user.
  416. * The trick is, the physical row id (stored in settings.data) is not necessary the same
  417. * as the logical (displayed) row id (e.g. when sorting is applied).
  418. */
  419. physicalRowsToLogical: function (index, amount) {
  420. var physicRow = (GridSettings.prototype.data.length + index) % GridSettings.prototype.data.length;
  421. var logicRows = [];
  422. var rowsToRemove = amount;
  423. while (physicRow < GridSettings.prototype.data.length && rowsToRemove) {
  424. this.get(physicRow, 0); //this performs an actual mapping and saves the result to getVars
  425. logicRows.push(this.getVars.row);
  426. rowsToRemove--;
  427. physicRow++;
  428. }
  429. return logicRows;
  430. },
  431. /**
  432. * Clears the data array
  433. */
  434. clear: function () {
  435. for (var r = 0; r < instance.countRows(); r++) {
  436. for (var c = 0; c < instance.countCols(); c++) {
  437. datamap.set(r, datamap.colToProp(c), '');
  438. }
  439. }
  440. },
  441. /**
  442. * Returns the data array
  443. * @return {Array}
  444. */
  445. getAll: function () {
  446. return priv.settings.data;
  447. },
  448. /**
  449. * Returns data range as array
  450. * @param {Object} start Start selection position
  451. * @param {Object} end End selection position
  452. * @return {Array}
  453. */
  454. getRange: function (start, end) {
  455. var r, rlen, c, clen, output = [], row;
  456. rlen = Math.max(start.row, end.row);
  457. clen = Math.max(start.col, end.col);
  458. for (r = Math.min(start.row, end.row); r <= rlen; r++) {
  459. row = [];
  460. for (c = Math.min(start.col, end.col); c <= clen; c++) {
  461. row.push(datamap.get(r, datamap.colToProp(c)));
  462. }
  463. output.push(row);
  464. }
  465. return output;
  466. },
  467. /**
  468. * Return data as text (tab separated columns)
  469. * @param {Object} start (Optional) Start selection position
  470. * @param {Object} end (Optional) End selection position
  471. * @return {String}
  472. */
  473. getText: function (start, end) {
  474. return SheetClip.stringify(datamap.getRange(start, end));
  475. }
  476. };
  477. grid = {
  478. /**
  479. * Inserts or removes rows and columns
  480. * @param {String} action Possible values: "insert_row", "insert_col", "remove_row", "remove_col"
  481. * @param {Number} index
  482. * @param {Number} amount
  483. * @param {String} [source] Optional. Source of hook runner.
  484. * @param {Boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows.
  485. */
  486. alter: function (action, index, amount, source, keepEmptyRows) {
  487. var delta;
  488. amount = amount || 1;
  489. switch (action) {
  490. case "insert_row":
  491. delta = datamap.createRow(index, amount);
  492. if (delta) {
  493. if (priv.selStart.exists() && priv.selStart.row() >= index) {
  494. priv.selStart.row(priv.selStart.row() + delta);
  495. selection.transformEnd(delta, 0); //will call render() internally
  496. }
  497. else {
  498. selection.refreshBorders(); //it will call render and prepare methods
  499. }
  500. }
  501. break;
  502. case "insert_col":
  503. delta = datamap.createCol(index, amount);
  504. if (delta) {
  505. if(Handsontable.helper.isArray(instance.getSettings().colHeaders)){
  506. var spliceArray = [index, 0];
  507. spliceArray.length += delta; //inserts empty (undefined) elements at the end of an array
  508. Array.prototype.splice.apply(instance.getSettings().colHeaders, spliceArray); //inserts empty (undefined) elements into the colHeader array
  509. }
  510. if (priv.selStart.exists() && priv.selStart.col() >= index) {
  511. priv.selStart.col(priv.selStart.col() + delta);
  512. selection.transformEnd(0, delta); //will call render() internally
  513. }
  514. else {
  515. selection.refreshBorders(); //it will call render and prepare methods
  516. }
  517. }
  518. break;
  519. case "remove_row":
  520. datamap.removeRow(index, amount);
  521. priv.cellSettings.splice(index, amount);
  522. grid.adjustRowsAndCols();
  523. selection.refreshBorders(); //it will call render and prepare methods
  524. break;
  525. case "remove_col":
  526. datamap.removeCol(index, amount);
  527. for(var row = 0, len = datamap.getAll().length; row < len; row++){
  528. if(row in priv.cellSettings){ //if row hasn't been rendered it wouldn't have cellSettings
  529. priv.cellSettings[row].splice(index, amount);
  530. }
  531. }
  532. if(Handsontable.helper.isArray(instance.getSettings().colHeaders)){
  533. if(typeof index == 'undefined'){
  534. index = -1;
  535. }
  536. instance.getSettings().colHeaders.splice(index, amount);
  537. }
  538. priv.columnSettings.splice(index, amount);
  539. grid.adjustRowsAndCols();
  540. selection.refreshBorders(); //it will call render and prepare methods
  541. break;
  542. default:
  543. throw new Error('There is no such action "' + action + '"');
  544. break;
  545. }
  546. if (!keepEmptyRows) {
  547. grid.adjustRowsAndCols(); //makes sure that we did not add rows that will be removed in next refresh
  548. }
  549. },
  550. /**
  551. * Makes sure there are empty rows at the bottom of the table
  552. */
  553. adjustRowsAndCols: function () {
  554. var r, rlen, emptyRows = instance.countEmptyRows(true), emptyCols;
  555. //should I add empty rows to data source to meet minRows?
  556. rlen = instance.countRows();
  557. if (rlen < priv.settings.minRows) {
  558. for (r = 0; r < priv.settings.minRows - rlen; r++) {
  559. datamap.createRow();
  560. }
  561. }
  562. //should I add empty rows to meet minSpareRows?
  563. if (emptyRows < priv.settings.minSpareRows) {
  564. for (; emptyRows < priv.settings.minSpareRows && instance.countRows() < priv.settings.maxRows; emptyRows++) {
  565. datamap.createRow();
  566. }
  567. }
  568. //count currently empty cols
  569. emptyCols = instance.countEmptyCols(true);
  570. //should I add empty cols to meet minCols?
  571. if (!priv.settings.columns && instance.countCols() < priv.settings.minCols) {
  572. for (; instance.countCols() < priv.settings.minCols; emptyCols++) {
  573. datamap.createCol();
  574. }
  575. }
  576. //should I add empty cols to meet minSpareCols?
  577. if (!priv.settings.columns && priv.dataType === 'array' && emptyCols < priv.settings.minSpareCols) {
  578. for (; emptyCols < priv.settings.minSpareCols && instance.countCols() < priv.settings.maxCols; emptyCols++) {
  579. datamap.createCol();
  580. }
  581. }
  582. if (priv.settings.enterBeginsEditing) {
  583. for (; (((priv.settings.minRows || priv.settings.minSpareRows) && instance.countRows() > priv.settings.minRows) && (priv.settings.minSpareRows && emptyRows > priv.settings.minSpareRows)); emptyRows--) {
  584. datamap.removeRow();
  585. }
  586. }
  587. if (priv.settings.enterBeginsEditing && !priv.settings.columns) {
  588. for (; (((priv.settings.minCols || priv.settings.minSpareCols) && instance.countCols() > priv.settings.minCols) && (priv.settings.minSpareCols && emptyCols > priv.settings.minSpareCols)); emptyCols--) {
  589. datamap.removeCol();
  590. }
  591. }
  592. var rowCount = instance.countRows();
  593. var colCount = instance.countCols();
  594. if (rowCount === 0 || colCount === 0) {
  595. selection.deselect();
  596. }
  597. if (priv.selStart.exists()) {
  598. var selectionChanged;
  599. var fromRow = priv.selStart.row();
  600. var fromCol = priv.selStart.col();
  601. var toRow = priv.selEnd.row();
  602. var toCol = priv.selEnd.col();
  603. //if selection is outside, move selection to last row
  604. if (fromRow > rowCount - 1) {
  605. fromRow = rowCount - 1;
  606. selectionChanged = true;
  607. if (toRow > fromRow) {
  608. toRow = fromRow;
  609. }
  610. } else if (toRow > rowCount - 1) {
  611. toRow = rowCount - 1;
  612. selectionChanged = true;
  613. if (fromRow > toRow) {
  614. fromRow = toRow;
  615. }
  616. }
  617. //if selection is outside, move selection to last row
  618. if (fromCol > colCount - 1) {
  619. fromCol = colCount - 1;
  620. selectionChanged = true;
  621. if (toCol > fromCol) {
  622. toCol = fromCol;
  623. }
  624. } else if (toCol > colCount - 1) {
  625. toCol = colCount - 1;
  626. selectionChanged = true;
  627. if (fromCol > toCol) {
  628. fromCol = toCol;
  629. }
  630. }
  631. if (selectionChanged) {
  632. instance.selectCell(fromRow, fromCol, toRow, toCol);
  633. }
  634. }
  635. },
  636. /**
  637. * Populate cells at position with 2d array
  638. * @param {Object} start Start selection position
  639. * @param {Array} input 2d array
  640. * @param {Object} [end] End selection position (only for drag-down mode)
  641. * @param {String} [source="populateFromArray"]
  642. * @param {String} [method="overwrite"]
  643. * @return {Object|undefined} ending td in pasted area (only if any cell was changed)
  644. */
  645. populateFromArray: function (start, input, end, source, method) {
  646. var r, rlen, c, clen, setData = [], current = {};
  647. rlen = input.length;
  648. if (rlen === 0) {
  649. return false;
  650. }
  651. var repeatCol
  652. , repeatRow
  653. , cmax
  654. , rmax;
  655. // insert data with specified pasteMode method
  656. switch (method) {
  657. case 'shift_down' :
  658. repeatCol = end ? end.col - start.col + 1 : 0;
  659. repeatRow = end ? end.row - start.row + 1 : 0;
  660. input = Handsontable.helper.translateRowsToColumns(input);
  661. for (c = 0, clen = input.length, cmax = Math.max(clen, repeatCol); c < cmax; c++) {
  662. if (c < clen) {
  663. for (r = 0, rlen = input[c].length; r < repeatRow - rlen; r++) {
  664. input[c].push(input[c][r % rlen]);
  665. }
  666. input[c].unshift(start.col + c, start.row, 0);
  667. instance.spliceCol.apply(instance, input[c]);
  668. }
  669. else {
  670. input[c % clen][0] = start.col + c;
  671. instance.spliceCol.apply(instance, input[c % clen]);
  672. }
  673. }
  674. break;
  675. case 'shift_right' :
  676. repeatCol = end ? end.col - start.col + 1 : 0;
  677. repeatRow = end ? end.row - start.row + 1 : 0;
  678. for (r = 0, rlen = input.length, rmax = Math.max(rlen, repeatRow); r < rmax; r++) {
  679. if (r < rlen) {
  680. for (c = 0, clen = input[r].length; c < repeatCol - clen; c++) {
  681. input[r].push(input[r][c % clen]);
  682. }
  683. input[r].unshift(start.row + r, start.col, 0);
  684. instance.spliceRow.apply(instance, input[r]);
  685. }
  686. else {
  687. input[r % rlen][0] = start.row + r;
  688. instance.spliceRow.apply(instance, input[r % rlen]);
  689. }
  690. }
  691. break;
  692. case 'overwrite' :
  693. default:
  694. // overwrite and other not specified options
  695. current.row = start.row;
  696. current.col = start.col;
  697. for (r = 0; r < rlen; r++) {
  698. if ((end && current.row > end.row) || (!priv.settings.minSpareRows && current.row > instance.countRows() - 1) || (current.row >= priv.settings.maxRows)) {
  699. break;
  700. }
  701. current.col = start.col;
  702. clen = input[r] ? input[r].length : 0;
  703. for (c = 0; c < clen; c++) {
  704. if ((end && current.col > end.col) || (!priv.settings.minSpareCols && current.col > instance.countCols() - 1) || (current.col >= priv.settings.maxCols)) {
  705. break;
  706. }
  707. if (!instance.getCellMeta(current.row, current.col).readOnly) {
  708. setData.push([current.row, current.col, input[r][c]]);
  709. }
  710. current.col++;
  711. if (end && c === clen - 1) {
  712. c = -1;
  713. }
  714. }
  715. current.row++;
  716. if (end && r === rlen - 1) {
  717. r = -1;
  718. }
  719. }
  720. instance.setDataAtCell(setData, null, null, source || 'populateFromArray');
  721. break;
  722. }
  723. },
  724. /**
  725. * Returns the top left (TL) and bottom right (BR) selection coordinates
  726. * @param {Object[]} coordsArr
  727. * @returns {Object}
  728. */
  729. getCornerCoords: function (coordsArr) {
  730. function mapProp(func, array, prop) {
  731. function getProp(el) {
  732. return el[prop];
  733. }
  734. if (Array.prototype.map) {
  735. return func.apply(Math, array.map(getProp));
  736. }
  737. return func.apply(Math, $.map(array, getProp));
  738. }
  739. return {
  740. TL: {
  741. row: mapProp(Math.min, coordsArr, "row"),
  742. col: mapProp(Math.min, coordsArr, "col")
  743. },
  744. BR: {
  745. row: mapProp(Math.max, coordsArr, "row"),
  746. col: mapProp(Math.max, coordsArr, "col")
  747. }
  748. };
  749. },
  750. /**
  751. * Returns array of td objects given start and end coordinates
  752. */
  753. getCellsAtCoords: function (start, end) {
  754. var corners = grid.getCornerCoords([start, end]);
  755. var r, c, output = [];
  756. for (r = corners.TL.row; r <= corners.BR.row; r++) {
  757. for (c = corners.TL.col; c <= corners.BR.col; c++) {
  758. output.push(instance.view.getCellAtCoords({
  759. row: r,
  760. col: c
  761. }));
  762. }
  763. }
  764. return output;
  765. }
  766. };
  767. this.selection = selection = { //this public assignment is only temporary
  768. inProgress: false,
  769. /**
  770. * Sets inProgress to true. This enables onSelectionEnd and onSelectionEndByProp to function as desired
  771. */
  772. begin: function () {
  773. instance.selection.inProgress = true;
  774. },
  775. /**
  776. * Sets inProgress to false. Triggers onSelectionEnd and onSelectionEndByProp
  777. */
  778. finish: function () {
  779. var sel = instance.getSelected();
  780. instance.PluginHooks.run("afterSelectionEnd", sel[0], sel[1], sel[2], sel[3]);
  781. instance.PluginHooks.run("afterSelectionEndByProp", sel[0], instance.colToProp(sel[1]), sel[2], instance.colToProp(sel[3]));
  782. instance.selection.inProgress = false;
  783. },
  784. isInProgress: function () {
  785. return instance.selection.inProgress;
  786. },
  787. /**
  788. * Starts selection range on given td object
  789. * @param {Object} coords
  790. */
  791. setRangeStart: function (coords) {
  792. priv.selStart.coords(coords);
  793. selection.setRangeEnd(coords);
  794. },
  795. /**
  796. * Ends selection range on given td object
  797. * @param {Object} coords
  798. * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to range end
  799. */
  800. setRangeEnd: function (coords, scrollToCell) {
  801. instance.selection.begin();
  802. priv.selEnd.coords(coords);
  803. if (!priv.settings.multiSelect) {
  804. priv.selStart.coords(coords);
  805. }
  806. //set up current selection
  807. instance.view.wt.selections.current.clear();
  808. instance.view.wt.selections.current.add(priv.selStart.arr());
  809. //set up area selection
  810. instance.view.wt.selections.area.clear();
  811. if (selection.isMultiple()) {
  812. instance.view.wt.selections.area.add(priv.selStart.arr());
  813. instance.view.wt.selections.area.add(priv.selEnd.arr());
  814. }
  815. //set up highlight
  816. if (priv.settings.currentRowClassName || priv.settings.currentColClassName) {
  817. instance.view.wt.selections.highlight.clear();
  818. instance.view.wt.selections.highlight.add(priv.selStart.arr());
  819. instance.view.wt.selections.highlight.add(priv.selEnd.arr());
  820. }
  821. //trigger handlers
  822. instance.PluginHooks.run("afterSelection", priv.selStart.row(), priv.selStart.col(), priv.selEnd.row(), priv.selEnd.col());
  823. instance.PluginHooks.run("afterSelectionByProp", priv.selStart.row(), datamap.colToProp(priv.selStart.col()), priv.selEnd.row(), datamap.colToProp(priv.selEnd.col()));
  824. if (scrollToCell !== false) {
  825. instance.view.scrollViewport(coords);
  826. }
  827. selection.refreshBorders();
  828. },
  829. /**
  830. * Destroys editor, redraws borders around cells, prepares editor
  831. * @param {Boolean} revertOriginal
  832. * @param {Boolean} keepEditor
  833. */
  834. refreshBorders: function (revertOriginal, keepEditor) {
  835. if (!keepEditor) {
  836. editorManager.destroyEditor(revertOriginal);
  837. }
  838. instance.view.render();
  839. if (selection.isSelected() && !keepEditor) {
  840. editorManager.prepareEditor();
  841. }
  842. },
  843. /**
  844. * Returns information if we have a multiselection
  845. * @return {Boolean}
  846. */
  847. isMultiple: function () {
  848. return !(priv.selEnd.col() === priv.selStart.col() && priv.selEnd.row() === priv.selStart.row());
  849. },
  850. /**
  851. * Selects cell relative to current cell (if possible)
  852. */
  853. transformStart: function (rowDelta, colDelta, force) {
  854. if (priv.selStart.row() + rowDelta > instance.countRows() - 1) {
  855. if (force && priv.settings.minSpareRows > 0) {
  856. instance.alter("insert_row", instance.countRows());
  857. }
  858. else if (priv.settings.autoWrapCol) {
  859. rowDelta = 1 - instance.countRows();
  860. colDelta = priv.selStart.col() + colDelta == instance.countCols() - 1 ? 1 - instance.countCols() : 1;
  861. }
  862. }
  863. else if (priv.settings.autoWrapCol && priv.selStart.row() + rowDelta < 0 && priv.selStart.col() + colDelta >= 0) {
  864. rowDelta = instance.countRows() - 1;
  865. colDelta = priv.selStart.col() + colDelta == 0 ? instance.countCols() - 1 : -1;
  866. }
  867. if (priv.selStart.col() + colDelta > instance.countCols() - 1) {
  868. if (force && priv.settings.minSpareCols > 0) {
  869. instance.alter("insert_col", instance.countCols());
  870. }
  871. else if (priv.settings.autoWrapRow) {
  872. rowDelta = priv.selStart.row() + rowDelta == instance.countRows() - 1 ? 1 - instance.countRows() : 1;
  873. colDelta = 1 - instance.countCols();
  874. }
  875. }
  876. else if (priv.settings.autoWrapRow && priv.selStart.col() + colDelta < 0 && priv.selStart.row() + rowDelta >= 0) {
  877. rowDelta = priv.selStart.row() + rowDelta == 0 ? instance.countRows() - 1 : -1;
  878. colDelta = instance.countCols() - 1;
  879. }
  880. var totalRows = instance.countRows();
  881. var totalCols = instance.countCols();
  882. var coords = {
  883. row: priv.selStart.row() + rowDelta,
  884. col: priv.selStart.col() + colDelta
  885. };
  886. if (coords.row < 0) {
  887. coords.row = 0;
  888. }
  889. else if (coords.row > 0 && coords.row >= totalRows) {
  890. coords.row = totalRows - 1;
  891. }
  892. if (coords.col < 0) {
  893. coords.col = 0;
  894. }
  895. else if (coords.col > 0 && coords.col >= totalCols) {
  896. coords.col = totalCols - 1;
  897. }
  898. selection.setRangeStart(coords);
  899. },
  900. /**
  901. * Sets selection end cell relative to current selection end cell (if possible)
  902. */
  903. transformEnd: function (rowDelta, colDelta) {
  904. if (priv.selEnd.exists()) {
  905. var totalRows = instance.countRows();
  906. var totalCols = instance.countCols();
  907. var coords = {
  908. row: priv.selEnd.row() + rowDelta,
  909. col: priv.selEnd.col() + colDelta
  910. };
  911. if (coords.row < 0) {
  912. coords.row = 0;
  913. }
  914. else if (coords.row > 0 && coords.row >= totalRows) {
  915. coords.row = totalRows - 1;
  916. }
  917. if (coords.col < 0) {
  918. coords.col = 0;
  919. }
  920. else if (coords.col > 0 && coords.col >= totalCols) {
  921. coords.col = totalCols - 1;
  922. }
  923. selection.setRangeEnd(coords);
  924. }
  925. },
  926. /**
  927. * Returns true if currently there is a selection on screen, false otherwise
  928. * @return {Boolean}
  929. */
  930. isSelected: function () {
  931. return priv.selEnd.exists();
  932. },
  933. /**
  934. * Returns true if coords is within current selection coords
  935. * @return {Boolean}
  936. */
  937. inInSelection: function (coords) {
  938. if (!selection.isSelected()) {
  939. return false;
  940. }
  941. var sel = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]);
  942. return (sel.TL.row <= coords.row && sel.BR.row >= coords.row && sel.TL.col <= coords.col && sel.BR.col >= coords.col);
  943. },
  944. /**
  945. * Deselects all selected cells
  946. */
  947. deselect: function () {
  948. if (!selection.isSelected()) {
  949. return;
  950. }
  951. instance.selection.inProgress = false; //needed by HT inception
  952. priv.selEnd = new Handsontable.SelectionPoint(); //create new empty point to remove the existing one
  953. instance.view.wt.selections.current.clear();
  954. instance.view.wt.selections.area.clear();
  955. editorManager.destroyEditor();
  956. selection.refreshBorders();
  957. instance.PluginHooks.run('afterDeselect');
  958. },
  959. /**
  960. * Select all cells
  961. */
  962. selectAll: function () {
  963. if (!priv.settings.multiSelect) {
  964. return;
  965. }
  966. selection.setRangeStart({
  967. row: 0,
  968. col: 0
  969. });
  970. selection.setRangeEnd({
  971. row: instance.countRows() - 1,
  972. col: instance.countCols() - 1
  973. }, false);
  974. },
  975. /**
  976. * Deletes data from selected cells
  977. */
  978. empty: function () {
  979. if (!selection.isSelected()) {
  980. return;
  981. }
  982. var corners = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]);
  983. var r, c, changes = [];
  984. for (r = corners.TL.row; r <= corners.BR.row; r++) {
  985. for (c = corners.TL.col; c <= corners.BR.col; c++) {
  986. if (!instance.getCellMeta(r, c).readOnly) {
  987. changes.push([r, c, '']);
  988. }
  989. }
  990. }
  991. instance.setDataAtCell(changes);
  992. }
  993. };
  994. this.autofill = autofill = { //this public assignment is only temporary
  995. handle: null,
  996. /**
  997. * Create fill handle and fill border objects
  998. */
  999. init: function () {
  1000. if (!autofill.handle) {
  1001. autofill.handle = {};
  1002. }
  1003. else {
  1004. autofill.handle.disabled = false;
  1005. }
  1006. },
  1007. /**
  1008. * Hide fill handle and fill border permanently
  1009. */
  1010. disable: function () {
  1011. autofill.handle.disabled = true;
  1012. },
  1013. /**
  1014. * Selects cells down to the last row in the left column, then fills down to that cell
  1015. */
  1016. selectAdjacent: function () {
  1017. var select, data, r, maxR, c;
  1018. if (selection.isMultiple()) {
  1019. select = instance.view.wt.selections.area.getCorners();
  1020. }
  1021. else {
  1022. select = instance.view.wt.selections.current.getCorners();
  1023. }
  1024. data = datamap.getAll();
  1025. rows : for (r = select[2] + 1; r < instance.countRows(); r++) {
  1026. for (c = select[1]; c <= select[3]; c++) {
  1027. if (data[r][c]) {
  1028. break rows;
  1029. }
  1030. }
  1031. if (!!data[r][select[1] - 1] || !!data[r][select[3] + 1]) {
  1032. maxR = r;
  1033. }
  1034. }
  1035. if (maxR) {
  1036. instance.view.wt.selections.fill.clear();
  1037. instance.view.wt.selections.fill.add([select[0], select[1]]);
  1038. instance.view.wt.selections.fill.add([maxR, select[3]]);
  1039. autofill.apply();
  1040. }
  1041. },
  1042. /**
  1043. * Apply fill values to the area in fill border, omitting the selection border
  1044. */
  1045. apply: function () {
  1046. var drag, select, start, end, _data;
  1047. autofill.handle.isDragged = 0;
  1048. drag = instance.view.wt.selections.fill.getCorners();
  1049. if (!drag) {
  1050. return;
  1051. }
  1052. instance.view.wt.selections.fill.clear();
  1053. if (selection.isMultiple()) {
  1054. select = instance.view.wt.selections.area.getCorners();
  1055. }
  1056. else {
  1057. select = instance.view.wt.selections.current.getCorners();
  1058. }
  1059. if (drag[0] === select[0] && drag[1] < select[1]) {
  1060. start = {
  1061. row: drag[0],
  1062. col: drag[1]
  1063. };
  1064. end = {
  1065. row: drag[2],
  1066. col: select[1] - 1
  1067. };
  1068. }
  1069. else if (drag[0] === select[0] && drag[3] > select[3]) {
  1070. start = {
  1071. row: drag[0],
  1072. col: select[3] + 1
  1073. };
  1074. end = {
  1075. row: drag[2],
  1076. col: drag[3]
  1077. };
  1078. }
  1079. else if (drag[0] < select[0] && drag[1] === select[1]) {
  1080. start = {
  1081. row: drag[0],
  1082. col: drag[1]
  1083. };
  1084. end = {
  1085. row: select[0] - 1,
  1086. col: drag[3]
  1087. };
  1088. }
  1089. else if (drag[2] > select[2] && drag[1] === select[1]) {
  1090. start = {
  1091. row: select[2] + 1,
  1092. col: drag[1]
  1093. };
  1094. end = {
  1095. row: drag[2],
  1096. col: drag[3]
  1097. };
  1098. }
  1099. if (start) {
  1100. _data = SheetClip.parse(datamap.getText(priv.selStart.coords(), priv.selEnd.coords()));
  1101. instance.PluginHooks.run('beforeAutofill', start, end, _data);
  1102. grid.populateFromArray(start, _data, end, 'autofill');
  1103. selection.setRangeStart({row: drag[0], col: drag[1]});
  1104. selection.setRangeEnd({row: drag[2], col: drag[3]});
  1105. }
  1106. /*else {
  1107. //reset to avoid some range bug
  1108. selection.refreshBorders();
  1109. }*/
  1110. },
  1111. /**
  1112. * Show fill border
  1113. */
  1114. showBorder: function (coords) {
  1115. coords.row = coords[0];
  1116. coords.col = coords[1];
  1117. var corners = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]);
  1118. if (priv.settings.fillHandle !== 'horizontal' && (corners.BR.row < coords.row || corners.TL.row > coords.row)) {
  1119. coords = [coords.row, corners.BR.col];
  1120. }
  1121. else if (priv.settings.fillHandle !== 'vertical') {
  1122. coords = [corners.BR.row, coords.col];
  1123. }
  1124. else {
  1125. return; //wrong direction
  1126. }
  1127. instance.view.wt.selections.fill.clear();
  1128. instance.view.wt.selections.fill.add([priv.selStart.coords().row, priv.selStart.coords().col]);
  1129. instance.view.wt.selections.fill.add([priv.selEnd.coords().row, priv.selEnd.coords().col]);
  1130. instance.view.wt.selections.fill.add(coords);
  1131. instance.view.render();
  1132. }
  1133. };
  1134. this.init = function () {
  1135. instance.PluginHooks.run('beforeInit');
  1136. this.view = new Handsontable.TableView(this);
  1137. editorManager = new Handsontable.EditorManager(instance, priv, selection, datamap);
  1138. this.updateSettings(priv.settings, true);
  1139. this.parseSettingsFromDOM();
  1140. this.forceFullRender = true; //used when data was changed
  1141. this.view.render();
  1142. if (typeof priv.firstRun === 'object') {
  1143. instance.PluginHooks.run('afterChange', priv.firstRun[0], priv.firstRun[1]);
  1144. priv.firstRun = false;
  1145. }
  1146. instance.PluginHooks.run('afterInit');
  1147. };
  1148. function ValidatorsQueue() { //moved this one level up so it can be used in any function here. Probably this should be moved to a separate file
  1149. var resolved = false;
  1150. return {
  1151. validatorsInQueue: 0,
  1152. addValidatorToQueue: function () {
  1153. this.validatorsInQueue++;
  1154. resolved = false;
  1155. },
  1156. removeValidatorFormQueue: function () {
  1157. this.validatorsInQueue = this.validatorsInQueue - 1 < 0 ? 0 : this.validatorsInQueue - 1;
  1158. this.checkIfQueueIsEmpty();
  1159. },
  1160. onQueueEmpty: function () {
  1161. },
  1162. checkIfQueueIsEmpty: function () {
  1163. if (this.validatorsInQueue == 0 && resolved == false) {
  1164. resolved = true;
  1165. this.onQueueEmpty();
  1166. }
  1167. }
  1168. };
  1169. }
  1170. function validateChanges(changes, source, callback) {
  1171. var waitingForValidator = new ValidatorsQueue();
  1172. waitingForValidator.onQueueEmpty = resolve;
  1173. for (var i = changes.length - 1; i >= 0; i--) {
  1174. if (changes[i] === null) {
  1175. changes.splice(i, 1);
  1176. }
  1177. else {
  1178. var row = changes[i][0];
  1179. var col = datamap.propToCol(changes[i][1]);
  1180. var logicalCol = instance.runHooksAndReturn('modifyCol', col); //column order may have changes, so we need to translate physical col index (stored in datasource) to logical (displayed to user)
  1181. var cellProperties = instance.getCellMeta(row, logicalCol);
  1182. if (cellProperties.type === 'numeric' && typeof changes[i][3] === 'string') {
  1183. if (changes[i][3].length > 0 && /^-?[\d\s]*\.?\d*$/.test(changes[i][3])) {
  1184. changes[i][3] = numeral().unformat(changes[i][3] || '0'); //numeral cannot unformat empty string
  1185. }
  1186. }
  1187. if (instance.getCellValidator(cellProperties)) {
  1188. waitingForValidator.addValidatorToQueue();
  1189. instance.validateCell(changes[i][3], cellProperties, (function (i, cellProperties) {
  1190. return function (result) {
  1191. if (typeof result !== 'boolean') {
  1192. throw new Error("Validation error: result is not boolean");
  1193. }
  1194. if (result === false && cellProperties.allowInvalid === false) {
  1195. changes.splice(i, 1); // cancel the change
  1196. cellProperties.valid = true; // we cancelled the change, so cell value is still valid
  1197. --i;
  1198. }
  1199. waitingForValidator.removeValidatorFormQueue();
  1200. }
  1201. })(i, cellProperties)
  1202. , source);
  1203. }
  1204. }
  1205. }
  1206. waitingForValidator.checkIfQueueIsEmpty();
  1207. function resolve() {
  1208. var beforeChangeResult;
  1209. if (changes.length) {
  1210. beforeChangeResult = instance.PluginHooks.execute("beforeChange", changes, source);
  1211. if (typeof beforeChangeResult === 'function') {
  1212. $.when(result).then(function () {
  1213. callback(); //called when async validators and async beforeChange are resolved
  1214. });
  1215. }
  1216. else if (beforeChangeResult === false) {
  1217. changes.splice(0, changes.length); //invalidate all changes (remove everything from array)
  1218. }
  1219. }
  1220. if (typeof beforeChangeResult !== 'function') {
  1221. callback(); //called when async validators are resolved and beforeChange was not async
  1222. }
  1223. }
  1224. }
  1225. /**
  1226. * Internal function to apply changes. Called after validateChanges
  1227. * @param {Array} changes Array in form of [row, prop, oldValue, newValue]
  1228. * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback)
  1229. */
  1230. function applyChanges(changes, source) {
  1231. var i = changes.length - 1;
  1232. if (i < 0) {
  1233. return;
  1234. }
  1235. for (; 0 <= i; i--) {
  1236. if (changes[i] === null) {
  1237. changes.splice(i, 1);
  1238. continue;
  1239. }
  1240. if (priv.settings.minSpareRows) {
  1241. while (changes[i][0] > instance.countRows() - 1) {
  1242. datamap.createRow();
  1243. }
  1244. }
  1245. if (priv.dataType === 'array' && priv.settings.minSpareCols) {
  1246. while (datamap.propToCol(changes[i][1]) > instance.countCols() - 1) {
  1247. datamap.createCol();
  1248. }
  1249. }
  1250. datamap.set(changes[i][0], changes[i][1], changes[i][3]);
  1251. }
  1252. instance.forceFullRender = true; //used when data was changed
  1253. grid.adjustRowsAndCols();
  1254. selection.refreshBorders(null, true);
  1255. instance.PluginHooks.run('afterChange', changes, source || 'edit');
  1256. }
  1257. this.validateCell = function (value, cellProperties, callback, source) {
  1258. var validator = instance.getCellValidator(cellProperties);
  1259. if (Object.prototype.toString.call(validator) === '[object RegExp]') {
  1260. validator = (function (validator) {
  1261. return function (value, callback) {
  1262. callback(validator.test(value));
  1263. }
  1264. })(validator);
  1265. }
  1266. if (typeof validator == 'function') {
  1267. value = instance.PluginHooks.execute("beforeValidate", value, cellProperties.row, cellProperties.prop, source);
  1268. // To provide consistent behaviour, validation should be always asynchronous
  1269. setTimeout(function () {
  1270. validator.call(cellProperties, value, function (valid) {
  1271. cellProperties.valid = valid;
  1272. valid = instance.PluginHooks.execute("afterValidate", valid, value, cellProperties.row, cellProperties.prop, source);
  1273. callback(valid);
  1274. });
  1275. });
  1276. } else { //resolve callback even if validator function was not found
  1277. cellProperties.valid = true;
  1278. callback(true);
  1279. }
  1280. };
  1281. function setDataInputToArray(row, prop_or_col, value) {
  1282. if (typeof row === "object") { //is it an array of changes
  1283. return row;
  1284. }
  1285. else if ($.isPlainObject(value)) { //backwards compatibility
  1286. return value;
  1287. }
  1288. else {
  1289. return [
  1290. [row, prop_or_col, value]
  1291. ];
  1292. }
  1293. }
  1294. /**
  1295. * Set data at given cell
  1296. * @public
  1297. * @param {Number|Array} row or array of changes in format [[row, col, value], ...]
  1298. * @param {Number|String} col or source String
  1299. * @param {String} value
  1300. * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback)
  1301. */
  1302. this.setDataAtCell = function (row, col, value, source) {
  1303. var input = setDataInputToArray(row, col, value)
  1304. , i
  1305. , ilen
  1306. , changes = []
  1307. , prop;
  1308. for (i = 0, ilen = input.length; i < ilen; i++) {
  1309. if (typeof input[i] !== 'object') {
  1310. throw new Error('Method `setDataAtCell` accepts row number or changes array of arrays as its first parameter');
  1311. }
  1312. if (typeof input[i][1] !== 'number') {
  1313. throw new Error('Method `setDataAtCell` accepts row and column number as its parameters. If you want to use object property name, use method `setDataAtRowProp`');
  1314. }
  1315. prop = datamap.colToProp(input[i][1]);
  1316. changes.push([
  1317. input[i][0],
  1318. prop,
  1319. datamap.get(input[i][0], prop),
  1320. input[i][2]
  1321. ]);
  1322. }
  1323. if (!source && typeof row === "object") {
  1324. source = col;
  1325. }
  1326. validateChanges(changes, source, function () {
  1327. applyChanges(changes, source);
  1328. });
  1329. };
  1330. /**
  1331. * Set data at given row property
  1332. * @public
  1333. * @param {Number|Array} row or array of changes in format [[row, prop, value], ...]
  1334. * @param {String} prop or source String
  1335. * @param {String} value
  1336. * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback)
  1337. */
  1338. this.setDataAtRowProp = function (row, prop, value, source) {
  1339. var input = setDataInputToArray(row, prop, value)
  1340. , i
  1341. , ilen
  1342. , changes = [];
  1343. for (i = 0, ilen = input.length; i < ilen; i++) {
  1344. changes.push([
  1345. input[i][0],
  1346. input[i][1],
  1347. datamap.get(input[i][0], input[i][1]),
  1348. input[i][2]
  1349. ]);
  1350. }
  1351. if (!source && typeof row === "object") {
  1352. source = prop;
  1353. }
  1354. validateChanges(changes, source, function () {
  1355. applyChanges(changes, source);
  1356. });
  1357. };
  1358. /**
  1359. * Listen to document body keyboard input
  1360. */
  1361. this.listen = function () {
  1362. Handsontable.activeGuid = instance.guid;
  1363. if (document.activeElement && document.activeElement !== document.body) {
  1364. document.activeElement.blur();
  1365. }
  1366. else if (!document.activeElement) { //IE
  1367. document.body.focus();
  1368. }
  1369. };
  1370. /**
  1371. * Stop listening to document body keyboard input
  1372. */
  1373. this.unlisten = function () {
  1374. Handsontable.activeGuid = null;
  1375. };
  1376. /**
  1377. * Returns true if current Handsontable instance is listening on document body keyboard input
  1378. */
  1379. this.isListening = function () {
  1380. return Handsontable.activeGuid === instance.guid;
  1381. };
  1382. /**
  1383. * Destroys current editor, renders and selects current cell. If revertOriginal != true, edited data is saved
  1384. * @param {Boolean} revertOriginal
  1385. */
  1386. this.destroyEditor = function (revertOriginal) {
  1387. selection.refreshBorders(revertOriginal);
  1388. };
  1389. /**
  1390. * Populate cells at position with 2d array
  1391. * @param {Number} row Start row
  1392. * @param {Number} col Start column
  1393. * @param {Array} input 2d array
  1394. * @param {Number=} endRow End row (use when you want to cut input when certain row is reached)
  1395. * @param {Number=} endCol End column (use when you want to cut input when certain column is reached)
  1396. * @param {String=} [source="populateFromArray"]
  1397. * @param {String=} [method="overwrite"]
  1398. * @return {Object|undefined} ending td in pasted area (only if any cell was changed)
  1399. */
  1400. this.populateFromArray = function (row, col, input, endRow, endCol, source, method) {
  1401. if (!(typeof input === 'object' && typeof input[0] === 'object')) {
  1402. throw new Error("populateFromArray parameter `input` must be an array of arrays"); //API changed in 0.9-beta2, let's check if you use it correctly
  1403. }
  1404. return grid.populateFromArray({row: row, col: col}, input, typeof endRow === 'number' ? {row: endRow, col: endCol} : null, source, method);
  1405. };
  1406. /**
  1407. * Adds/removes data from the column
  1408. * @param {Number} col Index of column in which do you want to do splice.
  1409. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  1410. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  1411. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  1412. */
  1413. this.spliceCol = function (col, index, amount/*, elements... */) {
  1414. return datamap.spliceCol.apply(null, arguments);
  1415. };
  1416. /**
  1417. * Adds/removes data from the row
  1418. * @param {Number} row Index of column in which do you want to do splice.
  1419. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  1420. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  1421. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  1422. */
  1423. this.spliceRow = function (row, index, amount/*, elements... */) {
  1424. return datamap.spliceRow.apply(null, arguments);
  1425. };
  1426. /**
  1427. * Returns the top left (TL) and bottom right (BR) selection coordinates
  1428. * @param {Object[]} coordsArr
  1429. * @returns {Object}
  1430. */
  1431. this.getCornerCoords = function (coordsArr) {
  1432. return grid.getCornerCoords(coordsArr);
  1433. };
  1434. /**
  1435. * Returns current selection. Returns undefined if there is no selection.
  1436. * @public
  1437. * @return {Array} [`startRow`, `startCol`, `endRow`, `endCol`]
  1438. */
  1439. this.getSelected = function () { //https://github.com/warpech/jquery-handsontable/issues/44 //cjl
  1440. if (selection.isSelected()) {
  1441. return [priv.selStart.row(), priv.selStart.col(), priv.selEnd.row(), priv.selEnd.col()];
  1442. }
  1443. };
  1444. /**
  1445. * Parse settings from DOM and CSS
  1446. * @public
  1447. */
  1448. this.parseSettingsFromDOM = function () {
  1449. var overflow = this.rootElement.css('overflow');
  1450. if (overflow === 'scroll' || overflow === 'auto') {
  1451. this.rootElement[0].style.overflow = 'visible';
  1452. priv.settingsFromDOM.overflow = overflow;
  1453. }
  1454. else if (priv.settings.width === void 0 || priv.settings.height === void 0) {
  1455. priv.settingsFromDOM.overflow = 'auto';
  1456. }
  1457. if (priv.settings.width === void 0) {
  1458. priv.settingsFromDOM.width = this.rootElement.width();
  1459. }
  1460. else {
  1461. priv.settingsFromDOM.width = void 0;
  1462. }
  1463. priv.settingsFromDOM.height = void 0;
  1464. if (priv.settings.height === void 0) {
  1465. if (priv.settingsFromDOM.overflow === 'scroll' || priv.settingsFromDOM.overflow === 'auto') {
  1466. //this needs to read only CSS/inline style and not actual height
  1467. //so we need to call getComputedStyle on cloned container
  1468. var clone = this.rootElement[0].cloneNode(false);
  1469. var parent = this.rootElement[0].parentNode;
  1470. if (parent) {
  1471. clone.removeAttribute('id');
  1472. parent.appendChild(clone);
  1473. var computedHeight = parseInt(window.getComputedStyle(clone, null).getPropertyValue('height'), 10);
  1474. if(isNaN(computedHeight) && clone.currentStyle){
  1475. computedHeight = parseInt(clone.currentStyle.height, 10)
  1476. }
  1477. if (computedHeight > 0) {
  1478. priv.settingsFromDOM.height = computedHeight;
  1479. }
  1480. parent.removeChild(clone);
  1481. }
  1482. }
  1483. }
  1484. };
  1485. /**
  1486. * Render visible data
  1487. * @public
  1488. */
  1489. this.render = function () {
  1490. if (instance.view) {
  1491. instance.forceFullRender = true; //used when data was changed
  1492. instance.parseSettingsFromDOM();
  1493. selection.refreshBorders(null, true);
  1494. }
  1495. };
  1496. /**
  1497. * Load data from array
  1498. * @public
  1499. * @param {Array} data
  1500. */
  1501. this.loadData = function (data) {
  1502. if (typeof data === 'object' && data !== null) {
  1503. if (!(data.push && data.splice)) { //check if data is array. Must use duck-type check so Backbone Collections also pass it
  1504. //when data is not an array, attempt to make a single-row array of it
  1505. data = [data];
  1506. }
  1507. }
  1508. else if(data === null) {
  1509. data = [];
  1510. var row;
  1511. for (var r = 0, rlen = priv.settings.startRows; r < rlen; r++) {
  1512. row = [];
  1513. for (var c = 0, clen = priv.settings.startCols; c < clen; c++) {
  1514. row.push(null);
  1515. }
  1516. data.push(row);
  1517. }
  1518. }
  1519. else {
  1520. throw new Error("loadData only accepts array of objects or array of arrays (" + typeof data + " given)");
  1521. }
  1522. priv.isPopulated = false;
  1523. GridSettings.prototype.data = data;
  1524. if (priv.settings.dataSchema instanceof Array || data[0] instanceof Array) {
  1525. priv.dataType = 'array';
  1526. }
  1527. else if (typeof priv.settings.dataSchema === 'function') {
  1528. priv.dataType = 'function';
  1529. }
  1530. else {
  1531. priv.dataType = 'object';
  1532. }
  1533. if (data[0]) {
  1534. priv.duckDataSchema = datamap.recursiveDuckSchema(data[0]);
  1535. }
  1536. else {
  1537. priv.duckDataSchema = {};
  1538. }
  1539. datamap.createMap();
  1540. grid.adjustRowsAndCols();
  1541. instance.PluginHooks.run('afterLoadData');
  1542. if (priv.firstRun) {
  1543. priv.firstRun = [null, 'loadData'];
  1544. }
  1545. else {
  1546. instance.PluginHooks.run('afterChange', null, 'loadData');
  1547. instance.render();
  1548. }
  1549. priv.isPopulated = true;
  1550. };
  1551. /**
  1552. * Return the current data object (the same that was passed by `data` configuration option or `loadData` method). Optionally you can provide cell range `r`, `c`, `r2`, `c2` to get only a fragment of grid data
  1553. * @public
  1554. * @param {Number} r (Optional) From row
  1555. * @param {Number} c (Optional) From col
  1556. * @param {Number} r2 (Optional) To row
  1557. * @param {Number} c2 (Optional) To col
  1558. * @return {Array|Object}
  1559. */
  1560. this.getData = function (r, c, r2, c2) {
  1561. if (typeof r === 'undefined') {
  1562. return datamap.getAll();
  1563. }
  1564. else {
  1565. return datamap.getRange({row: r, col: c}, {row: r2, col: c2});
  1566. }
  1567. };
  1568. /**
  1569. * Update settings
  1570. * @public
  1571. */
  1572. this.updateSettings = function (settings, init) {
  1573. var i, clen;
  1574. if (typeof settings.rows !== "undefined") {
  1575. throw new Error("'rows' setting is no longer supported. do you mean startRows, minRows or maxRows?");
  1576. }
  1577. if (typeof settings.cols !== "undefined") {
  1578. throw new Error("'cols' setting is no longer supported. do you mean startCols, minCols or maxCols?");
  1579. }
  1580. for (i in settings) {
  1581. if (i === 'data') {
  1582. continue; //loadData will be triggered later
  1583. }
  1584. else {
  1585. if (instance.PluginHooks.hooks[i] !== void 0 || instance.PluginHooks.legacy[i] !== void 0) {
  1586. instance.PluginHooks.add(i, settings[i]);
  1587. }
  1588. else {
  1589. // Update settings
  1590. if (!init && settings.hasOwnProperty(i)) {
  1591. GridSettings.prototype[i] = settings[i];
  1592. }
  1593. //launch extensions
  1594. if (Handsontable.extension[i]) {
  1595. priv.extensions[i] = new Handsontable.extension[i](instance, settings[i]);
  1596. }
  1597. }
  1598. }
  1599. }
  1600. // Load data or create data map
  1601. if (settings.data === void 0 && priv.settings.data === void 0) {
  1602. instance.loadData(null); //data source created just now
  1603. }
  1604. else if (settings.data !== void 0) {
  1605. instance.loadData(settings.data); //data source given as option
  1606. }
  1607. else if (settings.columns !== void 0) {
  1608. datamap.createMap();
  1609. }
  1610. // Init columns constructors configuration
  1611. clen = instance.countCols();
  1612. //Clear cellSettings cache
  1613. priv.cellSettings.length = 0;
  1614. if (clen > 0) {
  1615. var proto, column;
  1616. for (i = 0; i < clen; i++) {
  1617. priv.columnSettings[i] = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts);
  1618. // shortcut for prototype
  1619. proto = priv.columnSettings[i].prototype;
  1620. // Use settings provided by user
  1621. if (GridSettings.prototype.columns) {
  1622. column = GridSettings.prototype.columns[i];
  1623. Handsontable.helper.extend(proto, column);
  1624. Handsontable.helper.extend(proto, expandType(column));
  1625. }
  1626. }
  1627. }
  1628. if (typeof settings.fillHandle !== "undefined") {
  1629. if (autofill.handle && settings.fillHandle === false) {
  1630. autofill.disable();
  1631. }
  1632. else if (!autofill.handle && settings.fillHandle !== false) {
  1633. autofill.init();
  1634. }
  1635. }
  1636. if (typeof settings.className !== "undefined") {
  1637. if (GridSettings.prototype.className) {
  1638. instance.rootElement.removeClass(GridSettings.prototype.className);
  1639. }
  1640. if (settings.className) {
  1641. instance.rootElement.addClass(settings.className);
  1642. }
  1643. }
  1644. if (!init) {
  1645. instance.PluginHooks.run('afterUpdateSettings');
  1646. }
  1647. grid.adjustRowsAndCols();
  1648. if (instance.view && !priv.firstRun) {
  1649. instance.forceFullRender = true; //used when data was changed
  1650. selection.refreshBorders(null, true);
  1651. }
  1652. };
  1653. this.getValue = function () {
  1654. var sel = instance.getSelected();
  1655. if (GridSettings.prototype.getValue) {
  1656. if (typeof GridSettings.prototype.getValue === 'function') {
  1657. return GridSettings.prototype.getValue.call(instance);
  1658. }
  1659. else if (sel) {
  1660. return instance.getData()[sel[0]][GridSettings.prototype.getValue];
  1661. }
  1662. }
  1663. else if (sel) {
  1664. return instance.getDataAtCell(sel[0], sel[1]);
  1665. }
  1666. };
  1667. function expandType(obj) {
  1668. if (!obj.hasOwnProperty('type')) return; //ignore obj.prototype.type
  1669. var type, expandedType = {};
  1670. if (typeof obj.type === 'object') {
  1671. type = obj.type;
  1672. }
  1673. else if (typeof obj.type === 'string') {
  1674. type = Handsontable.cellTypes[obj.type];
  1675. if (type === void 0) {
  1676. throw new Error('You declared cell type "' + obj.type + '" as a string that is not mapped to a known object. Cell type must be an object or a string mapped to an object in Handsontable.cellTypes');
  1677. }
  1678. }
  1679. for (var i in type) {
  1680. if (type.hasOwnProperty(i) && !obj.hasOwnProperty(i)) {
  1681. expandedType[i] = type[i];
  1682. }
  1683. }
  1684. return expandedType;
  1685. }
  1686. /**
  1687. * Returns current settings object
  1688. * @return {Object}
  1689. */
  1690. this.getSettings = function () {
  1691. return priv.settings;
  1692. };
  1693. /**
  1694. * Returns current settingsFromDOM object
  1695. * @return {Object}
  1696. */
  1697. this.getSettingsFromDOM = function () {
  1698. return priv.settingsFromDOM;
  1699. };
  1700. /**
  1701. * Clears grid
  1702. * @public
  1703. */
  1704. this.clear = function () {
  1705. selection.selectAll();
  1706. selection.empty();
  1707. };
  1708. /**
  1709. * Inserts or removes rows and columns
  1710. * @param {String} action See grid.alter for possible values
  1711. * @param {Number} index
  1712. * @param {Number} amount
  1713. * @param {String} [source] Optional. Source of hook runner.
  1714. * @param {Boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows.
  1715. * @public
  1716. */
  1717. this.alter = function (action, index, amount, source, keepEmptyRows) {
  1718. grid.alter(action, index, amount, source, keepEmptyRows);
  1719. };
  1720. /**
  1721. * Returns <td> element corresponding to params row, col
  1722. * @param {Number} row
  1723. * @param {Number} col
  1724. * @public
  1725. * @return {Element}
  1726. */
  1727. this.getCell = function (row, col) {
  1728. return instance.view.getCellAtCoords({row: row, col: col});
  1729. };
  1730. /**
  1731. * Returns property name associated with column number
  1732. * @param {Number} col
  1733. * @public
  1734. * @return {String}
  1735. */
  1736. this.colToProp = function (col) {
  1737. return datamap.colToProp(col);
  1738. };
  1739. /**
  1740. * Returns column number associated with property name
  1741. * @param {String} prop
  1742. * @public
  1743. * @return {Number}
  1744. */
  1745. this.propToCol = function (prop) {
  1746. return datamap.propToCol(prop);
  1747. };
  1748. /**
  1749. * Return value at `row`, `col`
  1750. * @param {Number} row
  1751. * @param {Number} col
  1752. * @public
  1753. * @return value (mixed data type)
  1754. */
  1755. this.getDataAtCell = function (row, col) {
  1756. return datamap.get(row, datamap.colToProp(col));
  1757. };
  1758. /**
  1759. * Return value at `row`, `prop`
  1760. * @param {Number} row
  1761. * @param {String} prop
  1762. * @public
  1763. * @return value (mixed data type)
  1764. */
  1765. this.getDataAtRowProp = function (row, prop) {
  1766. return datamap.get(row, prop);
  1767. };
  1768. /**
  1769. * Return value at `col`
  1770. * @param {Number} col
  1771. * @public
  1772. * @return value (mixed data type)
  1773. */
  1774. this.getDataAtCol = function (col) {
  1775. return [].concat.apply([], datamap.getRange({row: 0, col: col}, {row: priv.settings.data.length - 1, col: col}));
  1776. };
  1777. /**
  1778. * Return value at `prop`
  1779. * @param {String} prop
  1780. * @public
  1781. * @return value (mixed data type)
  1782. */
  1783. this.getDataAtProp = function (prop) {
  1784. return [].concat.apply([], datamap.getRange({row: 0, col: datamap.propToCol(prop)}, {row: priv.settings.data.length - 1, col: datamap.propToCol(prop)}));
  1785. };
  1786. /**
  1787. * Return value at `row`
  1788. * @param {Number} row
  1789. * @public
  1790. * @return value (mixed data type)
  1791. */
  1792. this.getDataAtRow = function (row) {
  1793. return priv.settings.data[row];
  1794. };
  1795. /**
  1796. * Returns cell meta data object corresponding to params row, col
  1797. * @param {Number} row
  1798. * @param {Number} col
  1799. * @public
  1800. * @return {Object}
  1801. */
  1802. this.getCellMeta = function (row, col) {
  1803. var prop = datamap.colToProp(col)
  1804. , cellProperties;
  1805. row = translateRowIndex(row);
  1806. col = translateColIndex(col);
  1807. if ("undefined" === typeof priv.columnSettings[col]) {
  1808. priv.columnSettings[col] = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts);
  1809. }
  1810. if (!priv.cellSettings[row]) {
  1811. priv.cellSettings[row] = [];
  1812. }
  1813. if (!priv.cellSettings[row][col]) {
  1814. priv.cellSettings[row][col] = new priv.columnSettings[col]();
  1815. }
  1816. cellProperties = priv.cellSettings[row][col]; //retrieve cellProperties from cache
  1817. cellProperties.row = row;
  1818. cellProperties.col = col;
  1819. cellProperties.prop = prop;
  1820. cellProperties.instance = instance;
  1821. instance.PluginHooks.run('beforeGetCellMeta', row, col, cellProperties);
  1822. Handsontable.helper.extend(cellProperties, expandType(cellProperties)); //for `type` added in beforeGetCellMeta
  1823. if (cellProperties.cells) {
  1824. var settings = cellProperties.cells.call(cellProperties, row, col, prop);
  1825. if (settings) {
  1826. Handsontable.helper.extend(cellProperties, settings);
  1827. Handsontable.helper.extend(cellProperties, expandType(settings)); //for `type` added in cells
  1828. }
  1829. }
  1830. instance.PluginHooks.run('afterGetCellMeta', row, col, cellProperties);
  1831. return cellProperties;
  1832. /**
  1833. * If displayed rows order is different than the order of rows stored in memory (i.e. sorting is applied)
  1834. * we need to translate logical (stored) row index to physical (displayed) index.
  1835. * @param row - original row index
  1836. * @returns {int} translated row index
  1837. */
  1838. function translateRowIndex(row){
  1839. var getVars = {row: row};
  1840. instance.PluginHooks.execute('beforeGet', getVars);
  1841. return getVars.row;
  1842. }
  1843. /**
  1844. * If displayed columns order is different than the order of columns stored in memory (i.e. column were moved using manualColumnMove plugin)
  1845. * we need to translate logical (stored) column index to physical (displayed) index.
  1846. * @param col - original column index
  1847. * @returns {int} - translated column index
  1848. */
  1849. function translateColIndex(col){
  1850. return Handsontable.PluginHooks.execute(instance, 'modifyCol', col); // warning: this must be done after datamap.colToProp
  1851. }
  1852. };
  1853. this.getCellRenderer = function (row, col) {
  1854. var renderer = Handsontable.helper.cellMethodLookupFactory('renderer').call(this, row, col);
  1855. if(typeof renderer == 'string'){
  1856. renderer = Handsontable.cellLookup.renderer[renderer];
  1857. }
  1858. return renderer
  1859. };
  1860. this.getCellEditor = Handsontable.helper.cellMethodLookupFactory('editor');
  1861. this.getCellValidator = Handsontable.helper.cellMethodLookupFactory('validator');
  1862. /**
  1863. * Validates all cells using their validator functions and calls callback when finished. Does not render the view
  1864. * @param callback
  1865. */
  1866. this.validateCells = function (callback) {
  1867. var waitingForValidator = new ValidatorsQueue();
  1868. waitingForValidator.onQueueEmpty = callback;
  1869. var i = instance.countRows() - 1;
  1870. while (i >= 0) {
  1871. var j = instance.countCols() - 1;
  1872. while (j >= 0) {
  1873. waitingForValidator.addValidatorToQueue();
  1874. instance.validateCell(instance.getDataAtCell(i, j), instance.getCellMeta(i, j), function () {
  1875. waitingForValidator.removeValidatorFormQueue();
  1876. }, 'validateCells');
  1877. j--;
  1878. }
  1879. i--;
  1880. }
  1881. waitingForValidator.checkIfQueueIsEmpty();
  1882. };
  1883. /**
  1884. * Return array of row headers (if they are enabled). If param `row` given, return header at given row as string
  1885. * @param {Number} row (Optional)
  1886. * @return {Array|String}
  1887. */
  1888. this.getRowHeader = function (row) {
  1889. if (row === void 0) {
  1890. var out = [];
  1891. for (var i = 0, ilen = instance.countRows(); i < ilen; i++) {
  1892. out.push(instance.getRowHeader(i));
  1893. }
  1894. return out;
  1895. }
  1896. else if (Object.prototype.toString.call(priv.settings.rowHeaders) === '[object Array]' && priv.settings.rowHeaders[row] !== void 0) {
  1897. return priv.settings.rowHeaders[row];
  1898. }
  1899. else if (typeof priv.settings.rowHeaders === 'function') {
  1900. return priv.settings.rowHeaders(row);
  1901. }
  1902. else if (priv.settings.rowHeaders && typeof priv.settings.rowHeaders !== 'string' && typeof priv.settings.rowHeaders !== 'number') {
  1903. return row + 1;
  1904. }
  1905. else {
  1906. return priv.settings.rowHeaders;
  1907. }
  1908. };
  1909. /**
  1910. * Returns information of this table is configured to display row headers
  1911. * @returns {boolean}
  1912. */
  1913. this.hasRowHeaders = function () {
  1914. return !!priv.settings.rowHeaders;
  1915. };
  1916. /**
  1917. * Returns information of this table is configured to display column headers
  1918. * @returns {boolean}
  1919. */
  1920. this.hasColHeaders = function () {
  1921. if (priv.settings.colHeaders !== void 0) {
  1922. return !!priv.settings.colHeaders;
  1923. }
  1924. for (var i = 0, ilen = instance.countCols(); i < ilen; i++) {
  1925. if (instance.getColHeader(i)) {
  1926. return true;
  1927. }
  1928. }
  1929. return false;
  1930. };
  1931. /**
  1932. * Return array of column headers (if they are enabled). If param `col` given, return header at given column as string
  1933. * @param {Number} col (Optional)
  1934. * @return {Array|String}
  1935. */
  1936. this.getColHeader = function (col) {
  1937. if (col === void 0) {
  1938. var out = [];
  1939. for (var i = 0, ilen = instance.countCols(); i < ilen; i++) {
  1940. out.push(instance.getColHeader(i));
  1941. }
  1942. return out;
  1943. }
  1944. else {
  1945. col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col);
  1946. if (priv.settings.columns && priv.settings.columns[col] && priv.settings.columns[col].title) {
  1947. return priv.settings.columns[col].title;
  1948. }
  1949. else if (Object.prototype.toString.call(priv.settings.colHeaders) === '[object Array]' && priv.settings.colHeaders[col] !== void 0) {
  1950. return priv.settings.colHeaders[col];
  1951. }
  1952. else if (typeof priv.settings.colHeaders === 'function') {
  1953. return priv.settings.colHeaders(col);
  1954. }
  1955. else if (priv.settings.colHeaders && typeof priv.settings.colHeaders !== 'string' && typeof priv.settings.colHeaders !== 'number') {
  1956. return Handsontable.helper.spreadsheetColumnLabel(col);
  1957. }
  1958. else {
  1959. return priv.settings.colHeaders;
  1960. }
  1961. }
  1962. };
  1963. /**
  1964. * Return column width from settings (no guessing). Private use intended
  1965. * @param {Number} col
  1966. * @return {Number}
  1967. */
  1968. this._getColWidthFromSettings = function (col) {
  1969. var cellProperties = instance.getCellMeta(0, col);
  1970. var width = cellProperties.width;
  1971. if (width === void 0 || width === priv.settings.width) {
  1972. width = cellProperties.colWidths;
  1973. }
  1974. if (width !== void 0) {
  1975. switch (typeof width) {
  1976. case 'object': //array
  1977. width = width[col];
  1978. break;
  1979. case 'function':
  1980. width = width(col);
  1981. break;
  1982. }
  1983. if (typeof width === 'string') {
  1984. width = parseInt(width, 10);
  1985. }
  1986. }
  1987. return width;
  1988. };
  1989. /**
  1990. * Return column width
  1991. * @param {Number} col
  1992. * @return {Number}
  1993. */
  1994. this.getColWidth = function (col) {
  1995. col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col);
  1996. var response = {
  1997. width: instance._getColWidthFromSettings(col)
  1998. };
  1999. if (!response.width) {
  2000. response.width = 50;
  2001. }
  2002. instance.PluginHooks.run('afterGetColWidth', col, response);
  2003. return response.width;
  2004. };
  2005. /**
  2006. * Return total number of rows in grid
  2007. * @return {Number}
  2008. */
  2009. this.countRows = function () {
  2010. return priv.settings.data.length;
  2011. };
  2012. /**
  2013. * Return total number of columns in grid
  2014. * @return {Number}
  2015. */
  2016. this.countCols = function () {
  2017. if (priv.dataType === 'object' || priv.dataType === 'function') {
  2018. if (priv.settings.columns && priv.settings.columns.length) {
  2019. return priv.settings.columns.length;
  2020. }
  2021. else {
  2022. return priv.colToProp.length;
  2023. }
  2024. }
  2025. else if (priv.dataType === 'array') {
  2026. if (priv.settings.columns && priv.settings.columns.length) {
  2027. return priv.settings.columns.length;
  2028. }
  2029. else if (priv.settings.data && priv.settings.data[0] && priv.settings.data[0].length) {
  2030. return priv.settings.data[0].length;
  2031. }
  2032. else {
  2033. return 0;
  2034. }
  2035. }
  2036. };
  2037. /**
  2038. * Return index of first visible row
  2039. * @return {Number}
  2040. */
  2041. this.rowOffset = function () {
  2042. return instance.view.wt.getSetting('offsetRow');
  2043. };
  2044. /**
  2045. * Return index of first visible column
  2046. * @return {Number}
  2047. */
  2048. this.colOffset = function () {
  2049. return instance.view.wt.getSetting('offsetColumn');
  2050. };
  2051. /**
  2052. * Return number of visible rows. Returns -1 if table is not visible
  2053. * @return {Number}
  2054. */
  2055. this.countVisibleRows = function () {
  2056. return instance.view.wt.drawn ? instance.view.wt.wtTable.rowStrategy.countVisible() : -1;
  2057. };
  2058. /**
  2059. * Return number of visible columns. Returns -1 if table is not visible
  2060. * @return {Number}
  2061. */
  2062. this.countVisibleCols = function () {
  2063. return instance.view.wt.drawn ? instance.view.wt.wtTable.columnStrategy.countVisible() : -1;
  2064. };
  2065. /**
  2066. * Return number of empty rows
  2067. * @return {Boolean} ending If true, will only count empty rows at the end of the data source
  2068. */
  2069. this.countEmptyRows = function (ending) {
  2070. var i = instance.countRows() - 1
  2071. , empty = 0;
  2072. while (i >= 0) {
  2073. datamap.get(i, 0);
  2074. if (instance.isEmptyRow(datamap.getVars.row)) {
  2075. empty++;
  2076. }
  2077. else if (ending) {
  2078. break;
  2079. }
  2080. i--;
  2081. }
  2082. return empty;
  2083. };
  2084. /**
  2085. * Return number of empty columns
  2086. * @return {Boolean} ending If true, will only count empty columns at the end of the data source row
  2087. */
  2088. this.countEmptyCols = function (ending) {
  2089. if (instance.countRows() < 1) {
  2090. return 0;
  2091. }
  2092. var i = instance.countCols() - 1
  2093. , empty = 0;
  2094. while (i >= 0) {
  2095. if (instance.isEmptyCol(i)) {
  2096. empty++;
  2097. }
  2098. else if (ending) {
  2099. break;
  2100. }
  2101. i--;
  2102. }
  2103. return empty;
  2104. };
  2105. /**
  2106. * Return true if the row at the given index is empty, false otherwise
  2107. * @param {Number} r Row index
  2108. * @return {Boolean}
  2109. */
  2110. this.isEmptyRow = function (r) {
  2111. if (priv.settings.isEmptyRow) {
  2112. return priv.settings.isEmptyRow.call(instance, r);
  2113. }
  2114. var val;
  2115. for (var c = 0, clen = instance.countCols(); c < clen; c++) {
  2116. val = instance.getDataAtCell(r, c);
  2117. if (val !== '' && val !== null && typeof val !== 'undefined') {
  2118. return false;
  2119. }
  2120. }
  2121. return true;
  2122. };
  2123. /**
  2124. * Return true if the column at the given index is empty, false otherwise
  2125. * @param {Number} c Column index
  2126. * @return {Boolean}
  2127. */
  2128. this.isEmptyCol = function (c) {
  2129. if (priv.settings.isEmptyCol) {
  2130. return priv.settings.isEmptyCol.call(instance, c);
  2131. }
  2132. var val;
  2133. for (var r = 0, rlen = instance.countRows(); r < rlen; r++) {
  2134. val = instance.getDataAtCell(r, c);
  2135. if (val !== '' && val !== null && typeof val !== 'undefined') {
  2136. return false;
  2137. }
  2138. }
  2139. return true;
  2140. };
  2141. /**
  2142. * Selects cell on grid. Optionally selects range to another cell
  2143. * @param {Number} row
  2144. * @param {Number} col
  2145. * @param {Number} [endRow]
  2146. * @param {Number} [endCol]
  2147. * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to the selection
  2148. * @public
  2149. * @return {Boolean}
  2150. */
  2151. this.selectCell = function (row, col, endRow, endCol, scrollToCell) {
  2152. if (typeof row !== 'number' || row < 0 || row >= instance.countRows()) {
  2153. return false;
  2154. }
  2155. if (typeof col !== 'number' || col < 0 || col >= instance.countCols()) {
  2156. return false;
  2157. }
  2158. if (typeof endRow !== "undefined") {
  2159. if (typeof endRow !== 'number' || endRow < 0 || endRow >= instance.countRows()) {
  2160. return false;
  2161. }
  2162. if (typeof endCol !== 'number' || endCol < 0 || endCol >= instance.countCols()) {
  2163. return false;
  2164. }
  2165. }
  2166. priv.selStart.coords({row: row, col: col});
  2167. if (document.activeElement && document.activeElement !== document.documentElement && document.activeElement !== document.body) {
  2168. document.activeElement.blur(); //needed or otherwise prepare won't focus the cell. selectionSpec tests this (should move focus to selected cell)
  2169. }
  2170. instance.listen();
  2171. if (typeof endRow === "undefined") {
  2172. selection.setRangeEnd({row: row, col: col}, scrollToCell);
  2173. }
  2174. else {
  2175. selection.setRangeEnd({row: endRow, col: endCol}, scrollToCell);
  2176. }
  2177. instance.selection.finish();
  2178. return true;
  2179. };
  2180. this.selectCellByProp = function (row, prop, endRow, endProp, scrollToCell) {
  2181. arguments[1] = datamap.propToCol(arguments[1]);
  2182. if (typeof arguments[3] !== "undefined") {
  2183. arguments[3] = datamap.propToCol(arguments[3]);
  2184. }
  2185. return instance.selectCell.apply(instance, arguments);
  2186. };
  2187. /**
  2188. * Deselects current sell selection on grid
  2189. * @public
  2190. */
  2191. this.deselectCell = function () {
  2192. selection.deselect();
  2193. };
  2194. /**
  2195. * Remove grid from DOM
  2196. * @public
  2197. */
  2198. this.destroy = function () {
  2199. instance.clearTimeouts();
  2200. if (instance.view) { //in case HT is destroyed before initialization has finished
  2201. instance.view.wt.destroy();
  2202. }
  2203. instance.rootElement.empty();
  2204. instance.rootElement.removeData('handsontable');
  2205. instance.rootElement.off('.handsontable');
  2206. $(window).off('.' + instance.guid);
  2207. $document.off('.' + instance.guid);
  2208. $body.off('.' + instance.guid);
  2209. instance.copyPaste.removeCallback(priv.onCut);
  2210. instance.copyPaste.removeCallback(priv.onPaste);
  2211. instance.PluginHooks.run('afterDestroy');
  2212. };
  2213. /**
  2214. * Returns active editor object
  2215. * @returns {Object}
  2216. */
  2217. this.getActiveEditor = function(){
  2218. return editorManager.getActiveEditor();
  2219. };
  2220. /**
  2221. * Return Handsontable instance
  2222. * @public
  2223. * @return {Object}
  2224. */
  2225. this.getInstance = function () {
  2226. return instance.rootElement.data("handsontable");
  2227. };
  2228. (function () {
  2229. // Create new instance of plugin hooks
  2230. instance.PluginHooks = new Handsontable.PluginHookClass();
  2231. // Upgrade methods to call of global PluginHooks instance
  2232. var _run = instance.PluginHooks.run
  2233. , _exe = instance.PluginHooks.execute;
  2234. instance.PluginHooks.run = function (key, p1, p2, p3, p4, p5) {
  2235. _run.call(this, instance, key, p1, p2, p3, p4, p5);
  2236. Handsontable.PluginHooks.run(instance, key, p1, p2, p3, p4, p5);
  2237. };
  2238. instance.PluginHooks.execute = function (key, p1, p2, p3, p4, p5) {
  2239. var globalHandlerResult = Handsontable.PluginHooks.execute(instance, key, p1, p2, p3, p4, p5);
  2240. var localHandlerResult = _exe.call(this, instance, key, globalHandlerResult, p2, p3, p4, p5);
  2241. return typeof localHandlerResult == 'undefined' ? globalHandlerResult : localHandlerResult;
  2242. };
  2243. // Map old API with new methods
  2244. instance.addHook = function () {
  2245. instance.PluginHooks.add.apply(instance.PluginHooks, arguments);
  2246. };
  2247. instance.addHookOnce = function () {
  2248. instance.PluginHooks.once.apply(instance.PluginHooks, arguments);
  2249. };
  2250. instance.removeHook = function () {
  2251. instance.PluginHooks.remove.apply(instance.PluginHooks, arguments);
  2252. };
  2253. instance.runHooks = function () {
  2254. instance.PluginHooks.run.apply(instance.PluginHooks, arguments);
  2255. };
  2256. instance.runHooksAndReturn = function () {
  2257. return instance.PluginHooks.execute.apply(instance.PluginHooks, arguments);
  2258. };
  2259. })();
  2260. this.timeouts = {};
  2261. /**
  2262. * Sets timeout. Purpose of this method is to clear all known timeouts when `destroy` method is called
  2263. * @public
  2264. */
  2265. this.registerTimeout = function (key, handle, ms) {
  2266. clearTimeout(this.timeouts[key]);
  2267. this.timeouts[key] = setTimeout(handle, ms || 0);
  2268. };
  2269. /**
  2270. * Clears all known timeouts
  2271. * @public
  2272. */
  2273. this.clearTimeouts = function () {
  2274. for (var key in this.timeouts) {
  2275. if (this.timeouts.hasOwnProperty(key)) {
  2276. clearTimeout(this.timeouts[key]);
  2277. }
  2278. }
  2279. };
  2280. /**
  2281. * Handsontable version
  2282. */
  2283. this.version = '0.10.0-beta4'; //inserted by grunt from package.json
  2284. };
  2285. var DefaultSettings = function () {};
  2286. DefaultSettings.prototype = {
  2287. data: void 0,
  2288. width: void 0,
  2289. height: void 0,
  2290. startRows: 5,
  2291. startCols: 5,
  2292. minRows: 0,
  2293. minCols: 0,
  2294. maxRows: Infinity,
  2295. maxCols: Infinity,
  2296. minSpareRows: 0,
  2297. minSpareCols: 0,
  2298. multiSelect: true,
  2299. fillHandle: true,
  2300. fixedRowsTop: 0,
  2301. fixedColumnsLeft: 0,
  2302. outsideClickDeselects: true,
  2303. enterBeginsEditing: true,
  2304. enterMoves: {row: 1, col: 0},
  2305. tabMoves: {row: 0, col: 1},
  2306. autoWrapRow: false,
  2307. autoWrapCol: false,
  2308. copyRowsLimit: 1000,
  2309. copyColsLimit: 1000,
  2310. pasteMode: 'overwrite',
  2311. currentRowClassName: void 0,
  2312. currentColClassName: void 0,
  2313. stretchH: 'hybrid',
  2314. isEmptyRow: void 0,
  2315. isEmptyCol: void 0,
  2316. observeDOMVisibility: true,
  2317. allowInvalid: true,
  2318. invalidCellClassName: 'htInvalid',
  2319. fragmentSelection: false,
  2320. readOnly: false,
  2321. nativeScrollbars: false,
  2322. type: 'text'
  2323. };
  2324. Handsontable.DefaultSettings = DefaultSettings;
  2325. $.fn.handsontable = function (action) {
  2326. var i
  2327. , ilen
  2328. , args
  2329. , output
  2330. , userSettings
  2331. , $this = this.first() // Use only first element from list
  2332. , instance = $this.data('handsontable');
  2333. // Init case
  2334. if (typeof action !== 'string') {
  2335. userSettings = action || {};
  2336. if (instance) {
  2337. instance.updateSettings(userSettings);
  2338. }
  2339. else {
  2340. instance = new Handsontable.Core($this, userSettings);
  2341. $this.data('handsontable', instance);
  2342. instance.init();
  2343. }
  2344. return $this;
  2345. }
  2346. // Action case
  2347. else {
  2348. args = [];
  2349. if (arguments.length > 1) {
  2350. for (i = 1, ilen = arguments.length; i < ilen; i++) {
  2351. args.push(arguments[i]);
  2352. }
  2353. }
  2354. if (instance) {
  2355. if (typeof instance[action] !== 'undefined') {
  2356. output = instance[action].apply(instance, args);
  2357. }
  2358. else {
  2359. throw new Error('Handsontable do not provide action: ' + action);
  2360. }
  2361. }
  2362. return output;
  2363. }
  2364. };
  2365. /**
  2366. * Handsontable TableView constructor
  2367. * @param {Object} instance
  2368. */
  2369. Handsontable.TableView = function (instance) {
  2370. var that = this
  2371. , $window = $(window)
  2372. , $documentElement = $(document.documentElement);
  2373. this.instance = instance;
  2374. this.settings = instance.getSettings();
  2375. this.settingsFromDOM = instance.getSettingsFromDOM();
  2376. instance.rootElement.data('originalStyle', instance.rootElement[0].getAttribute('style')); //needed to retrieve original style in jsFiddle link generator in HT examples. may be removed in future versions
  2377. // in IE7 getAttribute('style') returns an object instead of a string, but we only support IE8+
  2378. instance.rootElement.addClass('handsontable');
  2379. var table = document.createElement('TABLE');
  2380. table.className = 'htCore';
  2381. this.THEAD = document.createElement('THEAD');
  2382. table.appendChild(this.THEAD);
  2383. this.TBODY = document.createElement('TBODY');
  2384. table.appendChild(this.TBODY);
  2385. instance.$table = $(table);
  2386. instance.rootElement.prepend(instance.$table);
  2387. instance.rootElement.on('mousedown.handsontable', function (event) {
  2388. if (!that.isTextSelectionAllowed(event.target)) {
  2389. clearTextSelection();
  2390. event.preventDefault();
  2391. window.focus(); //make sure that window that contains HOT is active. Important when HOT is in iframe.
  2392. }
  2393. });
  2394. $documentElement.on('keyup.' + instance.guid, function (event) {
  2395. if (instance.selection.isInProgress() && !event.shiftKey) {
  2396. instance.selection.finish();
  2397. }
  2398. });
  2399. var isMouseDown;
  2400. $documentElement.on('mouseup.' + instance.guid, function (event) {
  2401. if (instance.selection.isInProgress() && event.which === 1) { //is left mouse button
  2402. instance.selection.finish();
  2403. }
  2404. isMouseDown = false;
  2405. if (instance.autofill.handle && instance.autofill.handle.isDragged) {
  2406. if (instance.autofill.handle.isDragged > 1) {
  2407. instance.autofill.apply();
  2408. }
  2409. instance.autofill.handle.isDragged = 0;
  2410. }
  2411. if (Handsontable.helper.isOutsideInput(document.activeElement)) {
  2412. instance.unlisten();
  2413. }
  2414. });
  2415. $documentElement.on('mousedown.' + instance.guid, function (event) {
  2416. var next = event.target;
  2417. if (next !== that.wt.wtTable.spreader) { //immediate click on "spreader" means click on the right side of vertical scrollbar
  2418. while (next !== document.documentElement) {
  2419. if (next === null) {
  2420. return; //click on something that was a row but now is detached (possibly because your click triggered a rerender)
  2421. }
  2422. if (next === instance.rootElement[0] || next.nodeName === 'HANDSONTABLE-TABLE') {
  2423. return; //click inside container or Web Component (HANDSONTABLE-TABLE is the name of the custom element)
  2424. }
  2425. next = next.parentNode;
  2426. }
  2427. }
  2428. if (that.settings.outsideClickDeselects) {
  2429. instance.deselectCell();
  2430. }
  2431. else {
  2432. instance.destroyEditor();
  2433. }
  2434. });
  2435. instance.rootElement.on('mousedown.handsontable', '.dragdealer', function () {
  2436. instance.destroyEditor();
  2437. });
  2438. instance.$table.on('selectstart', function (event) {
  2439. if (that.settings.fragmentSelection) {
  2440. return;
  2441. }
  2442. //https://github.com/warpech/jquery-handsontable/issues/160
  2443. //selectstart is IE only event. Prevent text from being selected when performing drag down in IE8
  2444. event.preventDefault();
  2445. });
  2446. var clearTextSelection = function () {
  2447. //http://stackoverflow.com/questions/3169786/clear-text-selection-with-javascript
  2448. if (window.getSelection) {
  2449. if (window.getSelection().empty) { // Chrome
  2450. window.getSelection().empty();
  2451. } else if (window.getSelection().removeAllRanges) { // Firefox
  2452. window.getSelection().removeAllRanges();
  2453. }
  2454. } else if (document.selection) { // IE?
  2455. document.selection.empty();
  2456. }
  2457. };
  2458. var walkontableConfig = {
  2459. table: table,
  2460. stretchH: this.settings.stretchH,
  2461. data: instance.getDataAtCell,
  2462. totalRows: instance.countRows,
  2463. totalColumns: instance.countCols,
  2464. nativeScrollbars: this.settings.nativeScrollbars,
  2465. offsetRow: 0,
  2466. offsetColumn: 0,
  2467. width: this.getWidth(),
  2468. height: this.getHeight(),
  2469. fixedColumnsLeft: function () {
  2470. return that.settings.fixedColumnsLeft;
  2471. },
  2472. fixedRowsTop: function () {
  2473. return that.settings.fixedRowsTop;
  2474. },
  2475. rowHeaders: function () {
  2476. return instance.hasRowHeaders() ? [function (index, TH) {
  2477. that.appendRowHeader(index, TH);
  2478. }] : []
  2479. },
  2480. columnHeaders: function () {
  2481. return instance.hasColHeaders() ? [function (index, TH) {
  2482. that.appendColHeader(index, TH);
  2483. }] : []
  2484. },
  2485. columnWidth: instance.getColWidth,
  2486. cellRenderer: function (row, col, TD) {
  2487. var prop = that.instance.colToProp(col)
  2488. , cellProperties = that.instance.getCellMeta(row, col)
  2489. , renderer = that.instance.getCellRenderer(cellProperties)
  2490. var value = that.instance.getDataAtRowProp(row, prop);
  2491. renderer(that.instance, TD, row, col, prop, value, cellProperties);
  2492. that.instance.PluginHooks.run('afterRenderer', TD, row, col, prop, value, cellProperties);
  2493. },
  2494. selections: {
  2495. current: {
  2496. className: 'current',
  2497. border: {
  2498. width: 2,
  2499. color: '#5292F7',
  2500. style: 'solid',
  2501. cornerVisible: function () {
  2502. return that.settings.fillHandle && !that.isCellEdited() && !instance.selection.isMultiple()
  2503. }
  2504. }
  2505. },
  2506. area: {
  2507. className: 'area',
  2508. border: {
  2509. width: 1,
  2510. color: '#89AFF9',
  2511. style: 'solid',
  2512. cornerVisible: function () {
  2513. return that.settings.fillHandle && !that.isCellEdited() && instance.selection.isMultiple()
  2514. }
  2515. }
  2516. },
  2517. highlight: {
  2518. highlightRowClassName: that.settings.currentRowClassName,
  2519. highlightColumnClassName: that.settings.currentColClassName
  2520. },
  2521. fill: {
  2522. className: 'fill',
  2523. border: {
  2524. width: 1,
  2525. color: 'red',
  2526. style: 'solid'
  2527. }
  2528. }
  2529. },
  2530. hideBorderOnMouseDownOver: function () {
  2531. return that.settings.fragmentSelection;
  2532. },
  2533. onCellMouseDown: function (event, coords, TD) {
  2534. instance.listen();
  2535. isMouseDown = true;
  2536. var coordsObj = {row: coords[0], col: coords[1]};
  2537. if (event.button === 2 && instance.selection.inInSelection(coordsObj)) { //right mouse button
  2538. //do nothing
  2539. }
  2540. else if (event.shiftKey) {
  2541. instance.selection.setRangeEnd(coordsObj);
  2542. }
  2543. else {
  2544. instance.selection.setRangeStart(coordsObj);
  2545. }
  2546. instance.PluginHooks.run('afterOnCellMouseDown', event, coords, TD);
  2547. },
  2548. /*onCellMouseOut: function (/*event, coords, TD* /) {
  2549. if (isMouseDown && that.settings.fragmentSelection === 'single') {
  2550. clearTextSelection(); //otherwise text selection blinks during multiple cells selection
  2551. }
  2552. },*/
  2553. onCellMouseOver: function (event, coords/*, TD*/) {
  2554. var coordsObj = {row: coords[0], col: coords[1]};
  2555. if (isMouseDown) {
  2556. /*if (that.settings.fragmentSelection === 'single') {
  2557. clearTextSelection(); //otherwise text selection blinks during multiple cells selection
  2558. }*/
  2559. instance.selection.setRangeEnd(coordsObj);
  2560. }
  2561. else if (instance.autofill.handle && instance.autofill.handle.isDragged) {
  2562. instance.autofill.handle.isDragged++;
  2563. instance.autofill.showBorder(coords);
  2564. }
  2565. },
  2566. onCellCornerMouseDown: function (event) {
  2567. instance.autofill.handle.isDragged = 1;
  2568. event.preventDefault();
  2569. instance.PluginHooks.run('afterOnCellCornerMouseDown', event);
  2570. },
  2571. onCellCornerDblClick: function () {
  2572. instance.autofill.selectAdjacent();
  2573. },
  2574. beforeDraw: function (force) {
  2575. that.beforeRender(force);
  2576. },
  2577. onDraw: function(force){
  2578. that.onDraw(force);
  2579. }
  2580. };
  2581. instance.PluginHooks.run('beforeInitWalkontable', walkontableConfig);
  2582. this.wt = new Walkontable(walkontableConfig);
  2583. $window.on('resize.' + instance.guid, function () {
  2584. instance.registerTimeout('resizeTimeout', function () {
  2585. instance.parseSettingsFromDOM();
  2586. var newWidth = that.getWidth();
  2587. var newHeight = that.getHeight();
  2588. if (walkontableConfig.width !== newWidth || walkontableConfig.height !== newHeight) {
  2589. instance.forceFullRender = true;
  2590. that.render();
  2591. walkontableConfig.width = newWidth;
  2592. walkontableConfig.height = newHeight;
  2593. }
  2594. }, 60);
  2595. });
  2596. $(that.wt.wtTable.spreader).on('mousedown.handsontable, contextmenu.handsontable', function (event) {
  2597. if (event.target === that.wt.wtTable.spreader && event.which === 3) { //right mouse button exactly on spreader means right clickon the right hand side of vertical scrollbar
  2598. event.stopPropagation();
  2599. }
  2600. });
  2601. $documentElement.on('click.' + instance.guid, function () {
  2602. if (that.settings.observeDOMVisibility) {
  2603. if (that.wt.drawInterrupted) {
  2604. that.instance.forceFullRender = true;
  2605. that.render();
  2606. }
  2607. }
  2608. });
  2609. };
  2610. Handsontable.TableView.prototype.isTextSelectionAllowed = function (el) {
  2611. if (el.nodeName === 'TEXTAREA') {
  2612. return (true);
  2613. }
  2614. if (this.settings.fragmentSelection && this.wt.wtDom.isChildOf(el, this.TBODY)) {
  2615. return (true);
  2616. }
  2617. return false;
  2618. };
  2619. Handsontable.TableView.prototype.isCellEdited = function () {
  2620. var activeEditor = this.instance.getActiveEditor();
  2621. return activeEditor && activeEditor.isOpened();
  2622. };
  2623. Handsontable.TableView.prototype.getWidth = function () {
  2624. var val = this.settings.width !== void 0 ? this.settings.width : this.settingsFromDOM.width;
  2625. return typeof val === 'function' ? val() : val;
  2626. };
  2627. Handsontable.TableView.prototype.getHeight = function () {
  2628. var val = this.settings.height !== void 0 ? this.settings.height : this.settingsFromDOM.height;
  2629. return typeof val === 'function' ? val() : val;
  2630. };
  2631. Handsontable.TableView.prototype.beforeRender = function (force) {
  2632. if (force) { //force = did Walkontable decide to do full render
  2633. this.instance.PluginHooks.run('beforeRender', this.instance.forceFullRender); //this.instance.forceFullRender = did Handsontable request full render?
  2634. this.wt.update('width', this.getWidth());
  2635. this.wt.update('height', this.getHeight());
  2636. }
  2637. };
  2638. Handsontable.TableView.prototype.onDraw = function(force){
  2639. if (force) { //force = did Walkontable decide to do full render
  2640. this.instance.PluginHooks.run('afterRender', this.instance.forceFullRender); //this.instance.forceFullRender = did Handsontable request full render?
  2641. }
  2642. };
  2643. Handsontable.TableView.prototype.render = function () {
  2644. this.wt.draw(!this.instance.forceFullRender);
  2645. this.instance.forceFullRender = false;
  2646. this.instance.rootElement.triggerHandler('render.handsontable');
  2647. };
  2648. /**
  2649. * Returns td object given coordinates
  2650. */
  2651. Handsontable.TableView.prototype.getCellAtCoords = function (coords) {
  2652. var td = this.wt.wtTable.getCell([coords.row, coords.col]);
  2653. if (td < 0) { //there was an exit code (cell is out of bounds)
  2654. return null;
  2655. }
  2656. else {
  2657. return td;
  2658. }
  2659. };
  2660. /**
  2661. * Scroll viewport to selection
  2662. * @param coords
  2663. */
  2664. Handsontable.TableView.prototype.scrollViewport = function (coords) {
  2665. this.wt.scrollViewport([coords.row, coords.col]);
  2666. };
  2667. /**
  2668. * Append row header to a TH element
  2669. * @param row
  2670. * @param TH
  2671. */
  2672. Handsontable.TableView.prototype.appendRowHeader = function (row, TH) {
  2673. if (row > -1) {
  2674. this.wt.wtDom.fastInnerHTML(TH, this.instance.getRowHeader(row));
  2675. }
  2676. else {
  2677. var DIV = document.createElement('DIV');
  2678. DIV.className = 'relative';
  2679. this.wt.wtDom.fastInnerText(DIV, '\u00A0');
  2680. this.wt.wtDom.empty(TH);
  2681. TH.appendChild(DIV);
  2682. }
  2683. };
  2684. /**
  2685. * Append column header to a TH element
  2686. * @param col
  2687. * @param TH
  2688. */
  2689. Handsontable.TableView.prototype.appendColHeader = function (col, TH) {
  2690. var DIV = document.createElement('DIV')
  2691. , SPAN = document.createElement('SPAN');
  2692. DIV.className = 'relative';
  2693. SPAN.className = 'colHeader';
  2694. this.wt.wtDom.fastInnerHTML(SPAN, this.instance.getColHeader(col));
  2695. DIV.appendChild(SPAN);
  2696. this.wt.wtDom.empty(TH);
  2697. TH.appendChild(DIV);
  2698. this.instance.PluginHooks.run('afterGetColHeader', col, TH);
  2699. };
  2700. /**
  2701. * Given a element's left position relative to the viewport, returns maximum element width until the right edge of the viewport (before scrollbar)
  2702. * @param {Number} left
  2703. * @return {Number}
  2704. */
  2705. Handsontable.TableView.prototype.maximumVisibleElementWidth = function (left) {
  2706. var rootWidth = this.wt.wtViewport.getWorkspaceWidth();
  2707. if(this.settings.nativeScrollbars) {
  2708. return rootWidth;
  2709. }
  2710. return rootWidth - left;
  2711. };
  2712. /**
  2713. * Given a element's top position relative to the viewport, returns maximum element height until the bottom edge of the viewport (before scrollbar)
  2714. * @param {Number} top
  2715. * @return {Number}
  2716. */
  2717. Handsontable.TableView.prototype.maximumVisibleElementHeight = function (top) {
  2718. var rootHeight = this.wt.wtViewport.getWorkspaceHeight();
  2719. if(this.settings.nativeScrollbars) {
  2720. return rootHeight;
  2721. }
  2722. return rootHeight - top;
  2723. };
  2724. /**
  2725. * Utility to register editors and common namespace for keeping reference to all editor classes
  2726. */
  2727. (function (Handsontable) {
  2728. 'use strict';
  2729. function RegisteredEditor(editorClass) {
  2730. var clazz, instances;
  2731. instances = {};
  2732. clazz = editorClass;
  2733. this.getInstance = function (hotInstance) {
  2734. if (!(hotInstance.guid in instances)) {
  2735. instances[hotInstance.guid] = new clazz(hotInstance);
  2736. }
  2737. return instances[hotInstance.guid];
  2738. }
  2739. }
  2740. var registeredEditors = {};
  2741. Handsontable.editors = {
  2742. /**
  2743. * Registers editor under given name
  2744. * @param {String} editorName
  2745. * @param {Function} editorClass
  2746. */
  2747. registerEditor: function (editorName, editorClass) {
  2748. registeredEditors[editorName] = new RegisteredEditor(editorClass);
  2749. },
  2750. /**
  2751. * Returns instance (singleton) of editor class
  2752. * @param {String|Function} editorName/editorClass
  2753. * @returns {Function} editorClass
  2754. */
  2755. getEditor: function (editorName, hotInstance) {
  2756. if (typeof editorName == 'function'){
  2757. var editorClass = editorName;
  2758. editorName = editorClass.toString();
  2759. this.registerEditor(editorName, editorClass);
  2760. }
  2761. if (typeof editorName != 'string'){
  2762. throw Error('Only strings and functions can be passed as "editor" parameter ');
  2763. }
  2764. if (!(editorName in registeredEditors)) {
  2765. throw Error('No editor registered under name "' + editorName + '"');
  2766. }
  2767. return registeredEditors[editorName].getInstance(hotInstance);
  2768. }
  2769. };
  2770. })(Handsontable);
  2771. (function(Handsontable){
  2772. 'use strict';
  2773. Handsontable.EditorManager = function(instance, priv, selection, datamap){
  2774. var that = this;
  2775. var $document = $(document);
  2776. var keyCodes = Handsontable.helper.keyCode;
  2777. var activeEditor;
  2778. var init = function () {
  2779. priv.onCut = function onCut() {
  2780. if (!instance.isListening()) {
  2781. return;
  2782. }
  2783. selection.empty();
  2784. };
  2785. priv.onPaste = function onPaste(str) {
  2786. if (!instance.isListening() || !selection.isSelected()) {
  2787. return;
  2788. }
  2789. var input = str.replace(/^[\r\n]*/g, '').replace(/[\r\n]*$/g, '') //remove newline from the start and the end of the input
  2790. , inputArray = SheetClip.parse(input)
  2791. , coords = instance.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()])
  2792. , areaStart = coords.TL
  2793. , areaEnd = {
  2794. row: Math.max(coords.BR.row, inputArray.length - 1 + coords.TL.row),
  2795. col: Math.max(coords.BR.col, inputArray[0].length - 1 + coords.TL.col)
  2796. };
  2797. instance.PluginHooks.once('afterChange', function (changes, source) {
  2798. if (changes && changes.length) {
  2799. instance.selectCell(areaStart.row, areaStart.col, areaEnd.row, areaEnd.col);
  2800. }
  2801. });
  2802. instance.populateFromArray(areaStart.row, areaStart.col, inputArray, areaEnd.row, areaEnd.col, 'paste', priv.settings.pasteMode);
  2803. };
  2804. function onKeyDown(event) {
  2805. if (!instance.isListening()) {
  2806. return;
  2807. }
  2808. if (priv.settings.beforeOnKeyDown) { // HOT in HOT Plugin
  2809. priv.settings.beforeOnKeyDown.call(instance, event);
  2810. }
  2811. if (Handsontable.helper.isCtrlKey(event.keyCode)) {
  2812. //when CTRL is pressed, prepare selectable text in textarea
  2813. //http://stackoverflow.com/questions/3902635/how-does-one-capture-a-macs-command-key-via-javascript
  2814. that.setCopyableText();
  2815. return;
  2816. }
  2817. instance.PluginHooks.run('beforeKeyDown', event);
  2818. if (!event.isImmediatePropagationStopped()) {
  2819. priv.lastKeyCode = event.keyCode;
  2820. if (selection.isSelected()) {
  2821. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
  2822. if (Handsontable.helper.isPrintableChar(event.keyCode) && ctrlDown) {
  2823. if (event.keyCode === 65) { //CTRL + A
  2824. selection.selectAll(); //select all cells
  2825. that.setCopyableText();
  2826. event.preventDefault();
  2827. event.stopImmediatePropagation();
  2828. }
  2829. }
  2830. if (!activeEditor.isWaiting()) {
  2831. if (!Handsontable.helper.isMetaKey(event.keyCode) && !ctrlDown) {
  2832. that.openEditor('');
  2833. event.stopPropagation(); //required by HandsontableEditor
  2834. return;
  2835. }
  2836. }
  2837. var rangeModifier = event.shiftKey ? selection.setRangeEnd : selection.setRangeStart;
  2838. switch (event.keyCode) {
  2839. case keyCodes.ARROW_UP:
  2840. if (that.isEditorOpened() && !activeEditor.isWaiting()){
  2841. that.closeEditorAndSaveChanges(ctrlDown);
  2842. }
  2843. moveSelectionUp(event.shiftKey);
  2844. event.preventDefault();
  2845. event.stopPropagation(); //required by HandsontableEditor
  2846. break;
  2847. case keyCodes.ARROW_DOWN:
  2848. if (that.isEditorOpened() && !activeEditor.isWaiting()){
  2849. that.closeEditorAndSaveChanges(ctrlDown);
  2850. }
  2851. moveSelectionDown(event.shiftKey);
  2852. event.preventDefault();
  2853. event.stopPropagation(); //required by HandsontableEditor
  2854. break;
  2855. case keyCodes.ARROW_RIGHT:
  2856. if(that.isEditorOpened() && !activeEditor.isWaiting()){
  2857. that.closeEditorAndSaveChanges(ctrlDown);
  2858. }
  2859. moveSelectionRight(event.shiftKey);
  2860. event.preventDefault();
  2861. event.stopPropagation(); //required by HandsontableEditor
  2862. break;
  2863. case keyCodes.ARROW_LEFT:
  2864. if(that.isEditorOpened() && !activeEditor.isWaiting()){
  2865. that.closeEditorAndSaveChanges(ctrlDown);
  2866. }
  2867. moveSelectionLeft(event.shiftKey);
  2868. event.preventDefault();
  2869. event.stopPropagation(); //required by HandsontableEditor
  2870. break;
  2871. case keyCodes.TAB:
  2872. var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves;
  2873. if (event.shiftKey) {
  2874. selection.transformStart(-tabMoves.row, -tabMoves.col); //move selection left
  2875. }
  2876. else {
  2877. selection.transformStart(tabMoves.row, tabMoves.col, true); //move selection right (add a new column if needed)
  2878. }
  2879. event.preventDefault();
  2880. event.stopPropagation(); //required by HandsontableEditor
  2881. break;
  2882. case keyCodes.BACKSPACE:
  2883. case keyCodes.DELETE:
  2884. selection.empty(event);
  2885. that.prepareEditor();
  2886. event.preventDefault();
  2887. break;
  2888. case keyCodes.F2: /* F2 */
  2889. that.openEditor();
  2890. event.preventDefault(); //prevent Opera from opening Go to Page dialog
  2891. break;
  2892. case keyCodes.ENTER: /* return/enter */
  2893. if(that.isEditorOpened()){
  2894. if (activeEditor.state !== Handsontable.EditorState.WAITING){
  2895. that.closeEditorAndSaveChanges(ctrlDown);
  2896. }
  2897. moveSelectionAfterEnter(event.shiftKey);
  2898. } else {
  2899. if (instance.getSettings().enterBeginsEditing){
  2900. that.openEditor();
  2901. } else {
  2902. moveSelectionAfterEnter(event.shiftKey);
  2903. }
  2904. }
  2905. event.preventDefault(); //don't add newline to field
  2906. event.stopImmediatePropagation(); //required by HandsontableEditor
  2907. break;
  2908. case keyCodes.ESCAPE:
  2909. if(that.isEditorOpened()){
  2910. that.closeEditorAndRestoreOriginalValue(ctrlDown);
  2911. }
  2912. event.preventDefault();
  2913. break;
  2914. case keyCodes.HOME:
  2915. if (event.ctrlKey || event.metaKey) {
  2916. rangeModifier({row: 0, col: priv.selStart.col()});
  2917. }
  2918. else {
  2919. rangeModifier({row: priv.selStart.row(), col: 0});
  2920. }
  2921. event.preventDefault(); //don't scroll the window
  2922. event.stopPropagation(); //required by HandsontableEditor
  2923. break;
  2924. case keyCodes.END:
  2925. if (event.ctrlKey || event.metaKey) {
  2926. rangeModifier({row: instance.countRows() - 1, col: priv.selStart.col()});
  2927. }
  2928. else {
  2929. rangeModifier({row: priv.selStart.row(), col: instance.countCols() - 1});
  2930. }
  2931. event.preventDefault(); //don't scroll the window
  2932. event.stopPropagation(); //required by HandsontableEditor
  2933. break;
  2934. case keyCodes.PAGE_UP:
  2935. selection.transformStart(-instance.countVisibleRows(), 0);
  2936. instance.view.wt.scrollVertical(-instance.countVisibleRows());
  2937. instance.view.render();
  2938. event.preventDefault(); //don't page up the window
  2939. event.stopPropagation(); //required by HandsontableEditor
  2940. break;
  2941. case keyCodes.PAGE_DOWN:
  2942. selection.transformStart(instance.countVisibleRows(), 0);
  2943. instance.view.wt.scrollVertical(instance.countVisibleRows());
  2944. instance.view.render();
  2945. event.preventDefault(); //don't page down the window
  2946. event.stopPropagation(); //required by HandsontableEditor
  2947. break;
  2948. default:
  2949. break;
  2950. }
  2951. }
  2952. }
  2953. }
  2954. $document.on('keydown.handsontable.' + instance.guid, onKeyDown);
  2955. function onDblClick() {
  2956. // that.instance.destroyEditor();
  2957. that.openEditor();
  2958. }
  2959. instance.view.wt.update('onCellDblClick', onDblClick);
  2960. instance.copyPaste = CopyPaste.getInstance();
  2961. instance.copyPaste.onCut(priv.onCut);
  2962. instance.copyPaste.onPaste(priv.onPaste);
  2963. instance.addHook('afterDestroy', function(){
  2964. $document.off('keydown.handsontable.' + instance.guid);
  2965. });
  2966. function moveSelectionAfterEnter(shiftKey){
  2967. var enterMoves = typeof priv.settings.enterMoves === 'function' ? priv.settings.enterMoves(event) : priv.settings.enterMoves;
  2968. if (shiftKey) {
  2969. selection.transformStart(-enterMoves.row, -enterMoves.col); //move selection up
  2970. }
  2971. else {
  2972. selection.transformStart(enterMoves.row, enterMoves.col, true); //move selection down (add a new row if needed)
  2973. }
  2974. }
  2975. function moveSelectionUp(shiftKey){
  2976. if (shiftKey) {
  2977. selection.transformEnd(-1, 0);
  2978. }
  2979. else {
  2980. selection.transformStart(-1, 0);
  2981. }
  2982. }
  2983. function moveSelectionDown(shiftKey){
  2984. if (shiftKey) {
  2985. selection.transformEnd(1, 0); //expanding selection down with shift
  2986. }
  2987. else {
  2988. selection.transformStart(1, 0); //move selection down
  2989. }
  2990. }
  2991. function moveSelectionRight(shiftKey){
  2992. if (shiftKey) {
  2993. selection.transformEnd(0, 1);
  2994. }
  2995. else {
  2996. selection.transformStart(0, 1);
  2997. }
  2998. }
  2999. function moveSelectionLeft(shiftKey){
  3000. if (shiftKey) {
  3001. selection.transformEnd(0, -1);
  3002. }
  3003. else {
  3004. selection.transformStart(0, -1);
  3005. }
  3006. }
  3007. };
  3008. /**
  3009. * Destroy current editor, if exists
  3010. * @param {Boolean} revertOriginal
  3011. */
  3012. this.destroyEditor = function (revertOriginal) {
  3013. this.closeEditor(revertOriginal);
  3014. };
  3015. this.getActiveEditor = function () {
  3016. return activeEditor;
  3017. };
  3018. /**
  3019. * Prepares copyable text in the invisible textarea
  3020. */
  3021. this.setCopyableText = function () {
  3022. var selection = instance.getSelected();
  3023. var settings = instance.getSettings();
  3024. var copyRowsLimit = settings.copyRowsLimit;
  3025. var copyColsLimit = settings.copyColsLimit;
  3026. var startRow = Math.min(selection[0], selection[2]);
  3027. var startCol = Math.min(selection[1], selection[3]);
  3028. var endRow = Math.max(selection[0], selection[2]);
  3029. var endCol = Math.max(selection[1], selection[3]);
  3030. var finalEndRow = Math.min(endRow, startRow + copyRowsLimit - 1);
  3031. var finalEndCol = Math.min(endCol, startCol + copyColsLimit - 1);
  3032. instance.copyPaste.copyable(datamap.getText({row: startRow, col: startCol}, {row: finalEndRow, col: finalEndCol}));
  3033. if (endRow !== finalEndRow || endCol !== finalEndCol) {
  3034. instance.PluginHooks.run("afterCopyLimit", endRow - startRow + 1, endCol - startCol + 1, copyRowsLimit, copyColsLimit);
  3035. }
  3036. };
  3037. var pendingPrepare = false;
  3038. /**
  3039. * Prepare text input to be displayed at given grid cell
  3040. */
  3041. this.prepareEditor = function () {
  3042. if (activeEditor && activeEditor.state === Handsontable.EditorState.WAITING){
  3043. if(!pendingPrepare){
  3044. pendingPrepare = true;
  3045. this.closeEditor(false, false, function(){
  3046. pendingPrepare = false;
  3047. that.prepareEditor();
  3048. });
  3049. }
  3050. return;
  3051. }
  3052. var row = priv.selStart.row();
  3053. var col = priv.selStart.col();
  3054. var prop = instance.colToProp(col);
  3055. var td = instance.getCell(row, col);
  3056. var originalValue = instance.getDataAtCell(row, col);
  3057. var cellProperties = instance.getCellMeta(row, col);
  3058. var editorClass = instance.getCellEditor(cellProperties);
  3059. activeEditor = Handsontable.editors.getEditor(editorClass, instance);
  3060. activeEditor.prepare(row, col, prop, td, originalValue, cellProperties);
  3061. };
  3062. this.isEditorOpened = function () {
  3063. return activeEditor.isOpened();
  3064. };
  3065. this.openEditor = function (initialValue) {
  3066. activeEditor.beginEditing(initialValue);
  3067. };
  3068. this.closeEditor = function (restoreOriginalValue, ctrlDown, callback) {
  3069. if (!activeEditor){
  3070. if(callback) {
  3071. callback(false);
  3072. }
  3073. }
  3074. else {
  3075. activeEditor.finishEditing(restoreOriginalValue, ctrlDown, callback);
  3076. }
  3077. };
  3078. this.closeEditorAndSaveChanges = function(ctrlDown){
  3079. return this.closeEditor(false, ctrlDown);
  3080. };
  3081. this.closeEditorAndRestoreOriginalValue = function(ctrlDown){
  3082. return this.closeEditor(true, ctrlDown);
  3083. };
  3084. init();
  3085. };
  3086. })(Handsontable);
  3087. /**
  3088. * DOM helper optimized for maximum performance
  3089. * It is recommended for Handsontable plugins and renderers, because it is much faster than jQuery
  3090. * @type {WalkonableDom}
  3091. */
  3092. Handsontable.Dom = new WalkontableDom();
  3093. /**
  3094. * Returns true if keyCode represents a printable character
  3095. * @param {Number} keyCode
  3096. * @return {Boolean}
  3097. */
  3098. Handsontable.helper.isPrintableChar = function (keyCode) {
  3099. return ((keyCode == 32) || //space
  3100. (keyCode >= 48 && keyCode <= 57) || //0-9
  3101. (keyCode >= 96 && keyCode <= 111) || //numpad
  3102. (keyCode >= 186 && keyCode <= 192) || //;=,-./`
  3103. (keyCode >= 219 && keyCode <= 222) || //[]{}\|"'
  3104. keyCode >= 226 || //special chars (229 for Asian chars)
  3105. (keyCode >= 65 && keyCode <= 90)); //a-z
  3106. };
  3107. Handsontable.helper.isMetaKey = function (keyCode) {
  3108. var keyCodes = Handsontable.helper.keyCode;
  3109. var metaKeys = [
  3110. keyCodes.ARROW_DOWN,
  3111. keyCodes.ARROW_UP,
  3112. keyCodes.ARROW_LEFT,
  3113. keyCodes.ARROW_RIGHT,
  3114. keyCodes.HOME,
  3115. keyCodes.END,
  3116. keyCodes.DELETE,
  3117. keyCodes.BACKSPACE,
  3118. keyCodes.F1,
  3119. keyCodes.F2,
  3120. keyCodes.F3,
  3121. keyCodes.F4,
  3122. keyCodes.F5,
  3123. keyCodes.F6,
  3124. keyCodes.F7,
  3125. keyCodes.F8,
  3126. keyCodes.F9,
  3127. keyCodes.F10,
  3128. keyCodes.F11,
  3129. keyCodes.F12,
  3130. keyCodes.TAB,
  3131. keyCodes.PAGE_DOWN,
  3132. keyCodes.PAGE_UP,
  3133. keyCodes.ENTER,
  3134. keyCodes.ESCAPE,
  3135. keyCodes.SHIFT
  3136. ];
  3137. return metaKeys.indexOf(keyCode) != -1;
  3138. };
  3139. Handsontable.helper.isCtrlKey = function (keyCode) {
  3140. return [17, 224, 91, 93].indexOf(keyCode) != -1;
  3141. };
  3142. /**
  3143. * Converts a value to string
  3144. * @param value
  3145. * @return {String}
  3146. */
  3147. Handsontable.helper.stringify = function (value) {
  3148. switch (typeof value) {
  3149. case 'string':
  3150. case 'number':
  3151. return value + '';
  3152. break;
  3153. case 'object':
  3154. if (value === null) {
  3155. return '';
  3156. }
  3157. else {
  3158. return value.toString();
  3159. }
  3160. break;
  3161. case 'undefined':
  3162. return '';
  3163. break;
  3164. default:
  3165. return value.toString();
  3166. }
  3167. };
  3168. /**
  3169. * Generates spreadsheet-like column names: A, B, C, ..., Z, AA, AB, etc
  3170. * @param index
  3171. * @returns {String}
  3172. */
  3173. Handsontable.helper.spreadsheetColumnLabel = function (index) {
  3174. var dividend = index + 1;
  3175. var columnLabel = '';
  3176. var modulo;
  3177. while (dividend > 0) {
  3178. modulo = (dividend - 1) % 26;
  3179. columnLabel = String.fromCharCode(65 + modulo) + columnLabel;
  3180. dividend = parseInt((dividend - modulo) / 26, 10);
  3181. }
  3182. return columnLabel;
  3183. };
  3184. /**
  3185. * Checks if value of n is a numeric one
  3186. * http://jsperf.com/isnan-vs-isnumeric/4
  3187. * @param n
  3188. * @returns {boolean}
  3189. */
  3190. Handsontable.helper.isNumeric = function (n) {
  3191. var t = typeof n;
  3192. return t == 'number' ? !isNaN(n) && isFinite(n) :
  3193. t == 'string' ? !n.length ? false :
  3194. n.length == 1 ? /\d/.test(n) :
  3195. /^\s*[+-]?\s*(?:(?:\d+(?:\.\d+)?(?:e[+-]?\d+)?)|(?:0x[a-f\d]+))\s*$/i.test(n) :
  3196. t == 'object' ? !!n && typeof n.valueOf() == "number" && !(n instanceof Date) : false;
  3197. };
  3198. Handsontable.helper.isArray = function (obj) {
  3199. return Object.prototype.toString.call(obj).match(/array/i) !== null;
  3200. };
  3201. /**
  3202. * Checks if child is a descendant of given parent node
  3203. * http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another
  3204. * @param parent
  3205. * @param child
  3206. * @returns {boolean}
  3207. */
  3208. Handsontable.helper.isDescendant = function (parent, child) {
  3209. var node = child.parentNode;
  3210. while (node != null) {
  3211. if (node == parent) {
  3212. return true;
  3213. }
  3214. node = node.parentNode;
  3215. }
  3216. return false;
  3217. };
  3218. /**
  3219. * Generates a random hex string. Used as namespace for Handsontable instance events.
  3220. * @return {String} - 16 character random string: "92b1bfc74ec4"
  3221. */
  3222. Handsontable.helper.randomString = function () {
  3223. return walkontableRandomString();
  3224. };
  3225. /**
  3226. * Inherit without without calling parent constructor, and setting `Child.prototype.constructor` to `Child` instead of `Parent`.
  3227. * Creates temporary dummy function to call it as constructor.
  3228. * Described in ticket: https://github.com/warpech/jquery-handsontable/pull/516
  3229. * @param {Object} Child child class
  3230. * @param {Object} Parent parent class
  3231. * @return {Object} extended Child
  3232. */
  3233. Handsontable.helper.inherit = function (Child, Parent) {
  3234. Parent.prototype.constructor = Parent;
  3235. Child.prototype = new Parent();
  3236. Child.prototype.constructor = Child;
  3237. return Child;
  3238. };
  3239. /**
  3240. * Perform shallow extend of a target object with extension's own properties
  3241. * @param {Object} target An object that will receive the new properties
  3242. * @param {Object} extension An object containing additional properties to merge into the target
  3243. */
  3244. Handsontable.helper.extend = function (target, extension) {
  3245. for (var i in extension) {
  3246. if (extension.hasOwnProperty(i)) {
  3247. target[i] = extension[i];
  3248. }
  3249. }
  3250. };
  3251. Handsontable.helper.getPrototypeOf = function (obj) {
  3252. var prototype;
  3253. if(typeof obj.__proto__ == "object"){
  3254. prototype = obj.__proto__;
  3255. } else {
  3256. var oldConstructor,
  3257. constructor = obj.constructor;
  3258. if (typeof obj.constructor == "function") {
  3259. oldConstructor = constructor;
  3260. if (delete obj.constructor){
  3261. constructor = obj.constructor; // get real constructor
  3262. obj.constructor = oldConstructor; // restore constructor
  3263. }
  3264. }
  3265. prototype = constructor ? constructor.prototype : null; // needed for IE
  3266. }
  3267. return prototype;
  3268. };
  3269. /**
  3270. * Factory for columns constructors.
  3271. * @param {Object} GridSettings
  3272. * @param {Array} conflictList
  3273. * @return {Object} ColumnSettings
  3274. */
  3275. Handsontable.helper.columnFactory = function (GridSettings, conflictList) {
  3276. function ColumnSettings () {}
  3277. Handsontable.helper.inherit(ColumnSettings, GridSettings);
  3278. // Clear conflict settings
  3279. for (var i = 0, len = conflictList.length; i < len; i++) {
  3280. ColumnSettings.prototype[conflictList[i]] = void 0;
  3281. }
  3282. return ColumnSettings;
  3283. };
  3284. Handsontable.helper.translateRowsToColumns = function (input) {
  3285. var i
  3286. , ilen
  3287. , j
  3288. , jlen
  3289. , output = []
  3290. , olen = 0;
  3291. for (i = 0, ilen = input.length; i < ilen; i++) {
  3292. for (j = 0, jlen = input[i].length; j < jlen; j++) {
  3293. if (j == olen) {
  3294. output.push([]);
  3295. olen++;
  3296. }
  3297. output[j].push(input[i][j])
  3298. }
  3299. }
  3300. return output;
  3301. };
  3302. Handsontable.helper.to2dArray = function (arr) {
  3303. var i = 0
  3304. , ilen = arr.length;
  3305. while (i < ilen) {
  3306. arr[i] = [arr[i]];
  3307. i++;
  3308. }
  3309. };
  3310. Handsontable.helper.extendArray = function (arr, extension) {
  3311. var i = 0
  3312. , ilen = extension.length;
  3313. while (i < ilen) {
  3314. arr.push(extension[i]);
  3315. i++;
  3316. }
  3317. };
  3318. /**
  3319. * Determines if the given DOM element is an input field placed outside of HOT.
  3320. * Notice: By 'input' we mean input, textarea and select nodes
  3321. * @param element - DOM element
  3322. * @returns {boolean}
  3323. */
  3324. Handsontable.helper.isOutsideInput = function (element) {
  3325. var inputs = ['INPUT', 'SELECT', 'TEXTAREA'];
  3326. return inputs.indexOf(element.nodeName) > -1 && element.className.indexOf('handsontableInput') == -1;
  3327. };
  3328. Handsontable.helper.keyCode = {
  3329. MOUSE_LEFT: 1,
  3330. MOUSE_RIGHT: 3,
  3331. MOUSE_MIDDLE: 2,
  3332. BACKSPACE: 8,
  3333. COMMA: 188,
  3334. DELETE: 46,
  3335. END: 35,
  3336. ENTER: 13,
  3337. ESCAPE: 27,
  3338. HOME: 36,
  3339. PAGE_DOWN: 34,
  3340. PAGE_UP: 33,
  3341. PERIOD: 190,
  3342. SPACE: 32,
  3343. SHIFT: 16,
  3344. TAB: 9,
  3345. ARROW_RIGHT: 39,
  3346. ARROW_LEFT: 37,
  3347. ARROW_UP: 38,
  3348. ARROW_DOWN: 40,
  3349. F1: 112,
  3350. F2: 113,
  3351. F3: 114,
  3352. F4: 115,
  3353. F5: 116,
  3354. F6: 117,
  3355. F7: 118,
  3356. F8: 119,
  3357. F9: 120,
  3358. F10: 121,
  3359. F11: 122,
  3360. F12: 123,
  3361. A: 65,
  3362. X: 88,
  3363. C: 67,
  3364. V: 86
  3365. };
  3366. /**
  3367. * Determines whether given object is a plain Object.
  3368. * Note: String and Array are not plain Objects
  3369. * @param {*} obj
  3370. * @returns {boolean}
  3371. */
  3372. Handsontable.helper.isObject = function (obj) {
  3373. return Object.prototype.toString.call(obj) == '[object Object]';
  3374. };
  3375. /**
  3376. * Determines whether given object is an Array.
  3377. * Note: String is not an Array
  3378. * @param {*} obj
  3379. * @returns {boolean}
  3380. */
  3381. Handsontable.helper.isArray = function(obj){
  3382. return Array.isArray ? Array.isArray(obj) : Object.prototype.toString.call(obj) == '[object Array]';
  3383. };
  3384. Handsontable.helper.pivot = function (arr) {
  3385. var pivotedArr = [];
  3386. if(!arr || arr.length == 0 || !arr[0] || arr[0].length == 0){
  3387. return pivotedArr;
  3388. }
  3389. var rowCount = arr.length;
  3390. var colCount = arr[0].length;
  3391. for(var i = 0; i < rowCount; i++){
  3392. for(var j = 0; j < colCount; j++){
  3393. if(!pivotedArr[j]){
  3394. pivotedArr[j] = [];
  3395. }
  3396. pivotedArr[j][i] = arr[i][j];
  3397. }
  3398. }
  3399. return pivotedArr;
  3400. };
  3401. Handsontable.helper.proxy = function (fun, context) {
  3402. return function () {
  3403. return fun.apply(context, arguments);
  3404. };
  3405. };
  3406. Handsontable.helper.cellMethodLookupFactory = function (methodName) {
  3407. return function cellMethodLookup (row, col) {
  3408. return (function getMethodFromProperties(properties) {
  3409. if (!properties){
  3410. return; //method not found
  3411. }
  3412. else if(properties.hasOwnProperty(methodName)){
  3413. return properties[methodName]; //method defined directly
  3414. } else if(properties.hasOwnProperty('type')){
  3415. var type;
  3416. if(typeof properties.type != 'string' ){
  3417. throw new Error('Cell type must be a string ');
  3418. }
  3419. type = translateTypeNameToObject(properties.type);
  3420. return type[methodName]; //method defined in type. if does not exist (eg. validator), returns undefined
  3421. }
  3422. return getMethodFromProperties(Handsontable.helper.getPrototypeOf(properties));
  3423. })(typeof row == 'number' ? this.getCellMeta(row, col) : row);
  3424. };
  3425. function translateTypeNameToObject(typeName) {
  3426. var type = Handsontable.cellTypes[typeName];
  3427. if(typeof type == 'undefined'){
  3428. throw new Error('You declared cell type "' + typeName + '" as a string that is not mapped to a known object. Cell type must be an object or a string mapped to an object in Handsontable.cellTypes');
  3429. }
  3430. return type;
  3431. }
  3432. };
  3433. Handsontable.SelectionPoint = function () {
  3434. this._row = null; //private use intended
  3435. this._col = null;
  3436. };
  3437. Handsontable.SelectionPoint.prototype.exists = function () {
  3438. return (this._row !== null);
  3439. };
  3440. Handsontable.SelectionPoint.prototype.row = function (val) {
  3441. if (val !== void 0) {
  3442. this._row = val;
  3443. }
  3444. return this._row;
  3445. };
  3446. Handsontable.SelectionPoint.prototype.col = function (val) {
  3447. if (val !== void 0) {
  3448. this._col = val;
  3449. }
  3450. return this._col;
  3451. };
  3452. Handsontable.SelectionPoint.prototype.coords = function (coords) {
  3453. if (coords !== void 0) {
  3454. this._row = coords.row;
  3455. this._col = coords.col;
  3456. }
  3457. return {
  3458. row: this._row,
  3459. col: this._col
  3460. }
  3461. };
  3462. Handsontable.SelectionPoint.prototype.arr = function (arr) {
  3463. if (arr !== void 0) {
  3464. this._row = arr[0];
  3465. this._col = arr[1];
  3466. }
  3467. return [this._row, this._col]
  3468. };
  3469. /**
  3470. * Default text renderer
  3471. * @param {Object} instance Handsontable instance
  3472. * @param {Element} TD Table cell where to render
  3473. * @param {Number} row
  3474. * @param {Number} col
  3475. * @param {String|Number} prop Row object property name
  3476. * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
  3477. * @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
  3478. */
  3479. Handsontable.TextRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  3480. if (!value && cellProperties.placeholder) {
  3481. value = cellProperties.placeholder;
  3482. instance.view.wt.wtDom.addClass(TD, 'htPlaceholder');
  3483. }
  3484. var escaped = Handsontable.helper.stringify(value);
  3485. if (cellProperties.rendererTemplate) {
  3486. instance.view.wt.wtDom.empty(TD);
  3487. var TEMPLATE = document.createElement('TEMPLATE');
  3488. TEMPLATE.setAttribute('bind', '{{}}');
  3489. TEMPLATE.innerHTML = cellProperties.rendererTemplate;
  3490. HTMLTemplateElement.decorate(TEMPLATE);
  3491. TEMPLATE.model = instance.getDataAtRow(row);
  3492. TD.appendChild(TEMPLATE);
  3493. }
  3494. else {
  3495. instance.view.wt.wtDom.fastInnerText(TD, escaped); //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips
  3496. }
  3497. if (cellProperties.readOnly) {
  3498. instance.view.wt.wtDom.addClass(TD, 'htDimmed');
  3499. }
  3500. if (cellProperties.valid === false && cellProperties.invalidCellClassName) {
  3501. instance.view.wt.wtDom.addClass(TD, cellProperties.invalidCellClassName);
  3502. }
  3503. };
  3504. (function (Handsontable) {
  3505. var clonableWRAPPER = document.createElement('DIV');
  3506. clonableWRAPPER.className = 'htAutocompleteWrapper';
  3507. var clonableARROW = document.createElement('DIV');
  3508. clonableARROW.className = 'htAutocompleteArrow';
  3509. clonableARROW.appendChild(document.createTextNode('\u25BC'));
  3510. //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips
  3511. var wrapTdContentWithWrapper = function(TD, WRAPPER){
  3512. WRAPPER.innerHTML = TD.innerHTML;
  3513. Handsontable.Dom.empty(TD);
  3514. TD.appendChild(WRAPPER);
  3515. };
  3516. /**
  3517. * Autocomplete renderer
  3518. * @param {Object} instance Handsontable instance
  3519. * @param {Element} TD Table cell where to render
  3520. * @param {Number} row
  3521. * @param {Number} col
  3522. * @param {String|Number} prop Row object property name
  3523. * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
  3524. * @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
  3525. */
  3526. Handsontable.AutocompleteRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  3527. var WRAPPER = clonableWRAPPER.cloneNode(true); //this is faster than createElement
  3528. var ARROW = clonableARROW.cloneNode(true); //this is faster than createElement
  3529. Handsontable.TextRenderer(instance, TD, row, col, prop, value, cellProperties);
  3530. // wrapTdContentWithWrapper(TD, WRAPPER);
  3531. // WRAPPER.appendChild(ARROW);
  3532. TD.appendChild(ARROW);
  3533. Handsontable.Dom.addClass(TD, 'htAutocomplete');
  3534. if (!TD.firstChild) { //http://jsperf.com/empty-node-if-needed
  3535. //otherwise empty fields appear borderless in demo/renderers.html (IE)
  3536. TD.appendChild(document.createTextNode('\u00A0')); //\u00A0 equals &nbsp; for a text node
  3537. //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips
  3538. }
  3539. if (!instance.acArrowListener) {
  3540. //not very elegant but easy and fast
  3541. instance.acArrowListener = function () {
  3542. instance.view.wt.getSetting('onCellDblClick');
  3543. };
  3544. instance.rootElement.on('mousedown', '.htAutocompleteArrow', instance.acArrowListener); //this way we don't bind event listener to each arrow. We rely on propagation instead
  3545. }
  3546. };
  3547. })(Handsontable);
  3548. var clonableINPUT = document.createElement('INPUT');
  3549. clonableINPUT.className = 'htCheckboxRendererInput';
  3550. clonableINPUT.type = 'checkbox';
  3551. clonableINPUT.setAttribute('autocomplete', 'off');
  3552. /**
  3553. * Checkbox renderer
  3554. * @param {Object} instance Handsontable instance
  3555. * @param {Element} TD Table cell where to render
  3556. * @param {Number} row
  3557. * @param {Number} col
  3558. * @param {String|Number} prop Row object property name
  3559. * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
  3560. * @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
  3561. */
  3562. Handsontable.CheckboxRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  3563. if (typeof cellProperties.checkedTemplate === "undefined") {
  3564. cellProperties.checkedTemplate = true;
  3565. }
  3566. if (typeof cellProperties.uncheckedTemplate === "undefined") {
  3567. cellProperties.uncheckedTemplate = false;
  3568. }
  3569. instance.view.wt.wtDom.empty(TD); //TODO identify under what circumstances this line can be removed
  3570. var INPUT = clonableINPUT.cloneNode(false); //this is faster than createElement
  3571. if (value === cellProperties.checkedTemplate || value === Handsontable.helper.stringify(cellProperties.checkedTemplate)) {
  3572. INPUT.checked = true;
  3573. TD.appendChild(INPUT);
  3574. }
  3575. else if (value === cellProperties.uncheckedTemplate || value === Handsontable.helper.stringify(cellProperties.uncheckedTemplate)) {
  3576. TD.appendChild(INPUT);
  3577. }
  3578. else if (value === null) { //default value
  3579. INPUT.className += ' noValue';
  3580. TD.appendChild(INPUT);
  3581. }
  3582. else {
  3583. instance.view.wt.wtDom.fastInnerText(TD, '#bad value#'); //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips
  3584. }
  3585. var $input = $(INPUT);
  3586. if (cellProperties.readOnly) {
  3587. $input.on('click', function (event) {
  3588. event.preventDefault();
  3589. });
  3590. }
  3591. else {
  3592. $input.on('mousedown', function (event) {
  3593. event.stopPropagation(); //otherwise can confuse cell mousedown handler
  3594. });
  3595. $input.on('mouseup', function (event) {
  3596. event.stopPropagation(); //otherwise can confuse cell dblclick handler
  3597. });
  3598. $input.on('change', function(){
  3599. if (this.checked) {
  3600. instance.setDataAtRowProp(row, prop, cellProperties.checkedTemplate);
  3601. }
  3602. else {
  3603. instance.setDataAtRowProp(row, prop, cellProperties.uncheckedTemplate);
  3604. }
  3605. });
  3606. }
  3607. if(!instance.CheckboxRenderer || !instance.CheckboxRenderer.beforeKeyDownHookBound){
  3608. instance.CheckboxRenderer = {
  3609. beforeKeyDownHookBound : true
  3610. };
  3611. instance.addHook('beforeKeyDown', function(event){
  3612. if(event.keyCode == 32){
  3613. event.stopImmediatePropagation();
  3614. event.preventDefault();
  3615. var selection = instance.getSelected();
  3616. var cell, checkbox, cellProperties;
  3617. var selStart = {
  3618. row: Math.min(selection[0], selection[2]),
  3619. col: Math.min(selection[1], selection[3])
  3620. };
  3621. var selEnd = {
  3622. row: Math.max(selection[0], selection[2]),
  3623. col: Math.max(selection[1], selection[3])
  3624. };
  3625. for(var row = selStart.row; row <= selEnd.row; row++ ){
  3626. for(var col = selEnd.col; col <= selEnd.col; col++){
  3627. cell = instance.getCell(row, col);
  3628. cellProperties = instance.getCellMeta(row, col);
  3629. checkbox = cell.querySelectorAll('input[type=checkbox]');
  3630. if(checkbox.length > 0 && !cellProperties.readOnly){
  3631. for(var i = 0, len = checkbox.length; i < len; i++){
  3632. checkbox[i].checked = !checkbox[i].checked;
  3633. $(checkbox[i]).trigger('change');
  3634. }
  3635. }
  3636. }
  3637. }
  3638. }
  3639. });
  3640. }
  3641. return TD;
  3642. };
  3643. /**
  3644. * Numeric cell renderer
  3645. * @param {Object} instance Handsontable instance
  3646. * @param {Element} TD Table cell where to render
  3647. * @param {Number} row
  3648. * @param {Number} col
  3649. * @param {String|Number} prop Row object property name
  3650. * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
  3651. * @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
  3652. */
  3653. Handsontable.NumericRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  3654. if (Handsontable.helper.isNumeric(value)) {
  3655. if (typeof cellProperties.language !== 'undefined') {
  3656. numeral.language(cellProperties.language)
  3657. }
  3658. value = numeral(value).format(cellProperties.format || '0'); //docs: http://numeraljs.com/
  3659. instance.view.wt.wtDom.addClass(TD, 'htNumeric');
  3660. }
  3661. Handsontable.TextRenderer(instance, TD, row, col, prop, value, cellProperties);
  3662. };
  3663. (function(Handosntable){
  3664. Handsontable.PasswordRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  3665. Handsontable.TextRenderer.apply(this, arguments);
  3666. value = TD.innerHTML;
  3667. var hash;
  3668. var hashLength = cellProperties.hashLength || value.length;
  3669. var hashSymbol = cellProperties.hashSymbol || '*';
  3670. for( hash = ''; hash.split(hashSymbol).length - 1 < hashLength; hash += hashSymbol);
  3671. instance.view.wt.wtDom.fastInnerHTML(TD, hash);
  3672. };
  3673. })(Handsontable);
  3674. (function (Handsontable) {
  3675. 'use strict';
  3676. Handsontable.EditorState = {
  3677. VIRGIN: 'STATE_VIRGIN', //before editing
  3678. EDITING: 'STATE_EDITING',
  3679. WAITING: 'STATE_WAITING', //waiting for async validation
  3680. FINISHED: 'STATE_FINISHED'
  3681. };
  3682. function BaseEditor(instance) {
  3683. this.instance = instance;
  3684. this.state = Handsontable.EditorState.VIRGIN;
  3685. this._opened = false;
  3686. this._closeCallback = function () {
  3687. };
  3688. this.init();
  3689. }
  3690. BaseEditor.prototype._fireCallbacks = function(result) {
  3691. this._closeCallback(result);
  3692. }
  3693. BaseEditor.prototype.init = function(){};
  3694. BaseEditor.prototype.getValue = function(){
  3695. throw Error('Editor getValue() method unimplemented');
  3696. };
  3697. BaseEditor.prototype.setValue = function(newValue){
  3698. throw Error('Editor setValue() method unimplemented');
  3699. };
  3700. BaseEditor.prototype.open = function(){
  3701. throw Error('Editor open() method unimplemented');
  3702. };
  3703. BaseEditor.prototype.close = function(){
  3704. throw Error('Editor close() method unimplemented');
  3705. };
  3706. BaseEditor.prototype.prepare = function(row, col, prop, td, originalValue, cellProperties){
  3707. this.TD = td;
  3708. this.row = row;
  3709. this.col = col;
  3710. this.prop = prop;
  3711. this.originalValue = originalValue;
  3712. this.cellProperties = cellProperties;
  3713. this.state = Handsontable.EditorState.VIRGIN;
  3714. };
  3715. BaseEditor.prototype.extend = function(){
  3716. var baseClass = this.constructor;
  3717. function Editor(){
  3718. baseClass.apply(this, arguments);
  3719. }
  3720. function inherit(Child, Parent){
  3721. function Bridge() {
  3722. }
  3723. Bridge.prototype = Parent.prototype;
  3724. Child.prototype = new Bridge();
  3725. Child.prototype.constructor = Child;
  3726. return Child;
  3727. }
  3728. return inherit(Editor, baseClass);
  3729. };
  3730. BaseEditor.prototype.saveValue = function (val, ctrlDown) {
  3731. if (ctrlDown) { //if ctrl+enter and multiple cells selected, behave like Excel (finish editing and apply to all cells)
  3732. var sel = this.instance.getSelected();
  3733. this.instance.populateFromArray(sel[0], sel[1], val, sel[2], sel[3], 'edit');
  3734. }
  3735. else {
  3736. this.instance.populateFromArray(this.row, this.col, val, null, null, 'edit');
  3737. }
  3738. };
  3739. BaseEditor.prototype.beginEditing = function(initialValue){
  3740. if (this.state != Handsontable.EditorState.VIRGIN) {
  3741. return;
  3742. }
  3743. if (this.cellProperties.readOnly) {
  3744. return;
  3745. }
  3746. this.instance.view.scrollViewport({row: this.row, col: this.col});
  3747. this.instance.view.render();
  3748. this.state = Handsontable.EditorState.EDITING;
  3749. initialValue = typeof initialValue == 'string' ? initialValue : this.originalValue;
  3750. this.setValue(Handsontable.helper.stringify(initialValue));
  3751. this.open();
  3752. this._opened = true;
  3753. this.instance.view.render(); //only rerender the selections (FillHandle should disappear when beginediting is triggered)
  3754. };
  3755. BaseEditor.prototype.finishEditing = function (restoreOriginalValue, ctrlDown, callback) {
  3756. if (callback) {
  3757. var old = this._closeCallback;
  3758. this._closeCallback = function (result) {
  3759. old(result);
  3760. callback(result);
  3761. };
  3762. }
  3763. if (this.isWaiting()) {
  3764. return;
  3765. }
  3766. this._closeCallback = function () {
  3767. };
  3768. if (this.state == Handsontable.EditorState.VIRGIN) {
  3769. var that = this;
  3770. setTimeout(function () {
  3771. that._fireCallbacks(true);
  3772. });
  3773. return;
  3774. }
  3775. if (this.state == Handsontable.EditorState.EDITING) {
  3776. var val;
  3777. if (restoreOriginalValue) {
  3778. val = [
  3779. [this.originalValue]
  3780. ];
  3781. } else {
  3782. val = [
  3783. [String.prototype.trim.call(this.getValue())] //String.prototype.trim is defined in Walkontable polyfill.js
  3784. ];
  3785. }
  3786. this.state = Handsontable.EditorState.WAITING;
  3787. this.saveValue(val, ctrlDown);
  3788. if(this.instance.getCellValidator(this.cellProperties)){
  3789. var that = this;
  3790. this.instance.addHookOnce('afterValidate', function (result) {
  3791. that.state = Handsontable.EditorState.FINISHED;
  3792. that.discardEditor(result);
  3793. });
  3794. } else {
  3795. this.state = Handsontable.EditorState.FINISHED;
  3796. this.discardEditor(true);
  3797. }
  3798. }
  3799. };
  3800. BaseEditor.prototype.discardEditor = function (result) {
  3801. if (this.state !== Handsontable.EditorState.FINISHED) {
  3802. return;
  3803. }
  3804. if (result === false && this.cellProperties.allowInvalid !== true) { //validator was defined and failed
  3805. this.instance.selectCell(this.row, this.col);
  3806. this.focus();
  3807. this.state = Handsontable.EditorState.EDITING;
  3808. this._fireCallbacks(false);
  3809. }
  3810. else {
  3811. this.close();
  3812. this._opened = false;
  3813. this.state = Handsontable.EditorState.VIRGIN;
  3814. this._fireCallbacks(true);
  3815. }
  3816. };
  3817. BaseEditor.prototype.isOpened = function(){
  3818. return this._opened;
  3819. };
  3820. BaseEditor.prototype.isWaiting = function () {
  3821. return this.state === Handsontable.EditorState.WAITING;
  3822. };
  3823. Handsontable.editors.BaseEditor = BaseEditor;
  3824. })(Handsontable);
  3825. (function(Handsontable){
  3826. var TextEditor = Handsontable.editors.BaseEditor.prototype.extend();
  3827. TextEditor.prototype.init = function(){
  3828. this.createElements();
  3829. this.bindEvents();
  3830. };
  3831. TextEditor.prototype.getValue = function(){
  3832. return this.TEXTAREA.value
  3833. };
  3834. TextEditor.prototype.setValue = function(newValue){
  3835. this.TEXTAREA.value = newValue;
  3836. };
  3837. var onBeforeKeyDown = function onBeforeKeyDown(event){
  3838. var instance = this;
  3839. var that = instance.getActiveEditor();
  3840. var keyCodes = Handsontable.helper.keyCode;
  3841. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
  3842. //Process only events that have been fired in the editor
  3843. if (event.target !== that.TEXTAREA || event.isImmediatePropagationStopped()){
  3844. return;
  3845. }
  3846. if (event.keyCode === 17 || event.keyCode === 224 || event.keyCode === 91 || event.keyCode === 93) {
  3847. //when CTRL or its equivalent is pressed and cell is edited, don't prepare selectable text in textarea
  3848. event.stopImmediatePropagation();
  3849. return;
  3850. }
  3851. switch (event.keyCode) {
  3852. case keyCodes.ARROW_RIGHT:
  3853. if (that.wtDom.getCaretPosition(that.TEXTAREA) !== that.TEXTAREA.value.length) {
  3854. event.stopImmediatePropagation();
  3855. }
  3856. break;
  3857. case keyCodes.ARROW_LEFT: /* arrow left */
  3858. if (that.wtDom.getCaretPosition(that.TEXTAREA) !== 0) {
  3859. event.stopImmediatePropagation();
  3860. }
  3861. break;
  3862. case keyCodes.ENTER:
  3863. var selected = that.instance.getSelected();
  3864. var isMultipleSelection = !(selected[0] === selected[2] && selected[1] === selected[3]);
  3865. if ((ctrlDown && !isMultipleSelection) || event.altKey) { //if ctrl+enter or alt+enter, add new line
  3866. if(that.isOpened()){
  3867. that.setValue(that.getValue() + '\n');
  3868. that.focus();
  3869. } else {
  3870. that.beginEditing(that.originalValue + '\n')
  3871. }
  3872. event.stopImmediatePropagation();
  3873. }
  3874. event.preventDefault(); //don't add newline to field
  3875. break;
  3876. case keyCodes.A:
  3877. case keyCodes.X:
  3878. case keyCodes.C:
  3879. case keyCodes.V:
  3880. if(ctrlDown){
  3881. event.stopImmediatePropagation(); //CTRL+A, CTRL+C, CTRL+V, CTRL+X should only work locally when cell is edited (not in table context)
  3882. break;
  3883. }
  3884. case keyCodes.BACKSPACE:
  3885. case keyCodes.DELETE:
  3886. case keyCodes.HOME:
  3887. case keyCodes.END:
  3888. event.stopImmediatePropagation(); //backspace, delete, home, end should only work locally when cell is edited (not in table context)
  3889. break;
  3890. }
  3891. };
  3892. TextEditor.prototype.open = function(){
  3893. this.refreshDimensions(); //need it instantly, to prevent https://github.com/warpech/jquery-handsontable/issues/348
  3894. this.TEXTAREA.focus();
  3895. this.wtDom.setCaretPosition(this.TEXTAREA, this.TEXTAREA.value.length);
  3896. this.instance.addHook('beforeKeyDown', onBeforeKeyDown);
  3897. };
  3898. TextEditor.prototype.close = function(){
  3899. this.textareaParentStyle.display = 'none';
  3900. if (document.activeElement === this.TEXTAREA) {
  3901. this.instance.listen(); //don't refocus the table if user focused some cell outside of HT on purpose
  3902. }
  3903. this.instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  3904. };
  3905. TextEditor.prototype.focus = function(){
  3906. this.TEXTAREA.focus();
  3907. };
  3908. TextEditor.prototype.createElements = function () {
  3909. this.$body = $(document.body);
  3910. this.wtDom = new WalkontableDom();
  3911. this.TEXTAREA = document.createElement('TEXTAREA');
  3912. this.$textarea = $(this.TEXTAREA);
  3913. this.wtDom.addClass(this.TEXTAREA, 'handsontableInput');
  3914. this.textareaStyle = this.TEXTAREA.style;
  3915. this.textareaStyle.width = 0;
  3916. this.textareaStyle.height = 0;
  3917. this.TEXTAREA_PARENT = document.createElement('DIV');
  3918. this.wtDom.addClass(this.TEXTAREA_PARENT, 'handsontableInputHolder');
  3919. this.textareaParentStyle = this.TEXTAREA_PARENT.style;
  3920. this.textareaParentStyle.top = 0;
  3921. this.textareaParentStyle.left = 0;
  3922. this.textareaParentStyle.display = 'none';
  3923. this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
  3924. this.instance.rootElement[0].appendChild(this.TEXTAREA_PARENT);
  3925. var that = this;
  3926. Handsontable.PluginHooks.add('afterRender', function () {
  3927. that.instance.registerTimeout('refresh_editor_dimensions', function () {
  3928. that.refreshDimensions();
  3929. }, 0);
  3930. });
  3931. };
  3932. TextEditor.prototype.refreshDimensions = function () {
  3933. if (this.state !== Handsontable.EditorState.EDITING) {
  3934. return;
  3935. }
  3936. ///start prepare textarea position
  3937. this.TD = this.instance.getCell(this.row, this.col);
  3938. if (!this.TD) {
  3939. //TD is outside of the viewport. Otherwise throws exception when scrolling the table while a cell is edited
  3940. return;
  3941. }
  3942. var $td = $(this.TD); //because old td may have been scrolled out with scrollViewport
  3943. var currentOffset = this.wtDom.offset(this.TD);
  3944. var containerOffset = this.wtDom.offset(this.instance.rootElement[0]);
  3945. var scrollTop = this.instance.rootElement.scrollTop();
  3946. var scrollLeft = this.instance.rootElement.scrollLeft();
  3947. var editTop = currentOffset.top - containerOffset.top + scrollTop - 1;
  3948. var editLeft = currentOffset.left - containerOffset.left + scrollLeft - 1;
  3949. var settings = this.instance.getSettings();
  3950. var rowHeadersCount = settings.rowHeaders === false ? 0 : 1;
  3951. var colHeadersCount = settings.colHeaders === false ? 0 : 1;
  3952. if (editTop < 0) {
  3953. editTop = 0;
  3954. }
  3955. if (editLeft < 0) {
  3956. editLeft = 0;
  3957. }
  3958. if (rowHeadersCount > 0 && parseInt($td.css('border-top-width'), 10) > 0) {
  3959. editTop += 1;
  3960. }
  3961. if (colHeadersCount > 0 && parseInt($td.css('border-left-width'), 10) > 0) {
  3962. editLeft += 1;
  3963. }
  3964. this.textareaParentStyle.top = editTop + 'px';
  3965. this.textareaParentStyle.left = editLeft + 'px';
  3966. ///end prepare textarea position
  3967. var width = $td.width()
  3968. , maxWidth = this.instance.view.maximumVisibleElementWidth(editLeft) - 10 //10 is TEXTAREAs border and padding
  3969. , height = $td.outerHeight() - 4
  3970. , maxHeight = this.instance.view.maximumVisibleElementHeight(editTop) - 5; //10 is TEXTAREAs border and padding
  3971. if (parseInt($td.css('border-top-width'), 10) > 0) {
  3972. height -= 1;
  3973. }
  3974. if (parseInt($td.css('border-left-width'), 10) > 0) {
  3975. if (rowHeadersCount > 0) {
  3976. width -= 1;
  3977. }
  3978. }
  3979. //in future may change to pure JS http://stackoverflow.com/questions/454202/creating-a-textarea-with-auto-resize
  3980. this.$textarea.autoResize({
  3981. minHeight: Math.min(height, maxHeight),
  3982. maxHeight: maxHeight, //TEXTAREA should never be wider than visible part of the viewport (should not cover the scrollbar)
  3983. minWidth: Math.min(width, maxWidth),
  3984. maxWidth: maxWidth, //TEXTAREA should never be wider than visible part of the viewport (should not cover the scrollbar)
  3985. animate: false,
  3986. extraSpace: 0
  3987. });
  3988. this.textareaParentStyle.display = 'block';
  3989. };
  3990. TextEditor.prototype.bindEvents = function () {
  3991. this.$textarea.on('cut.editor', function (event) {
  3992. event.stopPropagation();
  3993. });
  3994. this.$textarea.on('paste.editor', function (event) {
  3995. event.stopPropagation();
  3996. });
  3997. };
  3998. Handsontable.editors.TextEditor = TextEditor;
  3999. Handsontable.editors.registerEditor('text', Handsontable.editors.TextEditor);
  4000. })(Handsontable);
  4001. (function(Handsontable){
  4002. //Blank editor, because all the work is done by renderer
  4003. var CheckboxEditor = Handsontable.editors.BaseEditor.prototype.extend();
  4004. CheckboxEditor.prototype.beginEditing = function () {
  4005. this.saveValue([
  4006. [!this.originalValue]
  4007. ]);
  4008. };
  4009. CheckboxEditor.prototype.finishEditing = function () {};
  4010. CheckboxEditor.prototype.init = function () {};
  4011. CheckboxEditor.prototype.open = function () {};
  4012. CheckboxEditor.prototype.close = function () {};
  4013. CheckboxEditor.prototype.getValue = function () {};
  4014. CheckboxEditor.prototype.setValue = function () {};
  4015. CheckboxEditor.prototype.focus = function () {};
  4016. Handsontable.editors.CheckboxEditor = CheckboxEditor;
  4017. Handsontable.editors.registerEditor('checkbox', CheckboxEditor);
  4018. })(Handsontable);
  4019. (function (Handsontable) {
  4020. var DateEditor = Handsontable.editors.TextEditor.prototype.extend();
  4021. DateEditor.prototype.init = function () {
  4022. if (!$.datepicker) {
  4023. throw new Error("jQuery UI Datepicker dependency not found. Did you forget to include jquery-ui.custom.js or its substitute?");
  4024. }
  4025. Handsontable.editors.TextEditor.prototype.init.apply(this, arguments);
  4026. this.isCellEdited = false;
  4027. var that = this;
  4028. this.instance.addHook('afterDestroy', function () {
  4029. that.destroyElements();
  4030. })
  4031. };
  4032. DateEditor.prototype.createElements = function () {
  4033. Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
  4034. this.datePicker = document.createElement('DIV');
  4035. this.instance.view.wt.wtDom.addClass(this.datePicker, 'htDatepickerHolder');
  4036. this.datePickerStyle = this.datePicker.style;
  4037. this.datePickerStyle.position = 'absolute';
  4038. this.datePickerStyle.top = 0;
  4039. this.datePickerStyle.left = 0;
  4040. this.datePickerStyle.zIndex = 99;
  4041. document.body.appendChild(this.datePicker);
  4042. this.$datePicker = $(this.datePicker);
  4043. var that = this;
  4044. var defaultOptions = {
  4045. dateFormat: "yy-mm-dd",
  4046. showButtonPanel: true,
  4047. changeMonth: true,
  4048. changeYear: true,
  4049. altField: this.$textarea,
  4050. onSelect: function () {
  4051. that.finishEditing(false);
  4052. }
  4053. };
  4054. this.$datePicker.datepicker(defaultOptions);
  4055. /**
  4056. * Prevent recognizing clicking on jQuery Datepicker as clicking outside of table
  4057. */
  4058. this.$datePicker.on('mousedown', function (event) {
  4059. event.stopPropagation();
  4060. });
  4061. this.hideDatepicker();
  4062. };
  4063. DateEditor.prototype.destroyElements = function () {
  4064. this.$datePicker.datepicker('destroy');
  4065. this.$datePicker.remove();
  4066. };
  4067. DateEditor.prototype.beginEditing = function (row, col, prop, useOriginalValue, suffix) {
  4068. Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
  4069. this.showDatepicker();
  4070. };
  4071. DateEditor.prototype.finishEditing = function (isCancelled, ctrlDown) {
  4072. this.hideDatepicker();
  4073. Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments);
  4074. };
  4075. DateEditor.prototype.showDatepicker = function () {
  4076. var $td = $(this.TD);
  4077. var offset = $td.offset();
  4078. this.datePickerStyle.top = (offset.top + $td.height()) + 'px';
  4079. this.datePickerStyle.left = offset.left + 'px';
  4080. var dateOptions = {
  4081. defaultDate: this.originalValue || void 0
  4082. };
  4083. $.extend(dateOptions, this.cellProperties);
  4084. this.$datePicker.datepicker("option", dateOptions);
  4085. if (this.originalValue) {
  4086. this.$datePicker.datepicker("setDate", this.originalValue);
  4087. }
  4088. this.datePickerStyle.display = 'block';
  4089. };
  4090. DateEditor.prototype.hideDatepicker = function () {
  4091. this.datePickerStyle.display = 'none';
  4092. };
  4093. Handsontable.editors.DateEditor = DateEditor;
  4094. Handsontable.editors.registerEditor('date', DateEditor);
  4095. })(Handsontable);
  4096. /**
  4097. * This is inception. Using Handsontable as Handsontable editor
  4098. */
  4099. (function (Handsontable) {
  4100. "use strict";
  4101. var HandsontableEditor = Handsontable.editors.TextEditor.prototype.extend();
  4102. HandsontableEditor.prototype.createElements = function () {
  4103. Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
  4104. var DIV = document.createElement('DIV');
  4105. DIV.className = 'handsontableEditor';
  4106. this.TEXTAREA_PARENT.appendChild(DIV);
  4107. this.$htContainer = $(DIV);
  4108. this.$htContainer.handsontable();
  4109. };
  4110. HandsontableEditor.prototype.prepare = function (td, row, col, prop, value, cellProperties) {
  4111. Handsontable.editors.TextEditor.prototype.prepare.apply(this, arguments);
  4112. var parent = this;
  4113. var options = {
  4114. startRows: 0,
  4115. startCols: 0,
  4116. minRows: 0,
  4117. minCols: 0,
  4118. className: 'listbox',
  4119. cells: function () {
  4120. return {
  4121. readOnly: true
  4122. }
  4123. },
  4124. fillHandle: false,
  4125. afterOnCellMouseDown: function () {
  4126. parent.setValue(this.getValue());
  4127. parent.instance.destroyEditor();
  4128. },
  4129. beforeOnKeyDown: function (event) {
  4130. var instance = this;
  4131. switch (event.keyCode) {
  4132. case Handsontable.helper.keyCode.ESCAPE:
  4133. parent.instance.destroyEditor(true);
  4134. event.stopImmediatePropagation();
  4135. event.preventDefault();
  4136. break;
  4137. case Handsontable.helper.keyCode.ENTER: //enter
  4138. var sel = instance.getSelected();
  4139. parent.setValue(this.getDataAtCell(sel[0], sel[1]));
  4140. parent.instance.destroyEditor();
  4141. break;
  4142. case Handsontable.helper.keyCode.ARROW_UP:
  4143. if (instance.getSelected() && instance.getSelected()[0] == 0 && !parent.cellProperties.strict){
  4144. instance.deselectCell();
  4145. parent.instance.listen();
  4146. parent.focus();
  4147. event.preventDefault();
  4148. event.stopImmediatePropagation();
  4149. }
  4150. break;
  4151. }
  4152. }
  4153. };
  4154. if (this.cellProperties.handsontable) {
  4155. options = $.extend(options, cellProperties.handsontable);
  4156. }
  4157. this.$htContainer.handsontable('destroy');
  4158. this.$htContainer.handsontable(options);
  4159. };
  4160. var onBeforeKeyDown = function (event) {
  4161. if (event.isImmediatePropagationStopped()) {
  4162. return;
  4163. }
  4164. var editor = this.getActiveEditor();
  4165. var innerHOT = editor.$htContainer.handsontable('getInstance');
  4166. if (event.keyCode == Handsontable.helper.keyCode.ARROW_DOWN) {
  4167. if (!innerHOT.getSelected()){
  4168. innerHOT.selectCell(0, 0);
  4169. } else {
  4170. var selectedRow = innerHOT.getSelected()[0];
  4171. var rowToSelect = selectedRow < innerHOT.countRows() - 1 ? selectedRow + 1 : selectedRow;
  4172. innerHOT.selectCell(rowToSelect, 0);
  4173. }
  4174. event.preventDefault();
  4175. event.stopImmediatePropagation();
  4176. }
  4177. };
  4178. HandsontableEditor.prototype.open = function () {
  4179. this.instance.addHook('beforeKeyDown', onBeforeKeyDown);
  4180. Handsontable.editors.TextEditor.prototype.open.apply(this, arguments);
  4181. this.$htContainer.handsontable('render');
  4182. if (this.cellProperties.strict) {
  4183. this.$htContainer.handsontable('selectCell', 0, 0);
  4184. this.$textarea[0].style.visibility = 'hidden';
  4185. } else {
  4186. this.$htContainer.handsontable('deselectCell');
  4187. this.$textarea[0].style.visibility = 'visible';
  4188. }
  4189. this.wtDom.setCaretPosition(this.$textarea[0], 0, this.$textarea[0].value.length);
  4190. };
  4191. HandsontableEditor.prototype.close = function () {
  4192. this.instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  4193. this.instance.listen();
  4194. Handsontable.editors.TextEditor.prototype.close.apply(this, arguments);
  4195. };
  4196. HandsontableEditor.prototype.focus = function () {
  4197. this.instance.listen();
  4198. Handsontable.editors.TextEditor.prototype.focus.apply(this, arguments);
  4199. };
  4200. HandsontableEditor.prototype.beginEditing = function (initialValue) {
  4201. var onBeginEditing = this.instance.getSettings().onBeginEditing;
  4202. if (onBeginEditing && onBeginEditing() === false) {
  4203. return;
  4204. }
  4205. Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
  4206. };
  4207. HandsontableEditor.prototype.finishEditing = function (isCancelled, ctrlDown) {
  4208. if (this.$htContainer.handsontable('isListening')) { //if focus is still in the HOT editor
  4209. this.instance.listen(); //return the focus to the parent HOT instance
  4210. }
  4211. if (this.$htContainer.handsontable('getSelected')) {
  4212. this.setValue(this.$htContainer.handsontable('getInstance').getValue());
  4213. }
  4214. return Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments);
  4215. };
  4216. Handsontable.editors.HandsontableEditor = HandsontableEditor;
  4217. Handsontable.editors.registerEditor('handsontable', HandsontableEditor);
  4218. })(Handsontable);
  4219. (function (Handsontable) {
  4220. var AutocompleteEditor = Handsontable.editors.HandsontableEditor.prototype.extend();
  4221. AutocompleteEditor.prototype.init = function () {
  4222. Handsontable.editors.HandsontableEditor.prototype.init.apply(this, arguments);
  4223. this.query = null;
  4224. }
  4225. AutocompleteEditor.prototype.createElements = function(){
  4226. Handsontable.editors.HandsontableEditor.prototype.createElements.apply(this, arguments);
  4227. this.$htContainer.addClass('autocompleteEditor');
  4228. };
  4229. AutocompleteEditor.prototype.bindEvents = function(){
  4230. var that = this;
  4231. this.$textarea.on('keydown.autocompleteEditor', function(event){
  4232. if(!Handsontable.helper.isMetaKey(event.keyCode) || [Handsontable.helper.keyCode.BACKSPACE, Handsontable.helper.keyCode.DELETE].indexOf(event.keyCode) != -1){
  4233. setTimeout(function () {
  4234. that.queryChoices(that.$textarea.val());
  4235. });
  4236. } else if (event.keyCode == Handsontable.helper.keyCode.ENTER && that.cellProperties.strict !== true){
  4237. that.$htContainer.handsontable('deselectCell');
  4238. }
  4239. });
  4240. this.$htContainer.on('mouseenter', function () {
  4241. that.$htContainer.handsontable('deselectCell');
  4242. });
  4243. this.$htContainer.on('mouseleave', function () {
  4244. that.queryChoices(that.query);
  4245. });
  4246. Handsontable.editors.HandsontableEditor.prototype.bindEvents.apply(this, arguments);
  4247. };
  4248. AutocompleteEditor.prototype.beginEditing = function () {
  4249. Handsontable.editors.HandsontableEditor.prototype.beginEditing.apply(this, arguments);
  4250. var that = this;
  4251. setTimeout(function () {
  4252. that.queryChoices(that.TEXTAREA.value);
  4253. });
  4254. var hot = this.$htContainer.handsontable('getInstance');
  4255. hot.updateSettings({
  4256. 'colWidths': [this.wtDom.outerWidth(this.TEXTAREA) - 2],
  4257. renderer: function (instance, TD, row, col, prop, value, cellProperties) {
  4258. Handsontable.TextRenderer.apply(this, arguments);
  4259. var match = TD.innerHTML.match(new RegExp(that.query, 'i'));
  4260. if(match){
  4261. TD.innerHTML = value.replace(match[0], '<strong>' + match[0] + '</strong>');
  4262. }
  4263. }
  4264. });
  4265. };
  4266. var onBeforeKeyDownInner;
  4267. AutocompleteEditor.prototype.open = function () {
  4268. var parent = this;
  4269. onBeforeKeyDownInner = function (event) {
  4270. var instance = this;
  4271. if (event.keyCode == Handsontable.helper.keyCode.ARROW_UP){
  4272. if (instance.getSelected() && instance.getSelected()[0] == 0){
  4273. if(!parent.cellProperties.strict){
  4274. instance.deselectCell();
  4275. }
  4276. parent.instance.listen();
  4277. parent.focus();
  4278. event.preventDefault();
  4279. event.stopImmediatePropagation();
  4280. }
  4281. }
  4282. };
  4283. this.$htContainer.handsontable('getInstance').addHook('beforeKeyDown', onBeforeKeyDownInner);
  4284. Handsontable.editors.HandsontableEditor.prototype.open.apply(this, arguments);
  4285. this.$textarea[0].style.visibility = 'visible';
  4286. parent.focus();
  4287. };
  4288. AutocompleteEditor.prototype.close = function () {
  4289. this.$htContainer.handsontable('getInstance').removeHook('beforeKeyDown', onBeforeKeyDownInner);
  4290. Handsontable.editors.HandsontableEditor.prototype.close.apply(this, arguments);
  4291. };
  4292. AutocompleteEditor.prototype.queryChoices = function(query){
  4293. this.query = query;
  4294. if (typeof this.cellProperties.source == 'function'){
  4295. var that = this;
  4296. this.cellProperties.source(query, function(choices){
  4297. that.updateChoicesList(choices)
  4298. });
  4299. } else if (Handsontable.helper.isArray(this.cellProperties.source)) {
  4300. var choices;
  4301. if(!query || this.cellProperties.filter === false){
  4302. choices = this.cellProperties.source;
  4303. } else {
  4304. choices = this.cellProperties.source.filter(function(choice){
  4305. return choice.indexOf(query) != -1
  4306. });
  4307. }
  4308. this.updateChoicesList(choices)
  4309. } else {
  4310. this.updateChoicesList([]);
  4311. }
  4312. };
  4313. function findItemIndexToHighlight(items, value){
  4314. var bestMatch = {};
  4315. var valueLength = value.length;
  4316. var currentItem;
  4317. var indexOfValue;
  4318. var charsLeft;
  4319. for(var i = 0, len = items.length; i < len; i++){
  4320. currentItem = items[i];
  4321. if(valueLength > 0){
  4322. indexOfValue = currentItem.indexOf(value)
  4323. } else {
  4324. indexOfValue = currentItem === value ? 0 : -1;
  4325. }
  4326. if(indexOfValue == -1) continue;
  4327. charsLeft = currentItem.length - indexOfValue - valueLength;
  4328. if( typeof bestMatch.indexOfValue == 'undefined'
  4329. || bestMatch.indexOfValue > indexOfValue
  4330. || ( bestMatch.indexOfValue == indexOfValue && bestMatch.charsLeft > charsLeft ) ){
  4331. bestMatch.indexOfValue = indexOfValue;
  4332. bestMatch.charsLeft = charsLeft;
  4333. bestMatch.index = i;
  4334. }
  4335. }
  4336. return bestMatch.index;
  4337. }
  4338. AutocompleteEditor.prototype.updateChoicesList = function (choices) {
  4339. this.$htContainer.handsontable('loadData', Handsontable.helper.pivot([choices]));
  4340. var value = this.getValue();
  4341. var rowToHighlight;
  4342. if(this.cellProperties.strict === true){
  4343. rowToHighlight = findItemIndexToHighlight(choices, value);
  4344. if ( typeof rowToHighlight == 'undefined'){
  4345. rowToHighlight = 0;
  4346. }
  4347. }
  4348. if(typeof rowToHighlight == 'undefined'){
  4349. this.$htContainer.handsontable('deselectCell');
  4350. } else {
  4351. this.$htContainer.handsontable('selectCell', rowToHighlight, 0);
  4352. }
  4353. this.focus();
  4354. };
  4355. Handsontable.editors.AutocompleteEditor = AutocompleteEditor;
  4356. Handsontable.editors.registerEditor('autocomplete', AutocompleteEditor);
  4357. })(Handsontable);
  4358. (function(Handsontable){
  4359. var PasswordEditor = Handsontable.editors.TextEditor.prototype.extend();
  4360. var wtDom = new WalkontableDom();
  4361. PasswordEditor.prototype.createElements = function () {
  4362. Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
  4363. this.TEXTAREA = document.createElement('input');
  4364. this.TEXTAREA.setAttribute('type', 'password');
  4365. this.TEXTAREA.className = 'handsontableInput';
  4366. this.textareaStyle = this.TEXTAREA.style;
  4367. this.textareaStyle.width = 0;
  4368. this.textareaStyle.height = 0;
  4369. this.$textarea = $(this.TEXTAREA);
  4370. wtDom.empty(this.TEXTAREA_PARENT);
  4371. this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
  4372. };
  4373. Handsontable.editors.PasswordEditor = PasswordEditor;
  4374. Handsontable.editors.registerEditor('password', PasswordEditor);
  4375. })(Handsontable);
  4376. (function (Handsontable) {
  4377. var SelectEditor = Handsontable.editors.BaseEditor.prototype.extend();
  4378. SelectEditor.prototype.init = function(){
  4379. this.select = $('<select />')
  4380. .addClass('htSelectEditor')
  4381. .hide();
  4382. this.instance.rootElement.append(this.select);
  4383. };
  4384. SelectEditor.prototype.prepare = function(){
  4385. Handsontable.editors.BaseEditor.prototype.prepare.apply(this, arguments);
  4386. var selectOptions = this.cellProperties.selectOptions;
  4387. var options;
  4388. if (typeof selectOptions == 'function'){
  4389. options = this.prepareOptions(selectOptions(this.row, this.col, this.prop))
  4390. } else {
  4391. options = this.prepareOptions(selectOptions);
  4392. }
  4393. var optionElements = [];
  4394. for (var option in options){
  4395. if (options.hasOwnProperty(option)){
  4396. var optionElement = $('<option />');
  4397. optionElement.val(option);
  4398. optionElement.html(options[option]);
  4399. optionElements.push(optionElement);
  4400. }
  4401. }
  4402. this.select.empty();
  4403. this.select.append(optionElements);
  4404. };
  4405. SelectEditor.prototype.prepareOptions = function(optionsToPrepare){
  4406. var preparedOptions = {};
  4407. if (Handsontable.helper.isArray(optionsToPrepare)){
  4408. for(var i = 0, len = optionsToPrepare.length; i < len; i++){
  4409. preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i];
  4410. }
  4411. }
  4412. else if (typeof optionsToPrepare == 'object') {
  4413. preparedOptions = optionsToPrepare;
  4414. }
  4415. return preparedOptions;
  4416. };
  4417. SelectEditor.prototype.getValue = function () {
  4418. return this.select.val();
  4419. };
  4420. SelectEditor.prototype.setValue = function (value) {
  4421. this.select.val(value);
  4422. };
  4423. var onBeforeKeyDown = function (event) {
  4424. var instance = this;
  4425. var editor = instance.getActiveEditor();
  4426. switch (event.keyCode){
  4427. case Handsontable.helper.keyCode.ARROW_UP:
  4428. var previousOption = editor.select.find('option:selected').prev();
  4429. if (previousOption.length == 1){
  4430. previousOption.prop('selected', true);
  4431. }
  4432. event.stopImmediatePropagation();
  4433. event.preventDefault();
  4434. break;
  4435. case Handsontable.helper.keyCode.ARROW_DOWN:
  4436. var nextOption = editor.select.find('option:selected').next();
  4437. if (nextOption.length == 1){
  4438. nextOption.prop('selected', true);
  4439. }
  4440. event.stopImmediatePropagation();
  4441. event.preventDefault();
  4442. break;
  4443. }
  4444. };
  4445. SelectEditor.prototype.open = function () {
  4446. this.select.css({
  4447. height: $(this.TD).height(),
  4448. 'min-width' : $(this.TD).outerWidth()
  4449. });
  4450. this.select.show();
  4451. this.select.offset($(this.TD).offset());
  4452. this.instance.addHook('beforeKeyDown', onBeforeKeyDown);
  4453. };
  4454. SelectEditor.prototype.close = function () {
  4455. this.select.hide();
  4456. this.instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  4457. };
  4458. Handsontable.editors.SelectEditor = SelectEditor;
  4459. Handsontable.editors.registerEditor('select', SelectEditor);
  4460. })(Handsontable);
  4461. (function (Handsontable) {
  4462. var DropdownEditor = Handsontable.editors.AutocompleteEditor.prototype.extend();
  4463. DropdownEditor.prototype.prepare = function () {
  4464. Handsontable.editors.AutocompleteEditor.prototype.prepare.apply(this, arguments);
  4465. this.cellProperties.filter = false;
  4466. this.cellProperties.strict = true;
  4467. };
  4468. Handsontable.editors.DropdownEditod = DropdownEditor;
  4469. Handsontable.editors.registerEditor('dropdown', DropdownEditor);
  4470. })(Handsontable);
  4471. /**
  4472. * Numeric cell validator
  4473. * @param {*} value - Value of edited cell
  4474. * @param {*} callback - Callback called with validation result
  4475. */
  4476. Handsontable.NumericValidator = function (value, callback) {
  4477. if (value === null) {
  4478. value = '';
  4479. }
  4480. callback(/^-?\d*\.?\d*$/.test(value));
  4481. };
  4482. /**
  4483. * Function responsible for validation of autocomplete value
  4484. * @param {*} value - Value of edited cell
  4485. * @param {*} calback - Callback called with validation result
  4486. */
  4487. var process = function (value, callback) {
  4488. var originalVal = value;
  4489. var lowercaseVal = typeof originalVal === 'string' ? originalVal.toLowerCase() : null;
  4490. return function (source) {
  4491. var found = false;
  4492. for (var s = 0, slen = source.length; s < slen; s++) {
  4493. if (originalVal === source[s]) {
  4494. found = true; //perfect match
  4495. break;
  4496. }
  4497. else if (lowercaseVal === source[s].toLowerCase()) {
  4498. // changes[i][3] = source[s]; //good match, fix the case << TODO?
  4499. found = true;
  4500. break;
  4501. }
  4502. }
  4503. callback(found);
  4504. }
  4505. };
  4506. /**
  4507. * Autocomplete cell validator
  4508. * @param {*} value - Value of edited cell
  4509. * @param {*} calback - Callback called with validation result
  4510. */
  4511. Handsontable.AutocompleteValidator = function (value, callback) {
  4512. if (this.strict && this.source) {
  4513. typeof this.source === 'function' ? this.source(value, process(value, callback)) : process(value, callback)(this.source);
  4514. } else {
  4515. callback(true);
  4516. }
  4517. };
  4518. /**
  4519. * Cell type is just a shortcut for setting bunch of cellProperties (used in getCellMeta)
  4520. */
  4521. Handsontable.AutocompleteCell = {
  4522. editor: 'autocomplete',
  4523. renderer: Handsontable.AutocompleteRenderer,
  4524. validator: Handsontable.AutocompleteValidator
  4525. };
  4526. Handsontable.CheckboxCell = {
  4527. editor: 'checkbox',
  4528. renderer: Handsontable.CheckboxRenderer
  4529. };
  4530. Handsontable.TextCell = {
  4531. editor: 'text',
  4532. renderer: Handsontable.TextRenderer
  4533. };
  4534. Handsontable.NumericCell = {
  4535. editor: 'text',
  4536. renderer: Handsontable.NumericRenderer,
  4537. validator: Handsontable.NumericValidator,
  4538. dataType: 'number'
  4539. };
  4540. Handsontable.DateCell = {
  4541. editor: 'date',
  4542. renderer: Handsontable.AutocompleteRenderer //displays small gray arrow on right side of the cell
  4543. };
  4544. Handsontable.HandsontableCell = {
  4545. editor: 'handsontable',
  4546. renderer: Handsontable.AutocompleteRenderer //displays small gray arrow on right side of the cell
  4547. };
  4548. Handsontable.PasswordCell = {
  4549. editor: 'password',
  4550. renderer: Handsontable.PasswordRenderer
  4551. };
  4552. Handsontable.DropdownCell = {
  4553. editor: 'dropdown',
  4554. renderer: Handsontable.AutocompleteRenderer,
  4555. validator: Handsontable.AutocompleteValidator
  4556. };
  4557. //here setup the friendly aliases that are used by cellProperties.type
  4558. Handsontable.cellTypes = {
  4559. text: Handsontable.TextCell,
  4560. date: Handsontable.DateCell,
  4561. numeric: Handsontable.NumericCell,
  4562. checkbox: Handsontable.CheckboxCell,
  4563. autocomplete: Handsontable.AutocompleteCell,
  4564. handsontable: Handsontable.HandsontableCell,
  4565. password: Handsontable.PasswordCell,
  4566. dropdown: Handsontable.DropdownCell
  4567. };
  4568. //here setup the friendly aliases that are used by cellProperties.renderer and cellProperties.editor
  4569. Handsontable.cellLookup = {
  4570. renderer: {
  4571. text: Handsontable.TextRenderer,
  4572. numeric: Handsontable.NumericRenderer,
  4573. checkbox: Handsontable.CheckboxRenderer,
  4574. autocomplete: Handsontable.AutocompleteRenderer,
  4575. password: Handsontable.PasswordRenderer
  4576. },
  4577. validator: {
  4578. numeric: Handsontable.NumericValidator,
  4579. autocomplete: Handsontable.AutocompleteValidator
  4580. }
  4581. };
  4582. Handsontable.PluginHookClass = (function () {
  4583. var Hooks = function () {
  4584. return {
  4585. // Hooks
  4586. beforeInitWalkontable: [],
  4587. beforeInit: [],
  4588. beforeRender: [],
  4589. beforeChange: [],
  4590. beforeRemoveCol: [],
  4591. beforeRemoveRow: [],
  4592. beforeValidate: [],
  4593. beforeGet: [],
  4594. beforeSet: [],
  4595. beforeGetCellMeta: [],
  4596. beforeAutofill: [],
  4597. beforeKeyDown: [],
  4598. beforeColumnSort: [],
  4599. afterInit : [],
  4600. afterLoadData : [],
  4601. afterUpdateSettings: [],
  4602. afterRender : [],
  4603. afterRenderer : [],
  4604. afterChange : [],
  4605. afterValidate: [],
  4606. afterGetCellMeta: [],
  4607. afterGetColHeader: [],
  4608. afterGetColWidth: [],
  4609. afterDestroy: [],
  4610. afterRemoveRow: [],
  4611. afterCreateRow: [],
  4612. afterRemoveCol: [],
  4613. afterCreateCol: [],
  4614. afterColumnResize: [],
  4615. afterColumnMove: [],
  4616. afterColumnSort: [],
  4617. afterDeselect: [],
  4618. afterSelection: [],
  4619. afterSelectionByProp: [],
  4620. afterSelectionEnd: [],
  4621. afterSelectionEndByProp: [],
  4622. afterCopyLimit: [],
  4623. afterOnCellMouseDown: [],
  4624. afterOnCellCornerMouseDown: [],
  4625. // Modifiers
  4626. modifyCol: []
  4627. }
  4628. };
  4629. var legacy = {
  4630. onBeforeChange: "beforeChange",
  4631. onChange: "afterChange",
  4632. onCreateRow: "afterCreateRow",
  4633. onCreateCol: "afterCreateCol",
  4634. onSelection: "afterSelection",
  4635. onCopyLimit: "afterCopyLimit",
  4636. onSelectionEnd: "afterSelectionEnd",
  4637. onSelectionByProp: "afterSelectionByProp",
  4638. onSelectionEndByProp: "afterSelectionEndByProp"
  4639. };
  4640. function PluginHookClass() {
  4641. this.hooks = Hooks();
  4642. this.legacy = legacy;
  4643. }
  4644. PluginHookClass.prototype.add = function (key, fn) {
  4645. // provide support for old versions of HOT
  4646. if (key in legacy) {
  4647. key = legacy[key];
  4648. }
  4649. if (typeof this.hooks[key] === "undefined") {
  4650. this.hooks[key] = [];
  4651. }
  4652. if (Handsontable.helper.isArray(fn)) {
  4653. for (var i = 0, len = fn.length; i < len; i++) {
  4654. this.hooks[key].push(fn[i]);
  4655. }
  4656. } else {
  4657. this.hooks[key].push(fn);
  4658. }
  4659. return this;
  4660. };
  4661. PluginHookClass.prototype.once = function(key, fn){
  4662. if(Handsontable.helper.isArray(fn)){
  4663. for(var i = 0, len = fn.length; i < len; i++){
  4664. fn[i].runOnce = true;
  4665. this.add(key, fn[i]);
  4666. }
  4667. } else {
  4668. fn.runOnce = true;
  4669. this.add(key, fn);
  4670. }
  4671. };
  4672. PluginHookClass.prototype.remove = function (key, fn) {
  4673. var status = false;
  4674. // provide support for old versions of HOT
  4675. if (key in legacy) {
  4676. key = legacy[key];
  4677. }
  4678. if (typeof this.hooks[key] !== 'undefined') {
  4679. for (var i = 0, leni = this.hooks[key].length; i < leni; i++) {
  4680. if (this.hooks[key][i] == fn) {
  4681. delete this.hooks[key][i].runOnce;
  4682. this.hooks[key].splice(i, 1);
  4683. status = true;
  4684. break;
  4685. }
  4686. }
  4687. }
  4688. return status;
  4689. };
  4690. PluginHookClass.prototype.run = function (instance, key, p1, p2, p3, p4, p5) {
  4691. // provide support for old versions of HOT
  4692. if (key in legacy) {
  4693. key = legacy[key];
  4694. }
  4695. //performance considerations - http://jsperf.com/call-vs-apply-for-a-plugin-architecture
  4696. if (typeof this.hooks[key] !== 'undefined') {
  4697. //Make a copy of handler array
  4698. var handlers = Array.prototype.slice.call(this.hooks[key]);
  4699. for (var i = 0, leni = handlers.length; i < leni; i++) {
  4700. handlers[i].call(instance, p1, p2, p3, p4, p5);
  4701. if(handlers[i].runOnce){
  4702. this.remove(key, handlers[i]);
  4703. }
  4704. }
  4705. }
  4706. };
  4707. PluginHookClass.prototype.execute = function (instance, key, p1, p2, p3, p4, p5) {
  4708. var res, handlers;
  4709. // provide support for old versions of HOT
  4710. if (key in legacy) {
  4711. key = legacy[key];
  4712. }
  4713. //performance considerations - http://jsperf.com/call-vs-apply-for-a-plugin-architecture
  4714. if (typeof this.hooks[key] !== 'undefined') {
  4715. handlers = Array.prototype.slice.call(this.hooks[key]);
  4716. for (var i = 0, leni = handlers.length; i < leni; i++) {
  4717. res = handlers[i].call(instance, p1, p2, p3, p4, p5);
  4718. if (res !== void 0) {
  4719. p1 = res;
  4720. }
  4721. if(handlers[i].runOnce){
  4722. this.remove(key, handlers[i]);
  4723. }
  4724. if(res === false){ //if any handler returned false
  4725. return false; //event has been cancelled and further execution of handler queue is being aborted
  4726. }
  4727. }
  4728. }
  4729. return p1;
  4730. };
  4731. return PluginHookClass;
  4732. })();
  4733. Handsontable.PluginHooks = new Handsontable.PluginHookClass();
  4734. (function (Handsontable) {
  4735. function HandsontableAutoColumnSize() {
  4736. var plugin = this
  4737. , sampleCount = 5; //number of samples to take of each value length
  4738. this.beforeInit = function () {
  4739. var instance = this;
  4740. instance.autoColumnWidths = [];
  4741. if (instance.getSettings().autoColumnSize !== false) {
  4742. if (!instance.autoColumnSizeTmp) {
  4743. instance.autoColumnSizeTmp = {
  4744. table: null,
  4745. tableStyle: null,
  4746. theadTh: null,
  4747. tbody: null,
  4748. container: null,
  4749. containerStyle: null,
  4750. determineBeforeNextRender: true
  4751. };
  4752. }
  4753. instance.addHook('beforeRender', htAutoColumnSize.determineIfChanged);
  4754. instance.addHook('afterGetColWidth', htAutoColumnSize.getColWidth);
  4755. instance.addHook('afterDestroy', htAutoColumnSize.afterDestroy);
  4756. instance.determineColumnWidth = plugin.determineColumnWidth;
  4757. } else {
  4758. instance.removeHook('beforeRender', htAutoColumnSize.determineIfChanged);
  4759. instance.removeHook('afterGetColWidth', htAutoColumnSize.getColWidth);
  4760. instance.removeHook('afterDestroy', htAutoColumnSize.afterDestroy);
  4761. delete instance.determineColumnWidth;
  4762. plugin.afterDestroy.call(instance);
  4763. }
  4764. };
  4765. this.determineIfChanged = function (force) {
  4766. if (force) {
  4767. htAutoColumnSize.determineColumnsWidth.apply(this, arguments);
  4768. }
  4769. };
  4770. this.determineColumnWidth = function (col) {
  4771. var instance = this
  4772. , tmp = instance.autoColumnSizeTmp;
  4773. if (!tmp.container) {
  4774. createTmpContainer.call(tmp, instance);
  4775. }
  4776. tmp.container.className = instance.rootElement[0].className + ' htAutoColumnSize';
  4777. tmp.table.className = instance.$table[0].className;
  4778. var rows = instance.countRows();
  4779. var samples = {};
  4780. var maxLen = 0;
  4781. for (var r = 0; r < rows; r++) {
  4782. var value = Handsontable.helper.stringify(instance.getDataAtCell(r, col));
  4783. var len = value.length;
  4784. if (len > maxLen) {
  4785. maxLen = len;
  4786. }
  4787. if (!samples[len]) {
  4788. samples[len] = {
  4789. needed: sampleCount,
  4790. strings: []
  4791. };
  4792. }
  4793. if (samples[len].needed) {
  4794. samples[len].strings.push({value: value, row: r});
  4795. samples[len].needed--;
  4796. }
  4797. }
  4798. var settings = instance.getSettings();
  4799. if (settings.colHeaders) {
  4800. instance.view.appendColHeader(col, tmp.theadTh); //TH innerHTML
  4801. }
  4802. instance.view.wt.wtDom.empty(tmp.tbody);
  4803. var cellProperties = instance.getCellMeta(0, col);
  4804. var renderer = instance.getCellRenderer(cellProperties);
  4805. for (var i in samples) {
  4806. if (samples.hasOwnProperty(i)) {
  4807. for (var j = 0, jlen = samples[i].strings.length; j < jlen; j++) {
  4808. var tr = document.createElement('tr');
  4809. var td = document.createElement('td');
  4810. renderer(instance, td, samples[i].strings[j].row, col, instance.colToProp(col), samples[i].strings[j].value, cellProperties);
  4811. r++;
  4812. tr.appendChild(td);
  4813. tmp.tbody.appendChild(tr);
  4814. }
  4815. }
  4816. }
  4817. var parent = instance.rootElement[0].parentNode;
  4818. parent.appendChild(tmp.container);
  4819. var width = instance.view.wt.wtDom.outerWidth(tmp.table);
  4820. parent.removeChild(tmp.container);
  4821. var maxWidth = instance.view.wt.wtViewport.getViewportWidth() - 2; //2 is some overhead for cell border
  4822. if (width > maxWidth) {
  4823. width = maxWidth;
  4824. }
  4825. return width;
  4826. };
  4827. this.determineColumnsWidth = function () {
  4828. var instance = this;
  4829. var settings = this.getSettings();
  4830. if (settings.autoColumnSize || !settings.colWidths) {
  4831. var cols = this.countCols();
  4832. for (var c = 0; c < cols; c++) {
  4833. if (!instance._getColWidthFromSettings(c)) {
  4834. this.autoColumnWidths[c] = plugin.determineColumnWidth.call(instance, c);
  4835. }
  4836. }
  4837. }
  4838. };
  4839. this.getColWidth = function (col, response) {
  4840. if (this.autoColumnWidths[col] && this.autoColumnWidths[col] > response.width) {
  4841. response.width = this.autoColumnWidths[col];
  4842. }
  4843. };
  4844. this.afterDestroy = function () {
  4845. var instance = this;
  4846. if (instance.autoColumnSizeTmp && instance.autoColumnSizeTmp.container && instance.autoColumnSizeTmp.container.parentNode) {
  4847. instance.autoColumnSizeTmp.container.parentNode.removeChild(instance.autoColumnSizeTmp.container);
  4848. }
  4849. };
  4850. function createTmpContainer(instance) {
  4851. var d = document
  4852. , tmp = this;
  4853. tmp.table = d.createElement('table');
  4854. tmp.theadTh = d.createElement('th');
  4855. tmp.table.appendChild(d.createElement('thead')).appendChild(d.createElement('tr')).appendChild(tmp.theadTh);
  4856. tmp.tableStyle = tmp.table.style;
  4857. tmp.tableStyle.tableLayout = 'auto';
  4858. tmp.tableStyle.width = 'auto';
  4859. tmp.tbody = d.createElement('tbody');
  4860. tmp.table.appendChild(tmp.tbody);
  4861. tmp.container = d.createElement('div');
  4862. tmp.container.className = instance.rootElement[0].className + ' hidden';
  4863. tmp.containerStyle = tmp.container.style;
  4864. tmp.container.appendChild(tmp.table);
  4865. }
  4866. }
  4867. var htAutoColumnSize = new HandsontableAutoColumnSize();
  4868. Handsontable.PluginHooks.add('beforeInit', htAutoColumnSize.beforeInit);
  4869. Handsontable.PluginHooks.add('afterUpdateSettings', htAutoColumnSize.beforeInit);
  4870. })(Handsontable);
  4871. /**
  4872. * This plugin sorts the view by a column (but does not sort the data source!)
  4873. * @constructor
  4874. */
  4875. function HandsontableColumnSorting() {
  4876. var plugin = this;
  4877. this.init = function (source) {
  4878. var instance = this;
  4879. var sortingSettings = instance.getSettings().columnSorting;
  4880. var sortingColumn, sortingOrder;
  4881. instance.sortingEnabled = !!(sortingSettings);
  4882. if (instance.sortingEnabled) {
  4883. instance.sortIndex = [];
  4884. var loadedSortingState = loadSortingState.call(instance);
  4885. if (typeof loadedSortingState != 'undefined') {
  4886. sortingColumn = loadedSortingState.sortColumn;
  4887. sortingOrder = loadedSortingState.sortOrder;
  4888. } else {
  4889. sortingColumn = sortingSettings.column;
  4890. sortingOrder = sortingSettings.sortOrder;
  4891. }
  4892. plugin.sortByColumn.call(instance, sortingColumn, sortingOrder);
  4893. instance.sort = function(){
  4894. var args = Array.prototype.slice.call(arguments);
  4895. return plugin.sortByColumn.apply(instance, args)
  4896. };
  4897. if (typeof instance.getSettings().observeChanges == 'undefined'){
  4898. enableObserveChangesPlugin.call(instance);
  4899. }
  4900. if (source == 'afterInit') {
  4901. bindColumnSortingAfterClick.call(instance);
  4902. instance.addHook('afterCreateRow', plugin.afterCreateRow);
  4903. instance.addHook('afterRemoveRow', plugin.afterRemoveRow);
  4904. instance.addHook('afterLoadData', plugin.init);
  4905. }
  4906. } else {
  4907. delete instance.sort;
  4908. instance.removeHook('afterCreateRow', plugin.afterCreateRow);
  4909. instance.removeHook('afterRemoveRow', plugin.afterRemoveRow);
  4910. instance.removeHook('afterLoadData', plugin.init);
  4911. }
  4912. };
  4913. this.setSortingColumn = function (col, order) {
  4914. var instance = this;
  4915. if (typeof col == 'undefined') {
  4916. delete instance.sortColumn;
  4917. delete instance.sortOrder;
  4918. return;
  4919. } else if (instance.sortColumn === col && typeof order == 'undefined') {
  4920. instance.sortOrder = !instance.sortOrder;
  4921. } else {
  4922. instance.sortOrder = typeof order != 'undefined' ? order : true;
  4923. }
  4924. instance.sortColumn = col;
  4925. };
  4926. this.sortByColumn = function (col, order) {
  4927. var instance = this;
  4928. plugin.setSortingColumn.call(instance, col, order);
  4929. if(typeof instance.sortColumn == 'undefined'){
  4930. return;
  4931. }
  4932. instance.PluginHooks.run('beforeColumnSort', instance.sortColumn, instance.sortOrder);
  4933. plugin.sort.call(instance);
  4934. instance.render();
  4935. saveSortingState.call(instance);
  4936. instance.PluginHooks.run('afterColumnSort', instance.sortColumn, instance.sortOrder);
  4937. };
  4938. var saveSortingState = function () {
  4939. var instance = this;
  4940. var sortingState = {};
  4941. if (typeof instance.sortColumn != 'undefined') {
  4942. sortingState.sortColumn = instance.sortColumn;
  4943. }
  4944. if (typeof instance.sortOrder != 'undefined') {
  4945. sortingState.sortOrder = instance.sortOrder;
  4946. }
  4947. if (sortingState.hasOwnProperty('sortColumn') || sortingState.hasOwnProperty('sortOrder')) {
  4948. instance.PluginHooks.run('persistentStateSave', 'columnSorting', sortingState);
  4949. }
  4950. };
  4951. var loadSortingState = function () {
  4952. var instance = this;
  4953. var storedState = {};
  4954. instance.PluginHooks.run('persistentStateLoad', 'columnSorting', storedState);
  4955. return storedState.value;
  4956. };
  4957. var bindColumnSortingAfterClick = function () {
  4958. var instance = this;
  4959. instance.rootElement.on('click.handsontable', '.columnSorting', function (e) {
  4960. if (instance.view.wt.wtDom.hasClass(e.target, 'columnSorting')) {
  4961. var col = getColumn(e.target);
  4962. plugin.sortByColumn.call(instance, col);
  4963. }
  4964. });
  4965. function countRowHeaders() {
  4966. var THs = instance.view.TBODY.querySelector('tr').querySelectorAll('th');
  4967. return THs.length;
  4968. }
  4969. function getColumn(target) {
  4970. var TH = instance.view.wt.wtDom.closest(target, 'TH');
  4971. return instance.view.wt.wtDom.index(TH) - countRowHeaders();
  4972. }
  4973. };
  4974. function enableObserveChangesPlugin () {
  4975. var instance = this;
  4976. instance.registerTimeout('enableObserveChanges', function(){
  4977. instance.updateSettings({
  4978. observeChanges: true
  4979. });
  4980. }, 0);
  4981. }
  4982. function defaultSort(sortOrder) {
  4983. return function (a, b) {
  4984. if (a[1] === b[1]) {
  4985. return 0;
  4986. }
  4987. if (a[1] === null) {
  4988. return 1;
  4989. }
  4990. if (b[1] === null) {
  4991. return -1;
  4992. }
  4993. if (a[1] < b[1]) return sortOrder ? -1 : 1;
  4994. if (a[1] > b[1]) return sortOrder ? 1 : -1;
  4995. return 0;
  4996. }
  4997. }
  4998. function dateSort(sortOrder) {
  4999. return function (a, b) {
  5000. if (a[1] === b[1]) {
  5001. return 0;
  5002. }
  5003. if (a[1] === null) {
  5004. return 1;
  5005. }
  5006. if (b[1] === null) {
  5007. return -1;
  5008. }
  5009. var aDate = new Date(a[1]);
  5010. var bDate = new Date(b[1]);
  5011. if (aDate < bDate) return sortOrder ? -1 : 1;
  5012. if (aDate > bDate) return sortOrder ? 1 : -1;
  5013. return 0;
  5014. }
  5015. }
  5016. this.sort = function () {
  5017. var instance = this;
  5018. if (typeof instance.sortOrder == 'undefined') {
  5019. return;
  5020. }
  5021. instance.sortingEnabled = false; //this is required by translateRow plugin hook
  5022. instance.sortIndex.length = 0;
  5023. var colOffset = this.colOffset();
  5024. for (var i = 0, ilen = this.countRows() - instance.getSettings()['minSpareRows']; i < ilen; i++) {
  5025. this.sortIndex.push([i, instance.getDataAtCell(i, this.sortColumn + colOffset)]);
  5026. }
  5027. var colMeta = instance.getCellMeta(0, instance.sortColumn);
  5028. var sortFunction;
  5029. switch (colMeta.type) {
  5030. case 'date':
  5031. sortFunction = dateSort;
  5032. break;
  5033. default:
  5034. sortFunction = defaultSort;
  5035. }
  5036. this.sortIndex.sort(sortFunction(instance.sortOrder));
  5037. //Append spareRows
  5038. for(var i = this.sortIndex.length; i < instance.countRows(); i++){
  5039. this.sortIndex.push([i, instance.getDataAtCell(i, this.sortColumn + colOffset)]);
  5040. }
  5041. instance.sortingEnabled = true; //this is required by translateRow plugin hook
  5042. };
  5043. this.translateRow = function (row) {
  5044. var instance = this;
  5045. if (instance.sortingEnabled && instance.sortIndex && instance.sortIndex.length && instance.sortIndex[row]) {
  5046. return instance.sortIndex[row][0];
  5047. }
  5048. return row;
  5049. };
  5050. this.onBeforeGetSet = function (getVars) {
  5051. var instance = this;
  5052. getVars.row = plugin.translateRow.call(instance, getVars.row);
  5053. };
  5054. this.untranslateRow = function (row) {
  5055. var instance = this;
  5056. if (instance.sortingEnabled && instance.sortIndex && instance.sortIndex.length) {
  5057. for (var i = 0; i < instance.sortIndex.length; i++) {
  5058. if (instance.sortIndex[i][0] == row) {
  5059. return i;
  5060. }
  5061. }
  5062. }
  5063. };
  5064. this.getColHeader = function (col, TH) {
  5065. if (this.getSettings().columnSorting) {
  5066. this.view.wt.wtDom.addClass(TH.querySelector('.colHeader'), 'columnSorting');
  5067. }
  5068. };
  5069. function isSorted(instance){
  5070. return typeof instance.sortColumn != 'undefined';
  5071. }
  5072. this.afterCreateRow = function(index, amount){
  5073. var instance = this;
  5074. if(!isSorted(instance)){
  5075. return;
  5076. }
  5077. for(var i = 0; i < instance.sortIndex.length; i++){
  5078. if (instance.sortIndex[i][0] >= index){
  5079. instance.sortIndex[i][0] += amount;
  5080. }
  5081. }
  5082. for(var i=0; i < amount; i++){
  5083. instance.sortIndex.splice(index+i, 0, [index+i, instance.getData()[index+i][instance.sortColumn + instance.colOffset()]]);
  5084. }
  5085. saveSortingState.call(instance);
  5086. };
  5087. this.afterRemoveRow = function(index, amount){
  5088. var instance = this;
  5089. if(!isSorted(instance)){
  5090. return;
  5091. }
  5092. var physicalRemovedIndex = plugin.untranslateRow.call(instance, index);
  5093. instance.sortIndex.splice(index, amount);
  5094. for(var i = 0; i < instance.sortIndex.length; i++){
  5095. if (instance.sortIndex[i][0] > physicalRemovedIndex){
  5096. instance.sortIndex[i][0] -= amount;
  5097. }
  5098. }
  5099. saveSortingState.call(instance);
  5100. };
  5101. this.afterChangeSort = function (changes/*, source*/) {
  5102. var instance = this;
  5103. var sortColumnChanged = false;
  5104. var selection = {};
  5105. if (!changes) {
  5106. return;
  5107. }
  5108. for (var i = 0; i < changes.length; i++) {
  5109. if (changes[i][1] == instance.sortColumn) {
  5110. sortColumnChanged = true;
  5111. selection.row = plugin.translateRow.call(instance, changes[i][0]);
  5112. selection.col = changes[i][1];
  5113. break;
  5114. }
  5115. }
  5116. if (sortColumnChanged) {
  5117. setTimeout(function () {
  5118. plugin.sort.call(instance);
  5119. instance.render();
  5120. instance.selectCell(plugin.untranslateRow.call(instance, selection.row), selection.col);
  5121. }, 0);
  5122. }
  5123. };
  5124. }
  5125. var htSortColumn = new HandsontableColumnSorting();
  5126. Handsontable.PluginHooks.add('afterInit', function () {
  5127. htSortColumn.init.call(this, 'afterInit')
  5128. });
  5129. Handsontable.PluginHooks.add('afterUpdateSettings', function () {
  5130. htSortColumn.init.call(this, 'afterUpdateSettings')
  5131. });
  5132. Handsontable.PluginHooks.add('beforeGet', htSortColumn.onBeforeGetSet);
  5133. Handsontable.PluginHooks.add('beforeSet', htSortColumn.onBeforeGetSet);
  5134. Handsontable.PluginHooks.add('afterGetColHeader', htSortColumn.getColHeader);
  5135. (function (Handsontable) {
  5136. 'use strict';
  5137. function ContextMenu(instance, customOptions){
  5138. this.instance = instance;
  5139. var contextMenu = this;
  5140. this.menu = createMenu();
  5141. this.enabled = true;
  5142. this.bindMouseEvents();
  5143. this.instance.addHook('afterDestroy', function () {
  5144. contextMenu.destroy();
  5145. });
  5146. this.defaultOptions = {
  5147. items: {
  5148. 'row_above': {
  5149. name: 'Insert row above',
  5150. callback: function(key, selection){
  5151. this.alter("insert_row", selection.start.row());
  5152. },
  5153. disabled: function () {
  5154. return this.countRows() >= this.getSettings().maxRows;
  5155. }
  5156. },
  5157. 'row_below': {
  5158. name: 'Insert row below',
  5159. callback: function(key, selection){
  5160. this.alter("insert_row", selection.end.row() + 1);
  5161. },
  5162. disabled: function () {
  5163. return this.countRows() >= this.getSettings().maxRows;
  5164. }
  5165. },
  5166. "hsep1": ContextMenu.SEPARATOR,
  5167. 'col_left': {
  5168. name: 'Insert column on the left',
  5169. callback: function(key, selection){
  5170. this.alter("insert_col", selection.start.col());
  5171. },
  5172. disabled: function () {
  5173. return this.countCols() >= this.getSettings().maxCols;
  5174. }
  5175. },
  5176. 'col_right': {
  5177. name: 'Insert column on the right',
  5178. callback: function(key, selection){
  5179. this.alter("insert_col", selection.end.col() + 1);
  5180. },
  5181. disabled: function () {
  5182. return this.countCols() >= this.getSettings().maxCols;
  5183. }
  5184. },
  5185. "hsep2": ContextMenu.SEPARATOR,
  5186. 'remove_row': {
  5187. name: 'Remove row',
  5188. callback: function(key, selection){
  5189. var amount = selection.end.row() - selection.start.row() + 1;
  5190. this.alter("remove_row", selection.start.row(), amount);
  5191. }
  5192. },
  5193. 'remove_col': {
  5194. name: 'Remove column',
  5195. callback: function(key, selection){
  5196. var amount = selection.end.col() - selection.start.col() + 1;
  5197. this.alter("remove_col", selection.start.col(), amount);
  5198. }
  5199. },
  5200. "hsep3": ContextMenu.SEPARATOR,
  5201. 'undo': {
  5202. name: 'Undo',
  5203. callback: function(){
  5204. this.undo();
  5205. },
  5206. disabled: function () {
  5207. return this.undoRedo && !this.undoRedo.isUndoAvailable();
  5208. }
  5209. },
  5210. 'redo': {
  5211. name: 'Redo',
  5212. callback: function(){
  5213. this.redo();
  5214. },
  5215. disabled: function () {
  5216. return this.undoRedo && !this.undoRedo.isRedoAvailable();
  5217. }
  5218. }
  5219. }
  5220. };
  5221. this.options = {};
  5222. Handsontable.helper.extend(this.options, this.defaultOptions);
  5223. this.updateOptions(customOptions);
  5224. function createMenu(){
  5225. var menu = document.createElement('DIV');
  5226. Handsontable.Dom.addClass(menu, 'htContextMenu');
  5227. instance.rootElement[0].appendChild(menu);
  5228. return menu;
  5229. }
  5230. }
  5231. ContextMenu.prototype.bindMouseEvents = function (){
  5232. function contextMenuOpenListener(event){
  5233. event.preventDefault();
  5234. if(event.target.nodeName != 'TD' && !(Handsontable.Dom.hasClass(event.target, 'current') && Handsontable.Dom.hasClass(event.target, 'wtBorder'))){
  5235. return;
  5236. }
  5237. this.show(event.pageY, event.pageX);
  5238. $(document).on('mousedown.htContextMenu', Handsontable.helper.proxy(ContextMenu.prototype.close, this));
  5239. }
  5240. this.instance.rootElement.on('contextmenu.htContextMenu', Handsontable.helper.proxy(contextMenuOpenListener, this));
  5241. $(this.menu).on('mousedown', Handsontable.helper.proxy(this.performAction, this));
  5242. };
  5243. ContextMenu.prototype.performAction = function (){
  5244. var hot = $(this.menu).handsontable('getInstance');
  5245. var selectedItemIndex = hot.getSelected()[0];
  5246. var selectedItem = hot.getData()[selectedItemIndex];
  5247. if (selectedItem.disabled === true || (typeof selectedItem.disabled == 'function' && selectedItem.disabled.call(this.instance) === true)){
  5248. return;
  5249. }
  5250. if(typeof selectedItem.callback != 'function'){
  5251. return;
  5252. }
  5253. var corners = this.instance.getSelected();
  5254. var normalizedSelection = ContextMenu.utils.normalizeSelection(corners);
  5255. selectedItem.callback.call(this.instance, selectedItem.key, normalizedSelection);
  5256. };
  5257. ContextMenu.prototype.unbindMouseEvents = function () {
  5258. this.instance.rootElement.off('contextmenu.htContextMenu');
  5259. $(document).off('mousedown.htContextMenu');
  5260. };
  5261. ContextMenu.prototype.show = function(top, left){
  5262. this.menu.style.display = 'block';
  5263. $(this.menu).handsontable({
  5264. data: ContextMenu.utils.convertItemsToArray(this.getItems()),
  5265. colHeaders: false,
  5266. colWidths: [160],
  5267. readOnly: true,
  5268. columns: [
  5269. {
  5270. data: 'name',
  5271. renderer: Handsontable.helper.proxy(this.renderer, this)
  5272. }
  5273. ],
  5274. beforeKeyDown: Handsontable.helper.proxy(this.onBeforeKeyDown, this)
  5275. });
  5276. this.setMenuPosition(top, left);
  5277. $(this.menu).handsontable('listen');
  5278. };
  5279. ContextMenu.prototype.renderer = function(instance, TD, row, col, prop, value, cellProperties){
  5280. var contextMenu = this;
  5281. var item = instance.getData()[row];
  5282. var wrapper = document.createElement('DIV');
  5283. Handsontable.Dom.empty(TD);
  5284. TD.appendChild(wrapper);
  5285. if(itemIsSeparator(item)){
  5286. Handsontable.Dom.addClass(TD, 'htSeparator');
  5287. } else {
  5288. Handsontable.Dom.fastInnerText(wrapper, value);
  5289. }
  5290. if (itemIsDisabled(item, contextMenu.instance)){
  5291. Handsontable.Dom.addClass(TD, 'htDisabled');
  5292. $(wrapper).on('mouseenter', function () {
  5293. instance.deselectCell();
  5294. });
  5295. } else {
  5296. Handsontable.Dom.removeClass(TD, 'htDisabled');
  5297. $(wrapper).on('mouseenter', function () {
  5298. instance.selectCell(row, col);
  5299. });
  5300. }
  5301. function itemIsSeparator(item){
  5302. return new RegExp(ContextMenu.SEPARATOR, 'i').test(item.name);
  5303. }
  5304. function itemIsDisabled(item, instance){
  5305. return item.disabled === true || (typeof item.disabled == 'function' && item.disabled.call(contextMenu.instance) === true);
  5306. }
  5307. };
  5308. ContextMenu.prototype.onBeforeKeyDown = function (event) {
  5309. var contextMenu = this;
  5310. var instance = $(contextMenu.menu).handsontable('getInstance');
  5311. var selection = instance.getSelected();
  5312. switch(event.keyCode){
  5313. case Handsontable.helper.keyCode.ESCAPE:
  5314. contextMenu.close();
  5315. event.preventDefault();
  5316. event.stopImmediatePropagation();
  5317. break;
  5318. case Handsontable.helper.keyCode.ENTER:
  5319. if(instance.getSelected()){
  5320. contextMenu.performAction();
  5321. contextMenu.close();
  5322. }
  5323. break;
  5324. case Handsontable.helper.keyCode.ARROW_DOWN:
  5325. if(!selection){
  5326. selectFirstCell(instance);
  5327. } else {
  5328. selectNextCell(selection[0], selection[1], instance);
  5329. }
  5330. event.preventDefault();
  5331. event.stopImmediatePropagation();
  5332. break;
  5333. case Handsontable.helper.keyCode.ARROW_UP:
  5334. if(!selection){
  5335. selectLastCell(instance);
  5336. } else {
  5337. selectPrevCell(selection[0], selection[1], instance);
  5338. }
  5339. event.preventDefault();
  5340. event.stopImmediatePropagation();
  5341. break;
  5342. }
  5343. function selectFirstCell(instance) {
  5344. var firstCell = instance.getCell(0, 0);
  5345. if(ContextMenu.utils.isSeparator(firstCell) || ContextMenu.utils.isDisabled(firstCell)){
  5346. selectNextCell(0, 0, instance);
  5347. } else {
  5348. instance.selectCell(0, 0);
  5349. }
  5350. }
  5351. function selectLastCell(instance) {
  5352. var lastRow = instance.countRows() - 1;
  5353. var lastCell = instance.getCell(lastRow, 0);
  5354. if(ContextMenu.utils.isSeparator(lastCell) || ContextMenu.utils.isDisabled(lastCell)){
  5355. selectPrevCell(lastRow, 0, instance);
  5356. } else {
  5357. instance.selectCell(lastRow, 0);
  5358. }
  5359. }
  5360. function selectNextCell(row, col, instance){
  5361. var nextRow = row + 1;
  5362. var nextCell = nextRow < instance.countRows() ? instance.getCell(nextRow, col) : null;
  5363. if(!nextCell){
  5364. return;
  5365. }
  5366. if(ContextMenu.utils.isSeparator(nextCell) || ContextMenu.utils.isDisabled(nextCell)){
  5367. selectNextCell(nextRow, col, instance);
  5368. } else {
  5369. instance.selectCell(nextRow, col);
  5370. }
  5371. }
  5372. function selectPrevCell(row, col, instance) {
  5373. var prevRow = row - 1;
  5374. var prevCell = prevRow >= 0 ? instance.getCell(prevRow, col) : null;
  5375. if (!prevCell) {
  5376. return;
  5377. }
  5378. if(ContextMenu.utils.isSeparator(prevCell) || ContextMenu.utils.isDisabled(prevCell)){
  5379. selectPrevCell(prevRow, col, instance);
  5380. } else {
  5381. instance.selectCell(prevRow, col);
  5382. }
  5383. }
  5384. };
  5385. ContextMenu.prototype.getItems = function () {
  5386. var items = {};
  5387. function Item(rawItem){
  5388. if(typeof rawItem == 'string'){
  5389. this.name = rawItem;
  5390. } else {
  5391. Handsontable.helper.extend(this, rawItem);
  5392. }
  5393. }
  5394. Item.prototype = this.options;
  5395. for(var itemName in this.options.items){
  5396. if(this.options.items.hasOwnProperty(itemName) && (!this.itemsFilter || this.itemsFilter.indexOf(itemName) != -1)){
  5397. items[itemName] = new Item(this.options.items[itemName]);
  5398. }
  5399. }
  5400. return items;
  5401. };
  5402. ContextMenu.prototype.updateOptions = function(newOptions){
  5403. newOptions = newOptions || {};
  5404. if(newOptions.items){
  5405. for(var itemName in newOptions.items){
  5406. var item = {};
  5407. if(newOptions.items.hasOwnProperty(itemName)) {
  5408. if(this.defaultOptions.items.hasOwnProperty(itemName)
  5409. && Handsontable.helper.isObject(newOptions.items[itemName])){
  5410. Handsontable.helper.extend(item, this.defaultOptions.items[itemName]);
  5411. Handsontable.helper.extend(item, newOptions.items[itemName]);
  5412. newOptions.items[itemName] = item;
  5413. }
  5414. }
  5415. }
  5416. }
  5417. Handsontable.helper.extend(this.options, newOptions);
  5418. };
  5419. ContextMenu.prototype.setMenuPosition = function (cursorY, cursorX) {
  5420. var cursor = {
  5421. top: cursorY,
  5422. topRelative: cursorY - window.pageYOffset,
  5423. left: cursorX,
  5424. leftRelative:cursorX - window.pageXOffset
  5425. };
  5426. if(this.menuFitsBelowCursor(cursor)){
  5427. this.positionMenuBelowCursor(cursor);
  5428. } else {
  5429. this.positionMenuAboveCursor(cursor);
  5430. }
  5431. if(this.menuFitsOnRightOfCursor(cursor)){
  5432. this.positionMenuOnRightOfCursor(cursor);
  5433. } else {
  5434. this.positionMenuOnLeftOfCursor(cursor);
  5435. }
  5436. };
  5437. ContextMenu.prototype.menuFitsBelowCursor = function (cursor) {
  5438. return cursor.topRelative + this.menu.offsetHeight <= document.documentElement.scrollTop + document.documentElement.clientHeight;
  5439. };
  5440. ContextMenu.prototype.menuFitsOnRightOfCursor = function (cursor) {
  5441. return cursor.leftRelative + this.menu.offsetWidth <= document.documentElement.scrollLeft + document.documentElement.clientWidth;
  5442. };
  5443. ContextMenu.prototype.positionMenuBelowCursor = function (cursor) {
  5444. this.menu.style.top = this.getCursorRelativeToContainer(cursor).top + 'px';
  5445. };
  5446. ContextMenu.prototype.positionMenuAboveCursor = function (cursor) {
  5447. this.menu.style.top = (this.getCursorRelativeToContainer(cursor).top - this.menu.offsetHeight) + 'px';
  5448. };
  5449. ContextMenu.prototype.positionMenuOnRightOfCursor = function (cursor) {
  5450. this.menu.style.left = this.getCursorRelativeToContainer(cursor).left + 'px';
  5451. };
  5452. ContextMenu.prototype.positionMenuOnLeftOfCursor = function (cursor) {
  5453. this.menu.style.left = (this.getCursorRelativeToContainer(cursor).left - this.menu.offsetWidth) + 'px';
  5454. };
  5455. ContextMenu.prototype.getCursorRelativeToContainer = function (cursor) {
  5456. var containerOffset = Handsontable.Dom.offset(this.instance.rootElement[0]);
  5457. return {
  5458. left: cursor.left - containerOffset.left,
  5459. top: cursor.top - containerOffset.top
  5460. }
  5461. };
  5462. ContextMenu.utils = {};
  5463. ContextMenu.utils.convertItemsToArray = function (items) {
  5464. var itemArray = [];
  5465. var item;
  5466. for(var itemName in items){
  5467. if(items.hasOwnProperty(itemName)){
  5468. if(typeof items[itemName] == 'string'){
  5469. item = {name: items[itemName]};
  5470. } else if (items[itemName].visible !== false) {
  5471. item = items[itemName];
  5472. } else {
  5473. continue;
  5474. }
  5475. item.key = itemName;
  5476. itemArray.push(item);
  5477. }
  5478. }
  5479. return itemArray;
  5480. };
  5481. ContextMenu.utils.normalizeSelection = function(corners){
  5482. var selection = {
  5483. start: new Handsontable.SelectionPoint(),
  5484. end: new Handsontable.SelectionPoint()
  5485. };
  5486. selection.start.row(Math.min(corners[0], corners[2]));
  5487. selection.start.col(Math.min(corners[1], corners[3]));
  5488. selection.end.row(Math.max(corners[0], corners[2]));
  5489. selection.end.col(Math.max(corners[1], corners[3]));
  5490. return selection;
  5491. };
  5492. ContextMenu.utils.isSeparator = function (cell) {
  5493. return Handsontable.Dom.hasClass(cell, 'htSeparator');
  5494. };
  5495. ContextMenu.utils.isDisabled = function (cell) {
  5496. return Handsontable.Dom.hasClass(cell, 'htDisabled');
  5497. };
  5498. ContextMenu.prototype.close = function () {
  5499. this.hide();
  5500. $(document).off('mousedown.htContextMenu');
  5501. this.instance.listen();
  5502. };
  5503. ContextMenu.prototype.hide = function(){
  5504. this.menu.style.display = 'none';
  5505. $(this.menu).handsontable('destroy');
  5506. };
  5507. ContextMenu.prototype.enable = function () {
  5508. if(!this.enabled){
  5509. this.enabled = true;
  5510. this.bindMouseEvents();
  5511. }
  5512. };
  5513. ContextMenu.prototype.disable = function () {
  5514. if(this.enabled){
  5515. this.enabled = false;
  5516. this.close();
  5517. this.unbindMouseEvents();
  5518. }
  5519. };
  5520. ContextMenu.prototype.destroy = function () {
  5521. this.close();
  5522. this.unbindMouseEvents();
  5523. if(this.menu.parentNode){
  5524. this.menu.parentNode.removeChild(this.menu);
  5525. }
  5526. };
  5527. ContextMenu.prototype.filterItems = function(itemsToLeave){
  5528. this.itemsFilter = itemsToLeave;
  5529. };
  5530. ContextMenu.SEPARATOR = "---------";
  5531. function init(){
  5532. var instance = this;
  5533. var contextMenuSetting = instance.getSettings().contextMenu;
  5534. var customOptions = Handsontable.helper.isObject(contextMenuSetting) ? contextMenuSetting : {};
  5535. if(contextMenuSetting){
  5536. if(!instance.contextMenu){
  5537. instance.contextMenu = new ContextMenu(instance, customOptions);
  5538. }
  5539. instance.contextMenu.enable();
  5540. if(Handsontable.helper.isArray(contextMenuSetting)){
  5541. instance.contextMenu.filterItems(contextMenuSetting);
  5542. }
  5543. } else if(instance.contextMenu){
  5544. instance.contextMenu.destroy();
  5545. delete instance.contextMenu;
  5546. }
  5547. }
  5548. Handsontable.PluginHooks.add('afterInit', init);
  5549. Handsontable.PluginHooks.add('afterUpdateSettings', init);
  5550. Handsontable.ContextMenu = ContextMenu;
  5551. })(Handsontable);
  5552. /**
  5553. * This plugin adds support for legacy features, deprecated APIs, etc.
  5554. */
  5555. /**
  5556. * Support for old autocomplete syntax
  5557. * For old syntax, see: https://github.com/warpech/jquery-handsontable/blob/8c9e701d090ea4620fe08b6a1a048672fadf6c7e/README.md#defining-autocomplete
  5558. */
  5559. Handsontable.PluginHooks.add('beforeGetCellMeta', function (row, col, cellProperties) {
  5560. var settings = this.getSettings(), data = this.getData(), i, ilen, a;
  5561. //isWritable - deprecated since 0.8.0
  5562. cellProperties.isWritable = !cellProperties.readOnly;
  5563. //autocomplete - deprecated since 0.7.1 (see CHANGELOG.md)
  5564. if (settings.autoComplete) {
  5565. for (i = 0, ilen = settings.autoComplete.length; i < ilen; i++) {
  5566. if (settings.autoComplete[i].match(row, col, data)) {
  5567. if (typeof cellProperties.type === 'undefined') {
  5568. cellProperties.type = Handsontable.AutocompleteCell;
  5569. }
  5570. else {
  5571. if (typeof cellProperties.type.renderer === 'undefined') {
  5572. cellProperties.type.renderer = Handsontable.AutocompleteCell.renderer;
  5573. }
  5574. if (typeof cellProperties.type.editor === 'undefined') {
  5575. cellProperties.type.editor = Handsontable.AutocompleteCell.editor;
  5576. }
  5577. }
  5578. for (a in settings.autoComplete[i]) {
  5579. if (settings.autoComplete[i].hasOwnProperty(a) && a !== 'match' && typeof cellProperties[i] === 'undefined') {
  5580. if (a === 'source') {
  5581. cellProperties[a] = settings.autoComplete[i][a](row, col);
  5582. }
  5583. else {
  5584. cellProperties[a] = settings.autoComplete[i][a];
  5585. }
  5586. }
  5587. }
  5588. break;
  5589. }
  5590. }
  5591. }
  5592. });
  5593. function HandsontableManualColumnMove() {
  5594. var pressed
  5595. , startCol
  5596. , endCol
  5597. , startX
  5598. , startOffset;
  5599. var ghost = document.createElement('DIV')
  5600. , ghostStyle = ghost.style;
  5601. ghost.className = 'ghost';
  5602. ghostStyle.position = 'absolute';
  5603. ghostStyle.top = '25px';
  5604. ghostStyle.left = 0;
  5605. ghostStyle.width = '10px';
  5606. ghostStyle.height = '10px';
  5607. ghostStyle.backgroundColor = '#CCC';
  5608. ghostStyle.opacity = 0.7;
  5609. var saveManualColumnPositions = function () {
  5610. var instance = this;
  5611. instance.PluginHooks.run('persistentStateSave', 'manualColumnPositions', instance.manualColumnPositions);
  5612. };
  5613. var loadManualColumnPositions = function () {
  5614. var instance = this;
  5615. var storedState = {};
  5616. instance.PluginHooks.run('persistentStateLoad', 'manualColumnPositions', storedState);
  5617. return storedState.value;
  5618. };
  5619. var bindMoveColEvents = function () {
  5620. var instance = this;
  5621. instance.rootElement.on('mousemove.manualColumnMove', function (e) {
  5622. if (pressed) {
  5623. ghostStyle.left = startOffset + e.pageX - startX + 6 + 'px';
  5624. if (ghostStyle.display === 'none') {
  5625. ghostStyle.display = 'block';
  5626. }
  5627. }
  5628. });
  5629. instance.rootElement.on('mouseup.manualColumnMove', function () {
  5630. if (pressed) {
  5631. if (startCol < endCol) {
  5632. endCol--;
  5633. }
  5634. if (instance.getSettings().rowHeaders) {
  5635. startCol--;
  5636. endCol--;
  5637. }
  5638. instance.manualColumnPositions.splice(endCol, 0, instance.manualColumnPositions.splice(startCol, 1)[0]);
  5639. $('.manualColumnMover.active').removeClass('active');
  5640. pressed = false;
  5641. instance.forceFullRender = true;
  5642. instance.view.render(); //updates all
  5643. ghostStyle.display = 'none';
  5644. saveManualColumnPositions.call(instance);
  5645. instance.PluginHooks.run('afterColumnMove', startCol, endCol);
  5646. }
  5647. });
  5648. instance.rootElement.on('mousedown.manualColumnMove', '.manualColumnMover', function (e) {
  5649. var mover = e.currentTarget;
  5650. var TH = instance.view.wt.wtDom.closest(mover, 'TH');
  5651. startCol = instance.view.wt.wtDom.index(TH) + instance.colOffset();
  5652. endCol = startCol;
  5653. pressed = true;
  5654. startX = e.pageX;
  5655. var TABLE = instance.$table[0];
  5656. TABLE.parentNode.appendChild(ghost);
  5657. ghostStyle.width = instance.view.wt.wtDom.outerWidth(TH) + 'px';
  5658. ghostStyle.height = instance.view.wt.wtDom.outerHeight(TABLE) + 'px';
  5659. startOffset = parseInt(instance.view.wt.wtDom.offset(TH).left - instance.view.wt.wtDom.offset(TABLE).left, 10);
  5660. ghostStyle.left = startOffset + 6 + 'px';
  5661. });
  5662. instance.rootElement.on('mouseenter.manualColumnMove', 'td, th', function () {
  5663. if (pressed) {
  5664. var active = instance.view.THEAD.querySelector('.manualColumnMover.active');
  5665. if (active) {
  5666. instance.view.wt.wtDom.removeClass(active, 'active');
  5667. }
  5668. endCol = instance.view.wt.wtDom.index(this) + instance.colOffset();
  5669. var THs = instance.view.THEAD.querySelectorAll('th');
  5670. var mover = THs[endCol].querySelector('.manualColumnMover');
  5671. instance.view.wt.wtDom.addClass(mover, 'active');
  5672. }
  5673. });
  5674. instance.addHook('afterDestroy', unbindMoveColEvents);
  5675. };
  5676. var unbindMoveColEvents = function(){
  5677. var instance = this;
  5678. instance.rootElement.off('mouseup.manualColumnMove');
  5679. instance.rootElement.off('mousemove.manualColumnMove');
  5680. instance.rootElement.off('mousedown.manualColumnMove');
  5681. instance.rootElement.off('mouseenter.manualColumnMove');
  5682. };
  5683. this.beforeInit = function () {
  5684. this.manualColumnPositions = [];
  5685. };
  5686. this.init = function (source) {
  5687. var instance = this;
  5688. var manualColMoveEnabled = !!(this.getSettings().manualColumnMove);
  5689. if (manualColMoveEnabled) {
  5690. var initialManualColumnPositions = this.getSettings().manualColumnMove;
  5691. var loadedManualColumnPositions = loadManualColumnPositions.call(instance);
  5692. if (typeof loadedManualColumnPositions != 'undefined') {
  5693. this.manualColumnPositions = loadedManualColumnPositions;
  5694. } else if (initialManualColumnPositions instanceof Array) {
  5695. this.manualColumnPositions = initialManualColumnPositions;
  5696. } else {
  5697. this.manualColumnPositions = [];
  5698. }
  5699. instance.forceFullRender = true;
  5700. if (source == 'afterInit') {
  5701. bindMoveColEvents.call(this);
  5702. if (this.manualColumnPositions.length > 0) {
  5703. this.forceFullRender = true;
  5704. this.render();
  5705. }
  5706. }
  5707. } else {
  5708. unbindMoveColEvents.call(this);
  5709. this.manualColumnPositions = [];
  5710. }
  5711. };
  5712. this.modifyCol = function (col) {
  5713. //TODO test performance: http://jsperf.com/object-wrapper-vs-primitive/2
  5714. if (this.getSettings().manualColumnMove) {
  5715. if (typeof this.manualColumnPositions[col] === 'undefined') {
  5716. this.manualColumnPositions[col] = col;
  5717. }
  5718. return this.manualColumnPositions[col];
  5719. }
  5720. return col;
  5721. };
  5722. this.getColHeader = function (col, TH) {
  5723. if (this.getSettings().manualColumnMove) {
  5724. var DIV = document.createElement('DIV');
  5725. DIV.className = 'manualColumnMover';
  5726. TH.firstChild.appendChild(DIV);
  5727. }
  5728. };
  5729. }
  5730. var htManualColumnMove = new HandsontableManualColumnMove();
  5731. Handsontable.PluginHooks.add('beforeInit', htManualColumnMove.beforeInit);
  5732. Handsontable.PluginHooks.add('afterInit', function () {
  5733. htManualColumnMove.init.call(this, 'afterInit')
  5734. });
  5735. Handsontable.PluginHooks.add('afterUpdateSettings', function () {
  5736. htManualColumnMove.init.call(this, 'afterUpdateSettings')
  5737. });
  5738. Handsontable.PluginHooks.add('afterGetColHeader', htManualColumnMove.getColHeader);
  5739. Handsontable.PluginHooks.add('modifyCol', htManualColumnMove.modifyCol);
  5740. function HandsontableManualColumnResize() {
  5741. var pressed
  5742. , currentTH
  5743. , currentCol
  5744. , currentWidth
  5745. , instance
  5746. , newSize
  5747. , startX
  5748. , startWidth
  5749. , startOffset
  5750. , resizer = document.createElement('DIV')
  5751. , handle = document.createElement('DIV')
  5752. , line = document.createElement('DIV')
  5753. , lineStyle = line.style;
  5754. resizer.className = 'manualColumnResizer';
  5755. handle.className = 'manualColumnResizerHandle';
  5756. resizer.appendChild(handle);
  5757. line.className = 'manualColumnResizerLine';
  5758. resizer.appendChild(line);
  5759. var $document = $(document);
  5760. $document.mousemove(function (e) {
  5761. if (pressed) {
  5762. currentWidth = startWidth + (e.pageX - startX);
  5763. newSize = setManualSize(currentCol, currentWidth); //save col width
  5764. resizer.style.left = startOffset + currentWidth + 'px';
  5765. }
  5766. });
  5767. $document.mouseup(function () {
  5768. if (pressed) {
  5769. instance.view.wt.wtDom.removeClass(resizer, 'active');
  5770. pressed = false;
  5771. if(newSize != startWidth){
  5772. instance.forceFullRender = true;
  5773. instance.view.render(); //updates all
  5774. saveManualColumnWidths.call(instance);
  5775. instance.PluginHooks.run('afterColumnResize', currentCol, newSize);
  5776. }
  5777. refreshResizerPosition.call(instance, currentTH);
  5778. }
  5779. });
  5780. var saveManualColumnWidths = function () {
  5781. var instance = this;
  5782. instance.PluginHooks.run('persistentStateSave', 'manualColumnWidths', instance.manualColumnWidths);
  5783. };
  5784. var loadManualColumnWidths = function () {
  5785. var instance = this;
  5786. var storedState = {};
  5787. instance.PluginHooks.run('persistentStateLoad', 'manualColumnWidths', storedState);
  5788. return storedState.value;
  5789. };
  5790. function refreshResizerPosition(TH) {
  5791. instance = this;
  5792. currentTH = TH;
  5793. var col = this.view.wt.wtTable.getCoords(TH)[1]; //getCoords returns array [row, col]
  5794. if (col >= 0) { //if not row header
  5795. currentCol = col;
  5796. var rootOffset = this.view.wt.wtDom.offset(this.rootElement[0]).left;
  5797. var thOffset = this.view.wt.wtDom.offset(TH).left;
  5798. startOffset = (thOffset - rootOffset) - 6;
  5799. resizer.style.left = startOffset + parseInt(this.view.wt.wtDom.outerWidth(TH), 10) + 'px';
  5800. this.rootElement[0].appendChild(resizer);
  5801. }
  5802. }
  5803. function refreshLinePosition() {
  5804. var instance = this;
  5805. startWidth = parseInt(this.view.wt.wtDom.outerWidth(currentTH), 10);
  5806. instance.view.wt.wtDom.addClass(resizer, 'active');
  5807. lineStyle.height = instance.view.wt.wtDom.outerHeight(instance.$table[0]) + 'px';
  5808. pressed = instance;
  5809. }
  5810. var bindManualColumnWidthEvents = function () {
  5811. var instance = this;
  5812. var dblclick = 0;
  5813. var autoresizeTimeout = null;
  5814. this.rootElement.on('mouseenter.handsontable', 'th', function (e) {
  5815. if (!pressed) {
  5816. refreshResizerPosition.call(instance, e.currentTarget);
  5817. }
  5818. });
  5819. this.rootElement.on('mousedown.handsontable', '.manualColumnResizer', function () {
  5820. if (autoresizeTimeout == null) {
  5821. autoresizeTimeout = setTimeout(function () {
  5822. if (dblclick >= 2) {
  5823. newSize = instance.determineColumnWidth.call(instance, currentCol);
  5824. setManualSize(currentCol, newSize);
  5825. instance.forceFullRender = true;
  5826. instance.view.render(); //updates all
  5827. instance.PluginHooks.run('afterColumnResize', currentCol, newSize);
  5828. }
  5829. dblclick = 0;
  5830. autoresizeTimeout = null;
  5831. }, 500);
  5832. }
  5833. dblclick++;
  5834. });
  5835. this.rootElement.on('mousedown.handsontable', '.manualColumnResizer', function (e) {
  5836. startX = e.pageX;
  5837. refreshLinePosition.call(instance);
  5838. newSize = startWidth;
  5839. });
  5840. };
  5841. this.beforeInit = function () {
  5842. this.manualColumnWidths = [];
  5843. };
  5844. this.init = function (source) {
  5845. var instance = this;
  5846. var manualColumnWidthEnabled = !!(this.getSettings().manualColumnResize);
  5847. if (manualColumnWidthEnabled) {
  5848. var initialColumnWidths = this.getSettings().manualColumnResize;
  5849. var loadedManualColumnWidths = loadManualColumnWidths.call(instance);
  5850. if (typeof loadedManualColumnWidths != 'undefined') {
  5851. this.manualColumnWidths = loadedManualColumnWidths;
  5852. } else if (initialColumnWidths instanceof Array) {
  5853. this.manualColumnWidths = initialColumnWidths;
  5854. } else {
  5855. this.manualColumnWidths = [];
  5856. }
  5857. if (source == 'afterInit') {
  5858. bindManualColumnWidthEvents.call(this);
  5859. instance.forceFullRender = true;
  5860. instance.render();
  5861. }
  5862. }
  5863. };
  5864. var setManualSize = function (col, width) {
  5865. width = Math.max(width, 20);
  5866. /**
  5867. * We need to run col through modifyCol hook, in case the order of displayed columns is different than the order
  5868. * in data source. For instance, this order can be modified by manualColumnMove plugin.
  5869. */
  5870. col = instance.PluginHooks.execute('modifyCol', col);
  5871. instance.manualColumnWidths[col] = width;
  5872. return width;
  5873. };
  5874. this.getColWidth = function (col, response) {
  5875. if (this.getSettings().manualColumnResize && this.manualColumnWidths[col]) {
  5876. response.width = this.manualColumnWidths[col];
  5877. }
  5878. };
  5879. }
  5880. var htManualColumnResize = new HandsontableManualColumnResize();
  5881. Handsontable.PluginHooks.add('beforeInit', htManualColumnResize.beforeInit);
  5882. Handsontable.PluginHooks.add('afterInit', function () {
  5883. htManualColumnResize.init.call(this, 'afterInit')
  5884. });
  5885. Handsontable.PluginHooks.add('afterUpdateSettings', function () {
  5886. htManualColumnResize.init.call(this, 'afterUpdateSettings')
  5887. });
  5888. Handsontable.PluginHooks.add('afterGetColWidth', htManualColumnResize.getColWidth);
  5889. (function HandsontableObserveChanges() {
  5890. Handsontable.PluginHooks.add('afterLoadData', init);
  5891. Handsontable.PluginHooks.add('afterUpdateSettings', init);
  5892. function init() {
  5893. var instance = this;
  5894. var pluginEnabled = instance.getSettings().observeChanges;
  5895. if (!instance.observer && pluginEnabled) {
  5896. createObserver.call(instance);
  5897. bindEvents.call(instance);
  5898. } else if (!pluginEnabled){
  5899. destroy.call(instance);
  5900. }
  5901. }
  5902. function createObserver(){
  5903. var instance = this;
  5904. instance.observeChangesActive = true;
  5905. instance.pauseObservingChanges = function(){
  5906. instance.observeChangesActive = false;
  5907. };
  5908. instance.resumeObservingChanges = function(){
  5909. instance.observeChangesActive = true;
  5910. };
  5911. instance.observer = jsonpatch.observe(instance.getData(), function (patches) {
  5912. if(instance.observeChangesActive){
  5913. runHookForOperation.call(instance, patches);
  5914. instance.render();
  5915. }
  5916. instance.runHooks('afterChangesObserved');
  5917. });
  5918. }
  5919. function runHookForOperation(rawPatches){
  5920. var instance = this;
  5921. var patches = cleanPatches(rawPatches);
  5922. for(var i = 0, len = patches.length; i < len; i++){
  5923. var patch = patches[i];
  5924. var parsedPath = parsePath(patch.path);
  5925. switch(patch.op){
  5926. case 'add':
  5927. if(isNaN(parsedPath.col)){
  5928. instance.runHooks('afterCreateRow', parsedPath.row);
  5929. } else {
  5930. instance.runHooks('afterCreateCol', parsedPath.col);
  5931. }
  5932. break;
  5933. case 'remove':
  5934. if(isNaN(parsedPath.col)){
  5935. instance.runHooks('afterRemoveRow', parsedPath.row, 1);
  5936. } else {
  5937. instance.runHooks('afterRemoveCol', parsedPath.col, 1);
  5938. }
  5939. break;
  5940. case 'replace':
  5941. instance.runHooks('afterChange', [parsedPath.row, parsedPath.col, null, patch.value], 'external');
  5942. break;
  5943. }
  5944. }
  5945. function cleanPatches(rawPatches){
  5946. var patches;
  5947. patches = removeLengthRelatedPatches(rawPatches);
  5948. patches = removeMultipleAddOrRemoveColPatches(patches);
  5949. return patches;
  5950. }
  5951. /**
  5952. * Removing or adding column will produce one patch for each table row.
  5953. * This function leaves only one patch for each column add/remove operation
  5954. */
  5955. function removeMultipleAddOrRemoveColPatches(rawPatches){
  5956. var newOrRemovedColumns = [];
  5957. return rawPatches.filter(function(patch){
  5958. var parsedPath = parsePath(patch.path);
  5959. if(['add', 'remove'].indexOf(patch.op) != -1 && !isNaN(parsedPath.col)){
  5960. if(newOrRemovedColumns.indexOf(parsedPath.col) != -1){
  5961. return false;
  5962. } else {
  5963. newOrRemovedColumns.push(parsedPath.col);
  5964. }
  5965. }
  5966. return true;
  5967. });
  5968. }
  5969. /**
  5970. * If observeChanges uses native Object.observe method, then it produces patches for length property.
  5971. * This function removes them.
  5972. */
  5973. function removeLengthRelatedPatches(rawPatches){
  5974. return rawPatches.filter(function(patch){
  5975. return !/[/]length/ig.test(patch.path);
  5976. })
  5977. }
  5978. function parsePath(path){
  5979. var match = path.match(/^\/(\d+)\/?(.*)?$/);
  5980. return {
  5981. row: parseInt(match[1], 10),
  5982. col: /^\d*$/.test(match[2]) ? parseInt(match[2], 10) : match[2]
  5983. }
  5984. }
  5985. }
  5986. function destroy(){
  5987. var instance = this;
  5988. if (instance.observer){
  5989. destroyObserver.call(instance);
  5990. unbindEvents.call(instance);
  5991. }
  5992. }
  5993. function destroyObserver(){
  5994. var instance = this;
  5995. jsonpatch.unobserve(instance.getData(), instance.observer);
  5996. delete instance.observeChangesActive;
  5997. delete instance.pauseObservingChanges;
  5998. delete instance.resumeObservingChanges;
  5999. }
  6000. function bindEvents(){
  6001. var instance = this;
  6002. instance.addHook('afterDestroy', destroy);
  6003. instance.addHook('afterCreateRow', afterTableAlter);
  6004. instance.addHook('afterRemoveRow', afterTableAlter);
  6005. instance.addHook('afterCreateCol', afterTableAlter);
  6006. instance.addHook('afterRemoveCol', afterTableAlter);
  6007. instance.addHook('afterChange', function(changes, source){
  6008. if(source != 'loadData'){
  6009. afterTableAlter.call(this);
  6010. }
  6011. });
  6012. }
  6013. function unbindEvents(){
  6014. var instance = this;
  6015. instance.removeHook('afterDestroy', destroy);
  6016. instance.removeHook('afterCreateRow', afterTableAlter);
  6017. instance.removeHook('afterRemoveRow', afterTableAlter);
  6018. instance.removeHook('afterCreateCol', afterTableAlter);
  6019. instance.removeHook('afterRemoveCol', afterTableAlter);
  6020. instance.removeHook('afterChange', afterTableAlter);
  6021. }
  6022. function afterTableAlter(){
  6023. var instance = this;
  6024. instance.pauseObservingChanges();
  6025. instance.addHookOnce('afterChangesObserved', function(){
  6026. instance.resumeObservingChanges();
  6027. });
  6028. }
  6029. })();
  6030. /*
  6031. *
  6032. * Plugin enables saving table state
  6033. *
  6034. * */
  6035. function Storage(prefix) {
  6036. var savedKeys;
  6037. var saveSavedKeys = function () {
  6038. window.localStorage[prefix + '__' + 'persistentStateKeys'] = JSON.stringify(savedKeys);
  6039. };
  6040. var loadSavedKeys = function () {
  6041. var keysJSON = window.localStorage[prefix + '__' + 'persistentStateKeys'];
  6042. var keys = typeof keysJSON == 'string' ? JSON.parse(keysJSON) : void 0;
  6043. savedKeys = keys ? keys : [];
  6044. };
  6045. var clearSavedKeys = function () {
  6046. savedKeys = [];
  6047. saveSavedKeys();
  6048. };
  6049. loadSavedKeys();
  6050. this.saveValue = function (key, value) {
  6051. window.localStorage[prefix + '_' + key] = JSON.stringify(value);
  6052. if (savedKeys.indexOf(key) == -1) {
  6053. savedKeys.push(key);
  6054. saveSavedKeys();
  6055. }
  6056. };
  6057. this.loadValue = function (key, defaultValue) {
  6058. key = typeof key != 'undefined' ? key : defaultValue;
  6059. var value = window.localStorage[prefix + '_' + key];
  6060. return typeof value == "undefined" ? void 0 : JSON.parse(value);
  6061. };
  6062. this.reset = function (key) {
  6063. window.localStorage.removeItem(prefix + '_' + key);
  6064. };
  6065. this.resetAll = function () {
  6066. for (var index = 0; index < savedKeys.length; index++) {
  6067. window.localStorage.removeItem(prefix + '_' + savedKeys[index]);
  6068. }
  6069. clearSavedKeys();
  6070. };
  6071. }
  6072. (function (StorageClass) {
  6073. function HandsontablePersistentState() {
  6074. var plugin = this;
  6075. this.init = function () {
  6076. var instance = this,
  6077. pluginSettings = instance.getSettings()['persistentState'];
  6078. plugin.enabled = !!(pluginSettings);
  6079. if (!plugin.enabled) {
  6080. removeHooks.call(instance);
  6081. return;
  6082. }
  6083. if (!instance.storage) {
  6084. instance.storage = new StorageClass(instance.rootElement[0].id);
  6085. }
  6086. instance.resetState = plugin.resetValue;
  6087. addHooks.call(instance);
  6088. };
  6089. this.saveValue = function (key, value) {
  6090. var instance = this;
  6091. instance.storage.saveValue(key, value);
  6092. };
  6093. this.loadValue = function (key, saveTo) {
  6094. var instance = this;
  6095. saveTo.value = instance.storage.loadValue(key);
  6096. };
  6097. this.resetValue = function (key) {
  6098. var instance = this;
  6099. if (typeof key != 'undefined') {
  6100. instance.storage.reset(key);
  6101. } else {
  6102. instance.storage.resetAll();
  6103. }
  6104. };
  6105. var hooks = {
  6106. 'persistentStateSave': plugin.saveValue,
  6107. 'persistentStateLoad': plugin.loadValue,
  6108. 'persistentStateReset': plugin.resetValue
  6109. };
  6110. function addHooks() {
  6111. var instance = this;
  6112. for (var hookName in hooks) {
  6113. if (hooks.hasOwnProperty(hookName) && !hookExists.call(instance, hookName)) {
  6114. instance.PluginHooks.add(hookName, hooks[hookName]);
  6115. }
  6116. }
  6117. }
  6118. function removeHooks() {
  6119. var instance = this;
  6120. for (var hookName in hooks) {
  6121. if (hooks.hasOwnProperty(hookName) && hookExists.call(instance, hookName)) {
  6122. instance.PluginHooks.remove(hookName, hooks[hookName]);
  6123. }
  6124. }
  6125. }
  6126. function hookExists(hookName) {
  6127. var instance = this;
  6128. return instance.PluginHooks.hooks.hasOwnProperty(hookName);
  6129. }
  6130. }
  6131. var htPersistentState = new HandsontablePersistentState();
  6132. Handsontable.PluginHooks.add('beforeInit', htPersistentState.init);
  6133. Handsontable.PluginHooks.add('afterUpdateSettings', htPersistentState.init);
  6134. })(Storage);
  6135. /**
  6136. * Handsontable UndoRedo class
  6137. */
  6138. (function(Handsontable){
  6139. Handsontable.UndoRedo = function (instance) {
  6140. var plugin = this;
  6141. this.instance = instance;
  6142. this.doneActions = [];
  6143. this.undoneActions = [];
  6144. this.ignoreNewActions = false;
  6145. instance.addHook("afterChange", function (changes, origin) {
  6146. if(changes){
  6147. var action = new Handsontable.UndoRedo.ChangeAction(changes);
  6148. plugin.done(action);
  6149. }
  6150. });
  6151. instance.addHook("afterCreateRow", function (index, amount) {
  6152. var action = new Handsontable.UndoRedo.CreateRowAction(index, amount);
  6153. plugin.done(action);
  6154. });
  6155. instance.addHook("beforeRemoveRow", function (index, amount) {
  6156. var originalData = plugin.instance.getData();
  6157. index = ( originalData.length + index ) % originalData.length;
  6158. var removedData = originalData.slice(index, index + amount);
  6159. var action = new Handsontable.UndoRedo.RemoveRowAction(index, removedData);
  6160. plugin.done(action);
  6161. });
  6162. instance.addHook("afterCreateCol", function (index, amount) {
  6163. var action = new Handsontable.UndoRedo.CreateColumnAction(index, amount);
  6164. plugin.done(action);
  6165. });
  6166. instance.addHook("beforeRemoveCol", function (index, amount) {
  6167. var originalData = plugin.instance.getData();
  6168. index = ( plugin.instance.countCols() + index ) % plugin.instance.countCols();
  6169. var removedData = [];
  6170. for (var i = 0, len = originalData.length; i < len; i++) {
  6171. removedData[i] = originalData[i].slice(index, index + amount);
  6172. }
  6173. var headers;
  6174. if(Handsontable.helper.isArray(instance.getSettings().colHeaders)){
  6175. headers = instance.getSettings().colHeaders.slice(index, index + removedData.length);
  6176. }
  6177. var action = new Handsontable.UndoRedo.RemoveColumnAction(index, removedData, headers);
  6178. plugin.done(action);
  6179. });
  6180. };
  6181. Handsontable.UndoRedo.prototype.done = function (action) {
  6182. if (!this.ignoreNewActions) {
  6183. this.doneActions.push(action);
  6184. this.undoneActions.length = 0;
  6185. }
  6186. };
  6187. /**
  6188. * Undo operation from current revision
  6189. */
  6190. Handsontable.UndoRedo.prototype.undo = function () {
  6191. if (this.isUndoAvailable()) {
  6192. var action = this.doneActions.pop();
  6193. this.ignoreNewActions = true;
  6194. action.undo(this.instance);
  6195. this.ignoreNewActions = false;
  6196. this.undoneActions.push(action);
  6197. }
  6198. };
  6199. /**
  6200. * Redo operation from current revision
  6201. */
  6202. Handsontable.UndoRedo.prototype.redo = function () {
  6203. if (this.isRedoAvailable()) {
  6204. var action = this.undoneActions.pop();
  6205. this.ignoreNewActions = true;
  6206. action.redo(this.instance);
  6207. this.ignoreNewActions = true;
  6208. this.doneActions.push(action);
  6209. }
  6210. };
  6211. /**
  6212. * Returns true if undo point is available
  6213. * @return {Boolean}
  6214. */
  6215. Handsontable.UndoRedo.prototype.isUndoAvailable = function () {
  6216. return this.doneActions.length > 0;
  6217. };
  6218. /**
  6219. * Returns true if redo point is available
  6220. * @return {Boolean}
  6221. */
  6222. Handsontable.UndoRedo.prototype.isRedoAvailable = function () {
  6223. return this.undoneActions.length > 0;
  6224. };
  6225. /**
  6226. * Clears undo history
  6227. */
  6228. Handsontable.UndoRedo.prototype.clear = function () {
  6229. this.doneActions.length = 0;
  6230. this.undoneActions.length = 0;
  6231. };
  6232. Handsontable.UndoRedo.Action = function () {
  6233. };
  6234. Handsontable.UndoRedo.Action.prototype.undo = function () {
  6235. };
  6236. Handsontable.UndoRedo.Action.prototype.redo = function () {
  6237. };
  6238. Handsontable.UndoRedo.ChangeAction = function (changes) {
  6239. this.changes = changes;
  6240. };
  6241. Handsontable.helper.inherit(Handsontable.UndoRedo.ChangeAction, Handsontable.UndoRedo.Action);
  6242. Handsontable.UndoRedo.ChangeAction.prototype.undo = function (instance) {
  6243. var data = $.extend(true, [], this.changes);
  6244. for (var i = 0, len = data.length; i < len; i++) {
  6245. data[i].splice(3, 1);
  6246. }
  6247. instance.setDataAtRowProp(data, null, null, 'undo');
  6248. };
  6249. Handsontable.UndoRedo.ChangeAction.prototype.redo = function (instance) {
  6250. var data = $.extend(true, [], this.changes);
  6251. for (var i = 0, len = data.length; i < len; i++) {
  6252. data[i].splice(2, 1);
  6253. }
  6254. instance.setDataAtRowProp(data, null, null, 'redo');
  6255. };
  6256. Handsontable.UndoRedo.CreateRowAction = function (index, amount) {
  6257. this.index = index;
  6258. this.amount = amount;
  6259. };
  6260. Handsontable.helper.inherit(Handsontable.UndoRedo.CreateRowAction, Handsontable.UndoRedo.Action);
  6261. Handsontable.UndoRedo.CreateRowAction.prototype.undo = function (instance) {
  6262. instance.alter('remove_row', this.index, this.amount);
  6263. };
  6264. Handsontable.UndoRedo.CreateRowAction.prototype.redo = function (instance) {
  6265. instance.alter('insert_row', this.index + 1, this.amount);
  6266. };
  6267. Handsontable.UndoRedo.RemoveRowAction = function (index, data) {
  6268. this.index = index;
  6269. this.data = data;
  6270. };
  6271. Handsontable.helper.inherit(Handsontable.UndoRedo.RemoveRowAction, Handsontable.UndoRedo.Action);
  6272. Handsontable.UndoRedo.RemoveRowAction.prototype.undo = function (instance) {
  6273. var spliceArgs = [this.index, 0];
  6274. Array.prototype.push.apply(spliceArgs, this.data);
  6275. Array.prototype.splice.apply(instance.getData(), spliceArgs);
  6276. instance.render();
  6277. };
  6278. Handsontable.UndoRedo.RemoveRowAction.prototype.redo = function (instance) {
  6279. instance.alter('remove_row', this.index, this.data.length);
  6280. };
  6281. Handsontable.UndoRedo.CreateColumnAction = function (index, amount) {
  6282. this.index = index;
  6283. this.amount = amount;
  6284. };
  6285. Handsontable.helper.inherit(Handsontable.UndoRedo.CreateColumnAction, Handsontable.UndoRedo.Action);
  6286. Handsontable.UndoRedo.CreateColumnAction.prototype.undo = function (instance) {
  6287. instance.alter('remove_col', this.index, this.amount);
  6288. };
  6289. Handsontable.UndoRedo.CreateColumnAction.prototype.redo = function (instance) {
  6290. instance.alter('insert_col', this.index + 1, this.amount);
  6291. };
  6292. Handsontable.UndoRedo.RemoveColumnAction = function (index, data, headers) {
  6293. this.index = index;
  6294. this.data = data;
  6295. this.amount = this.data[0].length;
  6296. this.headers = headers;
  6297. };
  6298. Handsontable.helper.inherit(Handsontable.UndoRedo.RemoveColumnAction, Handsontable.UndoRedo.Action);
  6299. Handsontable.UndoRedo.RemoveColumnAction.prototype.undo = function (instance) {
  6300. var row, spliceArgs;
  6301. for (var i = 0, len = instance.getData().length; i < len; i++) {
  6302. row = instance.getDataAtRow(i);
  6303. spliceArgs = [this.index, 0];
  6304. Array.prototype.push.apply(spliceArgs, this.data[i]);
  6305. Array.prototype.splice.apply(row, spliceArgs);
  6306. }
  6307. if(typeof this.headers != 'undefined'){
  6308. spliceArgs = [this.index, 0];
  6309. Array.prototype.push.apply(spliceArgs, this.headers);
  6310. Array.prototype.splice.apply(instance.getSettings().colHeaders, spliceArgs);
  6311. }
  6312. instance.render();
  6313. };
  6314. Handsontable.UndoRedo.RemoveColumnAction.prototype.redo = function (instance) {
  6315. instance.alter('remove_col', this.index, this.amount);
  6316. };
  6317. })(Handsontable);
  6318. (function(Handsontable){
  6319. function init(){
  6320. var instance = this;
  6321. var pluginEnabled = typeof instance.getSettings().undo == 'undefined' || instance.getSettings().undo;
  6322. if(pluginEnabled){
  6323. if(!instance.undoRedo){
  6324. instance.undoRedo = new Handsontable.UndoRedo(instance);
  6325. exposeUndoRedoMethods(instance);
  6326. instance.addHook('beforeKeyDown', onBeforeKeyDown);
  6327. instance.addHook('afterChange', onAfterChange);
  6328. }
  6329. } else {
  6330. if(instance.undoRedo){
  6331. delete instance.undoRedo;
  6332. removeExposedUndoRedoMethods(instance);
  6333. instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  6334. instance.removeHook('afterChange', onAfterChange);
  6335. }
  6336. }
  6337. }
  6338. function onBeforeKeyDown(event){
  6339. var instance = this;
  6340. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey;
  6341. if(ctrlDown){
  6342. if (event.keyCode === 89 || (event.shiftKey && event.keyCode === 90)) { //CTRL + Y or CTRL + SHIFT + Z
  6343. instance.undoRedo.redo();
  6344. event.stopImmediatePropagation();
  6345. }
  6346. else if (event.keyCode === 90) { //CTRL + Z
  6347. instance.undoRedo.undo();
  6348. event.stopImmediatePropagation();
  6349. }
  6350. }
  6351. }
  6352. function onAfterChange(changes, source){
  6353. var instance = this;
  6354. if (source == 'loadData'){
  6355. return instance.undoRedo.clear();
  6356. }
  6357. }
  6358. function exposeUndoRedoMethods(instance){
  6359. instance.undo = function(){
  6360. return instance.undoRedo.undo();
  6361. };
  6362. instance.redo = function(){
  6363. return instance.undoRedo.redo();
  6364. };
  6365. instance.isUndoAvailable = function(){
  6366. return instance.undoRedo.isUndoAvailable();
  6367. };
  6368. instance.isRedoAvailable = function(){
  6369. return instance.undoRedo.isRedoAvailable();
  6370. };
  6371. instance.clearUndo = function(){
  6372. return instance.undoRedo.clear();
  6373. };
  6374. }
  6375. function removeExposedUndoRedoMethods(instance){
  6376. delete instance.undo;
  6377. delete instance.redo;
  6378. delete instance.isUndoAvailable;
  6379. delete instance.isRedoAvailable;
  6380. delete instance.clearUndo;
  6381. }
  6382. Handsontable.PluginHooks.add('afterInit', init);
  6383. Handsontable.PluginHooks.add('afterUpdateSettings', init);
  6384. })(Handsontable);
  6385. /**
  6386. * Plugin used to scroll Handsontable by selecting a cell and dragging outside of visible viewport
  6387. * @constructor
  6388. */
  6389. function DragToScroll() {
  6390. this.boundaries = null;
  6391. this.callback = null;
  6392. }
  6393. /**
  6394. * @param boundaries {Object} compatible with getBoundingClientRect
  6395. */
  6396. DragToScroll.prototype.setBoundaries = function (boundaries) {
  6397. this.boundaries = boundaries;
  6398. };
  6399. /**
  6400. * @param callback {Function}
  6401. */
  6402. DragToScroll.prototype.setCallback = function (callback) {
  6403. this.callback = callback;
  6404. };
  6405. /**
  6406. * Check if mouse position (x, y) is outside of the viewport
  6407. * @param x
  6408. * @param y
  6409. */
  6410. DragToScroll.prototype.check = function (x, y) {
  6411. var diffX = 0;
  6412. var diffY = 0;
  6413. if (y < this.boundaries.top) {
  6414. //y is less than top
  6415. diffY = y - this.boundaries.top;
  6416. }
  6417. else if (y > this.boundaries.bottom) {
  6418. //y is more than bottom
  6419. diffY = y - this.boundaries.bottom;
  6420. }
  6421. if (x < this.boundaries.left) {
  6422. //x is less than left
  6423. diffX = x - this.boundaries.left;
  6424. }
  6425. else if (x > this.boundaries.right) {
  6426. //x is more than right
  6427. diffX = x - this.boundaries.right;
  6428. }
  6429. this.callback(diffX, diffY);
  6430. };
  6431. var listening = false;
  6432. var dragToScroll;
  6433. var instance;
  6434. if (typeof Handsontable !== 'undefined') {
  6435. var setupListening = function (instance) {
  6436. var scrollHandler = instance.view.wt.wtScrollbars.vertical.scrollHandler; //native scroll
  6437. dragToScroll = new DragToScroll();
  6438. if (scrollHandler === window) {
  6439. //not much we can do currently
  6440. return;
  6441. }
  6442. else if (scrollHandler) {
  6443. dragToScroll.setBoundaries(scrollHandler.getBoundingClientRect());
  6444. }
  6445. else {
  6446. dragToScroll.setBoundaries(instance.$table[0].getBoundingClientRect());
  6447. }
  6448. dragToScroll.setCallback(function (scrollX, scrollY) {
  6449. if (scrollX < 0) {
  6450. if (scrollHandler) {
  6451. scrollHandler.scrollLeft -= 50;
  6452. }
  6453. else {
  6454. instance.view.wt.scrollHorizontal(-1).draw();
  6455. }
  6456. }
  6457. else if (scrollX > 0) {
  6458. if (scrollHandler) {
  6459. scrollHandler.scrollLeft += 50;
  6460. }
  6461. else {
  6462. instance.view.wt.scrollHorizontal(1).draw();
  6463. }
  6464. }
  6465. if (scrollY < 0) {
  6466. if (scrollHandler) {
  6467. scrollHandler.scrollTop -= 20;
  6468. }
  6469. else {
  6470. instance.view.wt.scrollVertical(-1).draw();
  6471. }
  6472. }
  6473. else if (scrollY > 0) {
  6474. if (scrollHandler) {
  6475. scrollHandler.scrollTop += 20;
  6476. }
  6477. else {
  6478. instance.view.wt.scrollVertical(1).draw();
  6479. }
  6480. }
  6481. });
  6482. };
  6483. Handsontable.PluginHooks.add('afterInit', function () {
  6484. $(document).on('mouseup.' + this.guid, function () {
  6485. listening = false;
  6486. });
  6487. $(document).on('mousemove.' + this.guid, function (event) {
  6488. if (listening) {
  6489. dragToScroll.check(event.clientX, event.clientY);
  6490. }
  6491. });
  6492. });
  6493. Handsontable.PluginHooks.add('destroy', function () {
  6494. $(document).off('.' + this.guid);
  6495. });
  6496. Handsontable.PluginHooks.add('afterOnCellMouseDown', function () {
  6497. listening = true;
  6498. setupListening(this);
  6499. });
  6500. Handsontable.PluginHooks.add('afterOnCellCornerMouseDown', function () {
  6501. listening = true;
  6502. setupListening(this);
  6503. });
  6504. }
  6505. /*
  6506. * jQuery.fn.autoResize 1.1+
  6507. * --
  6508. * https://github.com/warpech/jQuery.fn.autoResize
  6509. *
  6510. * This fork differs from others in a way that it autoresizes textarea in 2-dimensions (horizontally and vertically).
  6511. * It was originally forked from alexbardas's repo but maybe should be merged with dpashkevich's repo in future.
  6512. *
  6513. * originally forked from:
  6514. * https://github.com/jamespadolsey/jQuery.fn.autoResize
  6515. * which is now located here:
  6516. * https://github.com/alexbardas/jQuery.fn.autoResize
  6517. * though the mostly maintained for is here:
  6518. * https://github.com/dpashkevich/jQuery.fn.autoResize/network
  6519. *
  6520. * --
  6521. * This program is free software. It comes without any warranty, to
  6522. * the extent permitted by applicable law. You can redistribute it
  6523. * and/or modify it under the terms of the Do What The Fuck You Want
  6524. * To Public License, Version 2, as published by Sam Hocevar. See
  6525. * http://sam.zoy.org/wtfpl/COPYING for more details. */
  6526. (function($){
  6527. autoResize.defaults = {
  6528. onResize: function(){},
  6529. animate: {
  6530. duration: 200,
  6531. complete: function(){}
  6532. },
  6533. extraSpace: 50,
  6534. minHeight: 'original',
  6535. maxHeight: 500,
  6536. minWidth: 'original',
  6537. maxWidth: 500
  6538. };
  6539. autoResize.cloneCSSProperties = [
  6540. 'lineHeight', 'textDecoration', 'letterSpacing',
  6541. 'fontSize', 'fontFamily', 'fontStyle', 'fontWeight',
  6542. 'textTransform', 'textAlign', 'direction', 'wordSpacing', 'fontSizeAdjust',
  6543. 'padding'
  6544. ];
  6545. autoResize.cloneCSSValues = {
  6546. position: 'absolute',
  6547. top: -9999,
  6548. left: -9999,
  6549. opacity: 0,
  6550. overflow: 'hidden',
  6551. overflowX: 'hidden',
  6552. overflowY: 'hidden',
  6553. border: '1px solid black',
  6554. padding: '0.49em' //this must be about the width of caps W character
  6555. };
  6556. autoResize.resizableFilterSelector = 'textarea,input:not(input[type]),input[type=text],input[type=password]';
  6557. autoResize.AutoResizer = AutoResizer;
  6558. $.fn.autoResize = autoResize;
  6559. function autoResize(config) {
  6560. this.filter(autoResize.resizableFilterSelector).each(function(){
  6561. new AutoResizer( $(this), config );
  6562. });
  6563. return this;
  6564. }
  6565. function AutoResizer(el, config) {
  6566. if(this.clones) return;
  6567. this.config = $.extend({}, autoResize.defaults, config);
  6568. this.el = el;
  6569. this.nodeName = el[0].nodeName.toLowerCase();
  6570. this.previousScrollTop = null;
  6571. if (config.maxWidth === 'original') config.maxWidth = el.width();
  6572. if (config.minWidth === 'original') config.minWidth = el.width();
  6573. if (config.maxHeight === 'original') config.maxHeight = el.height();
  6574. if (config.minHeight === 'original') config.minHeight = el.height();
  6575. if (this.nodeName === 'textarea') {
  6576. el.css({
  6577. resize: 'none',
  6578. overflowY: 'none'
  6579. });
  6580. }
  6581. el.data('AutoResizer', this);
  6582. this.createClone();
  6583. this.injectClone();
  6584. this.bind();
  6585. }
  6586. AutoResizer.prototype = {
  6587. bind: function() {
  6588. var check = $.proxy(function(){
  6589. this.check();
  6590. return true;
  6591. }, this);
  6592. this.unbind();
  6593. this.el
  6594. .bind('keyup.autoResize', check)
  6595. //.bind('keydown.autoResize', check)
  6596. .bind('change.autoResize', check);
  6597. this.check(null, true);
  6598. },
  6599. unbind: function() {
  6600. this.el.unbind('.autoResize');
  6601. },
  6602. createClone: function() {
  6603. var el = this.el,
  6604. self = this,
  6605. config = this.config;
  6606. this.clones = $();
  6607. if (config.minHeight !== 'original' || config.maxHeight !== 'original') {
  6608. this.hClone = el.clone().height('auto');
  6609. this.clones = this.clones.add(this.hClone);
  6610. }
  6611. if (config.minWidth !== 'original' || config.maxWidth !== 'original') {
  6612. this.wClone = $('<div/>').width('auto').css({
  6613. whiteSpace: 'nowrap',
  6614. 'float': 'left'
  6615. });
  6616. this.clones = this.clones.add(this.wClone);
  6617. }
  6618. $.each(autoResize.cloneCSSProperties, function(i, p){
  6619. self.clones.css(p, el.css(p));
  6620. });
  6621. this.clones
  6622. .removeAttr('name')
  6623. .removeAttr('id')
  6624. .attr('tabIndex', -1)
  6625. .css(autoResize.cloneCSSValues)
  6626. .css('overflowY', 'scroll');
  6627. },
  6628. check: function(e, immediate) {
  6629. var config = this.config,
  6630. wClone = this.wClone,
  6631. hClone = this.hClone,
  6632. el = this.el,
  6633. value = el.val();
  6634. if (wClone) {
  6635. wClone.text(value);
  6636. // Calculate new width + whether to change
  6637. var cloneWidth = wClone.outerWidth(),
  6638. newWidth = (cloneWidth + config.extraSpace) >= config.minWidth ?
  6639. cloneWidth + config.extraSpace : config.minWidth,
  6640. currentWidth = el.width();
  6641. newWidth = Math.min(newWidth, config.maxWidth);
  6642. if (
  6643. (newWidth < currentWidth && newWidth >= config.minWidth) ||
  6644. (newWidth >= config.minWidth && newWidth <= config.maxWidth)
  6645. ) {
  6646. config.onResize.call(el);
  6647. el.scrollLeft(0);
  6648. config.animate && !immediate ?
  6649. el.stop(1,1).animate({
  6650. width: newWidth
  6651. }, config.animate)
  6652. : el.width(newWidth);
  6653. }
  6654. }
  6655. if (hClone) {
  6656. if (newWidth) {
  6657. hClone.width(newWidth);
  6658. }
  6659. hClone.height(0).val(value).scrollTop(10000);
  6660. var scrollTop = hClone[0].scrollTop + config.extraSpace;
  6661. // Don't do anything if scrollTop hasen't changed:
  6662. if (this.previousScrollTop === scrollTop) {
  6663. return;
  6664. }
  6665. this.previousScrollTop = scrollTop;
  6666. if (scrollTop >= config.maxHeight) {
  6667. scrollTop = config.maxHeight;
  6668. }
  6669. if (scrollTop < config.minHeight) {
  6670. scrollTop = config.minHeight;
  6671. }
  6672. if(scrollTop == config.maxHeight && newWidth == config.maxWidth) {
  6673. el.css('overflowY', 'scroll');
  6674. }
  6675. else {
  6676. el.css('overflowY', 'hidden');
  6677. }
  6678. config.onResize.call(el);
  6679. // Either animate or directly apply height:
  6680. config.animate && !immediate ?
  6681. el.stop(1,1).animate({
  6682. height: scrollTop
  6683. }, config.animate)
  6684. : el.height(scrollTop);
  6685. }
  6686. },
  6687. destroy: function() {
  6688. this.unbind();
  6689. this.el.removeData('AutoResizer');
  6690. this.clones.remove();
  6691. delete this.el;
  6692. delete this.hClone;
  6693. delete this.wClone;
  6694. delete this.clones;
  6695. },
  6696. injectClone: function() {
  6697. (
  6698. autoResize.cloneContainer ||
  6699. (autoResize.cloneContainer = $('<arclones/>').appendTo('body'))
  6700. ).empty().append(this.clones); //this should be refactored so that a node is never cloned more than once
  6701. }
  6702. };
  6703. })(jQuery);
  6704. /**
  6705. * SheetClip - Spreadsheet Clipboard Parser
  6706. * version 0.2
  6707. *
  6708. * This tiny library transforms JavaScript arrays to strings that are pasteable by LibreOffice, OpenOffice,
  6709. * Google Docs and Microsoft Excel.
  6710. *
  6711. * Copyright 2012, Marcin Warpechowski
  6712. * Licensed under the MIT license.
  6713. * http://github.com/warpech/sheetclip/
  6714. */
  6715. /*jslint white: true*/
  6716. (function (global) {
  6717. "use strict";
  6718. function countQuotes(str) {
  6719. return str.split('"').length - 1;
  6720. }
  6721. global.SheetClip = {
  6722. parse: function (str) {
  6723. var r, rlen, rows, arr = [], a = 0, c, clen, multiline, last;
  6724. rows = str.split('\n');
  6725. if (rows.length > 1 && rows[rows.length - 1] === '') {
  6726. rows.pop();
  6727. }
  6728. for (r = 0, rlen = rows.length; r < rlen; r += 1) {
  6729. rows[r] = rows[r].split('\t');
  6730. for (c = 0, clen = rows[r].length; c < clen; c += 1) {
  6731. if (!arr[a]) {
  6732. arr[a] = [];
  6733. }
  6734. if (multiline && c === 0) {
  6735. last = arr[a].length - 1;
  6736. arr[a][last] = arr[a][last] + '\n' + rows[r][0];
  6737. if (multiline && (countQuotes(rows[r][0]) & 1)) { //& 1 is a bitwise way of performing mod 2
  6738. multiline = false;
  6739. arr[a][last] = arr[a][last].substring(0, arr[a][last].length - 1).replace(/""/g, '"');
  6740. }
  6741. }
  6742. else {
  6743. if (c === clen - 1 && rows[r][c].indexOf('"') === 0) {
  6744. arr[a].push(rows[r][c].substring(1).replace(/""/g, '"'));
  6745. multiline = true;
  6746. }
  6747. else {
  6748. arr[a].push(rows[r][c].replace(/""/g, '"'));
  6749. multiline = false;
  6750. }
  6751. }
  6752. }
  6753. if (!multiline) {
  6754. a += 1;
  6755. }
  6756. }
  6757. return arr;
  6758. },
  6759. stringify: function (arr) {
  6760. var r, rlen, c, clen, str = '', val;
  6761. for (r = 0, rlen = arr.length; r < rlen; r += 1) {
  6762. for (c = 0, clen = arr[r].length; c < clen; c += 1) {
  6763. if (c > 0) {
  6764. str += '\t';
  6765. }
  6766. val = arr[r][c];
  6767. if (typeof val === 'string') {
  6768. if (val.indexOf('\n') > -1) {
  6769. str += '"' + val.replace(/"/g, '""') + '"';
  6770. }
  6771. else {
  6772. str += val;
  6773. }
  6774. }
  6775. else if (val === null || val === void 0) { //void 0 resolves to undefined
  6776. str += '';
  6777. }
  6778. else {
  6779. str += val;
  6780. }
  6781. }
  6782. str += '\n';
  6783. }
  6784. return str;
  6785. }
  6786. };
  6787. }(window));
  6788. /**
  6789. * CopyPaste.js
  6790. * Creates a textarea that stays hidden on the page and gets focused when user presses CTRL while not having a form input focused
  6791. * In future we may implement a better driver when better APIs are available
  6792. * @constructor
  6793. */
  6794. var CopyPaste = (function () {
  6795. var instance;
  6796. return {
  6797. getInstance: function () {
  6798. if (!instance) {
  6799. instance = new CopyPasteClass();
  6800. }
  6801. return instance;
  6802. }
  6803. };
  6804. })();
  6805. function CopyPasteClass() {
  6806. var that = this
  6807. , style
  6808. , parent;
  6809. this.copyCallbacks = [];
  6810. this.cutCallbacks = [];
  6811. this.pasteCallbacks = [];
  6812. var listenerElement = document.documentElement;
  6813. parent = document.body;
  6814. if (document.getElementById('CopyPasteDiv')) {
  6815. this.elDiv = document.getElementById('CopyPasteDiv');
  6816. this.elTextarea = this.elDiv.firstChild;
  6817. }
  6818. else {
  6819. this.elDiv = document.createElement('DIV');
  6820. this.elDiv.id = 'CopyPasteDiv';
  6821. style = this.elDiv.style;
  6822. style.position = 'fixed';
  6823. style.top = 0;
  6824. style.left = 0;
  6825. parent.appendChild(this.elDiv);
  6826. this.elTextarea = document.createElement('TEXTAREA');
  6827. this.elTextarea.className = 'copyPaste';
  6828. style = this.elTextarea.style;
  6829. style.width = '1px';
  6830. style.height = '1px';
  6831. this.elDiv.appendChild(this.elTextarea);
  6832. if (typeof style.opacity !== 'undefined') {
  6833. style.opacity = 0;
  6834. }
  6835. else {
  6836. /*@cc_on @if (@_jscript)
  6837. if(typeof style.filter === 'string') {
  6838. style.filter = 'alpha(opacity=0)';
  6839. }
  6840. @end @*/
  6841. }
  6842. }
  6843. this._bindEvent(listenerElement, 'keydown', function (event) {
  6844. var isCtrlDown = false;
  6845. if (event.metaKey) { //mac
  6846. isCtrlDown = true;
  6847. }
  6848. else if (event.ctrlKey && navigator.userAgent.indexOf('Mac') === -1) { //pc
  6849. isCtrlDown = true;
  6850. }
  6851. if (isCtrlDown) {
  6852. if (document.activeElement !== that.elTextarea && (that.getSelectionText() != '' || ['INPUT', 'SELECT', 'TEXTAREA'].indexOf(document.activeElement.nodeName) != -1)) {
  6853. return; //this is needed by fragmentSelection in Handsontable. Ignore copypaste.js behavior if fragment of cell text is selected
  6854. }
  6855. that.selectNodeText(that.elTextarea);
  6856. setTimeout(function () {
  6857. that.selectNodeText(that.elTextarea);
  6858. }, 0);
  6859. }
  6860. /* 67 = c
  6861. * 86 = v
  6862. * 88 = x
  6863. */
  6864. if (isCtrlDown && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88)) {
  6865. // that.selectNodeText(that.elTextarea);
  6866. if (event.keyCode === 88) { //works in all browsers, incl. Opera < 12.12
  6867. setTimeout(function () {
  6868. that.triggerCut(event);
  6869. }, 0);
  6870. }
  6871. else if (event.keyCode === 86) {
  6872. setTimeout(function () {
  6873. that.triggerPaste(event);
  6874. }, 0);
  6875. }
  6876. }
  6877. });
  6878. }
  6879. //http://jsperf.com/textara-selection
  6880. //http://stackoverflow.com/questions/1502385/how-can-i-make-this-code-work-in-ie
  6881. CopyPasteClass.prototype.selectNodeText = function (el) {
  6882. el.select();
  6883. };
  6884. //http://stackoverflow.com/questions/5379120/get-the-highlighted-selected-text
  6885. CopyPasteClass.prototype.getSelectionText = function () {
  6886. var text = "";
  6887. if (window.getSelection) {
  6888. text = window.getSelection().toString();
  6889. } else if (document.selection && document.selection.type != "Control") {
  6890. text = document.selection.createRange().text;
  6891. }
  6892. return text;
  6893. };
  6894. CopyPasteClass.prototype.copyable = function (str) {
  6895. if (typeof str !== 'string' && str.toString === void 0) {
  6896. throw new Error('copyable requires string parameter');
  6897. }
  6898. this.elTextarea.value = str;
  6899. };
  6900. /*CopyPasteClass.prototype.onCopy = function (fn) {
  6901. this.copyCallbacks.push(fn);
  6902. };*/
  6903. CopyPasteClass.prototype.onCut = function (fn) {
  6904. this.cutCallbacks.push(fn);
  6905. };
  6906. CopyPasteClass.prototype.onPaste = function (fn) {
  6907. this.pasteCallbacks.push(fn);
  6908. };
  6909. CopyPasteClass.prototype.removeCallback = function (fn) {
  6910. var i, ilen;
  6911. for (i = 0, ilen = this.copyCallbacks.length; i < ilen; i++) {
  6912. if (this.copyCallbacks[i] === fn) {
  6913. this.copyCallbacks.splice(i, 1);
  6914. return true;
  6915. }
  6916. }
  6917. for (i = 0, ilen = this.cutCallbacks.length; i < ilen; i++) {
  6918. if (this.cutCallbacks[i] === fn) {
  6919. this.cutCallbacks.splice(i, 1);
  6920. return true;
  6921. }
  6922. }
  6923. for (i = 0, ilen = this.pasteCallbacks.length; i < ilen; i++) {
  6924. if (this.pasteCallbacks[i] === fn) {
  6925. this.pasteCallbacks.splice(i, 1);
  6926. return true;
  6927. }
  6928. }
  6929. return false;
  6930. };
  6931. CopyPasteClass.prototype.triggerCut = function (event) {
  6932. var that = this;
  6933. if (that.cutCallbacks) {
  6934. setTimeout(function () {
  6935. for (var i = 0, ilen = that.cutCallbacks.length; i < ilen; i++) {
  6936. that.cutCallbacks[i](event);
  6937. }
  6938. }, 50);
  6939. }
  6940. };
  6941. CopyPasteClass.prototype.triggerPaste = function (event, str) {
  6942. var that = this;
  6943. if (that.pasteCallbacks) {
  6944. setTimeout(function () {
  6945. var val = (str || that.elTextarea.value).replace(/\n$/, ''); //remove trailing newline
  6946. for (var i = 0, ilen = that.pasteCallbacks.length; i < ilen; i++) {
  6947. that.pasteCallbacks[i](val, event);
  6948. }
  6949. }, 50);
  6950. }
  6951. };
  6952. //old version used this:
  6953. // - http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/
  6954. // - http://stackoverflow.com/questions/4643249/cross-browser-event-object-normalization
  6955. //but that cannot work with jQuery.trigger
  6956. CopyPasteClass.prototype._bindEvent = (function () {
  6957. if (window.jQuery) { //if jQuery exists, use jQuery event (for compatibility with $.trigger and $.triggerHandler, which can only trigger jQuery events - and we use that in tests)
  6958. return function (elem, type, cb) {
  6959. $(elem).on(type + '.copypaste', cb);
  6960. };
  6961. }
  6962. else {
  6963. return function (elem, type, cb) {
  6964. elem.addEventListener(type, cb, false); //sorry, IE8 will only work with jQuery
  6965. };
  6966. }
  6967. })();
  6968. // json-patch-duplex.js 0.3.5
  6969. // (c) 2013 Joachim Wester
  6970. // MIT license
  6971. var jsonpatch;
  6972. (function (jsonpatch) {
  6973. var objOps = {
  6974. add: function (obj, key) {
  6975. obj[key] = this.value;
  6976. return true;
  6977. },
  6978. remove: function (obj, key) {
  6979. delete obj[key];
  6980. return true;
  6981. },
  6982. replace: function (obj, key) {
  6983. obj[key] = this.value;
  6984. return true;
  6985. },
  6986. move: function (obj, key, tree) {
  6987. var temp = { op: "_get", path: this.from };
  6988. apply(tree, [temp]);
  6989. apply(tree, [
  6990. { op: "remove", path: this.from }
  6991. ]);
  6992. apply(tree, [
  6993. { op: "add", path: this.path, value: temp.value }
  6994. ]);
  6995. return true;
  6996. },
  6997. copy: function (obj, key, tree) {
  6998. var temp = { op: "_get", path: this.from };
  6999. apply(tree, [temp]);
  7000. apply(tree, [
  7001. { op: "add", path: this.path, value: temp.value }
  7002. ]);
  7003. return true;
  7004. },
  7005. test: function (obj, key) {
  7006. return (JSON.stringify(obj[key]) === JSON.stringify(this.value));
  7007. },
  7008. _get: function (obj, key) {
  7009. this.value = obj[key];
  7010. }
  7011. };
  7012. var arrOps = {
  7013. add: function (arr, i) {
  7014. arr.splice(i, 0, this.value);
  7015. return true;
  7016. },
  7017. remove: function (arr, i) {
  7018. arr.splice(i, 1);
  7019. return true;
  7020. },
  7021. replace: function (arr, i) {
  7022. arr[i] = this.value;
  7023. return true;
  7024. },
  7025. move: objOps.move,
  7026. copy: objOps.copy,
  7027. test: objOps.test,
  7028. _get: objOps._get
  7029. };
  7030. var observeOps = {
  7031. 'new': function (patches, path) {
  7032. var patch = {
  7033. op: "add",
  7034. path: path + escapePathComponent(this.name),
  7035. value: this.object[this.name]
  7036. };
  7037. patches.push(patch);
  7038. },
  7039. deleted: function (patches, path) {
  7040. var patch = {
  7041. op: "remove",
  7042. path: path + escapePathComponent(this.name)
  7043. };
  7044. patches.push(patch);
  7045. },
  7046. updated: function (patches, path) {
  7047. var patch = {
  7048. op: "replace",
  7049. path: path + escapePathComponent(this.name),
  7050. value: this.object[this.name]
  7051. };
  7052. patches.push(patch);
  7053. }
  7054. };
  7055. function escapePathComponent(str) {
  7056. if (str.indexOf('/') === -1 && str.indexOf('~') === -1)
  7057. return str;
  7058. return str.replace(/~/g, '~0').replace(/\//g, '~1');
  7059. }
  7060. function _getPathRecursive(root, obj) {
  7061. var found;
  7062. for (var key in root) {
  7063. if (root.hasOwnProperty(key)) {
  7064. if (root[key] === obj) {
  7065. return escapePathComponent(key) + '/';
  7066. } else if (typeof root[key] === 'object') {
  7067. found = _getPathRecursive(root[key], obj);
  7068. if (found != '') {
  7069. return escapePathComponent(key) + '/' + found;
  7070. }
  7071. }
  7072. }
  7073. }
  7074. return '';
  7075. }
  7076. function getPath(root, obj) {
  7077. if (root === obj) {
  7078. return '/';
  7079. }
  7080. var path = _getPathRecursive(root, obj);
  7081. if (path === '') {
  7082. throw new Error("Object not found in root");
  7083. }
  7084. return '/' + path;
  7085. }
  7086. var beforeDict = [];
  7087. jsonpatch.intervals;
  7088. var Mirror = (function () {
  7089. function Mirror(obj) {
  7090. this.observers = [];
  7091. this.obj = obj;
  7092. }
  7093. return Mirror;
  7094. })();
  7095. var ObserverInfo = (function () {
  7096. function ObserverInfo(callback, observer) {
  7097. this.callback = callback;
  7098. this.observer = observer;
  7099. }
  7100. return ObserverInfo;
  7101. })();
  7102. function getMirror(obj) {
  7103. for (var i = 0, ilen = beforeDict.length; i < ilen; i++) {
  7104. if (beforeDict[i].obj === obj) {
  7105. return beforeDict[i];
  7106. }
  7107. }
  7108. }
  7109. function getObserverFromMirror(mirror, callback) {
  7110. for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) {
  7111. if (mirror.observers[j].callback === callback) {
  7112. return mirror.observers[j].observer;
  7113. }
  7114. }
  7115. }
  7116. function removeObserverFromMirror(mirror, observer) {
  7117. for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) {
  7118. if (mirror.observers[j].observer === observer) {
  7119. mirror.observers.splice(j, 1);
  7120. return;
  7121. }
  7122. }
  7123. }
  7124. function unobserve(root, observer) {
  7125. generate(observer);
  7126. if (Object.observe) {
  7127. _unobserve(observer, root);
  7128. } else {
  7129. clearTimeout(observer.next);
  7130. }
  7131. var mirror = getMirror(root);
  7132. removeObserverFromMirror(mirror, observer);
  7133. }
  7134. jsonpatch.unobserve = unobserve;
  7135. function observe(obj, callback) {
  7136. var patches = [];
  7137. var root = obj;
  7138. var observer;
  7139. var mirror = getMirror(obj);
  7140. if (!mirror) {
  7141. mirror = new Mirror(obj);
  7142. beforeDict.push(mirror);
  7143. } else {
  7144. observer = getObserverFromMirror(mirror, callback);
  7145. }
  7146. if (observer) {
  7147. return observer;
  7148. }
  7149. if (Object.observe) {
  7150. observer = function (arr) {
  7151. //This "refresh" is needed to begin observing new object properties
  7152. _unobserve(observer, obj);
  7153. _observe(observer, obj);
  7154. var a = 0, alen = arr.length;
  7155. while (a < alen) {
  7156. if (!(arr[a].name === 'length' && _isArray(arr[a].object)) && !(arr[a].name === '__Jasmine_been_here_before__')) {
  7157. observeOps[arr[a].type].call(arr[a], patches, getPath(root, arr[a].object));
  7158. }
  7159. a++;
  7160. }
  7161. if (patches) {
  7162. if (callback) {
  7163. callback(patches);
  7164. }
  7165. }
  7166. observer.patches = patches;
  7167. patches = [];
  7168. };
  7169. } else {
  7170. observer = {};
  7171. mirror.value = JSON.parse(JSON.stringify(obj));
  7172. if (callback) {
  7173. //callbacks.push(callback); this has no purpose
  7174. observer.callback = callback;
  7175. observer.next = null;
  7176. var intervals = this.intervals || [100, 1000, 10000, 60000];
  7177. var currentInterval = 0;
  7178. var dirtyCheck = function () {
  7179. generate(observer);
  7180. };
  7181. var fastCheck = function () {
  7182. clearTimeout(observer.next);
  7183. observer.next = setTimeout(function () {
  7184. dirtyCheck();
  7185. currentInterval = 0;
  7186. observer.next = setTimeout(slowCheck, intervals[currentInterval++]);
  7187. }, 0);
  7188. };
  7189. var slowCheck = function () {
  7190. dirtyCheck();
  7191. if (currentInterval == intervals.length)
  7192. currentInterval = intervals.length - 1;
  7193. observer.next = setTimeout(slowCheck, intervals[currentInterval++]);
  7194. };
  7195. if (typeof window !== 'undefined') {
  7196. if (window.addEventListener) {
  7197. window.addEventListener('mousedown', fastCheck);
  7198. window.addEventListener('mouseup', fastCheck);
  7199. window.addEventListener('keydown', fastCheck);
  7200. } else {
  7201. window.attachEvent('onmousedown', fastCheck);
  7202. window.attachEvent('onmouseup', fastCheck);
  7203. window.attachEvent('onkeydown', fastCheck);
  7204. }
  7205. }
  7206. observer.next = setTimeout(slowCheck, intervals[currentInterval++]);
  7207. }
  7208. }
  7209. observer.patches = patches;
  7210. observer.object = obj;
  7211. mirror.observers.push(new ObserverInfo(callback, observer));
  7212. return _observe(observer, obj);
  7213. }
  7214. jsonpatch.observe = observe;
  7215. /// Listen to changes on an object tree, accumulate patches
  7216. function _observe(observer, obj) {
  7217. if (Object.observe) {
  7218. Object.observe(obj, observer);
  7219. for (var key in obj) {
  7220. if (obj.hasOwnProperty(key)) {
  7221. var v = obj[key];
  7222. if (v && typeof (v) === "object") {
  7223. _observe(observer, v);
  7224. }
  7225. }
  7226. }
  7227. }
  7228. return observer;
  7229. }
  7230. function _unobserve(observer, obj) {
  7231. if (Object.observe) {
  7232. Object.unobserve(obj, observer);
  7233. for (var key in obj) {
  7234. if (obj.hasOwnProperty(key)) {
  7235. var v = obj[key];
  7236. if (v && typeof (v) === "object") {
  7237. _unobserve(observer, v);
  7238. }
  7239. }
  7240. }
  7241. }
  7242. return observer;
  7243. }
  7244. function generate(observer) {
  7245. if (Object.observe) {
  7246. Object.deliverChangeRecords(observer);
  7247. } else {
  7248. var mirror;
  7249. for (var i = 0, ilen = beforeDict.length; i < ilen; i++) {
  7250. if (beforeDict[i].obj === observer.object) {
  7251. mirror = beforeDict[i];
  7252. break;
  7253. }
  7254. }
  7255. _generate(mirror.value, observer.object, observer.patches, "");
  7256. }
  7257. var temp = observer.patches;
  7258. if (temp.length > 0) {
  7259. observer.patches = [];
  7260. if (observer.callback) {
  7261. observer.callback(temp);
  7262. }
  7263. }
  7264. return temp;
  7265. }
  7266. jsonpatch.generate = generate;
  7267. var _objectKeys;
  7268. if (Object.keys) {
  7269. _objectKeys = Object.keys;
  7270. } else {
  7271. _objectKeys = function (obj) {
  7272. var keys = [];
  7273. for (var o in obj) {
  7274. if (obj.hasOwnProperty(o)) {
  7275. keys.push(o);
  7276. }
  7277. }
  7278. return keys;
  7279. };
  7280. }
  7281. // Dirty check if obj is different from mirror, generate patches and update mirror
  7282. function _generate(mirror, obj, patches, path) {
  7283. var newKeys = _objectKeys(obj);
  7284. var oldKeys = _objectKeys(mirror);
  7285. var changed = false;
  7286. var deleted = false;
  7287. for (var t = oldKeys.length - 1; t >= 0; t--) {
  7288. var key = oldKeys[t];
  7289. var oldVal = mirror[key];
  7290. if (obj.hasOwnProperty(key)) {
  7291. var newVal = obj[key];
  7292. if (oldVal instanceof Object) {
  7293. _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key));
  7294. } else {
  7295. if (oldVal != newVal) {
  7296. changed = true;
  7297. patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: newVal });
  7298. mirror[key] = newVal;
  7299. }
  7300. }
  7301. } else {
  7302. patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) });
  7303. delete mirror[key];
  7304. deleted = true;
  7305. }
  7306. }
  7307. if (!deleted && newKeys.length == oldKeys.length) {
  7308. return;
  7309. }
  7310. for (var t = 0; t < newKeys.length; t++) {
  7311. var key = newKeys[t];
  7312. if (!mirror.hasOwnProperty(key)) {
  7313. patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: obj[key] });
  7314. mirror[key] = JSON.parse(JSON.stringify(obj[key]));
  7315. }
  7316. }
  7317. }
  7318. var _isArray;
  7319. if (Array.isArray) {
  7320. _isArray = Array.isArray;
  7321. } else {
  7322. _isArray = function (obj) {
  7323. return obj.push && typeof obj.length === 'number';
  7324. };
  7325. }
  7326. /// Apply a json-patch operation on an object tree
  7327. function apply(tree, patches) {
  7328. var result = false, p = 0, plen = patches.length, patch;
  7329. while (p < plen) {
  7330. patch = patches[p];
  7331. // Find the object
  7332. var keys = patch.path.split('/');
  7333. var obj = tree;
  7334. var t = 1;
  7335. var len = keys.length;
  7336. while (true) {
  7337. if (_isArray(obj)) {
  7338. var index = parseInt(keys[t], 10);
  7339. t++;
  7340. if (t >= len) {
  7341. result = arrOps[patch.op].call(patch, obj, index, tree);
  7342. break;
  7343. }
  7344. obj = obj[index];
  7345. } else {
  7346. var key = keys[t];
  7347. if (key.indexOf('~') != -1)
  7348. key = key.replace(/~1/g, '/').replace(/~0/g, '~');
  7349. t++;
  7350. if (t >= len) {
  7351. result = objOps[patch.op].call(patch, obj, key, tree);
  7352. break;
  7353. }
  7354. obj = obj[key];
  7355. }
  7356. }
  7357. p++;
  7358. }
  7359. return result;
  7360. }
  7361. jsonpatch.apply = apply;
  7362. })(jsonpatch || (jsonpatch = {}));
  7363. if (typeof exports !== "undefined") {
  7364. exports.apply = jsonpatch.apply;
  7365. exports.observe = jsonpatch.observe;
  7366. exports.unobserve = jsonpatch.unobserve;
  7367. exports.generate = jsonpatch.generate;
  7368. }
  7369. function WalkontableBorder(instance, settings) {
  7370. var style;
  7371. //reference to instance
  7372. this.instance = instance;
  7373. this.settings = settings;
  7374. this.wtDom = this.instance.wtDom;
  7375. this.main = document.createElement("div");
  7376. style = this.main.style;
  7377. style.position = 'absolute';
  7378. style.top = 0;
  7379. style.left = 0;
  7380. // style.visibility = 'hidden';
  7381. for (var i = 0; i < 5; i++) {
  7382. var DIV = document.createElement('DIV');
  7383. DIV.className = 'wtBorder ' + (settings.className || '');
  7384. style = DIV.style;
  7385. style.backgroundColor = settings.border.color;
  7386. style.height = settings.border.width + 'px';
  7387. style.width = settings.border.width + 'px';
  7388. this.main.appendChild(DIV);
  7389. }
  7390. this.top = this.main.childNodes[0];
  7391. this.left = this.main.childNodes[1];
  7392. this.bottom = this.main.childNodes[2];
  7393. this.right = this.main.childNodes[3];
  7394. /*$(this.top).on(sss, function(event) {
  7395. event.preventDefault();
  7396. event.stopImmediatePropagation();
  7397. $(this).hide();
  7398. });
  7399. $(this.left).on(sss, function(event) {
  7400. event.preventDefault();
  7401. event.stopImmediatePropagation();
  7402. $(this).hide();
  7403. });
  7404. $(this.bottom).on(sss, function(event) {
  7405. event.preventDefault();
  7406. event.stopImmediatePropagation();
  7407. $(this).hide();
  7408. });
  7409. $(this.right).on(sss, function(event) {
  7410. event.preventDefault();
  7411. event.stopImmediatePropagation();
  7412. $(this).hide();
  7413. });*/
  7414. this.topStyle = this.top.style;
  7415. this.leftStyle = this.left.style;
  7416. this.bottomStyle = this.bottom.style;
  7417. this.rightStyle = this.right.style;
  7418. this.corner = this.main.childNodes[4];
  7419. this.corner.className += ' corner';
  7420. this.cornerStyle = this.corner.style;
  7421. this.cornerStyle.width = '5px';
  7422. this.cornerStyle.height = '5px';
  7423. this.cornerStyle.border = '2px solid #FFF';
  7424. this.disappear();
  7425. if (!instance.wtTable.bordersHolder) {
  7426. instance.wtTable.bordersHolder = document.createElement('div');
  7427. instance.wtTable.bordersHolder.className = 'htBorders';
  7428. instance.wtTable.hider.appendChild(instance.wtTable.bordersHolder);
  7429. }
  7430. instance.wtTable.bordersHolder.appendChild(this.main);
  7431. var down = false;
  7432. var $body = $(document.body);
  7433. $body.on('mousedown.walkontable.' + instance.guid, function () {
  7434. down = true;
  7435. });
  7436. $body.on('mouseup.walkontable.' + instance.guid, function () {
  7437. down = false
  7438. });
  7439. $(this.main.childNodes).on('mouseenter', function (event) {
  7440. if (!down || !instance.getSetting('hideBorderOnMouseDownOver')) {
  7441. return;
  7442. }
  7443. event.preventDefault();
  7444. event.stopImmediatePropagation();
  7445. var bounds = this.getBoundingClientRect();
  7446. var $this = $(this);
  7447. $this.hide();
  7448. var isOutside = function (event) {
  7449. if (event.clientY < Math.floor(bounds.top)) {
  7450. return true;
  7451. }
  7452. if (event.clientY > Math.ceil(bounds.top + bounds.height)) {
  7453. return true;
  7454. }
  7455. if (event.clientX < Math.floor(bounds.left)) {
  7456. return true;
  7457. }
  7458. if (event.clientX > Math.ceil(bounds.left + bounds.width)) {
  7459. return true;
  7460. }
  7461. };
  7462. $body.on('mousemove.border.' + instance.guid, function (event) {
  7463. if (isOutside(event)) {
  7464. $body.off('mousemove.border.' + instance.guid);
  7465. $this.show();
  7466. }
  7467. });
  7468. });
  7469. }
  7470. /**
  7471. * Show border around one or many cells
  7472. * @param {Array} corners
  7473. */
  7474. WalkontableBorder.prototype.appear = function (corners) {
  7475. var isMultiple, fromTD, toTD, fromOffset, toOffset, containerOffset, top, minTop, left, minLeft, height, width;
  7476. if (this.disabled) {
  7477. return;
  7478. }
  7479. var instance = this.instance
  7480. , fromRow
  7481. , fromColumn
  7482. , toRow
  7483. , toColumn
  7484. , hideTop = false
  7485. , hideLeft = false
  7486. , hideBottom = false
  7487. , hideRight = false
  7488. , i
  7489. , ilen
  7490. , s;
  7491. if (!instance.wtTable.isRowInViewport(corners[0])) {
  7492. hideTop = true;
  7493. }
  7494. if (!instance.wtTable.isRowInViewport(corners[2])) {
  7495. hideBottom = true;
  7496. }
  7497. ilen = instance.wtTable.rowStrategy.countVisible();
  7498. for (i = 0; i < ilen; i++) {
  7499. s = instance.wtTable.rowFilter.visibleToSource(i);
  7500. if (s >= corners[0] && s <= corners[2]) {
  7501. fromRow = s;
  7502. break;
  7503. }
  7504. }
  7505. for (i = ilen - 1; i >= 0; i--) {
  7506. s = instance.wtTable.rowFilter.visibleToSource(i);
  7507. if (s >= corners[0] && s <= corners[2]) {
  7508. toRow = s;
  7509. break;
  7510. }
  7511. }
  7512. if (hideTop && hideBottom) {
  7513. hideLeft = true;
  7514. hideRight = true;
  7515. }
  7516. else {
  7517. if (!instance.wtTable.isColumnInViewport(corners[1])) {
  7518. hideLeft = true;
  7519. }
  7520. if (!instance.wtTable.isColumnInViewport(corners[3])) {
  7521. hideRight = true;
  7522. }
  7523. ilen = instance.wtTable.columnStrategy.countVisible();
  7524. for (i = 0; i < ilen; i++) {
  7525. s = instance.wtTable.columnFilter.visibleToSource(i);
  7526. if (s >= corners[1] && s <= corners[3]) {
  7527. fromColumn = s;
  7528. break;
  7529. }
  7530. }
  7531. for (i = ilen - 1; i >= 0; i--) {
  7532. s = instance.wtTable.columnFilter.visibleToSource(i);
  7533. if (s >= corners[1] && s <= corners[3]) {
  7534. toColumn = s;
  7535. break;
  7536. }
  7537. }
  7538. }
  7539. if (fromRow !== void 0 && fromColumn !== void 0) {
  7540. isMultiple = (fromRow !== toRow || fromColumn !== toColumn);
  7541. fromTD = instance.wtTable.getCell([fromRow, fromColumn]);
  7542. toTD = isMultiple ? instance.wtTable.getCell([toRow, toColumn]) : fromTD;
  7543. fromOffset = this.wtDom.offset(fromTD);
  7544. toOffset = isMultiple ? this.wtDom.offset(toTD) : fromOffset;
  7545. containerOffset = this.wtDom.offset(instance.wtTable.TABLE);
  7546. minTop = fromOffset.top;
  7547. height = toOffset.top + this.wtDom.outerHeight(toTD) - minTop;
  7548. minLeft = fromOffset.left;
  7549. width = toOffset.left + this.wtDom.outerWidth(toTD) - minLeft;
  7550. top = minTop - containerOffset.top - 1;
  7551. left = minLeft - containerOffset.left - 1;
  7552. var style = this.wtDom.getComputedStyle(fromTD);
  7553. if (parseInt(style['borderTopWidth'], 10) > 0) {
  7554. top += 1;
  7555. height = height > 0 ? height - 1 : 0;
  7556. }
  7557. if (parseInt(style['borderLeftWidth'], 10) > 0) {
  7558. left += 1;
  7559. width = width > 0 ? width - 1 : 0;
  7560. }
  7561. }
  7562. else {
  7563. this.disappear();
  7564. return;
  7565. }
  7566. if (hideTop) {
  7567. this.topStyle.display = 'none';
  7568. }
  7569. else {
  7570. this.topStyle.top = top + 'px';
  7571. this.topStyle.left = left + 'px';
  7572. this.topStyle.width = width + 'px';
  7573. this.topStyle.display = 'block';
  7574. }
  7575. if (hideLeft) {
  7576. this.leftStyle.display = 'none';
  7577. }
  7578. else {
  7579. this.leftStyle.top = top + 'px';
  7580. this.leftStyle.left = left + 'px';
  7581. this.leftStyle.height = height + 'px';
  7582. this.leftStyle.display = 'block';
  7583. }
  7584. var delta = Math.floor(this.settings.border.width / 2);
  7585. if (hideBottom) {
  7586. this.bottomStyle.display = 'none';
  7587. }
  7588. else {
  7589. this.bottomStyle.top = top + height - delta + 'px';
  7590. this.bottomStyle.left = left + 'px';
  7591. this.bottomStyle.width = width + 'px';
  7592. this.bottomStyle.display = 'block';
  7593. }
  7594. if (hideRight) {
  7595. this.rightStyle.display = 'none';
  7596. }
  7597. else {
  7598. this.rightStyle.top = top + 'px';
  7599. this.rightStyle.left = left + width - delta + 'px';
  7600. this.rightStyle.height = height + 1 + 'px';
  7601. this.rightStyle.display = 'block';
  7602. }
  7603. if (hideBottom || hideRight || !this.hasSetting(this.settings.border.cornerVisible)) {
  7604. this.cornerStyle.display = 'none';
  7605. }
  7606. else {
  7607. this.cornerStyle.top = top + height - 4 + 'px';
  7608. this.cornerStyle.left = left + width - 4 + 'px';
  7609. this.cornerStyle.display = 'block';
  7610. }
  7611. };
  7612. /**
  7613. * Hide border
  7614. */
  7615. WalkontableBorder.prototype.disappear = function () {
  7616. this.topStyle.display = 'none';
  7617. this.leftStyle.display = 'none';
  7618. this.bottomStyle.display = 'none';
  7619. this.rightStyle.display = 'none';
  7620. this.cornerStyle.display = 'none';
  7621. };
  7622. WalkontableBorder.prototype.hasSetting = function (setting) {
  7623. if (typeof setting === 'function') {
  7624. return setting();
  7625. }
  7626. return !!setting;
  7627. };
  7628. /**
  7629. * WalkontableCellFilter
  7630. * @constructor
  7631. */
  7632. function WalkontableCellFilter() {
  7633. this.offset = 0;
  7634. this.total = 0;
  7635. this.fixedCount = 0;
  7636. }
  7637. WalkontableCellFilter.prototype.source = function (n) {
  7638. return n;
  7639. };
  7640. WalkontableCellFilter.prototype.offsetted = function (n) {
  7641. return n + this.offset;
  7642. };
  7643. WalkontableCellFilter.prototype.unOffsetted = function (n) {
  7644. return n - this.offset;
  7645. };
  7646. WalkontableCellFilter.prototype.fixed = function (n) {
  7647. if (n < this.fixedCount) {
  7648. return n - this.offset;
  7649. }
  7650. else {
  7651. return n;
  7652. }
  7653. };
  7654. WalkontableCellFilter.prototype.unFixed = function (n) {
  7655. if (n < this.fixedCount) {
  7656. return n + this.offset;
  7657. }
  7658. else {
  7659. return n;
  7660. }
  7661. };
  7662. WalkontableCellFilter.prototype.visibleToSource = function (n) {
  7663. return this.source(this.offsetted(this.fixed(n)));
  7664. };
  7665. WalkontableCellFilter.prototype.sourceToVisible = function (n) {
  7666. return this.source(this.unOffsetted(this.unFixed(n)));
  7667. };
  7668. /**
  7669. * WalkontableCellStrategy
  7670. * @constructor
  7671. */
  7672. function WalkontableCellStrategy() {
  7673. }
  7674. WalkontableCellStrategy.prototype.getSize = function (index) {
  7675. return this.cellSizes[index];
  7676. };
  7677. WalkontableCellStrategy.prototype.getContainerSize = function (proposedSize) {
  7678. return typeof this.containerSizeFn === 'function' ? this.containerSizeFn(proposedSize) : this.containerSizeFn;
  7679. };
  7680. WalkontableCellStrategy.prototype.countVisible = function () {
  7681. return this.cellCount;
  7682. };
  7683. WalkontableCellStrategy.prototype.isLastIncomplete = function () {
  7684. return this.remainingSize >= 0;
  7685. };
  7686. /**
  7687. * WalkontableClassNameList
  7688. * @constructor
  7689. */
  7690. function WalkontableClassNameCache() {
  7691. this.cache = [];
  7692. }
  7693. WalkontableClassNameCache.prototype.add = function (r, c, cls) {
  7694. if (!this.cache[r]) {
  7695. this.cache[r] = [];
  7696. }
  7697. if (!this.cache[r][c]) {
  7698. this.cache[r][c] = [];
  7699. }
  7700. this.cache[r][c][cls] = true;
  7701. };
  7702. WalkontableClassNameCache.prototype.test = function (r, c, cls) {
  7703. return (this.cache[r] && this.cache[r][c] && this.cache[r][c][cls]);
  7704. };
  7705. /**
  7706. * WalkontableColumnFilter
  7707. * @constructor
  7708. */
  7709. function WalkontableColumnFilter() {
  7710. this.countTH = 0;
  7711. }
  7712. WalkontableColumnFilter.prototype = new WalkontableCellFilter();
  7713. WalkontableColumnFilter.prototype.readSettings = function (instance) {
  7714. this.offset = instance.wtSettings.settings.offsetColumn;
  7715. this.total = instance.getSetting('totalColumns');
  7716. this.fixedCount = instance.getSetting('fixedColumnsLeft');
  7717. this.countTH = instance.getSetting('rowHeaders').length;
  7718. };
  7719. WalkontableColumnFilter.prototype.offsettedTH = function (n) {
  7720. return n - this.countTH;
  7721. };
  7722. WalkontableColumnFilter.prototype.unOffsettedTH = function (n) {
  7723. return n + this.countTH;
  7724. };
  7725. WalkontableColumnFilter.prototype.visibleRowHeadedColumnToSourceColumn = function (n) {
  7726. return this.visibleToSource(this.offsettedTH(n));
  7727. };
  7728. WalkontableColumnFilter.prototype.sourceColumnToVisibleRowHeadedColumn = function (n) {
  7729. return this.unOffsettedTH(this.sourceToVisible(n));
  7730. };
  7731. /**
  7732. * WalkontableColumnStrategy
  7733. * @param containerSizeFn
  7734. * @param sizeAtIndex
  7735. * @param strategy - all, last, none
  7736. * @constructor
  7737. */
  7738. function WalkontableColumnStrategy(containerSizeFn, sizeAtIndex, strategy) {
  7739. var size
  7740. , i = 0;
  7741. this.containerSizeFn = containerSizeFn;
  7742. this.cellSizesSum = 0;
  7743. this.cellSizes = [];
  7744. this.cellStretch = [];
  7745. this.cellCount = 0;
  7746. this.remainingSize = 0;
  7747. this.strategy = strategy;
  7748. //step 1 - determine cells that fit containerSize and cache their widths
  7749. while (true) {
  7750. size = sizeAtIndex(i);
  7751. if (size === void 0) {
  7752. break; //total columns exceeded
  7753. }
  7754. if (this.cellSizesSum >= this.getContainerSize(this.cellSizesSum + size)) {
  7755. break; //total width exceeded
  7756. }
  7757. this.cellSizes.push(size);
  7758. this.cellSizesSum += size;
  7759. this.cellCount++;
  7760. i++;
  7761. }
  7762. var containerSize = this.getContainerSize(this.cellSizesSum);
  7763. this.remainingSize = this.cellSizesSum - containerSize;
  7764. //negative value means the last cell is fully visible and there is some space left for stretching
  7765. //positive value means the last cell is not fully visible
  7766. }
  7767. WalkontableColumnStrategy.prototype = new WalkontableCellStrategy();
  7768. WalkontableColumnStrategy.prototype.getSize = function (index) {
  7769. return this.cellSizes[index] + (this.cellStretch[index] || 0);
  7770. };
  7771. WalkontableColumnStrategy.prototype.stretch = function () {
  7772. //step 2 - apply stretching strategy
  7773. var containerSize = this.getContainerSize(this.cellSizesSum)
  7774. , i = 0;
  7775. this.remainingSize = this.cellSizesSum - containerSize;
  7776. this.cellStretch.length = 0; //clear previous stretch
  7777. if (this.strategy === 'all') {
  7778. if (this.remainingSize < 0) {
  7779. var ratio = containerSize / this.cellSizesSum;
  7780. var newSize;
  7781. while (i < this.cellCount - 1) { //"i < this.cellCount - 1" is needed because last cellSize is adjusted after the loop
  7782. newSize = Math.floor(ratio * this.cellSizes[i]);
  7783. this.remainingSize += newSize - this.cellSizes[i];
  7784. this.cellStretch[i] = newSize - this.cellSizes[i];
  7785. i++;
  7786. }
  7787. this.cellStretch[this.cellCount - 1] = -this.remainingSize;
  7788. this.remainingSize = 0;
  7789. }
  7790. }
  7791. else if (this.strategy === 'last') {
  7792. if (this.remainingSize < 0) {
  7793. this.cellStretch[this.cellCount - 1] = -this.remainingSize;
  7794. this.remainingSize = 0;
  7795. }
  7796. }
  7797. };
  7798. function Walkontable(settings) {
  7799. var that = this,
  7800. originalHeaders = [];
  7801. this.guid = 'wt_' + walkontableRandomString(); //this is the namespace for global events
  7802. //bootstrap from settings
  7803. this.wtDom = new WalkontableDom();
  7804. if (settings.cloneFrom) {
  7805. this.cloneFrom = settings.cloneFrom;
  7806. this.cloneDirection = settings.cloneDirection;
  7807. this.wtSettings = settings.cloneFrom.wtSettings;
  7808. this.wtTable = new WalkontableTable(this, settings.table);
  7809. this.wtScroll = new WalkontableScroll(this);
  7810. this.wtViewport = settings.cloneFrom.wtViewport;
  7811. }
  7812. else {
  7813. this.wtSettings = new WalkontableSettings(this, settings);
  7814. this.wtTable = new WalkontableTable(this, settings.table);
  7815. this.wtScroll = new WalkontableScroll(this);
  7816. this.wtViewport = new WalkontableViewport(this);
  7817. this.wtScrollbars = new WalkontableScrollbars(this);
  7818. this.wtWheel = new WalkontableWheel(this);
  7819. this.wtEvent = new WalkontableEvent(this);
  7820. }
  7821. //find original headers
  7822. if (this.wtTable.THEAD.childNodes.length && this.wtTable.THEAD.childNodes[0].childNodes.length) {
  7823. for (var c = 0, clen = this.wtTable.THEAD.childNodes[0].childNodes.length; c < clen; c++) {
  7824. originalHeaders.push(this.wtTable.THEAD.childNodes[0].childNodes[c].innerHTML);
  7825. }
  7826. if (!this.getSetting('columnHeaders').length) {
  7827. this.update('columnHeaders', [function (column, TH) {
  7828. that.wtDom.fastInnerText(TH, originalHeaders[column]);
  7829. }]);
  7830. }
  7831. }
  7832. //initialize selections
  7833. this.selections = {};
  7834. var selectionsSettings = this.getSetting('selections');
  7835. if (selectionsSettings) {
  7836. for (var i in selectionsSettings) {
  7837. if (selectionsSettings.hasOwnProperty(i)) {
  7838. this.selections[i] = new WalkontableSelection(this, selectionsSettings[i]);
  7839. }
  7840. }
  7841. }
  7842. this.drawn = false;
  7843. this.drawInterrupted = false;
  7844. if (window.Handsontable) {
  7845. Handsontable.PluginHooks.add('beforeChange', function () {
  7846. if (that.rowHeightCache) {
  7847. that.rowHeightCache.length = 0;
  7848. }
  7849. });
  7850. }
  7851. }
  7852. Walkontable.prototype.draw = function (selectionsOnly) {
  7853. this.drawInterrupted = false;
  7854. if (!selectionsOnly && !this.wtDom.isVisible(this.wtTable.TABLE)) {
  7855. this.drawInterrupted = true; //draw interrupted because TABLE is not visible
  7856. return;
  7857. }
  7858. this.getSetting('beforeDraw', !selectionsOnly);
  7859. selectionsOnly = selectionsOnly && this.getSetting('offsetRow') === this.lastOffsetRow && this.getSetting('offsetColumn') === this.lastOffsetColumn;
  7860. if (this.drawn) { //fix offsets that might have changed
  7861. this.scrollVertical(0);
  7862. this.scrollHorizontal(0);
  7863. }
  7864. this.lastOffsetRow = this.getSetting('offsetRow');
  7865. this.lastOffsetColumn = this.getSetting('offsetColumn');
  7866. this.wtTable.draw(selectionsOnly);
  7867. this.getSetting('onDraw', !selectionsOnly);
  7868. return this;
  7869. };
  7870. Walkontable.prototype.update = function (settings, value) {
  7871. return this.wtSettings.update(settings, value);
  7872. };
  7873. Walkontable.prototype.scrollVertical = function (delta) {
  7874. return this.wtScroll.scrollVertical(delta);
  7875. };
  7876. Walkontable.prototype.scrollHorizontal = function (delta) {
  7877. return this.wtScroll.scrollHorizontal(delta);
  7878. };
  7879. Walkontable.prototype.scrollViewport = function (coords) {
  7880. this.wtScroll.scrollViewport(coords);
  7881. return this;
  7882. };
  7883. Walkontable.prototype.getViewport = function () {
  7884. return [
  7885. this.wtTable.rowFilter.visibleToSource(0),
  7886. this.wtTable.columnFilter.visibleToSource(0),
  7887. this.wtTable.getLastVisibleRow(),
  7888. this.wtTable.getLastVisibleColumn()
  7889. ];
  7890. };
  7891. Walkontable.prototype.getSetting = function (key, param1, param2, param3) {
  7892. return this.wtSettings.getSetting(key, param1, param2, param3);
  7893. };
  7894. Walkontable.prototype.hasSetting = function (key) {
  7895. return this.wtSettings.has(key);
  7896. };
  7897. Walkontable.prototype.destroy = function () {
  7898. $(document.body).off('.' + this.guid);
  7899. this.wtScrollbars.destroy();
  7900. clearTimeout(this.wheelTimeout);
  7901. this.wtEvent && this.wtEvent.destroy();
  7902. };
  7903. function WalkontableDom() {
  7904. }
  7905. //goes up the DOM tree (including given element) until it finds an element that matches the nodeName
  7906. WalkontableDom.prototype.closest = function (elem, nodeNames, until) {
  7907. while (elem != null && elem !== until) {
  7908. if (elem.nodeType === 1 && nodeNames.indexOf(elem.nodeName) > -1) {
  7909. return elem;
  7910. }
  7911. elem = elem.parentNode;
  7912. }
  7913. return null;
  7914. };
  7915. //goes up the DOM tree and checks if element is child of another element
  7916. WalkontableDom.prototype.isChildOf = function (child, parent) {
  7917. var node = child.parentNode;
  7918. while (node != null) {
  7919. if (node == parent) {
  7920. return true;
  7921. }
  7922. node = node.parentNode;
  7923. }
  7924. return false;
  7925. };
  7926. /**
  7927. * Counts index of element within its parent
  7928. * WARNING: for performance reasons, assumes there are only element nodes (no text nodes). This is true for Walkotnable
  7929. * Otherwise would need to check for nodeType or use previousElementSibling
  7930. * @see http://jsperf.com/sibling-index/10
  7931. * @param {Element} elem
  7932. * @return {Number}
  7933. */
  7934. WalkontableDom.prototype.index = function (elem) {
  7935. var i = 0;
  7936. while (elem = elem.previousSibling) {
  7937. ++i
  7938. }
  7939. return i;
  7940. };
  7941. if (document.documentElement.classList) {
  7942. // HTML5 classList API
  7943. WalkontableDom.prototype.hasClass = function (ele, cls) {
  7944. return ele.classList.contains(cls);
  7945. };
  7946. WalkontableDom.prototype.addClass = function (ele, cls) {
  7947. ele.classList.add(cls);
  7948. };
  7949. WalkontableDom.prototype.removeClass = function (ele, cls) {
  7950. ele.classList.remove(cls);
  7951. };
  7952. }
  7953. else {
  7954. //http://snipplr.com/view/3561/addclass-removeclass-hasclass/
  7955. WalkontableDom.prototype.hasClass = function (ele, cls) {
  7956. return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
  7957. };
  7958. WalkontableDom.prototype.addClass = function (ele, cls) {
  7959. if (!this.hasClass(ele, cls)) ele.className += " " + cls;
  7960. };
  7961. WalkontableDom.prototype.removeClass = function (ele, cls) {
  7962. if (this.hasClass(ele, cls)) { //is this really needed?
  7963. var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
  7964. ele.className = ele.className.replace(reg, ' ').trim(); //String.prototype.trim is defined in polyfill.js
  7965. }
  7966. };
  7967. }
  7968. /*//http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/
  7969. WalkontableDom.prototype.addEvent = (function () {
  7970. var that = this;
  7971. if (document.addEventListener) {
  7972. return function (elem, type, cb) {
  7973. if ((elem && !elem.length) || elem === window) {
  7974. elem.addEventListener(type, cb, false);
  7975. }
  7976. else if (elem && elem.length) {
  7977. var len = elem.length;
  7978. for (var i = 0; i < len; i++) {
  7979. that.addEvent(elem[i], type, cb);
  7980. }
  7981. }
  7982. };
  7983. }
  7984. else {
  7985. return function (elem, type, cb) {
  7986. if ((elem && !elem.length) || elem === window) {
  7987. elem.attachEvent('on' + type, function () {
  7988. //normalize
  7989. //http://stackoverflow.com/questions/4643249/cross-browser-event-object-normalization
  7990. var e = window['event'];
  7991. e.target = e.srcElement;
  7992. //e.offsetX = e.layerX;
  7993. //e.offsetY = e.layerY;
  7994. e.relatedTarget = e.relatedTarget || e.type == 'mouseover' ? e.fromElement : e.toElement;
  7995. if (e.target.nodeType === 3) e.target = e.target.parentNode; //Safari bug
  7996. return cb.call(elem, e)
  7997. });
  7998. }
  7999. else if (elem.length) {
  8000. var len = elem.length;
  8001. for (var i = 0; i < len; i++) {
  8002. that.addEvent(elem[i], type, cb);
  8003. }
  8004. }
  8005. };
  8006. }
  8007. })();
  8008. WalkontableDom.prototype.triggerEvent = function (element, eventName, target) {
  8009. var event;
  8010. if (document.createEvent) {
  8011. event = document.createEvent("MouseEvents");
  8012. event.initEvent(eventName, true, true);
  8013. } else {
  8014. event = document.createEventObject();
  8015. event.eventType = eventName;
  8016. }
  8017. event.eventName = eventName;
  8018. event.target = target;
  8019. if (document.createEvent) {
  8020. target.dispatchEvent(event);
  8021. } else {
  8022. target.fireEvent("on" + event.eventType, event);
  8023. }
  8024. };*/
  8025. WalkontableDom.prototype.removeTextNodes = function (elem, parent) {
  8026. if (elem.nodeType === 3) {
  8027. parent.removeChild(elem); //bye text nodes!
  8028. }
  8029. else if (['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'].indexOf(elem.nodeName) > -1) {
  8030. var childs = elem.childNodes;
  8031. for (var i = childs.length - 1; i >= 0; i--) {
  8032. this.removeTextNodes(childs[i], elem);
  8033. }
  8034. }
  8035. };
  8036. /**
  8037. * Remove childs function
  8038. * WARNING - this doesn't unload events and data attached by jQuery
  8039. * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/9
  8040. * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/11 - no siginificant improvement with Chrome remove() method
  8041. * @param element
  8042. * @returns {void}
  8043. */
  8044. //
  8045. WalkontableDom.prototype.empty = function (element) {
  8046. var child;
  8047. while (child = element.lastChild) {
  8048. element.removeChild(child);
  8049. }
  8050. };
  8051. WalkontableDom.prototype.HTML_CHARACTERS = /(<(.*)>|&(.*);)/;
  8052. /**
  8053. * Insert content into element trying avoid innerHTML method.
  8054. * @return {void}
  8055. */
  8056. WalkontableDom.prototype.fastInnerHTML = function (element, content) {
  8057. if (this.HTML_CHARACTERS.test(content)) {
  8058. element.innerHTML = content;
  8059. }
  8060. else {
  8061. this.fastInnerText(element, content);
  8062. }
  8063. };
  8064. /**
  8065. * Insert text content into element
  8066. * @return {void}
  8067. */
  8068. if (document.createTextNode('test').textContent) { //STANDARDS
  8069. WalkontableDom.prototype.fastInnerText = function (element, content) {
  8070. var child = element.firstChild;
  8071. if (child && child.nodeType === 3 && child.nextSibling === null) {
  8072. //fast lane - replace existing text node
  8073. //http://jsperf.com/replace-text-vs-reuse
  8074. child.textContent = content;
  8075. }
  8076. else {
  8077. //slow lane - empty element and insert a text node
  8078. this.empty(element);
  8079. element.appendChild(document.createTextNode(content));
  8080. }
  8081. };
  8082. }
  8083. else { //IE8
  8084. WalkontableDom.prototype.fastInnerText = function (element, content) {
  8085. var child = element.firstChild;
  8086. if (child && child.nodeType === 3 && child.nextSibling === null) {
  8087. //fast lane - replace existing text node
  8088. //http://jsperf.com/replace-text-vs-reuse
  8089. child.data = content;
  8090. }
  8091. else {
  8092. //slow lane - empty element and insert a text node
  8093. this.empty(element);
  8094. element.appendChild(document.createTextNode(content));
  8095. }
  8096. };
  8097. }
  8098. /**
  8099. * Returns true/false depending if element has offset parent
  8100. * @param elem
  8101. * @returns {boolean}
  8102. */
  8103. /*if (document.createTextNode('test').textContent) { //STANDARDS
  8104. WalkontableDom.prototype.hasOffsetParent = function (elem) {
  8105. return !!elem.offsetParent;
  8106. }
  8107. }
  8108. else {
  8109. WalkontableDom.prototype.hasOffsetParent = function (elem) {
  8110. try {
  8111. if (!elem.offsetParent) {
  8112. return false;
  8113. }
  8114. }
  8115. catch (e) {
  8116. return false; //IE8 throws "Unspecified error" when offsetParent is not found - we catch it here
  8117. }
  8118. return true;
  8119. }
  8120. }*/
  8121. /**
  8122. * Returns true if element is attached to the DOM and visible, false otherwise
  8123. * @param elem
  8124. * @returns {boolean}
  8125. */
  8126. WalkontableDom.prototype.isVisible = function (elem) {
  8127. //fast method according to benchmarks, but requires layout so slow in our case
  8128. /*
  8129. if (!WalkontableDom.prototype.hasOffsetParent(elem)) {
  8130. return false; //fixes problem with UI Bootstrap <tabs> directive
  8131. }
  8132. // if (elem.offsetWidth > 0 || (elem.parentNode && elem.parentNode.offsetWidth > 0)) { //IE10 was mistaken here
  8133. if (elem.offsetWidth > 0) {
  8134. return true;
  8135. }
  8136. */
  8137. //slow method
  8138. var next = elem;
  8139. while (next !== document.documentElement) { //until <html> reached
  8140. if (next === null) { //parent detached from DOM
  8141. return false;
  8142. }
  8143. else if (next.nodeType === 11) { //nodeType == 1 -> DOCUMENT_FRAGMENT_NODE
  8144. return false;
  8145. }
  8146. else if (next.style.display === 'none') {
  8147. return false;
  8148. }
  8149. next = next.parentNode;
  8150. }
  8151. return true;
  8152. };
  8153. /**
  8154. * Returns elements top and left offset relative to the document. In our usage case compatible with jQuery but 2x faster
  8155. * @param {HTMLElement} elem
  8156. * @return {Object}
  8157. */
  8158. WalkontableDom.prototype.offset = function (elem) {
  8159. if (this.hasCaptionProblem() && elem.firstChild && elem.firstChild.nodeName === 'CAPTION') {
  8160. //fixes problem with Firefox ignoring <caption> in TABLE offset (see also WalkontableDom.prototype.outerHeight)
  8161. //http://jsperf.com/offset-vs-getboundingclientrect/8
  8162. var box = elem.getBoundingClientRect();
  8163. return {
  8164. top: box.top + (window.pageYOffset || document.documentElement.scrollTop) - (document.documentElement.clientTop || 0),
  8165. left: box.left + (window.pageXOffset || document.documentElement.scrollLeft) - (document.documentElement.clientLeft || 0)
  8166. };
  8167. }
  8168. var offsetLeft = elem.offsetLeft
  8169. , offsetTop = elem.offsetTop
  8170. , lastElem = elem;
  8171. while (elem = elem.offsetParent) {
  8172. if (elem === document.body) { //from my observation, document.body always has scrollLeft/scrollTop == 0
  8173. break;
  8174. }
  8175. offsetLeft += elem.offsetLeft;
  8176. offsetTop += elem.offsetTop;
  8177. lastElem = elem;
  8178. }
  8179. if (lastElem && lastElem.style.position === 'fixed') { //slow - http://jsperf.com/offset-vs-getboundingclientrect/6
  8180. //if(lastElem !== document.body) { //faster but does gives false positive in Firefox
  8181. offsetLeft += window.pageXOffset || document.documentElement.scrollLeft;
  8182. offsetTop += window.pageYOffset || document.documentElement.scrollTop;
  8183. }
  8184. return {
  8185. left: offsetLeft,
  8186. top: offsetTop
  8187. };
  8188. };
  8189. WalkontableDom.prototype.getComputedStyle = function (elem) {
  8190. return elem.currentStyle || document.defaultView.getComputedStyle(elem);
  8191. };
  8192. WalkontableDom.prototype.outerWidth = function (elem) {
  8193. return elem.offsetWidth;
  8194. };
  8195. WalkontableDom.prototype.outerHeight = function (elem) {
  8196. if (this.hasCaptionProblem() && elem.firstChild && elem.firstChild.nodeName === 'CAPTION') {
  8197. //fixes problem with Firefox ignoring <caption> in TABLE.offsetHeight
  8198. //jQuery (1.10.1) still has this unsolved
  8199. //may be better to just switch to getBoundingClientRect
  8200. //http://bililite.com/blog/2009/03/27/finding-the-size-of-a-table/
  8201. //http://lists.w3.org/Archives/Public/www-style/2009Oct/0089.html
  8202. //http://bugs.jquery.com/ticket/2196
  8203. //http://lists.w3.org/Archives/Public/www-style/2009Oct/0140.html#start140
  8204. return elem.offsetHeight + elem.firstChild.offsetHeight;
  8205. }
  8206. else {
  8207. return elem.offsetHeight;
  8208. }
  8209. };
  8210. (function () {
  8211. var hasCaptionProblem;
  8212. function detectCaptionProblem() {
  8213. var TABLE = document.createElement('TABLE');
  8214. TABLE.style.borderSpacing = 0;
  8215. TABLE.style.borderWidth = 0;
  8216. TABLE.style.padding = 0;
  8217. var TBODY = document.createElement('TBODY');
  8218. TABLE.appendChild(TBODY);
  8219. TBODY.appendChild(document.createElement('TR'));
  8220. TBODY.firstChild.appendChild(document.createElement('TD'));
  8221. TBODY.firstChild.firstChild.innerHTML = '<tr><td>t<br>t</td></tr>';
  8222. var CAPTION = document.createElement('CAPTION');
  8223. CAPTION.innerHTML = 'c<br>c<br>c<br>c';
  8224. CAPTION.style.padding = 0;
  8225. CAPTION.style.margin = 0;
  8226. TABLE.insertBefore(CAPTION, TBODY);
  8227. document.body.appendChild(TABLE);
  8228. hasCaptionProblem = (TABLE.offsetHeight < 2 * TABLE.lastChild.offsetHeight); //boolean
  8229. document.body.removeChild(TABLE);
  8230. }
  8231. WalkontableDom.prototype.hasCaptionProblem = function () {
  8232. if (hasCaptionProblem === void 0) {
  8233. detectCaptionProblem();
  8234. }
  8235. return hasCaptionProblem;
  8236. };
  8237. /**
  8238. * Returns caret position in text input
  8239. * @author http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
  8240. * @return {Number}
  8241. */
  8242. WalkontableDom.prototype.getCaretPosition = function (el) {
  8243. if (el.selectionStart) {
  8244. return el.selectionStart;
  8245. }
  8246. else if (document.selection) { //IE8
  8247. el.focus();
  8248. var r = document.selection.createRange();
  8249. if (r == null) {
  8250. return 0;
  8251. }
  8252. var re = el.createTextRange(),
  8253. rc = re.duplicate();
  8254. re.moveToBookmark(r.getBookmark());
  8255. rc.setEndPoint('EndToStart', re);
  8256. return rc.text.length;
  8257. }
  8258. return 0;
  8259. };
  8260. /**
  8261. * Sets caret position in text input
  8262. * @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/
  8263. * @param {Element} el
  8264. * @param {Number} pos
  8265. * @param {Number} endPos
  8266. */
  8267. WalkontableDom.prototype.setCaretPosition = function (el, pos, endPos) {
  8268. if (endPos === void 0) {
  8269. endPos = pos;
  8270. }
  8271. if (el.setSelectionRange) {
  8272. el.focus();
  8273. el.setSelectionRange(pos, endPos);
  8274. }
  8275. else if (el.createTextRange) { //IE8
  8276. var range = el.createTextRange();
  8277. range.collapse(true);
  8278. range.moveEnd('character', endPos);
  8279. range.moveStart('character', pos);
  8280. range.select();
  8281. }
  8282. };
  8283. })();
  8284. function WalkontableEvent(instance) {
  8285. var that = this;
  8286. //reference to instance
  8287. this.instance = instance;
  8288. this.wtDom = this.instance.wtDom;
  8289. var dblClickOrigin = [null, null];
  8290. var dblClickTimeout = [null, null];
  8291. var onMouseDown = function (event) {
  8292. var cell = that.parentCell(event.target);
  8293. if (that.wtDom.hasClass(event.target, 'corner')) {
  8294. that.instance.getSetting('onCellCornerMouseDown', event, event.target);
  8295. }
  8296. else if (cell.TD && cell.TD.nodeName === 'TD') {
  8297. if (that.instance.hasSetting('onCellMouseDown')) {
  8298. that.instance.getSetting('onCellMouseDown', event, cell.coords, cell.TD);
  8299. }
  8300. }
  8301. if (event.button !== 2) { //if not right mouse button
  8302. if (cell.TD && cell.TD.nodeName === 'TD') {
  8303. dblClickOrigin[0] = cell.TD;
  8304. clearTimeout(dblClickTimeout[0]);
  8305. dblClickTimeout[0] = setTimeout(function () {
  8306. dblClickOrigin[0] = null;
  8307. }, 1000);
  8308. }
  8309. }
  8310. };
  8311. var lastMouseOver;
  8312. var onMouseOver = function (event) {
  8313. if (that.instance.hasSetting('onCellMouseOver')) {
  8314. var TABLE = that.instance.wtTable.TABLE;
  8315. var TD = that.wtDom.closest(event.target, ['TD', 'TH'], TABLE);
  8316. if (TD && TD !== lastMouseOver && that.wtDom.isChildOf(TD, TABLE)) {
  8317. lastMouseOver = TD;
  8318. if (TD.nodeName === 'TD') {
  8319. that.instance.getSetting('onCellMouseOver', event, that.instance.wtTable.getCoords(TD), TD);
  8320. }
  8321. }
  8322. }
  8323. };
  8324. /* var lastMouseOut;
  8325. var onMouseOut = function (event) {
  8326. if (that.instance.hasSetting('onCellMouseOut')) {
  8327. var TABLE = that.instance.wtTable.TABLE;
  8328. var TD = that.wtDom.closest(event.target, ['TD', 'TH'], TABLE);
  8329. if (TD && TD !== lastMouseOut && that.wtDom.isChildOf(TD, TABLE)) {
  8330. lastMouseOut = TD;
  8331. if (TD.nodeName === 'TD') {
  8332. that.instance.getSetting('onCellMouseOut', event, that.instance.wtTable.getCoords(TD), TD);
  8333. }
  8334. }
  8335. }
  8336. };*/
  8337. var onMouseUp = function (event) {
  8338. if (event.button !== 2) { //if not right mouse button
  8339. var cell = that.parentCell(event.target);
  8340. if (cell.TD === dblClickOrigin[0] && cell.TD === dblClickOrigin[1]) {
  8341. if (that.wtDom.hasClass(event.target, 'corner')) {
  8342. that.instance.getSetting('onCellCornerDblClick', event, cell.coords, cell.TD);
  8343. }
  8344. else if (cell.TD) {
  8345. that.instance.getSetting('onCellDblClick', event, cell.coords, cell.TD);
  8346. }
  8347. dblClickOrigin[0] = null;
  8348. dblClickOrigin[1] = null;
  8349. }
  8350. else if (cell.TD === dblClickOrigin[0]) {
  8351. dblClickOrigin[1] = cell.TD;
  8352. clearTimeout(dblClickTimeout[1]);
  8353. dblClickTimeout[1] = setTimeout(function () {
  8354. dblClickOrigin[1] = null;
  8355. }, 1000);
  8356. }
  8357. }
  8358. };
  8359. $(this.instance.wtTable.holder).on('mousedown', onMouseDown);
  8360. $(this.instance.wtTable.TABLE).on('mouseover', onMouseOver);
  8361. // $(this.instance.wtTable.TABLE).on('mouseout', onMouseOut);
  8362. $(this.instance.wtTable.holder).on('mouseup', onMouseUp);
  8363. }
  8364. WalkontableEvent.prototype.parentCell = function (elem) {
  8365. var cell = {};
  8366. var TABLE = this.instance.wtTable.TABLE;
  8367. var TD = this.wtDom.closest(elem, ['TD', 'TH'], TABLE);
  8368. if (TD && this.wtDom.isChildOf(TD, TABLE)) {
  8369. cell.coords = this.instance.wtTable.getCoords(TD);
  8370. cell.TD = TD;
  8371. }
  8372. else if (this.wtDom.hasClass(elem, 'wtBorder') && this.wtDom.hasClass(elem, 'current')) {
  8373. cell.coords = this.instance.selections.current.selected[0];
  8374. cell.TD = this.instance.wtTable.getCell(cell.coords);
  8375. }
  8376. return cell;
  8377. };
  8378. WalkontableEvent.prototype.destroy = function () {
  8379. clearTimeout(this.dblClickTimeout0);
  8380. clearTimeout(this.dblClickTimeout1);
  8381. };
  8382. function walkontableRangesIntersect() {
  8383. var from = arguments[0];
  8384. var to = arguments[1];
  8385. for (var i = 1, ilen = arguments.length / 2; i < ilen; i++) {
  8386. if (from <= arguments[2 * i + 1] && to >= arguments[2 * i]) {
  8387. return true;
  8388. }
  8389. }
  8390. return false;
  8391. }
  8392. /**
  8393. * Generates a random hex string. Used as namespace for Walkontable instance events.
  8394. * @return {String} - 16 character random string: "92b1bfc74ec4"
  8395. */
  8396. function walkontableRandomString() {
  8397. function s4() {
  8398. return Math.floor((1 + Math.random()) * 0x10000)
  8399. .toString(16)
  8400. .substring(1);
  8401. }
  8402. return s4() + s4() + s4() + s4();
  8403. }
  8404. var cachedScrollbarWidth;
  8405. //http://stackoverflow.com/questions/986937/how-can-i-get-the-browsers-scrollbar-sizes
  8406. function walkontableCalculateScrollbarWidth() {
  8407. var inner = document.createElement('p');
  8408. inner.style.width = "100%";
  8409. inner.style.height = "200px";
  8410. var outer = document.createElement('div');
  8411. outer.style.position = "absolute";
  8412. outer.style.top = "0px";
  8413. outer.style.left = "0px";
  8414. outer.style.visibility = "hidden";
  8415. outer.style.width = "200px";
  8416. outer.style.height = "150px";
  8417. outer.style.overflow = "hidden";
  8418. outer.appendChild(inner);
  8419. document.body.appendChild(outer);
  8420. var w1 = inner.offsetWidth;
  8421. outer.style.overflow = 'scroll';
  8422. var w2 = inner.offsetWidth;
  8423. if (w1 == w2) w2 = outer.clientWidth;
  8424. document.body.removeChild(outer);
  8425. return (w1 - w2);
  8426. }
  8427. function walkontableGetScrollbarWidth() {
  8428. if (cachedScrollbarWidth === void 0) {
  8429. cachedScrollbarWidth = walkontableCalculateScrollbarWidth();
  8430. }
  8431. return cachedScrollbarWidth;
  8432. }
  8433. //http://stackoverflow.com/questions/3629183/why-doesnt-indexof-work-on-an-array-ie8
  8434. if (!Array.prototype.indexOf) {
  8435. Array.prototype.indexOf = function (elt /*, from*/) {
  8436. var len = this.length >>> 0;
  8437. var from = Number(arguments[1]) || 0;
  8438. from = (from < 0)
  8439. ? Math.ceil(from)
  8440. : Math.floor(from);
  8441. if (from < 0)
  8442. from += len;
  8443. for (; from < len; from++) {
  8444. if (from in this &&
  8445. this[from] === elt)
  8446. return from;
  8447. }
  8448. return -1;
  8449. };
  8450. }
  8451. /**
  8452. * http://notes.jetienne.com/2011/05/18/cancelRequestAnimFrame-for-paul-irish-requestAnimFrame.html
  8453. */
  8454. window.requestAnimFrame = (function () {
  8455. return window.requestAnimationFrame ||
  8456. window.webkitRequestAnimationFrame ||
  8457. window.mozRequestAnimationFrame ||
  8458. window.oRequestAnimationFrame ||
  8459. window.msRequestAnimationFrame ||
  8460. function (/* function */ callback, /* DOMElement */ element) {
  8461. return window.setTimeout(callback, 1000 / 60);
  8462. };
  8463. })();
  8464. window.cancelRequestAnimFrame = (function () {
  8465. return window.cancelAnimationFrame ||
  8466. window.webkitCancelRequestAnimationFrame ||
  8467. window.mozCancelRequestAnimationFrame ||
  8468. window.oCancelRequestAnimationFrame ||
  8469. window.msCancelRequestAnimationFrame ||
  8470. clearTimeout
  8471. })();
  8472. //http://snipplr.com/view/13523/
  8473. //modified for speed
  8474. //http://jsperf.com/getcomputedstyle-vs-style-vs-css/8
  8475. if (!window.getComputedStyle) {
  8476. (function () {
  8477. var elem;
  8478. var styleObj = {
  8479. getPropertyValue: function getPropertyValue(prop) {
  8480. if (prop == 'float') prop = 'styleFloat';
  8481. return elem.currentStyle[prop.toUpperCase()] || null;
  8482. }
  8483. };
  8484. window.getComputedStyle = function (el) {
  8485. elem = el;
  8486. return styleObj;
  8487. }
  8488. })();
  8489. }
  8490. /**
  8491. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim
  8492. */
  8493. if (!String.prototype.trim) {
  8494. var trimRegex = /^\s+|\s+$/g;
  8495. String.prototype.trim = function () {
  8496. return this.replace(trimRegex, '');
  8497. };
  8498. }
  8499. /**
  8500. * WalkontableRowFilter
  8501. * @constructor
  8502. */
  8503. function WalkontableRowFilter() {
  8504. }
  8505. WalkontableRowFilter.prototype = new WalkontableCellFilter();
  8506. WalkontableRowFilter.prototype.readSettings = function (instance) {
  8507. this.offset = instance.wtSettings.settings.offsetRow;
  8508. this.total = instance.getSetting('totalRows');
  8509. this.fixedCount = instance.getSetting('fixedRowsTop');
  8510. };
  8511. /**
  8512. * WalkontableRowStrategy
  8513. * @param containerSizeFn
  8514. * @param sizeAtIndex
  8515. * @constructor
  8516. */
  8517. function WalkontableRowStrategy(containerSizeFn, sizeAtIndex) {
  8518. this.containerSizeFn = containerSizeFn;
  8519. this.sizeAtIndex = sizeAtIndex;
  8520. this.cellSizesSum = 0;
  8521. this.cellSizes = [];
  8522. this.cellCount = 0;
  8523. this.remainingSize = -Infinity;
  8524. }
  8525. WalkontableRowStrategy.prototype = new WalkontableCellStrategy();
  8526. WalkontableRowStrategy.prototype.add = function (i, TD, reverse) {
  8527. if (!this.isLastIncomplete()) {
  8528. var size = this.sizeAtIndex(i, TD);
  8529. if (size === void 0) {
  8530. return false; //total rows exceeded
  8531. }
  8532. var containerSize = this.getContainerSize(this.cellSizesSum + size);
  8533. if (reverse) {
  8534. this.cellSizes.unshift(size);
  8535. }
  8536. else {
  8537. this.cellSizes.push(size);
  8538. }
  8539. this.cellSizesSum += size;
  8540. this.cellCount++;
  8541. this.remainingSize = this.cellSizesSum - containerSize;
  8542. if (reverse && this.isLastIncomplete()) { //something is outside of the screen, maybe even some full rows?
  8543. return false;
  8544. }
  8545. return true;
  8546. }
  8547. return false;
  8548. };
  8549. WalkontableRowStrategy.prototype.remove = function () {
  8550. var size = this.cellSizes.pop();
  8551. this.cellSizesSum -= size;
  8552. this.cellCount--;
  8553. this.remainingSize -= size;
  8554. };
  8555. WalkontableRowStrategy.prototype.removeOutstanding = function () {
  8556. while (this.cellCount > 0 && this.cellSizes[this.cellCount - 1] < this.remainingSize) { //this row is completely off screen!
  8557. this.remove();
  8558. }
  8559. };
  8560. function WalkontableScroll(instance) {
  8561. this.instance = instance;
  8562. }
  8563. WalkontableScroll.prototype.scrollVertical = function (delta) {
  8564. if (!this.instance.drawn) {
  8565. throw new Error('scrollVertical can only be called after table was drawn to DOM');
  8566. }
  8567. var instance = this.instance
  8568. , newOffset
  8569. , offset = instance.getSetting('offsetRow')
  8570. , fixedCount = instance.getSetting('fixedRowsTop')
  8571. , total = instance.getSetting('totalRows')
  8572. , maxSize = instance.wtViewport.getViewportHeight();
  8573. if (total > 0) {
  8574. newOffset = this.scrollLogicVertical(delta, offset, total, fixedCount, maxSize, function (row) {
  8575. if (row - offset < fixedCount && row - offset >= 0) {
  8576. return instance.getSetting('rowHeight', row - offset);
  8577. }
  8578. else {
  8579. return instance.getSetting('rowHeight', row);
  8580. }
  8581. }, function (isReverse) {
  8582. instance.wtTable.verticalRenderReverse = isReverse;
  8583. });
  8584. }
  8585. else {
  8586. newOffset = 0;
  8587. }
  8588. if (newOffset !== offset) {
  8589. this.instance.wtScrollbars.vertical.scrollTo(newOffset);
  8590. }
  8591. return instance;
  8592. };
  8593. WalkontableScroll.prototype.scrollHorizontal = function (delta) {
  8594. if (!this.instance.drawn) {
  8595. throw new Error('scrollHorizontal can only be called after table was drawn to DOM');
  8596. }
  8597. var instance = this.instance
  8598. , newOffset
  8599. , offset = instance.getSetting('offsetColumn')
  8600. , fixedCount = instance.getSetting('fixedColumnsLeft')
  8601. , total = instance.getSetting('totalColumns')
  8602. , maxSize = instance.wtViewport.getViewportWidth();
  8603. if (total > 0) {
  8604. newOffset = this.scrollLogicHorizontal(delta, offset, total, fixedCount, maxSize, function (col) {
  8605. if (col - offset < fixedCount && col - offset >= 0) {
  8606. return instance.getSetting('columnWidth', col - offset);
  8607. }
  8608. else {
  8609. return instance.getSetting('columnWidth', col);
  8610. }
  8611. });
  8612. }
  8613. else {
  8614. newOffset = 0;
  8615. }
  8616. if (newOffset !== offset) {
  8617. this.instance.wtScrollbars.horizontal.scrollTo(newOffset);
  8618. }
  8619. return instance;
  8620. };
  8621. WalkontableScroll.prototype.scrollLogicVertical = function (delta, offset, total, fixedCount, maxSize, cellSizeFn, setReverseRenderFn) {
  8622. var newOffset = offset + delta;
  8623. if (newOffset >= total - fixedCount) {
  8624. newOffset = total - fixedCount - 1;
  8625. setReverseRenderFn(true);
  8626. }
  8627. if (newOffset < 0) {
  8628. newOffset = 0;
  8629. }
  8630. return newOffset;
  8631. };
  8632. WalkontableScroll.prototype.scrollLogicHorizontal = function (delta, offset, total, fixedCount, maxSize, cellSizeFn) {
  8633. var newOffset = offset + delta
  8634. , sum = 0
  8635. , col;
  8636. if (newOffset > fixedCount) {
  8637. if (newOffset >= total - fixedCount) {
  8638. newOffset = total - fixedCount - 1;
  8639. }
  8640. col = newOffset;
  8641. while (sum < maxSize && col < total) {
  8642. sum += cellSizeFn(col);
  8643. col++;
  8644. }
  8645. if (sum < maxSize) {
  8646. while (newOffset > 0) {
  8647. //if sum still less than available width, we cannot scroll that far (must move offset to the left)
  8648. sum += cellSizeFn(newOffset - 1);
  8649. if (sum < maxSize) {
  8650. newOffset--;
  8651. }
  8652. else {
  8653. break;
  8654. }
  8655. }
  8656. }
  8657. }
  8658. else if (newOffset < 0) {
  8659. newOffset = 0;
  8660. }
  8661. return newOffset;
  8662. };
  8663. /**
  8664. * Scrolls viewport to a cell by minimum number of cells
  8665. */
  8666. WalkontableScroll.prototype.scrollViewport = function (coords) {
  8667. if (!this.instance.drawn) {
  8668. return;
  8669. }
  8670. var offsetRow = this.instance.getSetting('offsetRow')
  8671. , offsetColumn = this.instance.getSetting('offsetColumn')
  8672. , lastVisibleRow = this.instance.wtTable.getLastVisibleRow()
  8673. , totalRows = this.instance.getSetting('totalRows')
  8674. , totalColumns = this.instance.getSetting('totalColumns')
  8675. , fixedRowsTop = this.instance.getSetting('fixedRowsTop')
  8676. , fixedColumnsLeft = this.instance.getSetting('fixedColumnsLeft');
  8677. if (this.instance.getSetting('nativeScrollbars')) {
  8678. var TD = this.instance.wtTable.getCell(coords);
  8679. if (typeof TD === 'object') {
  8680. var offset = WalkontableDom.prototype.offset(TD);
  8681. var outerHeight = WalkontableDom.prototype.outerHeight(TD);
  8682. var scrollY = this.instance.wtScrollbars.vertical.getScrollPosition();
  8683. var clientHeight = WalkontableDom.prototype.outerHeight(this.instance.wtScrollbars.vertical.scrollHandler);
  8684. if (this.instance.wtScrollbars.vertical.scrollHandler !== window) {
  8685. offset.top = offset.top - WalkontableDom.prototype.offset(this.instance.wtScrollbars.vertical.scrollHandler).top;
  8686. }
  8687. clientHeight -= 20;
  8688. if (outerHeight < clientHeight) {
  8689. if (offset.top < scrollY) {
  8690. this.instance.wtScrollbars.vertical.setScrollPosition(offset.top);
  8691. }
  8692. else if (offset.top + outerHeight > scrollY + clientHeight) {
  8693. this.instance.wtScrollbars.vertical.setScrollPosition(offset.top - clientHeight + outerHeight);
  8694. }
  8695. }
  8696. return;
  8697. }
  8698. }
  8699. if (coords[0] < 0 || coords[0] > totalRows - 1) {
  8700. throw new Error('row ' + coords[0] + ' does not exist');
  8701. }
  8702. else if (coords[1] < 0 || coords[1] > totalColumns - 1) {
  8703. throw new Error('column ' + coords[1] + ' does not exist');
  8704. }
  8705. if (coords[0] > lastVisibleRow) {
  8706. // this.scrollVertical(coords[0] - lastVisibleRow + 1);
  8707. this.scrollVertical(coords[0] - fixedRowsTop - offsetRow);
  8708. this.instance.wtTable.verticalRenderReverse = true;
  8709. }
  8710. else if (coords[0] === lastVisibleRow && this.instance.wtTable.rowStrategy.isLastIncomplete()) {
  8711. // this.scrollVertical(coords[0] - lastVisibleRow + 1);
  8712. this.scrollVertical(coords[0] - fixedRowsTop - offsetRow);
  8713. this.instance.wtTable.verticalRenderReverse = true;
  8714. }
  8715. else if (coords[0] - fixedRowsTop < offsetRow) {
  8716. this.scrollVertical(coords[0] - fixedRowsTop - offsetRow);
  8717. }
  8718. else {
  8719. this.scrollVertical(0); //Craig's issue: remove row from the last scroll page should scroll viewport a row up if needed
  8720. }
  8721. if (this.instance.wtTable.isColumnBeforeViewport(coords[1])) {
  8722. //scroll left
  8723. this.instance.wtScrollbars.horizontal.scrollTo(coords[1] - fixedColumnsLeft);
  8724. }
  8725. else if (this.instance.wtTable.isColumnAfterViewport(coords[1]) || (this.instance.wtTable.getLastVisibleColumn() === coords[1] && !this.instance.wtTable.isLastColumnFullyVisible())) {
  8726. //scroll right
  8727. var sum = 0;
  8728. for (var i = 0; i < fixedColumnsLeft; i++) {
  8729. sum += this.instance.getSetting('columnWidth', i);
  8730. }
  8731. var scrollTo = coords[1];
  8732. sum += this.instance.getSetting('columnWidth', scrollTo);
  8733. var available = this.instance.wtViewport.getViewportWidth();
  8734. if (sum < available) {
  8735. var next = this.instance.getSetting('columnWidth', scrollTo - 1);
  8736. while (sum + next < available && scrollTo >= fixedColumnsLeft) {
  8737. scrollTo--;
  8738. sum += next;
  8739. }
  8740. }
  8741. this.instance.wtScrollbars.horizontal.scrollTo(scrollTo - fixedColumnsLeft);
  8742. }
  8743. /*else {
  8744. //no scroll
  8745. }*/
  8746. return this.instance;
  8747. };
  8748. function WalkontableScrollbar() {
  8749. }
  8750. WalkontableScrollbar.prototype.init = function () {
  8751. var that = this;
  8752. //reference to instance
  8753. this.$table = $(this.instance.wtTable.TABLE);
  8754. //create elements
  8755. this.slider = document.createElement('DIV');
  8756. this.sliderStyle = this.slider.style;
  8757. this.sliderStyle.position = 'absolute';
  8758. this.sliderStyle.top = '0';
  8759. this.sliderStyle.left = '0';
  8760. this.sliderStyle.display = 'none';
  8761. this.slider.className = 'dragdealer ' + this.type;
  8762. this.handle = document.createElement('DIV');
  8763. this.handleStyle = this.handle.style;
  8764. this.handle.className = 'handle';
  8765. this.slider.appendChild(this.handle);
  8766. this.container = this.instance.wtTable.holder;
  8767. this.container.appendChild(this.slider);
  8768. var firstRun = true;
  8769. this.dragTimeout = null;
  8770. var dragDelta;
  8771. var dragRender = function () {
  8772. that.onScroll(dragDelta);
  8773. };
  8774. this.dragdealer = new Dragdealer(this.slider, {
  8775. vertical: (this.type === 'vertical'),
  8776. horizontal: (this.type === 'horizontal'),
  8777. slide: false,
  8778. speed: 100,
  8779. animationCallback: function (x, y) {
  8780. if (firstRun) {
  8781. firstRun = false;
  8782. return;
  8783. }
  8784. that.skipRefresh = true;
  8785. dragDelta = that.type === 'vertical' ? y : x;
  8786. if (that.dragTimeout === null) {
  8787. that.dragTimeout = setInterval(dragRender, 100);
  8788. dragRender();
  8789. }
  8790. },
  8791. callback: function (x, y) {
  8792. that.skipRefresh = false;
  8793. clearInterval(that.dragTimeout);
  8794. that.dragTimeout = null;
  8795. dragDelta = that.type === 'vertical' ? y : x;
  8796. that.onScroll(dragDelta);
  8797. }
  8798. });
  8799. this.skipRefresh = false;
  8800. };
  8801. WalkontableScrollbar.prototype.onScroll = function (delta) {
  8802. if (this.instance.drawn) {
  8803. this.readSettings();
  8804. if (this.total > this.visibleCount) {
  8805. var newOffset = Math.round(this.handlePosition * this.total / this.sliderSize);
  8806. if (delta === 1) {
  8807. if (this.type === 'vertical') {
  8808. this.instance.scrollVertical(Infinity).draw();
  8809. }
  8810. else {
  8811. this.instance.scrollHorizontal(Infinity).draw();
  8812. }
  8813. }
  8814. else if (newOffset !== this.offset) { //is new offset different than old offset
  8815. if (this.type === 'vertical') {
  8816. this.instance.scrollVertical(newOffset - this.offset).draw();
  8817. }
  8818. else {
  8819. this.instance.scrollHorizontal(newOffset - this.offset).draw();
  8820. }
  8821. }
  8822. else {
  8823. this.refresh();
  8824. }
  8825. }
  8826. }
  8827. };
  8828. /**
  8829. * Returns what part of the scroller should the handle take
  8830. * @param viewportCount {Number} number of visible rows or columns
  8831. * @param totalCount {Number} total number of rows or columns
  8832. * @return {Number} 0..1
  8833. */
  8834. WalkontableScrollbar.prototype.getHandleSizeRatio = function (viewportCount, totalCount) {
  8835. if (!totalCount || viewportCount > totalCount || viewportCount == totalCount) {
  8836. return 1;
  8837. }
  8838. return 1 / totalCount;
  8839. };
  8840. WalkontableScrollbar.prototype.prepare = function () {
  8841. if (this.skipRefresh) {
  8842. return;
  8843. }
  8844. var ratio = this.getHandleSizeRatio(this.visibleCount, this.total);
  8845. if (((ratio === 1 || isNaN(ratio)) && this.scrollMode === 'auto') || this.scrollMode === 'none') {
  8846. //isNaN is needed because ratio equals NaN when totalRows/totalColumns equals 0
  8847. this.visible = false;
  8848. }
  8849. else {
  8850. this.visible = true;
  8851. }
  8852. };
  8853. WalkontableScrollbar.prototype.refresh = function () {
  8854. if (this.skipRefresh) {
  8855. return;
  8856. }
  8857. else if (!this.visible) {
  8858. this.sliderStyle.display = 'none';
  8859. return;
  8860. }
  8861. var ratio
  8862. , sliderSize
  8863. , handleSize
  8864. , handlePosition
  8865. , visibleCount = this.visibleCount
  8866. , tableWidth = this.instance.wtViewport.getWorkspaceWidth()
  8867. , tableHeight = this.instance.wtViewport.getWorkspaceHeight();
  8868. if (tableWidth === Infinity) {
  8869. tableWidth = this.instance.wtViewport.getWorkspaceActualWidth();
  8870. }
  8871. if (tableHeight === Infinity) {
  8872. tableHeight = this.instance.wtViewport.getWorkspaceActualHeight();
  8873. }
  8874. if (this.type === 'vertical') {
  8875. if (this.instance.wtTable.rowStrategy.isLastIncomplete()) {
  8876. visibleCount--;
  8877. }
  8878. sliderSize = tableHeight - 2; //2 is sliders border-width
  8879. this.sliderStyle.top = this.instance.wtDom.offset(this.$table[0]).top - this.instance.wtDom.offset(this.container).top + 'px';
  8880. this.sliderStyle.left = tableWidth - 1 + 'px'; //1 is sliders border-width
  8881. this.sliderStyle.height = Math.max(sliderSize, 0) + 'px';
  8882. }
  8883. else { //horizontal
  8884. sliderSize = tableWidth - 2; //2 is sliders border-width
  8885. this.sliderStyle.left = this.instance.wtDom.offset(this.$table[0]).left - this.instance.wtDom.offset(this.container).left + 'px';
  8886. this.sliderStyle.top = tableHeight - 1 + 'px'; //1 is sliders border-width
  8887. this.sliderStyle.width = Math.max(sliderSize, 0) + 'px';
  8888. }
  8889. ratio = this.getHandleSizeRatio(visibleCount, this.total);
  8890. handleSize = Math.round(sliderSize * ratio);
  8891. if (handleSize < 10) {
  8892. handleSize = 15;
  8893. }
  8894. handlePosition = Math.floor(sliderSize * (this.offset / this.total));
  8895. if (handleSize + handlePosition > sliderSize) {
  8896. handlePosition = sliderSize - handleSize;
  8897. }
  8898. if (this.type === 'vertical') {
  8899. this.handleStyle.height = handleSize + 'px';
  8900. this.handleStyle.top = handlePosition + 'px';
  8901. }
  8902. else { //horizontal
  8903. this.handleStyle.width = handleSize + 'px';
  8904. this.handleStyle.left = handlePosition + 'px';
  8905. }
  8906. this.sliderStyle.display = 'block';
  8907. };
  8908. WalkontableScrollbar.prototype.destroy = function () {
  8909. clearInterval(this.dragdealer.interval);
  8910. };
  8911. ///
  8912. var WalkontableVerticalScrollbar = function (instance) {
  8913. this.instance = instance;
  8914. this.type = 'vertical';
  8915. this.init();
  8916. };
  8917. WalkontableVerticalScrollbar.prototype = new WalkontableScrollbar();
  8918. WalkontableVerticalScrollbar.prototype.scrollTo = function (cell) {
  8919. this.instance.update('offsetRow', cell);
  8920. };
  8921. WalkontableVerticalScrollbar.prototype.readSettings = function () {
  8922. this.scrollMode = this.instance.getSetting('scrollV');
  8923. this.offset = this.instance.getSetting('offsetRow');
  8924. this.total = this.instance.getSetting('totalRows');
  8925. this.visibleCount = this.instance.wtTable.rowStrategy.countVisible();
  8926. if(this.visibleCount > 1 && this.instance.wtTable.rowStrategy.isLastIncomplete()) {
  8927. this.visibleCount--;
  8928. }
  8929. this.handlePosition = parseInt(this.handleStyle.top, 10);
  8930. this.sliderSize = parseInt(this.sliderStyle.height, 10);
  8931. this.fixedCount = this.instance.getSetting('fixedRowsTop');
  8932. };
  8933. ///
  8934. var WalkontableHorizontalScrollbar = function (instance) {
  8935. this.instance = instance;
  8936. this.type = 'horizontal';
  8937. this.init();
  8938. };
  8939. WalkontableHorizontalScrollbar.prototype = new WalkontableScrollbar();
  8940. WalkontableHorizontalScrollbar.prototype.scrollTo = function (cell) {
  8941. this.instance.update('offsetColumn', cell);
  8942. };
  8943. WalkontableHorizontalScrollbar.prototype.readSettings = function () {
  8944. this.scrollMode = this.instance.getSetting('scrollH');
  8945. this.offset = this.instance.getSetting('offsetColumn');
  8946. this.total = this.instance.getSetting('totalColumns');
  8947. this.visibleCount = this.instance.wtTable.columnStrategy.countVisible();
  8948. if(this.visibleCount > 1 && this.instance.wtTable.columnStrategy.isLastIncomplete()) {
  8949. this.visibleCount--;
  8950. }
  8951. this.handlePosition = parseInt(this.handleStyle.left, 10);
  8952. this.sliderSize = parseInt(this.sliderStyle.width, 10);
  8953. this.fixedCount = this.instance.getSetting('fixedColumnsLeft');
  8954. };
  8955. WalkontableHorizontalScrollbar.prototype.getHandleSizeRatio = function (viewportCount, totalCount) {
  8956. if (!totalCount || viewportCount > totalCount || viewportCount == totalCount) {
  8957. return 1;
  8958. }
  8959. return viewportCount / totalCount;
  8960. };
  8961. function WalkontableScrollbarNative() {
  8962. this.lastWindowScrollPosition = NaN;
  8963. this.maxOuts = 10; //max outs in one direction (before and after table)
  8964. this.lastBegin = 0;
  8965. this.lastEnd = 0;
  8966. }
  8967. /*
  8968. Possible optimizations:
  8969. [x] don't rerender if scroll delta is smaller than the fragment outside of the viewport
  8970. [ ] move .style.top change before .draw()
  8971. [ ] put .draw() in requestAnimationFrame
  8972. [ ] don't rerender rows that remain visible after the scroll
  8973. */
  8974. WalkontableScrollbarNative.prototype.init = function () {
  8975. this.TABLE = this.instance.wtTable.TABLE;
  8976. this.fixed = this.instance.wtTable.hider;
  8977. this.fixedContainer = this.instance.wtTable.holder;
  8978. this.fixed.style.position = 'absolute';
  8979. this.fixed.style.left = '0';
  8980. this.scrollHandler = this.getScrollableElement(this.TABLE);
  8981. this.$scrollHandler = $(this.scrollHandler); //in future remove jQuery from here
  8982. };
  8983. WalkontableScrollbarNative.prototype.getScrollableElement = function (TABLE) {
  8984. var el = TABLE.parentNode;
  8985. while (el && el.style) {
  8986. if (el.style.overflow === 'scroll') {
  8987. return el;
  8988. }
  8989. el = el.parentNode;
  8990. }
  8991. return window;
  8992. };
  8993. WalkontableScrollbarNative.prototype.prepare = function () {
  8994. };
  8995. WalkontableScrollbarNative.prototype.availableSize = function () {
  8996. var availableSize;
  8997. if (this.windowScrollPosition > this.tableParentOffset /*&& last > -1*/) { //last -1 means that viewport is scrolled behind the table
  8998. if (this.instance.wtTable.getLastVisibleRow() === this.total - 1) {
  8999. availableSize = this.instance.wtDom.outerHeight(this.TABLE);
  9000. }
  9001. else {
  9002. availableSize = this.windowSize;
  9003. }
  9004. }
  9005. else {
  9006. availableSize = this.windowSize - (this.tableParentOffset);
  9007. }
  9008. return availableSize;
  9009. };
  9010. WalkontableScrollbarNative.prototype.refresh = function (selectionsOnly) {
  9011. var last = this.getLastCell();
  9012. this.measureBefore = this.sumCellSizes(0, this.offset);
  9013. if (last === -1) { //last -1 means that viewport is scrolled behind the table
  9014. this.measureAfter = 0;
  9015. }
  9016. else {
  9017. this.measureAfter = this.sumCellSizes(last, this.total - last);
  9018. }
  9019. this.applyToDOM();
  9020. this.clone && this.clone.draw(selectionsOnly);
  9021. };
  9022. WalkontableScrollbarNative.prototype.destroy = function () {
  9023. this.$scrollHandler.off('.' + this.instance.guid);
  9024. $(window).off('.' + this.instance.guid);
  9025. $(document).off('.' + this.instance.guid);
  9026. };
  9027. function WalkontableCornerScrollbarNative(instance) {
  9028. this.instance = instance;
  9029. this.init();
  9030. this.clone = this.makeClone('corner');
  9031. }
  9032. WalkontableCornerScrollbarNative.prototype = new WalkontableScrollbarNative();
  9033. WalkontableCornerScrollbarNative.prototype.makeClone = function (direction) {
  9034. if (this.instance.cloneFrom) {
  9035. return;
  9036. }
  9037. var clone = $('<div id="cln_' + direction + '" class="handsontable"></div>');
  9038. this.instance.wtTable.holder.parentNode.appendChild(clone[0]);
  9039. clone.css({
  9040. position: 'fixed',
  9041. overflow: 'hidden'
  9042. });
  9043. var table2 = $('<table class="htCore"></table>');
  9044. table2.className = this.instance.wtTable.TABLE.className;
  9045. clone.append(table2);
  9046. var walkontableConfig = {};
  9047. walkontableConfig.cloneFrom = this.instance;
  9048. walkontableConfig.cloneDirection = direction;
  9049. walkontableConfig.table = table2[0];
  9050. var wt = new Walkontable(walkontableConfig);
  9051. return wt;
  9052. };
  9053. WalkontableCornerScrollbarNative.prototype.resetFixedPosition = function () {
  9054. if (!this.instance.wtTable.holder.parentNode) {
  9055. return; //removed from DOM
  9056. }
  9057. var elem = this.clone.wtTable.holder.parentNode;
  9058. var box;
  9059. if (this.scrollHandler === window) {
  9060. box = this.instance.wtTable.holder.getBoundingClientRect();
  9061. var top = Math.ceil(box.top, 10);
  9062. var bottom = Math.ceil(box.bottom, 10);
  9063. if (top < 0 && bottom > 0) {
  9064. elem.style.top = '0';
  9065. }
  9066. else {
  9067. elem.style.top = top + 'px';
  9068. }
  9069. var left = Math.ceil(box.left, 10);
  9070. var right = Math.ceil(box.right, 10);
  9071. if (left < 0 && right > 0) {
  9072. elem.style.left = '0';
  9073. }
  9074. else {
  9075. elem.style.left = left + 'px';
  9076. }
  9077. }
  9078. else {
  9079. box = this.scrollHandler.getBoundingClientRect();
  9080. elem.style.top = Math.ceil(box.top, 10) + 'px';
  9081. elem.style.left = Math.ceil(box.left, 10) + 'px';
  9082. }
  9083. elem.style.width = WalkontableDom.prototype.outerWidth(this.clone.wtTable.TABLE) + 4 + 'px';
  9084. elem.style.height = WalkontableDom.prototype.outerHeight(this.clone.wtTable.TABLE) + 4 + 'px';
  9085. };
  9086. WalkontableCornerScrollbarNative.prototype.prepare = function () {
  9087. };
  9088. WalkontableCornerScrollbarNative.prototype.refresh = function (selectionsOnly) {
  9089. this.measureBefore = 0;
  9090. this.measureAfter = 0;
  9091. this.clone && this.clone.draw(selectionsOnly);
  9092. };
  9093. WalkontableCornerScrollbarNative.prototype.getScrollPosition = function () {
  9094. };
  9095. WalkontableCornerScrollbarNative.prototype.getLastCell = function () {
  9096. };
  9097. WalkontableCornerScrollbarNative.prototype.getTableSize = function () {
  9098. };
  9099. WalkontableCornerScrollbarNative.prototype.applyToDOM = function () {
  9100. };
  9101. WalkontableCornerScrollbarNative.prototype.scrollTo = function () {
  9102. };
  9103. WalkontableCornerScrollbarNative.prototype.readWindowSize = function () {
  9104. };
  9105. WalkontableCornerScrollbarNative.prototype.readSettings = function () {
  9106. };
  9107. function WalkontableHorizontalScrollbarNative(instance) {
  9108. this.instance = instance;
  9109. this.type = 'horizontal';
  9110. this.cellSize = 50;
  9111. this.init();
  9112. this.clone = this.makeClone('left');
  9113. }
  9114. WalkontableHorizontalScrollbarNative.prototype = new WalkontableScrollbarNative();
  9115. WalkontableHorizontalScrollbarNative.prototype.makeClone = function (direction) {
  9116. if (this.instance.cloneFrom) {
  9117. return;
  9118. }
  9119. var that = this;
  9120. var clone = $('<div id="cln_' + direction + '" class="handsontable"></div>');
  9121. this.instance.wtTable.holder.parentNode.appendChild(clone[0]);
  9122. clone.css({
  9123. position: 'fixed',
  9124. overflow: 'hidden'
  9125. });
  9126. // clone[0].style.height = '184px';
  9127. // clone[0].style.width = '55px';
  9128. var table2 = $('<table class="htCore"></table>');
  9129. table2.className = this.instance.wtTable.TABLE.className;
  9130. clone.append(table2);
  9131. var walkontableConfig = {};
  9132. walkontableConfig.cloneFrom = this.instance;
  9133. walkontableConfig.cloneDirection = direction;
  9134. walkontableConfig.table = table2[0];
  9135. var wt = new Walkontable(walkontableConfig);
  9136. return wt;
  9137. };
  9138. WalkontableHorizontalScrollbarNative.prototype.resetFixedPosition = function () {
  9139. if (!this.instance.wtTable.holder.parentNode) {
  9140. return; //removed from DOM
  9141. }
  9142. var elem = this.clone.wtTable.holder.parentNode;
  9143. var box;
  9144. if (this.scrollHandler === window) {
  9145. box = this.instance.wtTable.holder.getBoundingClientRect();
  9146. var left = Math.ceil(box.left, 10);
  9147. var right = Math.ceil(box.right, 10);
  9148. if (left < 0 && right > 0) {
  9149. elem.style.left = '0';
  9150. }
  9151. else {
  9152. elem.style.left = left + 'px';
  9153. }
  9154. }
  9155. else {
  9156. box = this.scrollHandler.getBoundingClientRect();
  9157. elem.style.top = Math.ceil(box.top, 10) + 'px';
  9158. elem.style.left = Math.ceil(box.left, 10) + 'px';
  9159. }
  9160. elem.style.width = WalkontableDom.prototype.outerWidth(this.clone.wtTable.TABLE) + 4 + 'px';
  9161. elem.style.height = this.instance.wtViewport.getWorkspaceHeight() - this.instance.getSetting('scrollbarHeight') + 'px';
  9162. };
  9163. WalkontableHorizontalScrollbarNative.prototype.prepare = function () {
  9164. };
  9165. WalkontableHorizontalScrollbarNative.prototype.refresh = function (selectionsOnly) {
  9166. this.measureBefore = 0;
  9167. this.measureAfter = 0;
  9168. this.clone && this.clone.draw(selectionsOnly);
  9169. };
  9170. WalkontableHorizontalScrollbarNative.prototype.getScrollPosition = function () {
  9171. if (this.scrollHandler === window) {
  9172. return this.scrollHandler.scrollX;
  9173. }
  9174. else {
  9175. return this.scrollHandler.scrollLeft;
  9176. }
  9177. };
  9178. WalkontableHorizontalScrollbarNative.prototype.onScroll = function () {
  9179. if (this.instance.cloneFrom) {
  9180. return;
  9181. }
  9182. this.windowScrollPosition = this.getScrollPosition();
  9183. };
  9184. WalkontableHorizontalScrollbarNative.prototype.getLastCell = function () {
  9185. return this.instance.wtTable.getLastVisibleColumn();
  9186. };
  9187. WalkontableHorizontalScrollbarNative.prototype.getTableSize = function () {
  9188. return this.instance.wtDom.outerWidth(this.TABLE);
  9189. };
  9190. WalkontableHorizontalScrollbarNative.prototype.applyToDOM = function () {
  9191. this.fixedContainer.style.paddingLeft = this.measureBefore + 'px';
  9192. this.fixedContainer.style.paddingRight = this.measureAfter + 'px';
  9193. };
  9194. WalkontableHorizontalScrollbarNative.prototype.scrollTo = function (cell) {
  9195. this.$scrollHandler.scrollLeft(this.tableParentOffset + cell * this.cellSize);
  9196. };
  9197. WalkontableHorizontalScrollbarNative.prototype.readWindowSize = function () {
  9198. if (this.scrollHandler === window) {
  9199. this.windowSize = document.documentElement.clientWidth;
  9200. this.tableParentOffset = this.instance.wtTable.holderOffset.left;
  9201. }
  9202. else {
  9203. this.windowSize = WalkontableDom.prototype.outerWidth(this.scrollHandler);
  9204. this.tableParentOffset = 0;
  9205. }
  9206. this.windowScrollPosition = this.getScrollPosition();
  9207. };
  9208. WalkontableHorizontalScrollbarNative.prototype.readSettings = function () {
  9209. this.offset = this.instance.getSetting('offsetColumn');
  9210. this.total = this.instance.getSetting('totalColumns');
  9211. };
  9212. function WalkontableVerticalScrollbarNative(instance) {
  9213. this.instance = instance;
  9214. this.type = 'vertical';
  9215. this.cellSize = 23;
  9216. this.init();
  9217. var that = this;
  9218. WalkontableCellStrategy.prototype.isLastIncomplete = function () { //monkey patch needed. In future get rid of it to improve performance
  9219. /*
  9220. * this.remainingSize = window viewport reduced by sum of all rendered cells (also those before the visible part)
  9221. * that.sumCellSizes(...) = sum of the sizes of cells that are before the visible part + 1 cell that is partially visible on top of the screen
  9222. */
  9223. return this.remainingSize > that.sumCellSizes(that.offset, that.offset + that.curOuts + 1);
  9224. };
  9225. this.clone = this.makeClone('top');
  9226. }
  9227. WalkontableVerticalScrollbarNative.prototype = new WalkontableScrollbarNative();
  9228. WalkontableVerticalScrollbarNative.prototype.makeClone = function (direction) {
  9229. if (this.instance.cloneFrom) {
  9230. return;
  9231. }
  9232. var that = this;
  9233. var clone = $('<div id="cln_' + direction + '" class="handsontable"></div>');
  9234. this.instance.wtTable.holder.parentNode.appendChild(clone[0]);
  9235. clone.css({
  9236. position: 'fixed',
  9237. overflow: 'hidden'
  9238. });
  9239. var table2 = $('<table class="htCore"></table>');
  9240. table2.className = this.instance.wtTable.TABLE.className;
  9241. clone.append(table2);
  9242. var walkontableConfig = {};
  9243. walkontableConfig.cloneFrom = this.instance;
  9244. walkontableConfig.cloneDirection = direction;
  9245. walkontableConfig.table = table2[0];
  9246. var wt = new Walkontable(walkontableConfig);
  9247. var cloneTable = clone.find('table')[0];
  9248. this.$scrollHandler.on('scroll.' + this.instance.guid, function () {
  9249. if (!that.instance.wtTable.holder.parentNode) {
  9250. //Walkontable was detached from DOM, but this handler was not removed
  9251. that.destroy();
  9252. return;
  9253. }
  9254. that.onScroll();
  9255. that.instance.wtScrollbars.horizontal.onScroll(); //it's done here to make sure that all onScroll's are executed before changing styles
  9256. cloneTable.style.left = that.instance.wtScrollbars.horizontal.measureBefore - that.instance.wtScrollbars.horizontal.windowScrollPosition + 'px';
  9257. that.instance.wtScrollbars.horizontal.clone.wtTable.TABLE.style.top = that.instance.wtScrollbars.vertical.measureBefore - that.instance.wtScrollbars.vertical.windowScrollPosition + 'px';
  9258. });
  9259. $(window).on('load.' + this.instance.guid, function () {
  9260. that.resetFixedPosition();
  9261. that.instance.wtScrollbars.horizontal.resetFixedPosition();
  9262. that.instance.wtScrollbars.corner.resetFixedPosition();
  9263. });
  9264. $(window).on('scroll.' + this.instance.guid, function () {
  9265. that.resetFixedPosition();
  9266. that.instance.wtScrollbars.horizontal.resetFixedPosition();
  9267. that.instance.wtScrollbars.corner.resetFixedPosition();
  9268. });
  9269. $(window).on('resize.' + this.instance.guid, function () {
  9270. that.resetFixedPosition();
  9271. that.instance.wtScrollbars.horizontal.resetFixedPosition();
  9272. that.instance.wtScrollbars.corner.resetFixedPosition();
  9273. });
  9274. $(document).on('ready.' + this.instance.guid, function () {
  9275. that.resetFixedPosition();
  9276. that.instance.wtScrollbars.horizontal.resetFixedPosition();
  9277. that.instance.wtScrollbars.corner.resetFixedPosition();
  9278. });
  9279. return wt;
  9280. };
  9281. WalkontableVerticalScrollbarNative.prototype.resetFixedPosition = function () {
  9282. if (!this.instance.wtTable.holder.parentNode) {
  9283. return; //removed from DOM
  9284. }
  9285. var elem = this.clone.wtTable.holder.parentNode;
  9286. var box;
  9287. if (this.scrollHandler === window) {
  9288. box = this.instance.wtTable.holder.getBoundingClientRect();
  9289. var top = Math.ceil(box.top, 10);
  9290. var bottom = Math.ceil(box.bottom, 10);
  9291. if (top < 0 && bottom > 0) {
  9292. elem.style.top = '0';
  9293. }
  9294. else {
  9295. elem.style.top = top + 'px';
  9296. }
  9297. }
  9298. else {
  9299. box = this.scrollHandler.getBoundingClientRect();
  9300. elem.style.top = Math.ceil(box.top, 10) + 'px';
  9301. elem.style.left = Math.ceil(box.left, 10) + 'px';
  9302. }
  9303. elem.style.width = WalkontableDom.prototype.outerWidth(this.instance.wtTable.holder.parentNode) + 'px';
  9304. elem.style.height = WalkontableDom.prototype.outerHeight(this.clone.wtTable.TABLE) + 4 + 'px';
  9305. };
  9306. WalkontableVerticalScrollbarNative.prototype.getScrollPosition = function () {
  9307. if (this.scrollHandler === window) {
  9308. return this.scrollHandler.scrollY;
  9309. }
  9310. else {
  9311. return this.scrollHandler.scrollTop;
  9312. }
  9313. };
  9314. WalkontableVerticalScrollbarNative.prototype.setScrollPosition = function (pos) {
  9315. this.scrollHandler.scrollTop = pos;
  9316. };
  9317. WalkontableVerticalScrollbarNative.prototype.onScroll = function (forcePosition) {
  9318. if (this.instance.cloneFrom) {
  9319. return;
  9320. }
  9321. this.windowScrollPosition = this.getScrollPosition();
  9322. this.readSettings(); //read window scroll position
  9323. if (forcePosition) {
  9324. this.windowScrollPosition = forcePosition;
  9325. }
  9326. if (this.windowScrollPosition === this.lastWindowScrollPosition) {
  9327. return;
  9328. }
  9329. if (this.windowScrollPosition > this.lastBegin && this.windowScrollPosition + this.windowSize < this.lastEnd) {
  9330. return;
  9331. }
  9332. this.lastWindowScrollPosition = this.windowScrollPosition;
  9333. var scrollDelta;
  9334. var newOffset = 0;
  9335. if (1 == 1 || this.windowScrollPosition > this.tableParentOffset) {
  9336. scrollDelta = this.windowScrollPosition - this.tableParentOffset;
  9337. partialOffset = 0;
  9338. if (scrollDelta > 0) {
  9339. var sum = 0;
  9340. var last;
  9341. for (var i = 0; i < this.total; i++) {
  9342. last = this.instance.getSetting('rowHeight', i);
  9343. sum += last;
  9344. if (sum > scrollDelta) {
  9345. break;
  9346. }
  9347. }
  9348. if (this.offset > 0) {
  9349. partialOffset = (sum - scrollDelta);
  9350. }
  9351. newOffset = i;
  9352. newOffset = Math.min(newOffset, this.total);
  9353. }
  9354. }
  9355. this.curOuts = newOffset > this.maxOuts ? this.maxOuts : newOffset;
  9356. newOffset -= this.curOuts;
  9357. this.instance.update('offsetRow', newOffset);
  9358. this.readSettings(); //read new offset
  9359. this.instance.draw();
  9360. };
  9361. WalkontableVerticalScrollbarNative.prototype.getLastCell = function () {
  9362. return this.instance.getSetting('offsetRow') + this.instance.wtTable.tbodyChildrenLength - 1;
  9363. };
  9364. WalkontableVerticalScrollbarNative.prototype.getTableSize = function () {
  9365. return this.instance.wtDom.outerHeight(this.TABLE);
  9366. };
  9367. var partialOffset = 0;
  9368. WalkontableVerticalScrollbarNative.prototype.sumCellSizes = function (from, length) {
  9369. var sum = 0;
  9370. while (from < length) {
  9371. sum += this.instance.getSetting('rowHeight', from);
  9372. from++;
  9373. }
  9374. return sum;
  9375. };
  9376. WalkontableVerticalScrollbarNative.prototype.applyToDOM = function () {
  9377. if (this.instance.cloneDirection === 'top') {
  9378. return;
  9379. }
  9380. var headerSize = this.instance.wtViewport.getColumnHeaderHeight();
  9381. this.fixedContainer.style.height = headerSize + this.sumCellSizes(0, this.total) + 'px';
  9382. this.fixed.style.top = this.measureBefore + 'px';
  9383. this.fixed.style.bottom = '';
  9384. this.lastBegin = this.tableParentOffset + this.measureBefore;
  9385. this.lastEnd = this.lastBegin + headerSize + this.instance.wtTable.rowStrategy.cellSizesSum;
  9386. };
  9387. WalkontableVerticalScrollbarNative.prototype.scrollTo = function (cell) {
  9388. var newY = this.tableParentOffset + cell * this.cellSize;
  9389. this.$scrollHandler.scrollTop(newY);
  9390. this.onScroll(newY);
  9391. };
  9392. WalkontableVerticalScrollbarNative.prototype.readWindowSize = function () {
  9393. if (this.scrollHandler === window) {
  9394. this.windowSize = document.documentElement.clientHeight;
  9395. this.tableParentOffset = this.instance.wtTable.holderOffset.left;
  9396. }
  9397. else {
  9398. this.windowSize = WalkontableDom.prototype.outerHeight(this.scrollHandler);
  9399. this.tableParentOffset = 0;
  9400. }
  9401. this.windowScrollPosition = this.getScrollPosition();
  9402. };
  9403. WalkontableVerticalScrollbarNative.prototype.readSettings = function () {
  9404. this.offset = this.instance.getSetting('offsetRow');
  9405. this.total = this.instance.getSetting('totalRows');
  9406. };
  9407. function WalkontableScrollbars(instance) {
  9408. if (instance.getSetting('nativeScrollbars')) {
  9409. instance.update('scrollbarWidth', walkontableGetScrollbarWidth());
  9410. instance.update('scrollbarHeight', walkontableGetScrollbarWidth());
  9411. this.vertical = new WalkontableVerticalScrollbarNative(instance);
  9412. this.horizontal = new WalkontableHorizontalScrollbarNative(instance);
  9413. this.corner = new WalkontableCornerScrollbarNative(instance);
  9414. }
  9415. else {
  9416. this.vertical = new WalkontableVerticalScrollbar(instance);
  9417. this.horizontal = new WalkontableHorizontalScrollbar(instance);
  9418. }
  9419. }
  9420. WalkontableScrollbars.prototype.destroy = function () {
  9421. this.vertical && this.vertical.destroy();
  9422. this.horizontal && this.horizontal.destroy();
  9423. };
  9424. WalkontableScrollbars.prototype.refresh = function (selectionsOnly) {
  9425. this.horizontal && this.horizontal.readSettings();
  9426. this.vertical && this.vertical.readSettings();
  9427. this.horizontal && this.horizontal.prepare();
  9428. this.vertical && this.vertical.prepare();
  9429. this.horizontal && this.horizontal.refresh(selectionsOnly);
  9430. this.vertical && this.vertical.refresh(selectionsOnly);
  9431. this.corner && this.corner.refresh(selectionsOnly);
  9432. };
  9433. function WalkontableSelection(instance, settings) {
  9434. this.instance = instance;
  9435. this.settings = settings;
  9436. this.selected = [];
  9437. if (settings.border) {
  9438. this.border = new WalkontableBorder(instance, settings);
  9439. }
  9440. }
  9441. WalkontableSelection.prototype.add = function (coords) {
  9442. this.selected.push(coords);
  9443. };
  9444. WalkontableSelection.prototype.clear = function () {
  9445. this.selected.length = 0; //http://jsperf.com/clear-arrayxxx
  9446. };
  9447. /**
  9448. * Returns the top left (TL) and bottom right (BR) selection coordinates
  9449. * @returns {Object}
  9450. */
  9451. WalkontableSelection.prototype.getCorners = function () {
  9452. var minRow
  9453. , minColumn
  9454. , maxRow
  9455. , maxColumn
  9456. , i
  9457. , ilen = this.selected.length;
  9458. if (ilen > 0) {
  9459. minRow = maxRow = this.selected[0][0];
  9460. minColumn = maxColumn = this.selected[0][1];
  9461. if (ilen > 1) {
  9462. for (i = 1; i < ilen; i++) {
  9463. if (this.selected[i][0] < minRow) {
  9464. minRow = this.selected[i][0];
  9465. }
  9466. else if (this.selected[i][0] > maxRow) {
  9467. maxRow = this.selected[i][0];
  9468. }
  9469. if (this.selected[i][1] < minColumn) {
  9470. minColumn = this.selected[i][1];
  9471. }
  9472. else if (this.selected[i][1] > maxColumn) {
  9473. maxColumn = this.selected[i][1];
  9474. }
  9475. }
  9476. }
  9477. }
  9478. return [minRow, minColumn, maxRow, maxColumn];
  9479. };
  9480. WalkontableSelection.prototype.draw = function () {
  9481. var corners, r, c, source_r, source_c;
  9482. var visibleRows = this.instance.wtTable.rowStrategy.countVisible()
  9483. , visibleColumns = this.instance.wtTable.columnStrategy.countVisible();
  9484. if (this.selected.length) {
  9485. corners = this.getCorners();
  9486. for (r = 0; r < visibleRows; r++) {
  9487. for (c = 0; c < visibleColumns; c++) {
  9488. source_r = this.instance.wtTable.rowFilter.visibleToSource(r);
  9489. source_c = this.instance.wtTable.columnFilter.visibleToSource(c);
  9490. if (source_r >= corners[0] && source_r <= corners[2] && source_c >= corners[1] && source_c <= corners[3]) {
  9491. //selected cell
  9492. this.instance.wtTable.currentCellCache.add(r, c, this.settings.className);
  9493. }
  9494. else if (source_r >= corners[0] && source_r <= corners[2]) {
  9495. //selection is in this row
  9496. this.instance.wtTable.currentCellCache.add(r, c, this.settings.highlightRowClassName);
  9497. }
  9498. else if (source_c >= corners[1] && source_c <= corners[3]) {
  9499. //selection is in this column
  9500. this.instance.wtTable.currentCellCache.add(r, c, this.settings.highlightColumnClassName);
  9501. }
  9502. }
  9503. }
  9504. this.border && this.border.appear(corners); //warning! border.appear modifies corners!
  9505. }
  9506. else {
  9507. this.border && this.border.disappear();
  9508. }
  9509. };
  9510. function WalkontableSettings(instance, settings) {
  9511. var that = this;
  9512. this.instance = instance;
  9513. //default settings. void 0 means it is required, null means it can be empty
  9514. this.defaults = {
  9515. table: void 0,
  9516. //presentation mode
  9517. scrollH: 'auto', //values: scroll (always show scrollbar), auto (show scrollbar if table does not fit in the container), none (never show scrollbar)
  9518. scrollV: 'auto', //values: see above
  9519. nativeScrollbars: false, //values: false (dragdealer), true (native)
  9520. stretchH: 'hybrid', //values: hybrid, all, last, none
  9521. currentRowClassName: null,
  9522. currentColumnClassName: null,
  9523. //data source
  9524. data: void 0,
  9525. offsetRow: 0,
  9526. offsetColumn: 0,
  9527. fixedColumnsLeft: 0,
  9528. fixedRowsTop: 0,
  9529. rowHeaders: function () {
  9530. return []
  9531. }, //this must be array of functions: [function (row, TH) {}]
  9532. columnHeaders: function () {
  9533. return []
  9534. }, //this must be array of functions: [function (column, TH) {}]
  9535. totalRows: void 0,
  9536. totalColumns: void 0,
  9537. width: null,
  9538. height: null,
  9539. cellRenderer: function (row, column, TD) {
  9540. var cellData = that.getSetting('data', row, column);
  9541. that.instance.wtDom.fastInnerText(TD, cellData === void 0 || cellData === null ? '' : cellData);
  9542. },
  9543. columnWidth: 50,
  9544. selections: null,
  9545. hideBorderOnMouseDownOver: false,
  9546. //callbacks
  9547. onCellMouseDown: null,
  9548. onCellMouseOver: null,
  9549. // onCellMouseOut: null,
  9550. onCellDblClick: null,
  9551. onCellCornerMouseDown: null,
  9552. onCellCornerDblClick: null,
  9553. beforeDraw: null,
  9554. onDraw: null,
  9555. //constants
  9556. scrollbarWidth: 10,
  9557. scrollbarHeight: 10
  9558. };
  9559. //reference to settings
  9560. this.settings = {};
  9561. for (var i in this.defaults) {
  9562. if (this.defaults.hasOwnProperty(i)) {
  9563. if (settings[i] !== void 0) {
  9564. this.settings[i] = settings[i];
  9565. }
  9566. else if (this.defaults[i] === void 0) {
  9567. throw new Error('A required setting "' + i + '" was not provided');
  9568. }
  9569. else {
  9570. this.settings[i] = this.defaults[i];
  9571. }
  9572. }
  9573. }
  9574. }
  9575. /**
  9576. * generic methods
  9577. */
  9578. WalkontableSettings.prototype.update = function (settings, value) {
  9579. if (value === void 0) { //settings is object
  9580. for (var i in settings) {
  9581. if (settings.hasOwnProperty(i)) {
  9582. this.settings[i] = settings[i];
  9583. }
  9584. }
  9585. }
  9586. else { //if value is defined then settings is the key
  9587. this.settings[settings] = value;
  9588. }
  9589. return this.instance;
  9590. };
  9591. WalkontableSettings.prototype.getSetting = function (key, param1, param2, param3) {
  9592. if (this[key]) {
  9593. return this[key](param1, param2, param3);
  9594. }
  9595. else {
  9596. return this._getSetting(key, param1, param2, param3);
  9597. }
  9598. };
  9599. WalkontableSettings.prototype._getSetting = function (key, param1, param2, param3) {
  9600. if (typeof this.settings[key] === 'function') {
  9601. return this.settings[key](param1, param2, param3);
  9602. }
  9603. else if (param1 !== void 0 && Object.prototype.toString.call(this.settings[key]) === '[object Array]') {
  9604. return this.settings[key][param1];
  9605. }
  9606. else {
  9607. return this.settings[key];
  9608. }
  9609. };
  9610. WalkontableSettings.prototype.has = function (key) {
  9611. return !!this.settings[key]
  9612. };
  9613. /**
  9614. * specific methods
  9615. */
  9616. WalkontableSettings.prototype.rowHeight = function (row, TD) {
  9617. if (!this.instance.rowHeightCache) {
  9618. this.instance.rowHeightCache = []; //hack. This cache is being invalidated in WOT core.js
  9619. }
  9620. if (this.instance.rowHeightCache[row] === void 0) {
  9621. var size = 23; //guess
  9622. if (TD) {
  9623. size = this.instance.wtDom.outerHeight(TD); //measure
  9624. this.instance.rowHeightCache[row] = size; //cache only something we measured
  9625. }
  9626. return size;
  9627. }
  9628. else {
  9629. return this.instance.rowHeightCache[row];
  9630. }
  9631. };
  9632. function WalkontableTable(instance, table) {
  9633. //reference to instance
  9634. this.instance = instance;
  9635. this.TABLE = table;
  9636. this.wtDom = this.instance.wtDom;
  9637. this.wtDom.removeTextNodes(this.TABLE);
  9638. //wtSpreader
  9639. var parent = this.TABLE.parentNode;
  9640. if (!parent || parent.nodeType !== 1 || !this.wtDom.hasClass(parent, 'wtHolder')) {
  9641. var spreader = document.createElement('DIV');
  9642. spreader.className = 'wtSpreader';
  9643. if (parent) {
  9644. parent.insertBefore(spreader, this.TABLE); //if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it
  9645. }
  9646. spreader.appendChild(this.TABLE);
  9647. }
  9648. this.spreader = this.TABLE.parentNode;
  9649. //wtHider
  9650. parent = this.spreader.parentNode;
  9651. if (!parent || parent.nodeType !== 1 || !this.wtDom.hasClass(parent, 'wtHolder')) {
  9652. var hider = document.createElement('DIV');
  9653. hider.className = 'wtHider';
  9654. if (parent) {
  9655. parent.insertBefore(hider, this.spreader); //if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it
  9656. }
  9657. hider.appendChild(this.spreader);
  9658. }
  9659. this.hider = this.spreader.parentNode;
  9660. this.hiderStyle = this.hider.style;
  9661. this.hiderStyle.position = 'relative';
  9662. //wtHolder
  9663. parent = this.hider.parentNode;
  9664. if (!parent || parent.nodeType !== 1 || !this.wtDom.hasClass(parent, 'wtHolder')) {
  9665. var holder = document.createElement('DIV');
  9666. holder.style.position = 'relative';
  9667. holder.className = 'wtHolder';
  9668. if (parent) {
  9669. parent.insertBefore(holder, this.hider); //if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it
  9670. }
  9671. holder.appendChild(this.hider);
  9672. }
  9673. this.holder = this.hider.parentNode;
  9674. //bootstrap from settings
  9675. this.TBODY = this.TABLE.getElementsByTagName('TBODY')[0];
  9676. if (!this.TBODY) {
  9677. this.TBODY = document.createElement('TBODY');
  9678. this.TABLE.appendChild(this.TBODY);
  9679. }
  9680. this.THEAD = this.TABLE.getElementsByTagName('THEAD')[0];
  9681. if (!this.THEAD) {
  9682. this.THEAD = document.createElement('THEAD');
  9683. this.TABLE.insertBefore(this.THEAD, this.TBODY);
  9684. }
  9685. this.COLGROUP = this.TABLE.getElementsByTagName('COLGROUP')[0];
  9686. if (!this.COLGROUP) {
  9687. this.COLGROUP = document.createElement('COLGROUP');
  9688. this.TABLE.insertBefore(this.COLGROUP, this.THEAD);
  9689. }
  9690. if (this.instance.getSetting('columnHeaders').length) {
  9691. if (!this.THEAD.childNodes.length) {
  9692. var TR = document.createElement('TR');
  9693. this.THEAD.appendChild(TR);
  9694. }
  9695. }
  9696. this.colgroupChildrenLength = this.COLGROUP.childNodes.length;
  9697. this.theadChildrenLength = this.THEAD.firstChild ? this.THEAD.firstChild.childNodes.length : 0;
  9698. this.tbodyChildrenLength = this.TBODY.childNodes.length;
  9699. this.oldCellCache = new WalkontableClassNameCache();
  9700. this.currentCellCache = new WalkontableClassNameCache();
  9701. this.rowFilter = new WalkontableRowFilter();
  9702. this.columnFilter = new WalkontableColumnFilter();
  9703. this.verticalRenderReverse = false;
  9704. }
  9705. WalkontableTable.prototype.refreshHiderDimensions = function () {
  9706. var height = this.instance.wtViewport.getWorkspaceHeight();
  9707. var width = this.instance.wtViewport.getWorkspaceWidth();
  9708. var spreaderStyle = this.spreader.style;
  9709. if ((height !== Infinity || width !== Infinity) && !this.instance.getSetting('nativeScrollbars')) {
  9710. if (height === Infinity) {
  9711. height = this.instance.wtViewport.getWorkspaceActualHeight();
  9712. }
  9713. if (width === Infinity) {
  9714. width = this.instance.wtViewport.getWorkspaceActualWidth();
  9715. }
  9716. this.hiderStyle.overflow = 'hidden';
  9717. spreaderStyle.position = 'absolute';
  9718. spreaderStyle.top = '0';
  9719. spreaderStyle.left = '0';
  9720. if (!this.instance.getSetting('nativeScrollbars')) {
  9721. spreaderStyle.height = '4000px';
  9722. spreaderStyle.width = '4000px';
  9723. }
  9724. if (height < 0) { //this happens with WalkontableScrollbarNative and causes "Invalid argument" error in IE8
  9725. height = 0;
  9726. }
  9727. this.hiderStyle.height = height + 'px';
  9728. this.hiderStyle.width = width + 'px';
  9729. }
  9730. else {
  9731. spreaderStyle.position = 'relative';
  9732. spreaderStyle.width = 'auto';
  9733. spreaderStyle.height = 'auto';
  9734. }
  9735. };
  9736. WalkontableTable.prototype.refreshStretching = function () {
  9737. var instance = this.instance
  9738. , stretchH = instance.getSetting('stretchH')
  9739. , totalRows = instance.getSetting('totalRows')
  9740. , totalColumns = instance.getSetting('totalColumns')
  9741. , offsetColumn = instance.getSetting('offsetColumn');
  9742. var containerWidthFn = function (cacheWidth) {
  9743. var viewportWidth = that.instance.wtViewport.getViewportWidth(cacheWidth);
  9744. if (viewportWidth < cacheWidth && that.instance.getSetting('nativeScrollbars')) {
  9745. return Infinity; //disable stretching when viewport is bigger than sum of the cell widths
  9746. }
  9747. return viewportWidth;
  9748. };
  9749. var that = this;
  9750. var columnWidthFn = function (i) {
  9751. var source_c = that.columnFilter.visibleToSource(i);
  9752. if (source_c < totalColumns) {
  9753. return instance.getSetting('columnWidth', source_c);
  9754. }
  9755. };
  9756. if (stretchH === 'hybrid') {
  9757. if (offsetColumn > 0) {
  9758. stretchH = 'last';
  9759. }
  9760. else {
  9761. stretchH = 'none';
  9762. }
  9763. }
  9764. var containerHeightFn = function (cacheHeight) {
  9765. if (that.instance.getSetting('nativeScrollbars')) {
  9766. return 2 * that.instance.wtViewport.getViewportHeight(cacheHeight);
  9767. }
  9768. return that.instance.wtViewport.getViewportHeight(cacheHeight);
  9769. };
  9770. var rowHeightFn = function (i, TD) {
  9771. if (that.instance.getSetting('nativeScrollbars')) {
  9772. return 20;
  9773. }
  9774. var source_r = that.rowFilter.visibleToSource(i);
  9775. if (source_r < totalRows) {
  9776. if (that.verticalRenderReverse && i === 0) {
  9777. return that.instance.getSetting('rowHeight', source_r, TD) - 1;
  9778. }
  9779. else {
  9780. return that.instance.getSetting('rowHeight', source_r, TD);
  9781. }
  9782. }
  9783. };
  9784. this.columnStrategy = new WalkontableColumnStrategy(containerWidthFn, columnWidthFn, stretchH);
  9785. this.rowStrategy = new WalkontableRowStrategy(containerHeightFn, rowHeightFn);
  9786. };
  9787. WalkontableTable.prototype.adjustAvailableNodes = function () {
  9788. var displayTds
  9789. , rowHeaders = this.instance.getSetting('rowHeaders')
  9790. , displayThs = rowHeaders.length
  9791. , columnHeaders = this.instance.getSetting('columnHeaders')
  9792. , TR
  9793. , TD
  9794. , c;
  9795. //adjust COLGROUP
  9796. while (this.colgroupChildrenLength < displayThs) {
  9797. this.COLGROUP.appendChild(document.createElement('COL'));
  9798. this.colgroupChildrenLength++;
  9799. }
  9800. this.refreshStretching();
  9801. if (this.instance.cloneFrom && (this.instance.cloneDirection === 'left' || this.instance.cloneDirection === 'corner')) {
  9802. this.columnStrategy.cellCount = 0;
  9803. }
  9804. displayTds = this.columnStrategy.cellCount;
  9805. //adjust COLGROUP
  9806. while (this.colgroupChildrenLength < displayTds + displayThs) {
  9807. this.COLGROUP.appendChild(document.createElement('COL'));
  9808. this.colgroupChildrenLength++;
  9809. }
  9810. while (this.colgroupChildrenLength > displayTds + displayThs) {
  9811. this.COLGROUP.removeChild(this.COLGROUP.lastChild);
  9812. this.colgroupChildrenLength--;
  9813. }
  9814. //adjust THEAD
  9815. TR = this.THEAD.firstChild;
  9816. if (columnHeaders.length) {
  9817. if (!TR) {
  9818. TR = document.createElement('TR');
  9819. this.THEAD.appendChild(TR);
  9820. }
  9821. this.theadChildrenLength = TR.childNodes.length;
  9822. while (this.theadChildrenLength < displayTds + displayThs) {
  9823. TR.appendChild(document.createElement('TH'));
  9824. this.theadChildrenLength++;
  9825. }
  9826. while (this.theadChildrenLength > displayTds + displayThs) {
  9827. TR.removeChild(TR.lastChild);
  9828. this.theadChildrenLength--;
  9829. }
  9830. }
  9831. else if (TR) {
  9832. this.wtDom.empty(TR);
  9833. }
  9834. //draw COLGROUP
  9835. for (c = 0; c < this.colgroupChildrenLength; c++) {
  9836. if (c < displayThs) {
  9837. this.wtDom.addClass(this.COLGROUP.childNodes[c], 'rowHeader');
  9838. }
  9839. else {
  9840. this.wtDom.removeClass(this.COLGROUP.childNodes[c], 'rowHeader');
  9841. }
  9842. }
  9843. //draw THEAD
  9844. if (columnHeaders.length) {
  9845. TR = this.THEAD.firstChild;
  9846. if (displayThs) {
  9847. TD = TR.firstChild; //actually it is TH but let's reuse single variable
  9848. for (c = 0; c < displayThs; c++) {
  9849. rowHeaders[c](-displayThs + c, TD);
  9850. TD = TD.nextSibling;
  9851. }
  9852. }
  9853. }
  9854. for (c = 0; c < displayTds; c++) {
  9855. if (columnHeaders.length) {
  9856. columnHeaders[0](this.columnFilter.visibleToSource(c), TR.childNodes[displayThs + c]);
  9857. }
  9858. }
  9859. };
  9860. WalkontableTable.prototype.adjustColumns = function (TR, desiredCount) {
  9861. var count = TR.childNodes.length;
  9862. while (count < desiredCount) {
  9863. var TD = document.createElement('TD');
  9864. TR.appendChild(TD);
  9865. count++;
  9866. }
  9867. while (count > desiredCount) {
  9868. TR.removeChild(TR.lastChild);
  9869. count--;
  9870. }
  9871. };
  9872. WalkontableTable.prototype.draw = function (selectionsOnly) {
  9873. if (this.instance.getSetting('nativeScrollbars')) {
  9874. this.verticalRenderReverse = false; //this is only supported in dragdealer mode, not in native
  9875. }
  9876. this.rowFilter.readSettings(this.instance);
  9877. this.columnFilter.readSettings(this.instance);
  9878. if (!selectionsOnly) {
  9879. if (this.instance.getSetting('nativeScrollbars')) {
  9880. if (this.instance.cloneFrom) {
  9881. this.tableOffset = this.instance.cloneFrom.wtTable.tableOffset;
  9882. }
  9883. else {
  9884. this.holderOffset = this.wtDom.offset(this.holder);
  9885. this.tableOffset = this.wtDom.offset(this.TABLE);
  9886. this.instance.wtScrollbars.vertical.readWindowSize();
  9887. this.instance.wtScrollbars.horizontal.readWindowSize();
  9888. this.instance.wtViewport.resetSettings();
  9889. }
  9890. }
  9891. else {
  9892. this.tableOffset = this.wtDom.offset(this.TABLE);
  9893. this.instance.wtViewport.resetSettings();
  9894. }
  9895. this._doDraw();
  9896. }
  9897. else {
  9898. this.instance.wtScrollbars && this.instance.wtScrollbars.refresh(true);
  9899. }
  9900. this.refreshPositions(selectionsOnly);
  9901. this.instance.drawn = true;
  9902. return this;
  9903. };
  9904. WalkontableTable.prototype._doDraw = function () {
  9905. var r = 0
  9906. , source_r
  9907. , c
  9908. , source_c
  9909. , offsetRow = this.instance.getSetting('offsetRow')
  9910. , totalRows = this.instance.getSetting('totalRows')
  9911. , totalColumns = this.instance.getSetting('totalColumns')
  9912. , displayTds
  9913. , rowHeaders = this.instance.getSetting('rowHeaders')
  9914. , displayThs = rowHeaders.length
  9915. , TR
  9916. , TD
  9917. , TH
  9918. , adjusted = false
  9919. , workspaceWidth
  9920. , mustBeInViewport
  9921. , res;
  9922. if (this.verticalRenderReverse) {
  9923. mustBeInViewport = offsetRow;
  9924. }
  9925. var noPartial = false;
  9926. if (this.verticalRenderReverse) {
  9927. if (offsetRow === totalRows - this.rowFilter.fixedCount - 1) {
  9928. noPartial = true;
  9929. }
  9930. else {
  9931. this.instance.update('offsetRow', offsetRow + 1); //if we are scrolling reverse
  9932. this.rowFilter.readSettings(this.instance);
  9933. }
  9934. }
  9935. //draw TBODY
  9936. if (totalColumns > 0) {
  9937. source_r = this.rowFilter.visibleToSource(r);
  9938. var first = true;
  9939. var fixedRowsTop = this.instance.getSetting('fixedRowsTop');
  9940. while (source_r < totalRows && source_r >= 0) {
  9941. if (r > 1000) {
  9942. throw new Error('Security brake: Too much TRs. Please define height for your table, which will enforce scrollbars.');
  9943. }
  9944. if (this.instance.cloneFrom && (this.instance.cloneDirection === 'top' || this.instance.cloneDirection === 'corner') && r === fixedRowsTop) {
  9945. break;
  9946. }
  9947. if (r >= this.tbodyChildrenLength || (this.verticalRenderReverse && r >= this.rowFilter.fixedCount)) {
  9948. TR = document.createElement('TR');
  9949. for (c = 0; c < displayThs; c++) {
  9950. TR.appendChild(document.createElement('TH'));
  9951. }
  9952. if (this.verticalRenderReverse && r >= this.rowFilter.fixedCount) {
  9953. this.TBODY.insertBefore(TR, this.TBODY.childNodes[this.rowFilter.fixedCount] || this.TBODY.firstChild);
  9954. }
  9955. else {
  9956. this.TBODY.appendChild(TR);
  9957. }
  9958. this.tbodyChildrenLength++;
  9959. }
  9960. else if (r === 0) {
  9961. TR = this.TBODY.firstChild;
  9962. }
  9963. else {
  9964. TR = TR.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes
  9965. }
  9966. //TH
  9967. TH = TR.firstChild;
  9968. for (c = 0; c < displayThs; c++) {
  9969. //If the number of row headers increased we need to replace TD with TH
  9970. if (TH.nodeName == 'TD') {
  9971. TD = TH;
  9972. TH = document.createElement('TH');
  9973. TR.insertBefore(TH, TD);
  9974. TR.removeChild(TD);
  9975. }
  9976. rowHeaders[c](source_r, TH); //actually TH
  9977. TH = TH.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes
  9978. }
  9979. if (first) {
  9980. // if (r === 0) {
  9981. first = false;
  9982. this.adjustAvailableNodes();
  9983. adjusted = true;
  9984. displayTds = this.columnStrategy.cellCount;
  9985. //TD
  9986. this.adjustColumns(TR, displayTds + displayThs);
  9987. workspaceWidth = this.instance.wtViewport.getWorkspaceWidth();
  9988. this.columnStrategy.stretch();
  9989. for (c = 0; c < displayTds; c++) {
  9990. this.COLGROUP.childNodes[c + displayThs].style.width = this.columnStrategy.getSize(c) + 'px';
  9991. }
  9992. }
  9993. else {
  9994. //TD
  9995. this.adjustColumns(TR, displayTds + displayThs);
  9996. }
  9997. for (c = 0; c < displayTds; c++) {
  9998. source_c = this.columnFilter.visibleToSource(c);
  9999. if (c === 0) {
  10000. TD = TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(source_c)];
  10001. }
  10002. else {
  10003. TD = TD.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes
  10004. }
  10005. //If the number of headers has been reduced, we need to replace excess TH with TD
  10006. if (TD.nodeName == 'TH') {
  10007. TH = TD;
  10008. TD = document.createElement('TD');
  10009. TR.insertBefore(TD, TH);
  10010. TR.removeChild(TH);
  10011. }
  10012. TD.className = '';
  10013. TD.removeAttribute('style');
  10014. this.instance.getSetting('cellRenderer', source_r, source_c, TD);
  10015. }
  10016. offsetRow = this.instance.getSetting('offsetRow'); //refresh the value
  10017. //after last column is rendered, check if last cell is fully displayed
  10018. if (this.verticalRenderReverse && noPartial) {
  10019. if (-this.wtDom.outerHeight(TR.firstChild) < this.rowStrategy.remainingSize) {
  10020. this.TBODY.removeChild(TR);
  10021. this.instance.update('offsetRow', offsetRow + 1);
  10022. this.tbodyChildrenLength--;
  10023. this.rowFilter.readSettings(this.instance);
  10024. break;
  10025. }
  10026. else {
  10027. res = this.rowStrategy.add(r, TD, this.verticalRenderReverse);
  10028. if (res === false) {
  10029. this.rowStrategy.removeOutstanding();
  10030. }
  10031. }
  10032. }
  10033. else {
  10034. res = this.rowStrategy.add(r, TD, this.verticalRenderReverse);
  10035. if (res === false) {
  10036. if (!this.instance.getSetting('nativeScrollbars')) {
  10037. this.rowStrategy.removeOutstanding();
  10038. }
  10039. }
  10040. if (this.rowStrategy.isLastIncomplete()) {
  10041. if (this.verticalRenderReverse && !this.isRowInViewport(mustBeInViewport)) {
  10042. //we failed because one of the cells was by far too large. Recover by rendering from top
  10043. this.verticalRenderReverse = false;
  10044. this.instance.update('offsetRow', mustBeInViewport);
  10045. this.draw();
  10046. return;
  10047. }
  10048. break;
  10049. }
  10050. }
  10051. if (this.verticalRenderReverse && r >= this.rowFilter.fixedCount) {
  10052. if (offsetRow === 0) {
  10053. break;
  10054. }
  10055. this.instance.update('offsetRow', offsetRow - 1);
  10056. this.rowFilter.readSettings(this.instance);
  10057. }
  10058. else {
  10059. r++;
  10060. }
  10061. source_r = this.rowFilter.visibleToSource(r);
  10062. }
  10063. }
  10064. if (!adjusted) {
  10065. this.adjustAvailableNodes();
  10066. }
  10067. r = this.rowStrategy.countVisible();
  10068. while (this.tbodyChildrenLength > r) {
  10069. this.TBODY.removeChild(this.TBODY.lastChild);
  10070. this.tbodyChildrenLength--;
  10071. }
  10072. this.instance.wtScrollbars && this.instance.wtScrollbars.refresh(false);
  10073. if (workspaceWidth !== this.instance.wtViewport.getWorkspaceWidth()) {
  10074. //workspace width changed though to shown/hidden vertical scrollbar. Let's reapply stretching
  10075. this.columnStrategy.stretch();
  10076. for (c = 0; c < this.columnStrategy.cellCount; c++) {
  10077. this.COLGROUP.childNodes[c + displayThs].style.width = this.columnStrategy.getSize(c) + 'px';
  10078. }
  10079. }
  10080. this.verticalRenderReverse = false;
  10081. };
  10082. WalkontableTable.prototype.refreshPositions = function (selectionsOnly) {
  10083. this.refreshHiderDimensions();
  10084. this.refreshSelections(selectionsOnly);
  10085. };
  10086. WalkontableTable.prototype.refreshSelections = function (selectionsOnly) {
  10087. var vr
  10088. , r
  10089. , vc
  10090. , c
  10091. , s
  10092. , slen
  10093. , classNames = []
  10094. , visibleRows = this.rowStrategy.countVisible()
  10095. , visibleColumns = this.columnStrategy.countVisible();
  10096. this.oldCellCache = this.currentCellCache;
  10097. this.currentCellCache = new WalkontableClassNameCache();
  10098. if (this.instance.selections) {
  10099. for (r in this.instance.selections) {
  10100. if (this.instance.selections.hasOwnProperty(r)) {
  10101. this.instance.selections[r].draw();
  10102. if (this.instance.selections[r].settings.className) {
  10103. classNames.push(this.instance.selections[r].settings.className);
  10104. }
  10105. if (this.instance.selections[r].settings.highlightRowClassName) {
  10106. classNames.push(this.instance.selections[r].settings.highlightRowClassName);
  10107. }
  10108. if (this.instance.selections[r].settings.highlightColumnClassName) {
  10109. classNames.push(this.instance.selections[r].settings.highlightColumnClassName);
  10110. }
  10111. }
  10112. }
  10113. }
  10114. slen = classNames.length;
  10115. for (vr = 0; vr < visibleRows; vr++) {
  10116. for (vc = 0; vc < visibleColumns; vc++) {
  10117. r = this.rowFilter.visibleToSource(vr);
  10118. c = this.columnFilter.visibleToSource(vc);
  10119. for (s = 0; s < slen; s++) {
  10120. if (this.currentCellCache.test(vr, vc, classNames[s])) {
  10121. this.wtDom.addClass(this.getCell([r, c]), classNames[s]);
  10122. }
  10123. else if (selectionsOnly && this.oldCellCache.test(vr, vc, classNames[s])) {
  10124. this.wtDom.removeClass(this.getCell([r, c]), classNames[s]);
  10125. }
  10126. }
  10127. }
  10128. }
  10129. };
  10130. /**
  10131. * getCell
  10132. * @param {Array} coords
  10133. * @return {Object} HTMLElement on success or {Number} one of the exit codes on error:
  10134. * -1 row before viewport
  10135. * -2 row after viewport
  10136. * -3 column before viewport
  10137. * -4 column after viewport
  10138. *
  10139. */
  10140. WalkontableTable.prototype.getCell = function (coords) {
  10141. if (this.isRowBeforeViewport(coords[0])) {
  10142. return -1; //row before viewport
  10143. }
  10144. else if (this.isRowAfterViewport(coords[0])) {
  10145. return -2; //row after viewport
  10146. }
  10147. else {
  10148. if (this.isColumnBeforeViewport(coords[1])) {
  10149. return -3; //column before viewport
  10150. }
  10151. else if (this.isColumnAfterViewport(coords[1])) {
  10152. return -4; //column after viewport
  10153. }
  10154. else {
  10155. return this.TBODY.childNodes[this.rowFilter.sourceToVisible(coords[0])].childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(coords[1])];
  10156. }
  10157. }
  10158. };
  10159. WalkontableTable.prototype.getCoords = function (TD) {
  10160. return [
  10161. this.rowFilter.visibleToSource(this.wtDom.index(TD.parentNode)),
  10162. this.columnFilter.visibleRowHeadedColumnToSourceColumn(TD.cellIndex)
  10163. ];
  10164. };
  10165. //returns -1 if no row is visible
  10166. WalkontableTable.prototype.getLastVisibleRow = function () {
  10167. return this.rowFilter.visibleToSource(this.rowStrategy.cellCount - 1);
  10168. };
  10169. //returns -1 if no column is visible
  10170. WalkontableTable.prototype.getLastVisibleColumn = function () {
  10171. return this.columnFilter.visibleToSource(this.columnStrategy.cellCount - 1);
  10172. };
  10173. WalkontableTable.prototype.isRowBeforeViewport = function (r) {
  10174. return (this.rowFilter.sourceToVisible(r) < this.rowFilter.fixedCount && r >= this.rowFilter.fixedCount);
  10175. };
  10176. WalkontableTable.prototype.isRowAfterViewport = function (r) {
  10177. return (r > this.getLastVisibleRow());
  10178. };
  10179. WalkontableTable.prototype.isColumnBeforeViewport = function (c) {
  10180. return (this.columnFilter.sourceToVisible(c) < this.columnFilter.fixedCount && c >= this.columnFilter.fixedCount);
  10181. };
  10182. WalkontableTable.prototype.isColumnAfterViewport = function (c) {
  10183. return (c > this.getLastVisibleColumn());
  10184. };
  10185. WalkontableTable.prototype.isRowInViewport = function (r) {
  10186. return (!this.isRowBeforeViewport(r) && !this.isRowAfterViewport(r));
  10187. };
  10188. WalkontableTable.prototype.isColumnInViewport = function (c) {
  10189. return (!this.isColumnBeforeViewport(c) && !this.isColumnAfterViewport(c));
  10190. };
  10191. WalkontableTable.prototype.isLastRowFullyVisible = function () {
  10192. return (this.getLastVisibleRow() === this.instance.getSetting('totalRows') - 1 && !this.rowStrategy.isLastIncomplete());
  10193. };
  10194. WalkontableTable.prototype.isLastColumnFullyVisible = function () {
  10195. return (this.getLastVisibleColumn() === this.instance.getSetting('totalColumns') - 1 && !this.columnStrategy.isLastIncomplete());
  10196. };
  10197. function WalkontableViewport(instance) {
  10198. this.instance = instance;
  10199. this.resetSettings();
  10200. if (this.instance.getSetting('nativeScrollbars')) {
  10201. var that = this;
  10202. $(window).on('resize', function () {
  10203. that.clientHeight = that.getWorkspaceHeight();
  10204. });
  10205. }
  10206. }
  10207. /*WalkontableViewport.prototype.isInSightVertical = function () {
  10208. //is table outside viewport bottom edge
  10209. if (tableTop > windowHeight + scrollTop) {
  10210. return -1;
  10211. }
  10212. //is table outside viewport top edge
  10213. else if (scrollTop > tableTop + tableFakeHeight) {
  10214. return -2;
  10215. }
  10216. //table is in viewport but how much exactly?
  10217. else {
  10218. }
  10219. };*/
  10220. //used by scrollbar
  10221. WalkontableViewport.prototype.getWorkspaceHeight = function (proposedHeight) {
  10222. if (this.instance.getSetting('nativeScrollbars')) {
  10223. return this.instance.wtScrollbars.vertical.windowSize;
  10224. }
  10225. var height = this.instance.getSetting('height');
  10226. if (height === Infinity || height === void 0 || height === null || height < 1) {
  10227. if (this.instance.wtScrollbars.vertical instanceof WalkontableScrollbarNative) {
  10228. height = this.instance.wtScrollbars.vertical.availableSize();
  10229. }
  10230. else {
  10231. height = Infinity;
  10232. }
  10233. }
  10234. if (height !== Infinity) {
  10235. if (proposedHeight >= height) {
  10236. height -= this.instance.getSetting('scrollbarHeight');
  10237. }
  10238. else if (this.instance.wtScrollbars.horizontal.visible) {
  10239. height -= this.instance.getSetting('scrollbarHeight');
  10240. }
  10241. }
  10242. return height;
  10243. };
  10244. WalkontableViewport.prototype.getWorkspaceWidth = function (proposedWidth) {
  10245. var width = this.instance.getSetting('width');
  10246. if (width === Infinity || width === void 0 || width === null || width < 1) {
  10247. if (this.instance.wtScrollbars.horizontal instanceof WalkontableScrollbarNative) {
  10248. width = this.instance.wtScrollbars.horizontal.availableSize();
  10249. }
  10250. else {
  10251. width = Infinity;
  10252. }
  10253. }
  10254. if (width !== Infinity) {
  10255. if (proposedWidth >= width) {
  10256. width -= this.instance.getSetting('scrollbarWidth');
  10257. }
  10258. else if (this.instance.wtScrollbars.vertical.visible) {
  10259. width -= this.instance.getSetting('scrollbarWidth');
  10260. }
  10261. }
  10262. return width;
  10263. };
  10264. WalkontableViewport.prototype.getWorkspaceActualHeight = function () {
  10265. return this.instance.wtDom.outerHeight(this.instance.wtTable.TABLE);
  10266. };
  10267. WalkontableViewport.prototype.getWorkspaceActualWidth = function () {
  10268. return this.instance.wtDom.outerWidth(this.instance.wtTable.TABLE) || this.instance.wtDom.outerWidth(this.instance.wtTable.TBODY) || this.instance.wtDom.outerWidth(this.instance.wtTable.THEAD); //IE8 reports 0 as <table> offsetWidth;
  10269. };
  10270. WalkontableViewport.prototype.getColumnHeaderHeight = function () {
  10271. if (isNaN(this.columnHeaderHeight)) {
  10272. var cellOffset = this.instance.wtDom.offset(this.instance.wtTable.TBODY)
  10273. , tableOffset = this.instance.wtTable.tableOffset;
  10274. this.columnHeaderHeight = cellOffset.top - tableOffset.top;
  10275. }
  10276. return this.columnHeaderHeight;
  10277. };
  10278. WalkontableViewport.prototype.getViewportHeight = function (proposedHeight) {
  10279. var containerHeight = this.getWorkspaceHeight(proposedHeight);
  10280. if (containerHeight === Infinity) {
  10281. return containerHeight;
  10282. }
  10283. var columnHeaderHeight = this.getColumnHeaderHeight();
  10284. if (columnHeaderHeight > 0) {
  10285. return containerHeight - columnHeaderHeight;
  10286. }
  10287. else {
  10288. return containerHeight;
  10289. }
  10290. };
  10291. WalkontableViewport.prototype.getRowHeaderHeight = function () {
  10292. if (this.instance.cloneFrom) {
  10293. return this.instance.cloneFrom.wtViewport.getRowHeaderHeight();
  10294. }
  10295. if (isNaN(this.rowHeaderWidth)) {
  10296. var TR = this.instance.wtTable.TBODY ? this.instance.wtTable.TBODY.firstChild : null;
  10297. if (TR) {
  10298. var TD = TR.firstChild;
  10299. this.rowHeaderWidth = 0;
  10300. while (TD && TD.nodeName === 'TH') {
  10301. this.rowHeaderWidth += this.instance.wtDom.outerWidth(TD);
  10302. TD = TD.nextSibling;
  10303. }
  10304. }
  10305. }
  10306. return this.rowHeaderWidth;
  10307. };
  10308. WalkontableViewport.prototype.getViewportWidth = function (proposedWidth) {
  10309. var containerWidth = this.getWorkspaceWidth(proposedWidth);
  10310. if (containerWidth === Infinity) {
  10311. return containerWidth;
  10312. }
  10313. var rowHeaderWidth = this.getRowHeaderHeight();
  10314. if (rowHeaderWidth > 0) {
  10315. return containerWidth - rowHeaderWidth;
  10316. }
  10317. else {
  10318. return containerWidth;
  10319. }
  10320. };
  10321. WalkontableViewport.prototype.resetSettings = function () {
  10322. this.rowHeaderWidth = NaN;
  10323. this.columnHeaderHeight = NaN;
  10324. };
  10325. function WalkontableWheel(instance) {
  10326. if (instance.getSetting('nativeScrollbars')) {
  10327. return;
  10328. }
  10329. //spreader === instance.wtTable.TABLE.parentNode
  10330. $(instance.wtTable.spreader).on('mousewheel', function (event, delta, deltaX, deltaY) {
  10331. if (!deltaX && !deltaY && delta) { //we are in IE8, see https://github.com/brandonaaron/jquery-mousewheel/issues/53
  10332. deltaY = delta;
  10333. }
  10334. if (!deltaX && !deltaY) { //this happens in IE8 test case
  10335. return;
  10336. }
  10337. if (deltaY > 0 && instance.getSetting('offsetRow') === 0) {
  10338. return; //attempt to scroll up when it's already showing first row
  10339. }
  10340. else if (deltaY < 0 && instance.wtTable.isLastRowFullyVisible()) {
  10341. return; //attempt to scroll down when it's already showing last row
  10342. }
  10343. else if (deltaX < 0 && instance.getSetting('offsetColumn') === 0) {
  10344. return; //attempt to scroll left when it's already showing first column
  10345. }
  10346. else if (deltaX > 0 && instance.wtTable.isLastColumnFullyVisible()) {
  10347. return; //attempt to scroll right when it's already showing last column
  10348. }
  10349. //now we are sure we really want to scroll
  10350. clearTimeout(instance.wheelTimeout);
  10351. instance.wheelTimeout = setTimeout(function () { //timeout is needed because with fast-wheel scrolling mousewheel event comes dozen times per second
  10352. if (deltaY) {
  10353. //ceil is needed because jquery-mousewheel reports fractional mousewheel deltas on touchpad scroll
  10354. //see http://stackoverflow.com/questions/5527601/normalizing-mousewheel-speed-across-browsers
  10355. if (instance.wtScrollbars.vertical.visible) { // if we see scrollbar
  10356. instance.scrollVertical(-Math.ceil(deltaY)).draw();
  10357. }
  10358. }
  10359. else if (deltaX) {
  10360. if (instance.wtScrollbars.horizontal.visible) { // if we see scrollbar
  10361. instance.scrollHorizontal(Math.ceil(deltaX)).draw();
  10362. }
  10363. }
  10364. }, 0);
  10365. event.preventDefault();
  10366. });
  10367. }
  10368. /**
  10369. * Dragdealer JS v0.9.5 - patched by Walkontable at lines 66, 309-310, 339-340
  10370. * http://code.ovidiu.ch/dragdealer-js
  10371. *
  10372. * Copyright (c) 2010, Ovidiu Chereches
  10373. * MIT License
  10374. * http://legal.ovidiu.ch/licenses/MIT
  10375. */
  10376. /* Cursor */
  10377. var Cursor =
  10378. {
  10379. x: 0, y: 0,
  10380. init: function()
  10381. {
  10382. this.setEvent('mouse');
  10383. this.setEvent('touch');
  10384. },
  10385. setEvent: function(type)
  10386. {
  10387. var moveHandler = document['on' + type + 'move'] || function(){};
  10388. document['on' + type + 'move'] = function(e)
  10389. {
  10390. moveHandler(e);
  10391. Cursor.refresh(e);
  10392. }
  10393. },
  10394. refresh: function(e)
  10395. {
  10396. if(!e)
  10397. {
  10398. e = window.event;
  10399. }
  10400. if(e.type == 'mousemove')
  10401. {
  10402. this.set(e);
  10403. }
  10404. else if(e.touches)
  10405. {
  10406. this.set(e.touches[0]);
  10407. }
  10408. },
  10409. set: function(e)
  10410. {
  10411. if(e.pageX || e.pageY)
  10412. {
  10413. this.x = e.pageX;
  10414. this.y = e.pageY;
  10415. }
  10416. else if(e.clientX || e.clientY)
  10417. {
  10418. this.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
  10419. this.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  10420. }
  10421. }
  10422. };
  10423. Cursor.init();
  10424. /* Position */
  10425. var Position =
  10426. {
  10427. get: function(obj)
  10428. {
  10429. var curtop = 0, curleft = 0; //Walkontable patch. Original (var curleft = curtop = 0;) created curtop in global scope
  10430. if(obj.offsetParent)
  10431. {
  10432. do
  10433. {
  10434. curleft += obj.offsetLeft;
  10435. curtop += obj.offsetTop;
  10436. }
  10437. while((obj = obj.offsetParent));
  10438. }
  10439. return [curleft, curtop];
  10440. }
  10441. };
  10442. /* Dragdealer */
  10443. var Dragdealer = function(wrapper, options)
  10444. {
  10445. if(typeof(wrapper) == 'string')
  10446. {
  10447. wrapper = document.getElementById(wrapper);
  10448. }
  10449. if(!wrapper)
  10450. {
  10451. return;
  10452. }
  10453. var handle = wrapper.getElementsByTagName('div')[0];
  10454. if(!handle || handle.className.search(/(^|\s)handle(\s|$)/) == -1)
  10455. {
  10456. return;
  10457. }
  10458. this.init(wrapper, handle, options || {});
  10459. this.setup();
  10460. };
  10461. Dragdealer.prototype =
  10462. {
  10463. init: function(wrapper, handle, options)
  10464. {
  10465. this.wrapper = wrapper;
  10466. this.handle = handle;
  10467. this.options = options;
  10468. this.disabled = this.getOption('disabled', false);
  10469. this.horizontal = this.getOption('horizontal', true);
  10470. this.vertical = this.getOption('vertical', false);
  10471. this.slide = this.getOption('slide', true);
  10472. this.steps = this.getOption('steps', 0);
  10473. this.snap = this.getOption('snap', false);
  10474. this.loose = this.getOption('loose', false);
  10475. this.speed = this.getOption('speed', 10) / 100;
  10476. this.xPrecision = this.getOption('xPrecision', 0);
  10477. this.yPrecision = this.getOption('yPrecision', 0);
  10478. this.callback = options.callback || null;
  10479. this.animationCallback = options.animationCallback || null;
  10480. this.bounds = {
  10481. left: options.left || 0, right: -(options.right || 0),
  10482. top: options.top || 0, bottom: -(options.bottom || 0),
  10483. x0: 0, x1: 0, xRange: 0,
  10484. y0: 0, y1: 0, yRange: 0
  10485. };
  10486. this.value = {
  10487. prev: [-1, -1],
  10488. current: [options.x || 0, options.y || 0],
  10489. target: [options.x || 0, options.y || 0]
  10490. };
  10491. this.offset = {
  10492. wrapper: [0, 0],
  10493. mouse: [0, 0],
  10494. prev: [-999999, -999999],
  10495. current: [0, 0],
  10496. target: [0, 0]
  10497. };
  10498. this.change = [0, 0];
  10499. this.activity = false;
  10500. this.dragging = false;
  10501. this.tapping = false;
  10502. },
  10503. getOption: function(name, defaultValue)
  10504. {
  10505. return this.options[name] !== undefined ? this.options[name] : defaultValue;
  10506. },
  10507. setup: function()
  10508. {
  10509. this.setWrapperOffset();
  10510. this.setBoundsPadding();
  10511. this.setBounds();
  10512. this.setSteps();
  10513. this.addListeners();
  10514. },
  10515. setWrapperOffset: function()
  10516. {
  10517. this.offset.wrapper = Position.get(this.wrapper);
  10518. },
  10519. setBoundsPadding: function()
  10520. {
  10521. if(!this.bounds.left && !this.bounds.right)
  10522. {
  10523. this.bounds.left = Position.get(this.handle)[0] - this.offset.wrapper[0];
  10524. this.bounds.right = -this.bounds.left;
  10525. }
  10526. if(!this.bounds.top && !this.bounds.bottom)
  10527. {
  10528. this.bounds.top = Position.get(this.handle)[1] - this.offset.wrapper[1];
  10529. this.bounds.bottom = -this.bounds.top;
  10530. }
  10531. },
  10532. setBounds: function()
  10533. {
  10534. this.bounds.x0 = this.bounds.left;
  10535. this.bounds.x1 = this.wrapper.offsetWidth + this.bounds.right;
  10536. this.bounds.xRange = (this.bounds.x1 - this.bounds.x0) - this.handle.offsetWidth;
  10537. this.bounds.y0 = this.bounds.top;
  10538. this.bounds.y1 = this.wrapper.offsetHeight + this.bounds.bottom;
  10539. this.bounds.yRange = (this.bounds.y1 - this.bounds.y0) - this.handle.offsetHeight;
  10540. this.bounds.xStep = 1 / (this.xPrecision || Math.max(this.wrapper.offsetWidth, this.handle.offsetWidth));
  10541. this.bounds.yStep = 1 / (this.yPrecision || Math.max(this.wrapper.offsetHeight, this.handle.offsetHeight));
  10542. },
  10543. setSteps: function()
  10544. {
  10545. if(this.steps > 1)
  10546. {
  10547. this.stepRatios = [];
  10548. for(var i = 0; i <= this.steps - 1; i++)
  10549. {
  10550. this.stepRatios[i] = i / (this.steps - 1);
  10551. }
  10552. }
  10553. },
  10554. addListeners: function()
  10555. {
  10556. var self = this;
  10557. this.wrapper.onselectstart = function()
  10558. {
  10559. return false;
  10560. }
  10561. this.handle.onmousedown = this.handle.ontouchstart = function(e)
  10562. {
  10563. self.handleDownHandler(e);
  10564. };
  10565. this.wrapper.onmousedown = this.wrapper.ontouchstart = function(e)
  10566. {
  10567. self.wrapperDownHandler(e);
  10568. };
  10569. var mouseUpHandler = document.onmouseup || function(){};
  10570. document.onmouseup = function(e)
  10571. {
  10572. mouseUpHandler(e);
  10573. self.documentUpHandler(e);
  10574. };
  10575. var touchEndHandler = document.ontouchend || function(){};
  10576. document.ontouchend = function(e)
  10577. {
  10578. touchEndHandler(e);
  10579. self.documentUpHandler(e);
  10580. };
  10581. var resizeHandler = window.onresize || function(){};
  10582. window.onresize = function(e)
  10583. {
  10584. resizeHandler(e);
  10585. self.documentResizeHandler(e);
  10586. };
  10587. this.wrapper.onmousemove = function(e)
  10588. {
  10589. self.activity = true;
  10590. }
  10591. this.wrapper.onclick = function(e)
  10592. {
  10593. return !self.activity;
  10594. }
  10595. this.interval = setInterval(function(){ self.animate() }, 25);
  10596. self.animate(false, true);
  10597. },
  10598. handleDownHandler: function(e)
  10599. {
  10600. this.activity = false;
  10601. Cursor.refresh(e);
  10602. this.preventDefaults(e, true);
  10603. this.startDrag();
  10604. },
  10605. wrapperDownHandler: function(e)
  10606. {
  10607. Cursor.refresh(e);
  10608. this.preventDefaults(e, true);
  10609. this.startTap();
  10610. },
  10611. documentUpHandler: function(e)
  10612. {
  10613. this.stopDrag();
  10614. this.stopTap();
  10615. },
  10616. documentResizeHandler: function(e)
  10617. {
  10618. this.setWrapperOffset();
  10619. this.setBounds();
  10620. this.update();
  10621. },
  10622. enable: function()
  10623. {
  10624. this.disabled = false;
  10625. this.handle.className = this.handle.className.replace(/\s?disabled/g, '');
  10626. },
  10627. disable: function()
  10628. {
  10629. this.disabled = true;
  10630. this.handle.className += ' disabled';
  10631. },
  10632. setStep: function(x, y, snap)
  10633. {
  10634. this.setValue(
  10635. this.steps && x > 1 ? (x - 1) / (this.steps - 1) : 0,
  10636. this.steps && y > 1 ? (y - 1) / (this.steps - 1) : 0,
  10637. snap
  10638. );
  10639. },
  10640. setValue: function(x, y, snap)
  10641. {
  10642. this.setTargetValue([x, y || 0]);
  10643. if(snap)
  10644. {
  10645. this.groupCopy(this.value.current, this.value.target);
  10646. }
  10647. },
  10648. startTap: function(target)
  10649. {
  10650. if(this.disabled)
  10651. {
  10652. return;
  10653. }
  10654. this.tapping = true;
  10655. this.setWrapperOffset();
  10656. this.setBounds();
  10657. if(target === undefined)
  10658. {
  10659. target = [
  10660. Cursor.x - this.offset.wrapper[0] - (this.handle.offsetWidth / 2),
  10661. Cursor.y - this.offset.wrapper[1] - (this.handle.offsetHeight / 2)
  10662. ];
  10663. }
  10664. this.setTargetOffset(target);
  10665. },
  10666. stopTap: function()
  10667. {
  10668. if(this.disabled || !this.tapping)
  10669. {
  10670. return;
  10671. }
  10672. this.tapping = false;
  10673. this.setTargetValue(this.value.current);
  10674. this.result();
  10675. },
  10676. startDrag: function()
  10677. {
  10678. if(this.disabled)
  10679. {
  10680. return;
  10681. }
  10682. this.setWrapperOffset();
  10683. this.setBounds();
  10684. this.offset.mouse = [
  10685. Cursor.x - Position.get(this.handle)[0],
  10686. Cursor.y - Position.get(this.handle)[1]
  10687. ];
  10688. this.dragging = true;
  10689. },
  10690. stopDrag: function()
  10691. {
  10692. if(this.disabled || !this.dragging)
  10693. {
  10694. return;
  10695. }
  10696. this.dragging = false;
  10697. var target = this.groupClone(this.value.current);
  10698. if(this.slide)
  10699. {
  10700. var ratioChange = this.change;
  10701. target[0] += ratioChange[0] * 4;
  10702. target[1] += ratioChange[1] * 4;
  10703. }
  10704. this.setTargetValue(target);
  10705. this.result();
  10706. },
  10707. feedback: function()
  10708. {
  10709. var value = this.value.current;
  10710. if(this.snap && this.steps > 1)
  10711. {
  10712. value = this.getClosestSteps(value);
  10713. }
  10714. if(!this.groupCompare(value, this.value.prev))
  10715. {
  10716. if(typeof(this.animationCallback) == 'function')
  10717. {
  10718. this.animationCallback(value[0], value[1]);
  10719. }
  10720. this.groupCopy(this.value.prev, value);
  10721. }
  10722. },
  10723. result: function()
  10724. {
  10725. if(typeof(this.callback) == 'function')
  10726. {
  10727. this.callback(this.value.target[0], this.value.target[1]);
  10728. }
  10729. },
  10730. animate: function(direct, first)
  10731. {
  10732. if(direct && !this.dragging)
  10733. {
  10734. return;
  10735. }
  10736. if(this.dragging)
  10737. {
  10738. var prevTarget = this.groupClone(this.value.target);
  10739. var offset = [
  10740. Cursor.x - this.offset.wrapper[0] - this.offset.mouse[0],
  10741. Cursor.y - this.offset.wrapper[1] - this.offset.mouse[1]
  10742. ];
  10743. this.setTargetOffset(offset, this.loose);
  10744. this.change = [
  10745. this.value.target[0] - prevTarget[0],
  10746. this.value.target[1] - prevTarget[1]
  10747. ];
  10748. }
  10749. if(this.dragging || first)
  10750. {
  10751. this.groupCopy(this.value.current, this.value.target);
  10752. }
  10753. if(this.dragging || this.glide() || first)
  10754. {
  10755. this.update();
  10756. this.feedback();
  10757. }
  10758. },
  10759. glide: function()
  10760. {
  10761. var diff = [
  10762. this.value.target[0] - this.value.current[0],
  10763. this.value.target[1] - this.value.current[1]
  10764. ];
  10765. if(!diff[0] && !diff[1])
  10766. {
  10767. return false;
  10768. }
  10769. if(Math.abs(diff[0]) > this.bounds.xStep || Math.abs(diff[1]) > this.bounds.yStep)
  10770. {
  10771. this.value.current[0] += diff[0] * this.speed;
  10772. this.value.current[1] += diff[1] * this.speed;
  10773. }
  10774. else
  10775. {
  10776. this.groupCopy(this.value.current, this.value.target);
  10777. }
  10778. return true;
  10779. },
  10780. update: function()
  10781. {
  10782. if(!this.snap)
  10783. {
  10784. this.offset.current = this.getOffsetsByRatios(this.value.current);
  10785. }
  10786. else
  10787. {
  10788. this.offset.current = this.getOffsetsByRatios(
  10789. this.getClosestSteps(this.value.current)
  10790. );
  10791. }
  10792. this.show();
  10793. },
  10794. show: function()
  10795. {
  10796. if(!this.groupCompare(this.offset.current, this.offset.prev))
  10797. {
  10798. if(this.horizontal)
  10799. {
  10800. this.handle.style.left = String(this.offset.current[0]) + 'px';
  10801. }
  10802. if(this.vertical)
  10803. {
  10804. this.handle.style.top = String(this.offset.current[1]) + 'px';
  10805. }
  10806. this.groupCopy(this.offset.prev, this.offset.current);
  10807. }
  10808. },
  10809. setTargetValue: function(value, loose)
  10810. {
  10811. var target = loose ? this.getLooseValue(value) : this.getProperValue(value);
  10812. this.groupCopy(this.value.target, target);
  10813. this.offset.target = this.getOffsetsByRatios(target);
  10814. },
  10815. setTargetOffset: function(offset, loose)
  10816. {
  10817. var value = this.getRatiosByOffsets(offset);
  10818. var target = loose ? this.getLooseValue(value) : this.getProperValue(value);
  10819. this.groupCopy(this.value.target, target);
  10820. this.offset.target = this.getOffsetsByRatios(target);
  10821. },
  10822. getLooseValue: function(value)
  10823. {
  10824. var proper = this.getProperValue(value);
  10825. return [
  10826. proper[0] + ((value[0] - proper[0]) / 4),
  10827. proper[1] + ((value[1] - proper[1]) / 4)
  10828. ];
  10829. },
  10830. getProperValue: function(value)
  10831. {
  10832. var proper = this.groupClone(value);
  10833. proper[0] = Math.max(proper[0], 0);
  10834. proper[1] = Math.max(proper[1], 0);
  10835. proper[0] = Math.min(proper[0], 1);
  10836. proper[1] = Math.min(proper[1], 1);
  10837. if((!this.dragging && !this.tapping) || this.snap)
  10838. {
  10839. if(this.steps > 1)
  10840. {
  10841. proper = this.getClosestSteps(proper);
  10842. }
  10843. }
  10844. return proper;
  10845. },
  10846. getRatiosByOffsets: function(group)
  10847. {
  10848. return [
  10849. this.getRatioByOffset(group[0], this.bounds.xRange, this.bounds.x0),
  10850. this.getRatioByOffset(group[1], this.bounds.yRange, this.bounds.y0)
  10851. ];
  10852. },
  10853. getRatioByOffset: function(offset, range, padding)
  10854. {
  10855. return range ? (offset - padding) / range : 0;
  10856. },
  10857. getOffsetsByRatios: function(group)
  10858. {
  10859. return [
  10860. this.getOffsetByRatio(group[0], this.bounds.xRange, this.bounds.x0),
  10861. this.getOffsetByRatio(group[1], this.bounds.yRange, this.bounds.y0)
  10862. ];
  10863. },
  10864. getOffsetByRatio: function(ratio, range, padding)
  10865. {
  10866. return Math.round(ratio * range) + padding;
  10867. },
  10868. getClosestSteps: function(group)
  10869. {
  10870. return [
  10871. this.getClosestStep(group[0]),
  10872. this.getClosestStep(group[1])
  10873. ];
  10874. },
  10875. getClosestStep: function(value)
  10876. {
  10877. var k = 0;
  10878. var min = 1;
  10879. for(var i = 0; i <= this.steps - 1; i++)
  10880. {
  10881. if(Math.abs(this.stepRatios[i] - value) < min)
  10882. {
  10883. min = Math.abs(this.stepRatios[i] - value);
  10884. k = i;
  10885. }
  10886. }
  10887. return this.stepRatios[k];
  10888. },
  10889. groupCompare: function(a, b)
  10890. {
  10891. return a[0] == b[0] && a[1] == b[1];
  10892. },
  10893. groupCopy: function(a, b)
  10894. {
  10895. a[0] = b[0];
  10896. a[1] = b[1];
  10897. },
  10898. groupClone: function(a)
  10899. {
  10900. return [a[0], a[1]];
  10901. },
  10902. preventDefaults: function(e, selection)
  10903. {
  10904. if(!e)
  10905. {
  10906. e = window.event;
  10907. }
  10908. if(e.preventDefault)
  10909. {
  10910. e.preventDefault();
  10911. }
  10912. e.returnValue = false;
  10913. if(selection && document.selection)
  10914. {
  10915. document.selection.empty();
  10916. }
  10917. },
  10918. cancelEvent: function(e)
  10919. {
  10920. if(!e)
  10921. {
  10922. e = window.event;
  10923. }
  10924. if(e.stopPropagation)
  10925. {
  10926. e.stopPropagation();
  10927. }
  10928. e.cancelBubble = true;
  10929. }
  10930. };
  10931. /*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net)
  10932. * Licensed under the MIT License (LICENSE.txt).
  10933. *
  10934. * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
  10935. * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
  10936. * Thanks to: Seamus Leahy for adding deltaX and deltaY
  10937. *
  10938. * Version: 3.1.3
  10939. *
  10940. * Requires: 1.2.2+
  10941. */
  10942. (function (factory) {
  10943. if ( typeof define === 'function' && define.amd ) {
  10944. // AMD. Register as an anonymous module.
  10945. define(['jquery'], factory);
  10946. } else if (typeof exports === 'object') {
  10947. // Node/CommonJS style for Browserify
  10948. module.exports = factory;
  10949. } else {
  10950. // Browser globals
  10951. factory(jQuery);
  10952. }
  10953. }(function ($) {
  10954. var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
  10955. var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
  10956. var lowestDelta, lowestDeltaXY;
  10957. if ( $.event.fixHooks ) {
  10958. for ( var i = toFix.length; i; ) {
  10959. $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
  10960. }
  10961. }
  10962. $.event.special.mousewheel = {
  10963. setup: function() {
  10964. if ( this.addEventListener ) {
  10965. for ( var i = toBind.length; i; ) {
  10966. this.addEventListener( toBind[--i], handler, false );
  10967. }
  10968. } else {
  10969. this.onmousewheel = handler;
  10970. }
  10971. },
  10972. teardown: function() {
  10973. if ( this.removeEventListener ) {
  10974. for ( var i = toBind.length; i; ) {
  10975. this.removeEventListener( toBind[--i], handler, false );
  10976. }
  10977. } else {
  10978. this.onmousewheel = null;
  10979. }
  10980. }
  10981. };
  10982. $.fn.extend({
  10983. mousewheel: function(fn) {
  10984. return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
  10985. },
  10986. unmousewheel: function(fn) {
  10987. return this.unbind("mousewheel", fn);
  10988. }
  10989. });
  10990. function handler(event) {
  10991. var orgEvent = event || window.event,
  10992. args = [].slice.call(arguments, 1),
  10993. delta = 0,
  10994. deltaX = 0,
  10995. deltaY = 0,
  10996. absDelta = 0,
  10997. absDeltaXY = 0,
  10998. fn;
  10999. event = $.event.fix(orgEvent);
  11000. event.type = "mousewheel";
  11001. // Old school scrollwheel delta
  11002. if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
  11003. if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
  11004. // New school wheel delta (wheel event)
  11005. if ( orgEvent.deltaY ) {
  11006. deltaY = orgEvent.deltaY * -1;
  11007. delta = deltaY;
  11008. }
  11009. if ( orgEvent.deltaX ) {
  11010. deltaX = orgEvent.deltaX;
  11011. delta = deltaX * -1;
  11012. }
  11013. // Webkit
  11014. if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
  11015. if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; }
  11016. // Look for lowest delta to normalize the delta values
  11017. absDelta = Math.abs(delta);
  11018. if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
  11019. absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
  11020. if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
  11021. // Get a whole value for the deltas
  11022. fn = delta > 0 ? 'floor' : 'ceil';
  11023. delta = Math[fn](delta / lowestDelta);
  11024. deltaX = Math[fn](deltaX / lowestDeltaXY);
  11025. deltaY = Math[fn](deltaY / lowestDeltaXY);
  11026. // Add event and delta to the front of the arguments
  11027. args.unshift(event, delta, deltaX, deltaY);
  11028. return ($.event.dispatch || $.event.handle).apply(this, args);
  11029. }
  11030. }));
  11031. /**
  11032. * Array.filter() shim by Trevor Menagh (https://github.com/trevmex) with some modifications
  11033. */
  11034. if (!Array.prototype.filter) {
  11035. Array.prototype.filter = function (fun, thisp) {
  11036. "use strict";
  11037. if (typeof this === "undefined" || this === null) {
  11038. throw new TypeError();
  11039. }
  11040. if (typeof fun !== "function") {
  11041. throw new TypeError();
  11042. }
  11043. thisp = thisp || this;
  11044. if (isNodeList(thisp)) {
  11045. thisp = convertNodeListToArray(thisp);
  11046. }
  11047. var len = thisp.length,
  11048. res = [],
  11049. i,
  11050. val;
  11051. for (i = 0; i < len; i += 1) {
  11052. if (thisp.hasOwnProperty(i)) {
  11053. val = thisp[i]; // in case fun mutates this
  11054. if (fun.call(thisp, val, i, thisp)) {
  11055. res.push(val);
  11056. }
  11057. }
  11058. }
  11059. return res;
  11060. function isNodeList(object) {
  11061. return /NodeList/i.test(object.item);
  11062. }
  11063. function convertNodeListToArray(nodeList) {
  11064. var array = [];
  11065. for (var i = 0, len = nodeList.length; i < len; i++){
  11066. array[i] = nodeList[i]
  11067. }
  11068. return array;
  11069. }
  11070. };
  11071. }
  11072. })(jQuery, window, Handsontable);
  11073. // numeral.js
  11074. // version : 1.4.7
  11075. // author : Adam Draper
  11076. // license : MIT
  11077. // http://adamwdraper.github.com/Numeral-js/
  11078. (function () {
  11079. /************************************
  11080. Constants
  11081. ************************************/
  11082. var numeral,
  11083. VERSION = '1.4.7',
  11084. // internal storage for language config files
  11085. languages = {},
  11086. currentLanguage = 'en',
  11087. zeroFormat = null,
  11088. // check for nodeJS
  11089. hasModule = (typeof module !== 'undefined' && module.exports);
  11090. /************************************
  11091. Constructors
  11092. ************************************/
  11093. // Numeral prototype object
  11094. function Numeral (number) {
  11095. this._n = number;
  11096. }
  11097. /**
  11098. * Implementation of toFixed() that treats floats more like decimals
  11099. *
  11100. * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present
  11101. * problems for accounting- and finance-related software.
  11102. */
  11103. function toFixed (value, precision, optionals) {
  11104. var power = Math.pow(10, precision),
  11105. output;
  11106. // Multiply up by precision, round accurately, then divide and use native toFixed():
  11107. output = (Math.round(value * power) / power).toFixed(precision);
  11108. if (optionals) {
  11109. var optionalsRegExp = new RegExp('0{1,' + optionals + '}$');
  11110. output = output.replace(optionalsRegExp, '');
  11111. }
  11112. return output;
  11113. }
  11114. /************************************
  11115. Formatting
  11116. ************************************/
  11117. // determine what type of formatting we need to do
  11118. function formatNumeral (n, format) {
  11119. var output;
  11120. // figure out what kind of format we are dealing with
  11121. if (format.indexOf('$') > -1) { // currency!!!!!
  11122. output = formatCurrency(n, format);
  11123. } else if (format.indexOf('%') > -1) { // percentage
  11124. output = formatPercentage(n, format);
  11125. } else if (format.indexOf(':') > -1) { // time
  11126. output = formatTime(n, format);
  11127. } else { // plain ol' numbers or bytes
  11128. output = formatNumber(n, format);
  11129. }
  11130. // return string
  11131. return output;
  11132. }
  11133. // revert to number
  11134. function unformatNumeral (n, string) {
  11135. if (string.indexOf(':') > -1) {
  11136. n._n = unformatTime(string);
  11137. } else {
  11138. if (string === zeroFormat) {
  11139. n._n = 0;
  11140. } else {
  11141. var stringOriginal = string;
  11142. if (languages[currentLanguage].delimiters.decimal !== '.') {
  11143. string = string.replace(/\./g,'').replace(languages[currentLanguage].delimiters.decimal, '.');
  11144. }
  11145. // see if abbreviations are there so that we can multiply to the correct number
  11146. var thousandRegExp = new RegExp(languages[currentLanguage].abbreviations.thousand + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'),
  11147. millionRegExp = new RegExp(languages[currentLanguage].abbreviations.million + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'),
  11148. billionRegExp = new RegExp(languages[currentLanguage].abbreviations.billion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'),
  11149. trillionRegExp = new RegExp(languages[currentLanguage].abbreviations.trillion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
  11150. // see if bytes are there so that we can multiply to the correct number
  11151. var prefixes = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  11152. bytesMultiplier = false;
  11153. for (var power = 0; power <= prefixes.length; power++) {
  11154. bytesMultiplier = (string.indexOf(prefixes[power]) > -1) ? Math.pow(1024, power + 1) : false;
  11155. if (bytesMultiplier) {
  11156. break;
  11157. }
  11158. }
  11159. // do some math to create our number
  11160. n._n = ((bytesMultiplier) ? bytesMultiplier : 1) * ((stringOriginal.match(thousandRegExp)) ? Math.pow(10, 3) : 1) * ((stringOriginal.match(millionRegExp)) ? Math.pow(10, 6) : 1) * ((stringOriginal.match(billionRegExp)) ? Math.pow(10, 9) : 1) * ((stringOriginal.match(trillionRegExp)) ? Math.pow(10, 12) : 1) * ((string.indexOf('%') > -1) ? 0.01 : 1) * Number(((string.indexOf('(') > -1) ? '-' : '') + string.replace(/[^0-9\.-]+/g, ''));
  11161. // round if we are talking about bytes
  11162. n._n = (bytesMultiplier) ? Math.ceil(n._n) : n._n;
  11163. }
  11164. }
  11165. return n._n;
  11166. }
  11167. function formatCurrency (n, format) {
  11168. var prependSymbol = (format.indexOf('$') <= 1) ? true : false;
  11169. // remove $ for the moment
  11170. var space = '';
  11171. // check for space before or after currency
  11172. if (format.indexOf(' $') > -1) {
  11173. space = ' ';
  11174. format = format.replace(' $', '');
  11175. } else if (format.indexOf('$ ') > -1) {
  11176. space = ' ';
  11177. format = format.replace('$ ', '');
  11178. } else {
  11179. format = format.replace('$', '');
  11180. }
  11181. // format the number
  11182. var output = formatNumeral(n, format);
  11183. // position the symbol
  11184. if (prependSymbol) {
  11185. if (output.indexOf('(') > -1 || output.indexOf('-') > -1) {
  11186. output = output.split('');
  11187. output.splice(1, 0, languages[currentLanguage].currency.symbol + space);
  11188. output = output.join('');
  11189. } else {
  11190. output = languages[currentLanguage].currency.symbol + space + output;
  11191. }
  11192. } else {
  11193. if (output.indexOf(')') > -1) {
  11194. output = output.split('');
  11195. output.splice(-1, 0, space + languages[currentLanguage].currency.symbol);
  11196. output = output.join('');
  11197. } else {
  11198. output = output + space + languages[currentLanguage].currency.symbol;
  11199. }
  11200. }
  11201. return output;
  11202. }
  11203. function formatPercentage (n, format) {
  11204. var space = '';
  11205. // check for space before %
  11206. if (format.indexOf(' %') > -1) {
  11207. space = ' ';
  11208. format = format.replace(' %', '');
  11209. } else {
  11210. format = format.replace('%', '');
  11211. }
  11212. n._n = n._n * 100;
  11213. var output = formatNumeral(n, format);
  11214. if (output.indexOf(')') > -1 ) {
  11215. output = output.split('');
  11216. output.splice(-1, 0, space + '%');
  11217. output = output.join('');
  11218. } else {
  11219. output = output + space + '%';
  11220. }
  11221. return output;
  11222. }
  11223. function formatTime (n, format) {
  11224. var hours = Math.floor(n._n/60/60),
  11225. minutes = Math.floor((n._n - (hours * 60 * 60))/60),
  11226. seconds = Math.round(n._n - (hours * 60 * 60) - (minutes * 60));
  11227. return hours + ':' + ((minutes < 10) ? '0' + minutes : minutes) + ':' + ((seconds < 10) ? '0' + seconds : seconds);
  11228. }
  11229. function unformatTime (string) {
  11230. var timeArray = string.split(':'),
  11231. seconds = 0;
  11232. // turn hours and minutes into seconds and add them all up
  11233. if (timeArray.length === 3) {
  11234. // hours
  11235. seconds = seconds + (Number(timeArray[0]) * 60 * 60);
  11236. // minutes
  11237. seconds = seconds + (Number(timeArray[1]) * 60);
  11238. // seconds
  11239. seconds = seconds + Number(timeArray[2]);
  11240. } else if (timeArray.lenght === 2) {
  11241. // minutes
  11242. seconds = seconds + (Number(timeArray[0]) * 60);
  11243. // seconds
  11244. seconds = seconds + Number(timeArray[1]);
  11245. }
  11246. return Number(seconds);
  11247. }
  11248. function formatNumber (n, format) {
  11249. var negP = false,
  11250. optDec = false,
  11251. abbr = '',
  11252. bytes = '',
  11253. ord = '',
  11254. abs = Math.abs(n._n);
  11255. // check if number is zero and a custom zero format has been set
  11256. if (n._n === 0 && zeroFormat !== null) {
  11257. return zeroFormat;
  11258. } else {
  11259. // see if we should use parentheses for negative number
  11260. if (format.indexOf('(') > -1) {
  11261. negP = true;
  11262. format = format.slice(1, -1);
  11263. }
  11264. // see if abbreviation is wanted
  11265. if (format.indexOf('a') > -1) {
  11266. // check for space before abbreviation
  11267. if (format.indexOf(' a') > -1) {
  11268. abbr = ' ';
  11269. format = format.replace(' a', '');
  11270. } else {
  11271. format = format.replace('a', '');
  11272. }
  11273. if (abs >= Math.pow(10, 12)) {
  11274. // trillion
  11275. abbr = abbr + languages[currentLanguage].abbreviations.trillion;
  11276. n._n = n._n / Math.pow(10, 12);
  11277. } else if (abs < Math.pow(10, 12) && abs >= Math.pow(10, 9)) {
  11278. // billion
  11279. abbr = abbr + languages[currentLanguage].abbreviations.billion;
  11280. n._n = n._n / Math.pow(10, 9);
  11281. } else if (abs < Math.pow(10, 9) && abs >= Math.pow(10, 6)) {
  11282. // million
  11283. abbr = abbr + languages[currentLanguage].abbreviations.million;
  11284. n._n = n._n / Math.pow(10, 6);
  11285. } else if (abs < Math.pow(10, 6) && abs >= Math.pow(10, 3)) {
  11286. // thousand
  11287. abbr = abbr + languages[currentLanguage].abbreviations.thousand;
  11288. n._n = n._n / Math.pow(10, 3);
  11289. }
  11290. }
  11291. // see if we are formatting bytes
  11292. if (format.indexOf('b') > -1) {
  11293. // check for space before
  11294. if (format.indexOf(' b') > -1) {
  11295. bytes = ' ';
  11296. format = format.replace(' b', '');
  11297. } else {
  11298. format = format.replace('b', '');
  11299. }
  11300. var prefixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  11301. min,
  11302. max;
  11303. for (var power = 0; power <= prefixes.length; power++) {
  11304. min = Math.pow(1024, power);
  11305. max = Math.pow(1024, power+1);
  11306. if (n._n >= min && n._n < max) {
  11307. bytes = bytes + prefixes[power];
  11308. if (min > 0) {
  11309. n._n = n._n / min;
  11310. }
  11311. break;
  11312. }
  11313. }
  11314. }
  11315. // see if ordinal is wanted
  11316. if (format.indexOf('o') > -1) {
  11317. // check for space before
  11318. if (format.indexOf(' o') > -1) {
  11319. ord = ' ';
  11320. format = format.replace(' o', '');
  11321. } else {
  11322. format = format.replace('o', '');
  11323. }
  11324. ord = ord + languages[currentLanguage].ordinal(n._n);
  11325. }
  11326. if (format.indexOf('[.]') > -1) {
  11327. optDec = true;
  11328. format = format.replace('[.]', '.');
  11329. }
  11330. var w = n._n.toString().split('.')[0],
  11331. precision = format.split('.')[1],
  11332. thousands = format.indexOf(','),
  11333. d = '',
  11334. neg = false;
  11335. if (precision) {
  11336. if (precision.indexOf('[') > -1) {
  11337. precision = precision.replace(']', '');
  11338. precision = precision.split('[');
  11339. d = toFixed(n._n, (precision[0].length + precision[1].length), precision[1].length);
  11340. } else {
  11341. d = toFixed(n._n, precision.length);
  11342. }
  11343. w = d.split('.')[0];
  11344. if (d.split('.')[1].length) {
  11345. d = languages[currentLanguage].delimiters.decimal + d.split('.')[1];
  11346. } else {
  11347. d = '';
  11348. }
  11349. if (optDec && Number(d) === 0) {
  11350. d = '';
  11351. }
  11352. } else {
  11353. w = toFixed(n._n, null);
  11354. }
  11355. // format number
  11356. if (w.indexOf('-') > -1) {
  11357. w = w.slice(1);
  11358. neg = true;
  11359. }
  11360. if (thousands > -1) {
  11361. w = w.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + languages[currentLanguage].delimiters.thousands);
  11362. }
  11363. if (format.indexOf('.') === 0) {
  11364. w = '';
  11365. }
  11366. return ((negP && neg) ? '(' : '') + ((!negP && neg) ? '-' : '') + w + d + ((ord) ? ord : '') + ((abbr) ? abbr : '') + ((bytes) ? bytes : '') + ((negP && neg) ? ')' : '');
  11367. }
  11368. }
  11369. /************************************
  11370. Top Level Functions
  11371. ************************************/
  11372. numeral = function (input) {
  11373. if (numeral.isNumeral(input)) {
  11374. input = input.value();
  11375. } else if (!Number(input)) {
  11376. input = 0;
  11377. }
  11378. return new Numeral(Number(input));
  11379. };
  11380. // version number
  11381. numeral.version = VERSION;
  11382. // compare numeral object
  11383. numeral.isNumeral = function (obj) {
  11384. return obj instanceof Numeral;
  11385. };
  11386. // This function will load languages and then set the global language. If
  11387. // no arguments are passed in, it will simply return the current global
  11388. // language key.
  11389. numeral.language = function (key, values) {
  11390. if (!key) {
  11391. return currentLanguage;
  11392. }
  11393. if (key && !values) {
  11394. currentLanguage = key;
  11395. }
  11396. if (values || !languages[key]) {
  11397. loadLanguage(key, values);
  11398. }
  11399. return numeral;
  11400. };
  11401. numeral.language('en', {
  11402. delimiters: {
  11403. thousands: ',',
  11404. decimal: '.'
  11405. },
  11406. abbreviations: {
  11407. thousand: 'k',
  11408. million: 'm',
  11409. billion: 'b',
  11410. trillion: 't'
  11411. },
  11412. ordinal: function (number) {
  11413. var b = number % 10;
  11414. return (~~ (number % 100 / 10) === 1) ? 'th' :
  11415. (b === 1) ? 'st' :
  11416. (b === 2) ? 'nd' :
  11417. (b === 3) ? 'rd' : 'th';
  11418. },
  11419. currency: {
  11420. symbol: '$'
  11421. }
  11422. });
  11423. numeral.zeroFormat = function (format) {
  11424. if (typeof(format) === 'string') {
  11425. zeroFormat = format;
  11426. } else {
  11427. zeroFormat = null;
  11428. }
  11429. };
  11430. /************************************
  11431. Helpers
  11432. ************************************/
  11433. function loadLanguage(key, values) {
  11434. languages[key] = values;
  11435. }
  11436. /************************************
  11437. Numeral Prototype
  11438. ************************************/
  11439. numeral.fn = Numeral.prototype = {
  11440. clone : function () {
  11441. return numeral(this);
  11442. },
  11443. format : function (inputString) {
  11444. return formatNumeral(this, inputString ? inputString : numeral.defaultFormat);
  11445. },
  11446. unformat : function (inputString) {
  11447. return unformatNumeral(this, inputString ? inputString : numeral.defaultFormat);
  11448. },
  11449. value : function () {
  11450. return this._n;
  11451. },
  11452. valueOf : function () {
  11453. return this._n;
  11454. },
  11455. set : function (value) {
  11456. this._n = Number(value);
  11457. return this;
  11458. },
  11459. add : function (value) {
  11460. this._n = this._n + Number(value);
  11461. return this;
  11462. },
  11463. subtract : function (value) {
  11464. this._n = this._n - Number(value);
  11465. return this;
  11466. },
  11467. multiply : function (value) {
  11468. this._n = this._n * Number(value);
  11469. return this;
  11470. },
  11471. divide : function (value) {
  11472. this._n = this._n / Number(value);
  11473. return this;
  11474. },
  11475. difference : function (value) {
  11476. var difference = this._n - Number(value);
  11477. if (difference < 0) {
  11478. difference = -difference;
  11479. }
  11480. return difference;
  11481. }
  11482. };
  11483. /************************************
  11484. Exposing Numeral
  11485. ************************************/
  11486. // CommonJS module is defined
  11487. if (hasModule) {
  11488. module.exports = numeral;
  11489. }
  11490. /*global ender:false */
  11491. if (typeof ender === 'undefined') {
  11492. // here, `this` means `window` in the browser, or `global` on the server
  11493. // add `numeral` as a global object via a string identifier,
  11494. // for Closure Compiler 'advanced' mode
  11495. this['numeral'] = numeral;
  11496. }
  11497. /*global define:false */
  11498. if (typeof define === 'function' && define.amd) {
  11499. define([], function () {
  11500. return numeral;
  11501. });
  11502. }
  11503. }).call(this);