handsontable.full.js 614 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273
  1. /*!
  2. * Handsontable 0.12.3
  3. * Handsontable is a JavaScript library for editable tables with basic copy-paste compatibility with Excel and Google Docs
  4. *
  5. * Copyright 2012-2014 Marcin Warpechowski
  6. * Licensed under the MIT license.
  7. * http://handsontable.com/
  8. *
  9. * Date: Mon Jan 12 2015 10:24:46 GMT+0100 (CET)
  10. */
  11. /*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */
  12. //var Handsontable = { //class namespace
  13. // plugins: {}, //plugin namespace
  14. // helper: {} //helper namespace
  15. //};
  16. var Handsontable = function (rootElement, userSettings) {
  17. userSettings = userSettings || {};
  18. var instance = new Handsontable.Core(rootElement, userSettings);
  19. instance.init();
  20. return instance;
  21. };
  22. Handsontable.plugins = {};
  23. (function (window, Handsontable) {
  24. "use strict";
  25. //http://stackoverflow.com/questions/3629183/why-doesnt-indexof-work-on-an-array-ie8
  26. if (!Array.prototype.indexOf) {
  27. Array.prototype.indexOf = function (elt /*, from*/) {
  28. var len = this.length >>> 0;
  29. var from = Number(arguments[1]) || 0;
  30. from = (from < 0)
  31. ? Math.ceil(from)
  32. : Math.floor(from);
  33. if (from < 0)
  34. from += len;
  35. for (; from < len; from++) {
  36. if (from in this &&
  37. this[from] === elt)
  38. return from;
  39. }
  40. return -1;
  41. };
  42. }
  43. /**
  44. * Array.filter() shim by Trevor Menagh (https://github.com/trevmex) with some modifications
  45. */
  46. if (!Array.prototype.filter) {
  47. Array.prototype.filter = function (fun, thisp) {
  48. "use strict";
  49. if (typeof this === "undefined" || this === null) {
  50. throw new TypeError();
  51. }
  52. if (typeof fun !== "function") {
  53. throw new TypeError();
  54. }
  55. thisp = thisp || this;
  56. if (isNodeList(thisp)) {
  57. thisp = convertNodeListToArray(thisp);
  58. }
  59. var len = thisp.length,
  60. res = [],
  61. i,
  62. val;
  63. for (i = 0; i < len; i += 1) {
  64. if (thisp.hasOwnProperty(i)) {
  65. val = thisp[i]; // in case fun mutates this
  66. if (fun.call(thisp, val, i, thisp)) {
  67. res.push(val);
  68. }
  69. }
  70. }
  71. return res;
  72. function isNodeList(object) {
  73. return /NodeList/i.test(object.item);
  74. }
  75. function convertNodeListToArray(nodeList) {
  76. var array = [];
  77. for (var i = 0, len = nodeList.length; i < len; i++){
  78. array[i] = nodeList[i]
  79. }
  80. return array;
  81. }
  82. };
  83. }
  84. if (!Array.isArray) {
  85. Array.isArray = function(obj) {
  86. return toString.call(obj) == '[object Array]';
  87. };
  88. }
  89. // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
  90. // License CC-BY-SA v2.5
  91. if (!Object.keys) {
  92. Object.keys = (function() {
  93. 'use strict';
  94. var hasOwnProperty = Object.prototype.hasOwnProperty,
  95. hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
  96. dontEnums = [
  97. 'toString',
  98. 'toLocaleString',
  99. 'valueOf',
  100. 'hasOwnProperty',
  101. 'isPrototypeOf',
  102. 'propertyIsEnumerable',
  103. 'constructor'
  104. ],
  105. dontEnumsLength = dontEnums.length;
  106. return function(obj) {
  107. if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
  108. throw new TypeError('Object.keys called on non-object');
  109. }
  110. var result = [], prop, i;
  111. for (prop in obj) {
  112. if (hasOwnProperty.call(obj, prop)) {
  113. result.push(prop);
  114. }
  115. }
  116. if (hasDontEnumBug) {
  117. for (i = 0; i < dontEnumsLength; i++) {
  118. if (hasOwnProperty.call(obj, dontEnums[i])) {
  119. result.push(dontEnums[i]);
  120. }
  121. }
  122. }
  123. return result;
  124. };
  125. }());
  126. }
  127. /*
  128. * Copyright 2012 The Polymer Authors. All rights reserved.
  129. * Use of this source code is governed by a BSD-style
  130. * license that can be found in the LICENSE file.
  131. */
  132. if (typeof WeakMap === 'undefined') {
  133. (function() {
  134. var defineProperty = Object.defineProperty;
  135. try {
  136. var properDefineProperty = true;
  137. defineProperty(function(){}, 'foo', {});
  138. } catch (e) {
  139. properDefineProperty = false;
  140. }
  141. /*
  142. IE8 does not support Date.now() but IE8 compatibility mode in IE9 and IE10 does.
  143. M$ deserves a high five for this one :)
  144. */
  145. var counter = +(new Date) % 1e9;
  146. var WeakMap = function() {
  147. this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
  148. if(!properDefineProperty){
  149. this._wmCache = [];
  150. }
  151. };
  152. if(properDefineProperty){
  153. WeakMap.prototype = {
  154. set: function(key, value) {
  155. var entry = key[this.name];
  156. if (entry && entry[0] === key)
  157. entry[1] = value;
  158. else
  159. defineProperty(key, this.name, {value: [key, value], writable: true});
  160. },
  161. get: function(key) {
  162. var entry;
  163. return (entry = key[this.name]) && entry[0] === key ?
  164. entry[1] : undefined;
  165. },
  166. 'delete': function(key) {
  167. this.set(key, undefined);
  168. }
  169. };
  170. } else {
  171. WeakMap.prototype = {
  172. set: function(key, value) {
  173. if(typeof key == 'undefined' || typeof value == 'undefined') return;
  174. for(var i = 0, len = this._wmCache.length; i < len; i++){
  175. if(this._wmCache[i].key == key){
  176. this._wmCache[i].value = value;
  177. return;
  178. }
  179. }
  180. this._wmCache.push({key: key, value: value});
  181. },
  182. get: function(key) {
  183. if(typeof key == 'undefined') return;
  184. for(var i = 0, len = this._wmCache.length; i < len; i++){
  185. if(this._wmCache[i].key == key){
  186. return this._wmCache[i].value;
  187. }
  188. }
  189. return;
  190. },
  191. 'delete': function(key) {
  192. if(typeof key == 'undefined') return;
  193. for(var i = 0, len = this._wmCache.length; i < len; i++){
  194. if(this._wmCache[i].key == key){
  195. Array.prototype.slice.call(this._wmCache, i, 1);
  196. }
  197. }
  198. }
  199. };
  200. }
  201. window.WeakMap = WeakMap;
  202. })();
  203. }
  204. Handsontable.activeGuid = null;
  205. /**
  206. * Handsontable constructor
  207. * @param rootElement The DOM element in which Handsontable DOM will be inserted
  208. * @param userSettings
  209. * @constructor
  210. */
  211. Handsontable.Core = function (rootElement, userSettings) {
  212. var priv
  213. , datamap
  214. , grid
  215. , selection
  216. , editorManager
  217. , instance = this
  218. , GridSettings = function () {}
  219. , eventManager = Handsontable.eventManager(instance);
  220. Handsontable.helper.extend(GridSettings.prototype, DefaultSettings.prototype); //create grid settings as a copy of default settings
  221. Handsontable.helper.extend(GridSettings.prototype, userSettings); //overwrite defaults with user settings
  222. Handsontable.helper.extend(GridSettings.prototype, expandType(userSettings));
  223. this.rootElement = rootElement;
  224. this.container = document.createElement('DIV');
  225. this.container.className = 'htContainer';
  226. rootElement.insertBefore(this.container, rootElement.firstChild);
  227. this.guid = 'ht_' + Handsontable.helper.randomString(); //this is the namespace for global events
  228. if (!this.rootElement.id || this.rootElement.id.substring(0, 3) === "ht_") {
  229. this.rootElement.id = this.guid; //if root element does not have an id, assign a random id
  230. }
  231. priv = {
  232. cellSettings: [],
  233. columnSettings: [],
  234. columnsSettingConflicts: ['data', 'width'],
  235. settings: new GridSettings(), // current settings instance
  236. selRange: null, //exposed by public method `getSelectedRange`
  237. isPopulated: null,
  238. scrollable: null,
  239. firstRun: true
  240. };
  241. grid = {
  242. /**
  243. * Inserts or removes rows and columns
  244. * @param {String} action Possible values: "insert_row", "insert_col", "remove_row", "remove_col"
  245. * @param {Number} index
  246. * @param {Number} amount
  247. * @param {String} [source] Optional. Source of hook runner.
  248. * @param {Boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows.
  249. */
  250. alter: function (action, index, amount, source, keepEmptyRows) {
  251. var delta;
  252. amount = amount || 1;
  253. switch (action) {
  254. case "insert_row":
  255. delta = datamap.createRow(index, amount);
  256. if (delta) {
  257. if (selection.isSelected() && priv.selRange.from.row >= index) {
  258. priv.selRange.from.row = priv.selRange.from.row + delta;
  259. selection.transformEnd(delta, 0); //will call render() internally
  260. }
  261. else {
  262. selection.refreshBorders(); //it will call render and prepare methods
  263. }
  264. }
  265. break;
  266. case "insert_col":
  267. // //column order may have changes, so we need to translate the selection column index -> source array index
  268. // index = instance.runHooksAndReturn('modifyCol', index);
  269. delta = datamap.createCol(index, amount);
  270. if (delta) {
  271. if(Array.isArray(instance.getSettings().colHeaders)){
  272. var spliceArray = [index, 0];
  273. spliceArray.length += delta; //inserts empty (undefined) elements at the end of an array
  274. Array.prototype.splice.apply(instance.getSettings().colHeaders, spliceArray); //inserts empty (undefined) elements into the colHeader array
  275. }
  276. if (selection.isSelected() && priv.selRange.from.col >= index) {
  277. priv.selRange.from.col = priv.selRange.from.col + delta;
  278. selection.transformEnd(0, delta); //will call render() internally
  279. }
  280. else {
  281. selection.refreshBorders(); //it will call render and prepare methods
  282. }
  283. }
  284. break;
  285. case "remove_row":
  286. //column order may have changes, so we need to translate the selection column index -> source array index
  287. index = instance.runHooksAndReturn('modifyCol', index);
  288. datamap.removeRow(index, amount);
  289. priv.cellSettings.splice(index, amount);
  290. grid.adjustRowsAndCols();
  291. selection.refreshBorders(); //it will call render and prepare methods
  292. break;
  293. case "remove_col":
  294. datamap.removeCol(index, amount);
  295. for(var row = 0, len = datamap.getAll().length; row < len; row++){
  296. if(row in priv.cellSettings){ //if row hasn't been rendered it wouldn't have cellSettings
  297. priv.cellSettings[row].splice(index, amount);
  298. }
  299. }
  300. if(Array.isArray(instance.getSettings().colHeaders)){
  301. if(typeof index == 'undefined'){
  302. index = -1;
  303. }
  304. instance.getSettings().colHeaders.splice(index, amount);
  305. }
  306. //priv.columnSettings.splice(index, amount);
  307. grid.adjustRowsAndCols();
  308. selection.refreshBorders(); //it will call render and prepare methods
  309. break;
  310. default:
  311. throw new Error('There is no such action "' + action + '"');
  312. break;
  313. }
  314. if (!keepEmptyRows) {
  315. grid.adjustRowsAndCols(); //makes sure that we did not add rows that will be removed in next refresh
  316. }
  317. },
  318. /**
  319. * Makes sure there are empty rows at the bottom of the table
  320. */
  321. adjustRowsAndCols: function () {
  322. var r, rlen, emptyRows, emptyCols;
  323. //should I add empty rows to data source to meet minRows?
  324. rlen = instance.countRows();
  325. if (rlen < priv.settings.minRows) {
  326. for (r = 0; r < priv.settings.minRows - rlen; r++) {
  327. datamap.createRow(instance.countRows(), 1, true);
  328. }
  329. }
  330. emptyRows = instance.countEmptyRows(true);
  331. //should I add empty rows to meet minSpareRows?
  332. if (emptyRows < priv.settings.minSpareRows) {
  333. for (; emptyRows < priv.settings.minSpareRows && instance.countRows() < priv.settings.maxRows; emptyRows++) {
  334. datamap.createRow(instance.countRows(), 1, true);
  335. }
  336. }
  337. //count currently empty cols
  338. emptyCols = instance.countEmptyCols(true);
  339. //should I add empty cols to meet minCols?
  340. if (!priv.settings.columns && instance.countCols() < priv.settings.minCols) {
  341. for (; instance.countCols() < priv.settings.minCols; emptyCols++) {
  342. datamap.createCol(instance.countCols(), 1, true);
  343. }
  344. }
  345. //should I add empty cols to meet minSpareCols?
  346. if (!priv.settings.columns && instance.dataType === 'array' && emptyCols < priv.settings.minSpareCols) {
  347. for (; emptyCols < priv.settings.minSpareCols && instance.countCols() < priv.settings.maxCols; emptyCols++) {
  348. datamap.createCol(instance.countCols(), 1, true);
  349. }
  350. }
  351. // if (priv.settings.enterBeginsEditing) {
  352. // for (; (((priv.settings.minRows || priv.settings.minSpareRows) && instance.countRows() > priv.settings.minRows) && (priv.settings.minSpareRows && emptyRows > priv.settings.minSpareRows)); emptyRows--) {
  353. // datamap.removeRow();
  354. // }
  355. // }
  356. // if (priv.settings.enterBeginsEditing && !priv.settings.columns) {
  357. // for (; (((priv.settings.minCols || priv.settings.minSpareCols) && instance.countCols() > priv.settings.minCols) && (priv.settings.minSpareCols && emptyCols > priv.settings.minSpareCols)); emptyCols--) {
  358. // datamap.removeCol();
  359. // }
  360. // }
  361. var rowCount = instance.countRows();
  362. var colCount = instance.countCols();
  363. if (rowCount === 0 || colCount === 0) {
  364. selection.deselect();
  365. }
  366. if (selection.isSelected()) {
  367. var selectionChanged;
  368. var fromRow = priv.selRange.from.row;
  369. var fromCol = priv.selRange.from.col;
  370. var toRow = priv.selRange.to.row;
  371. var toCol = priv.selRange.to.col;
  372. //if selection is outside, move selection to last row
  373. if (fromRow > rowCount - 1) {
  374. fromRow = rowCount - 1;
  375. selectionChanged = true;
  376. if (toRow > fromRow) {
  377. toRow = fromRow;
  378. }
  379. } else if (toRow > rowCount - 1) {
  380. toRow = rowCount - 1;
  381. selectionChanged = true;
  382. if (fromRow > toRow) {
  383. fromRow = toRow;
  384. }
  385. }
  386. //if selection is outside, move selection to last row
  387. if (fromCol > colCount - 1) {
  388. fromCol = colCount - 1;
  389. selectionChanged = true;
  390. if (toCol > fromCol) {
  391. toCol = fromCol;
  392. }
  393. } else if (toCol > colCount - 1) {
  394. toCol = colCount - 1;
  395. selectionChanged = true;
  396. if (fromCol > toCol) {
  397. fromCol = toCol;
  398. }
  399. }
  400. if (selectionChanged) {
  401. instance.selectCell(fromRow, fromCol, toRow, toCol);
  402. }
  403. }
  404. },
  405. /**
  406. * Populate cells at position with 2d array
  407. * @param {Object} start Start selection position
  408. * @param {Array} input 2d array
  409. * @param {Object} [end] End selection position (only for drag-down mode)
  410. * @param {String} [source="populateFromArray"]
  411. * @param {String} [method="overwrite"]
  412. * @param {String} direction (left|right|up|down)
  413. * @param {Array} deltas array
  414. * @return {Object|undefined} ending td in pasted area (only if any cell was changed)
  415. */
  416. populateFromArray: function (start, input, end, source, method, direction, deltas) {
  417. var r, rlen, c, clen, setData = [], current = {};
  418. rlen = input.length;
  419. if (rlen === 0) {
  420. return false;
  421. }
  422. var repeatCol
  423. , repeatRow
  424. , cmax
  425. , rmax;
  426. // insert data with specified pasteMode method
  427. switch (method) {
  428. case 'shift_down' :
  429. repeatCol = end ? end.col - start.col + 1 : 0;
  430. repeatRow = end ? end.row - start.row + 1 : 0;
  431. input = Handsontable.helper.translateRowsToColumns(input);
  432. for (c = 0, clen = input.length, cmax = Math.max(clen, repeatCol); c < cmax; c++) {
  433. if (c < clen) {
  434. for (r = 0, rlen = input[c].length; r < repeatRow - rlen; r++) {
  435. input[c].push(input[c][r % rlen]);
  436. }
  437. input[c].unshift(start.col + c, start.row, 0);
  438. instance.spliceCol.apply(instance, input[c]);
  439. }
  440. else {
  441. input[c % clen][0] = start.col + c;
  442. instance.spliceCol.apply(instance, input[c % clen]);
  443. }
  444. }
  445. break;
  446. case 'shift_right' :
  447. repeatCol = end ? end.col - start.col + 1 : 0;
  448. repeatRow = end ? end.row - start.row + 1 : 0;
  449. for (r = 0, rlen = input.length, rmax = Math.max(rlen, repeatRow); r < rmax; r++) {
  450. if (r < rlen) {
  451. for (c = 0, clen = input[r].length; c < repeatCol - clen; c++) {
  452. input[r].push(input[r][c % clen]);
  453. }
  454. input[r].unshift(start.row + r, start.col, 0);
  455. instance.spliceRow.apply(instance, input[r]);
  456. }
  457. else {
  458. input[r % rlen][0] = start.row + r;
  459. instance.spliceRow.apply(instance, input[r % rlen]);
  460. }
  461. }
  462. break;
  463. case 'overwrite' :
  464. default:
  465. // overwrite and other not specified options
  466. current.row = start.row;
  467. current.col = start.col;
  468. var iterators = {row: 0, col: 0}, // number of packages
  469. selected = { // selected range
  470. row: (end && start) ? (end.row - start.row + 1) : 1,
  471. col: (end && start) ? (end.col - start.col + 1) : 1
  472. };
  473. if (['up', 'left'].indexOf(direction) !== -1) {
  474. iterators = {
  475. row: Math.ceil(selected.row / rlen) || 1,
  476. col: Math.ceil(selected.col / input[0].length) || 1
  477. }
  478. } else if (['down', 'right'].indexOf(direction) !== -1) {
  479. iterators = {
  480. row: 1,
  481. col: 1
  482. };
  483. }
  484. for (r = 0; r < rlen; r++) {
  485. if ((end && current.row > end.row) || (!priv.settings.allowInsertRow && current.row > instance.countRows() - 1) || (current.row >= priv.settings.maxRows)) {
  486. break;
  487. }
  488. current.col = start.col;
  489. clen = input[r] ? input[r].length : 0;
  490. for (c = 0; c < clen; c++) {
  491. if ((end && current.col > end.col) || (!priv.settings.allowInsertColumn && current.col > instance.countCols() - 1) || (current.col >= priv.settings.maxCols)) {
  492. break;
  493. }
  494. if (!instance.getCellMeta(current.row, current.col).readOnly) {
  495. var result,
  496. value = input[r][c],
  497. index = {
  498. row: r,
  499. col: c
  500. };
  501. if (source === 'autofill') {
  502. result = instance.runHooksAndReturn('beforeAutofillInsidePopulate', index, direction, input, deltas, iterators, selected);
  503. if (result) {
  504. iterators = typeof(result.iterators) !== 'undefined' ? result.iterators : iterators;
  505. value = typeof(result.value) !== 'undefined' ? result.value : value;
  506. }
  507. }
  508. setData.push([current.row, current.col, value]);
  509. }
  510. current.col++;
  511. if (end && c === clen - 1) {
  512. c = -1;
  513. if (['down', 'right'].indexOf(direction) !== -1) {
  514. iterators.col++;
  515. } else if (['up', 'left'].indexOf(direction) !== -1) {
  516. if (iterators.col > 1) {
  517. iterators.col--;
  518. }
  519. }
  520. }
  521. }
  522. current.row++;
  523. iterators.col = 1;
  524. if (end && r === rlen - 1) {
  525. r = -1;
  526. if (['down', 'right'].indexOf(direction) !== -1) {
  527. iterators.row++;
  528. } else if (['up', 'left'].indexOf(direction) !== -1) {
  529. if (iterators.row > 1) {
  530. iterators.row--;
  531. }
  532. }
  533. }
  534. }
  535. instance.setDataAtCell(setData, null, null, source || 'populateFromArray');
  536. break;
  537. }
  538. }
  539. };
  540. this.selection = selection = { //this public assignment is only temporary
  541. inProgress: false,
  542. selectedHeader: {
  543. cols: false,
  544. rows: false
  545. },
  546. setSelectedHeaders: function (rows, cols) {
  547. instance.selection.selectedHeader.rows = rows;
  548. instance.selection.selectedHeader.cols = cols;
  549. },
  550. /**
  551. * Sets inProgress to true. This enables onSelectionEnd and onSelectionEndByProp to function as desired
  552. */
  553. begin: function () {
  554. instance.selection.inProgress = true;
  555. },
  556. /**
  557. * Sets inProgress to false. Triggers onSelectionEnd and onSelectionEndByProp
  558. */
  559. finish: function () {
  560. var sel = instance.getSelected();
  561. Handsontable.hooks.run(instance, "afterSelectionEnd", sel[0], sel[1], sel[2], sel[3]);
  562. Handsontable.hooks.run(instance, "afterSelectionEndByProp", sel[0], instance.colToProp(sel[1]), sel[2], instance.colToProp(sel[3]));
  563. instance.selection.inProgress = false;
  564. },
  565. isInProgress: function () {
  566. return instance.selection.inProgress;
  567. },
  568. /**
  569. * Starts selection range on given td object
  570. * @param {WalkontableCellCoords} coords
  571. */
  572. setRangeStart: function (coords, keepEditorOpened) {
  573. Handsontable.hooks.run(instance, "beforeSetRangeStart", coords);
  574. priv.selRange = new WalkontableCellRange(coords, coords, coords);
  575. selection.setRangeEnd(coords, null, keepEditorOpened);
  576. },
  577. /**
  578. * Ends selection range on given td object
  579. * @param {WalkontableCellCoords} coords
  580. * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to range end
  581. */
  582. setRangeEnd: function (coords, scrollToCell, keepEditorOpened) {
  583. //trigger handlers
  584. Handsontable.hooks.run(instance, "beforeSetRangeEnd", coords);
  585. instance.selection.begin();
  586. priv.selRange.to = new WalkontableCellCoords(coords.row, coords.col);
  587. if (!priv.settings.multiSelect) {
  588. priv.selRange.from = coords;
  589. }
  590. //set up current selection
  591. instance.view.wt.selections.current.clear();
  592. instance.view.wt.selections.current.add(priv.selRange.highlight);
  593. //set up area selection
  594. instance.view.wt.selections.area.clear();
  595. if (selection.isMultiple()) {
  596. instance.view.wt.selections.area.add(priv.selRange.from);
  597. instance.view.wt.selections.area.add(priv.selRange.to);
  598. }
  599. //set up highlight
  600. if (priv.settings.currentRowClassName || priv.settings.currentColClassName) {
  601. instance.view.wt.selections.highlight.clear();
  602. instance.view.wt.selections.highlight.add(priv.selRange.from);
  603. instance.view.wt.selections.highlight.add(priv.selRange.to);
  604. }
  605. //trigger handlers
  606. Handsontable.hooks.run(instance, "afterSelection", priv.selRange.from.row, priv.selRange.from.col, priv.selRange.to.row, priv.selRange.to.col);
  607. Handsontable.hooks.run(instance, "afterSelectionByProp", priv.selRange.from.row, datamap.colToProp(priv.selRange.from.col), priv.selRange.to.row, datamap.colToProp(priv.selRange.to.col));
  608. if (scrollToCell !== false && instance.view.mainViewIsActive()) {
  609. instance.view.scrollViewport(coords);
  610. }
  611. selection.refreshBorders(null, keepEditorOpened);
  612. },
  613. /**
  614. * Destroys editor, redraws borders around cells, prepares editor
  615. * @param {Boolean} revertOriginal
  616. * @param {Boolean} keepEditor
  617. */
  618. refreshBorders: function (revertOriginal, keepEditor) {
  619. if (!keepEditor) {
  620. editorManager.destroyEditor(revertOriginal);
  621. }
  622. instance.view.render();
  623. if (selection.isSelected() && !keepEditor) {
  624. editorManager.prepareEditor();
  625. }
  626. },
  627. /**
  628. * Returns information if we have a multiselection
  629. * @return {Boolean}
  630. */
  631. isMultiple: function () {
  632. var isMultiple = !(priv.selRange.to.col === priv.selRange.from.col && priv.selRange.to.row === priv.selRange.from.row)
  633. , modifier = Handsontable.hooks.execute(instance, 'afterIsMultipleSelection', isMultiple);
  634. if(isMultiple) {
  635. return modifier;
  636. }
  637. },
  638. /**
  639. * Selects cell relative to current cell (if possible)
  640. */
  641. transformStart: function (rowDelta, colDelta, force, keepEditorOpened) {
  642. var delta = new WalkontableCellCoords(rowDelta, colDelta);
  643. instance.runHooks('modifyTransformStart', delta);
  644. if (priv.selRange.highlight.row + rowDelta > instance.countRows() - 1) {
  645. if (force && priv.settings.minSpareRows > 0) {
  646. instance.alter("insert_row", instance.countRows());
  647. }
  648. else if (priv.settings.autoWrapCol) {
  649. delta.row = 1 - instance.countRows();
  650. delta.col = priv.selRange.highlight.col + delta.col == instance.countCols() - 1 ? 1 - instance.countCols() : 1;
  651. }
  652. }
  653. else if (priv.settings.autoWrapCol && priv.selRange.highlight.row + delta.row < 0 && priv.selRange.highlight.col + delta.col >= 0) {
  654. delta.row = instance.countRows() - 1;
  655. delta.col = priv.selRange.highlight.col + delta.col == 0 ? instance.countCols() - 1 : -1;
  656. }
  657. if (priv.selRange.highlight.col + delta.col > instance.countCols() - 1) {
  658. if (force && priv.settings.minSpareCols > 0) {
  659. instance.alter("insert_col", instance.countCols());
  660. }
  661. else if (priv.settings.autoWrapRow) {
  662. delta.row = priv.selRange.highlight.row + delta.row == instance.countRows() - 1 ? 1 - instance.countRows() : 1;
  663. delta.col = 1 - instance.countCols();
  664. }
  665. }
  666. else if (priv.settings.autoWrapRow && priv.selRange.highlight.col + delta.col < 0 && priv.selRange.highlight.row + delta.row >= 0) {
  667. delta.row = priv.selRange.highlight.row + delta.row == 0 ? instance.countRows() - 1 : -1;
  668. delta.col = instance.countCols() - 1;
  669. }
  670. var totalRows = instance.countRows();
  671. var totalCols = instance.countCols();
  672. var coords = new WalkontableCellCoords(priv.selRange.highlight.row + delta.row, priv.selRange.highlight.col + delta.col);
  673. if (coords.row < 0) {
  674. coords.row = 0;
  675. }
  676. else if (coords.row > 0 && coords.row >= totalRows) {
  677. coords.row = totalRows - 1;
  678. }
  679. if (coords.col < 0) {
  680. coords.col = 0;
  681. }
  682. else if (coords.col > 0 && coords.col >= totalCols) {
  683. coords.col = totalCols - 1;
  684. }
  685. selection.setRangeStart(coords, keepEditorOpened);
  686. },
  687. /**
  688. * Sets selection end cell relative to current selection end cell (if possible)
  689. */
  690. transformEnd: function (rowDelta, colDelta) {
  691. var delta = new WalkontableCellCoords(rowDelta, colDelta);
  692. instance.runHooks('modifyTransformEnd', delta);
  693. var totalRows = instance.countRows();
  694. var totalCols = instance.countCols();
  695. var coords = new WalkontableCellCoords(priv.selRange.to.row + delta.row, priv.selRange.to.col + delta.col);
  696. if (coords.row < 0) {
  697. coords.row = 0;
  698. }
  699. else if (coords.row > 0 && coords.row >= totalRows) {
  700. coords.row = totalRows - 1;
  701. }
  702. if (coords.col < 0) {
  703. coords.col = 0;
  704. }
  705. else if (coords.col > 0 && coords.col >= totalCols) {
  706. coords.col = totalCols - 1;
  707. }
  708. selection.setRangeEnd(coords);
  709. },
  710. /**
  711. * Returns true if currently there is a selection on screen, false otherwise
  712. * @return {Boolean}
  713. */
  714. isSelected: function () {
  715. return (priv.selRange !== null);
  716. },
  717. /**
  718. * Returns true if coords is within current selection coords
  719. * @param {WalkontableCellCoords} coords
  720. * @return {Boolean}
  721. */
  722. inInSelection: function (coords) {
  723. if (!selection.isSelected()) {
  724. return false;
  725. }
  726. return priv.selRange.includes(coords);
  727. },
  728. /**
  729. * Deselects all selected cells
  730. */
  731. deselect: function () {
  732. if (!selection.isSelected()) {
  733. return;
  734. }
  735. instance.selection.inProgress = false; //needed by HT inception
  736. priv.selRange = null;
  737. instance.view.wt.selections.current.clear();
  738. instance.view.wt.selections.area.clear();
  739. if (priv.settings.currentRowClassName || priv.settings.currentColClassName) {
  740. instance.view.wt.selections.highlight.clear();
  741. }
  742. editorManager.destroyEditor();
  743. selection.refreshBorders();
  744. Handsontable.hooks.run(instance, 'afterDeselect');
  745. },
  746. /**
  747. * Select all cells
  748. */
  749. selectAll: function () {
  750. if (!priv.settings.multiSelect) {
  751. return;
  752. }
  753. selection.setRangeStart(new WalkontableCellCoords(0, 0));
  754. selection.setRangeEnd(new WalkontableCellCoords(instance.countRows() - 1, instance.countCols() - 1), false);
  755. },
  756. /**
  757. * Deletes data from selected cells
  758. */
  759. empty: function () {
  760. if (!selection.isSelected()) {
  761. return;
  762. }
  763. var topLeft = priv.selRange.getTopLeftCorner();
  764. var bottomRight = priv.selRange.getBottomRightCorner();
  765. var r, c, changes = [];
  766. for (r = topLeft.row; r <= bottomRight.row; r++) {
  767. for (c = topLeft.col; c <= bottomRight.col; c++) {
  768. if (!instance.getCellMeta(r, c).readOnly) {
  769. changes.push([r, c, '']);
  770. }
  771. }
  772. }
  773. instance.setDataAtCell(changes);
  774. }
  775. };
  776. this.init = function () {
  777. Handsontable.hooks.run(instance, 'beforeInit');
  778. if(Handsontable.mobileBrowser) {
  779. Handsontable.Dom.addClass(instance.rootElement, 'mobile');
  780. }
  781. this.updateSettings(priv.settings, true);
  782. this.view = new Handsontable.TableView(this);
  783. editorManager = new Handsontable.EditorManager(instance, priv, selection, datamap);
  784. this.forceFullRender = true; //used when data was changed
  785. this.view.render();
  786. if (typeof priv.firstRun === 'object') {
  787. Handsontable.hooks.run(instance, 'afterChange', priv.firstRun[0], priv.firstRun[1]);
  788. priv.firstRun = false;
  789. }
  790. Handsontable.hooks.run(instance, 'afterInit');
  791. };
  792. 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
  793. var resolved = false;
  794. return {
  795. validatorsInQueue: 0,
  796. addValidatorToQueue: function () {
  797. this.validatorsInQueue++;
  798. resolved = false;
  799. },
  800. removeValidatorFormQueue: function () {
  801. this.validatorsInQueue = this.validatorsInQueue - 1 < 0 ? 0 : this.validatorsInQueue - 1;
  802. this.checkIfQueueIsEmpty();
  803. },
  804. onQueueEmpty: function () {
  805. },
  806. checkIfQueueIsEmpty: function () {
  807. if (this.validatorsInQueue == 0 && resolved == false) {
  808. resolved = true;
  809. this.onQueueEmpty();
  810. }
  811. }
  812. };
  813. }
  814. function validateChanges(changes, source, callback) {
  815. var waitingForValidator = new ValidatorsQueue();
  816. waitingForValidator.onQueueEmpty = resolve;
  817. for (var i = changes.length - 1; i >= 0; i--) {
  818. if (changes[i] === null) {
  819. changes.splice(i, 1);
  820. }
  821. else {
  822. var row = changes[i][0];
  823. var col = datamap.propToCol(changes[i][1]);
  824. 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)
  825. var cellProperties = instance.getCellMeta(row, logicalCol);
  826. if (cellProperties.type === 'numeric' && typeof changes[i][3] === 'string') {
  827. if (changes[i][3].length > 0 && (/^-?[\d\s]*(\.|\,)?\d*$/.test(changes[i][3]) || cellProperties.format )) {
  828. var len = changes[i][3].length;
  829. if (typeof cellProperties.language == 'undefined') {
  830. numeral.language('en');
  831. }
  832. else if (changes[i][3].indexOf(".") === len - 3 && changes[i][3].indexOf(",") === -1) { //this input in format XXXX.XX is likely to come from paste. Let's parse it using international rules
  833. numeral.language('en');
  834. }
  835. else {
  836. numeral.language(cellProperties.language);
  837. }
  838. changes[i][3] = numeral().unformat(changes[i][3] || '0'); //numeral cannot unformat empty string
  839. }
  840. }
  841. if (instance.getCellValidator(cellProperties)) {
  842. waitingForValidator.addValidatorToQueue();
  843. instance.validateCell(changes[i][3], cellProperties, (function (i, cellProperties) {
  844. return function (result) {
  845. if (typeof result !== 'boolean') {
  846. throw new Error("Validation error: result is not boolean");
  847. }
  848. if (result === false && cellProperties.allowInvalid === false) {
  849. changes.splice(i, 1); // cancel the change
  850. cellProperties.valid = true; // we cancelled the change, so cell value is still valid
  851. --i;
  852. }
  853. waitingForValidator.removeValidatorFormQueue();
  854. }
  855. })(i, cellProperties)
  856. , source);
  857. }
  858. }
  859. }
  860. waitingForValidator.checkIfQueueIsEmpty();
  861. function resolve() {
  862. var beforeChangeResult;
  863. if (changes.length) {
  864. beforeChangeResult = Handsontable.hooks.execute(instance, "beforeChange", changes, source);
  865. if (typeof beforeChangeResult === 'function') {
  866. console.warn("Your beforeChange callback returns a function. It's not supported since Handsontable 0.12.1 (and the returned function will not be executed).");
  867. } else if (beforeChangeResult === false) {
  868. changes.splice(0, changes.length); //invalidate all changes (remove everything from array)
  869. }
  870. }
  871. callback(); //called when async validators are resolved and beforeChange was not async
  872. }
  873. }
  874. /**
  875. * Internal function to apply changes. Called after validateChanges
  876. * @param {Array} changes Array in form of [row, prop, oldValue, newValue]
  877. * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback)
  878. */
  879. function applyChanges(changes, source) {
  880. var i = changes.length - 1;
  881. if (i < 0) {
  882. return;
  883. }
  884. for (; 0 <= i; i--) {
  885. if (changes[i] === null) {
  886. changes.splice(i, 1);
  887. continue;
  888. }
  889. if(changes[i][2] == null && changes[i][3] == null) {
  890. continue;
  891. }
  892. if (priv.settings.allowInsertRow) {
  893. while (changes[i][0] > instance.countRows() - 1) {
  894. datamap.createRow();
  895. }
  896. }
  897. if (instance.dataType === 'array' && priv.settings.allowInsertColumn) {
  898. while (datamap.propToCol(changes[i][1]) > instance.countCols() - 1) {
  899. datamap.createCol();
  900. }
  901. }
  902. datamap.set(changes[i][0], changes[i][1], changes[i][3]);
  903. }
  904. instance.forceFullRender = true; //used when data was changed
  905. grid.adjustRowsAndCols();
  906. Handsontable.hooks.run(instance, 'beforeChangeRender', changes, source);
  907. selection.refreshBorders(null, true);
  908. Handsontable.hooks.run(instance, 'afterChange', changes, source || 'edit');
  909. }
  910. this.validateCell = function (value, cellProperties, callback, source) {
  911. var validator = instance.getCellValidator(cellProperties);
  912. if (Object.prototype.toString.call(validator) === '[object RegExp]') {
  913. validator = (function (validator) {
  914. return function (value, callback) {
  915. callback(validator.test(value));
  916. }
  917. })(validator);
  918. }
  919. if (typeof validator == 'function') {
  920. value = Handsontable.hooks.execute(instance, "beforeValidate", value, cellProperties.row, cellProperties.prop, source);
  921. // To provide consistent behaviour, validation should be always asynchronous
  922. instance._registerTimeout(setTimeout(function () {
  923. validator.call(cellProperties, value, function (valid) {
  924. cellProperties.valid = valid;
  925. valid = Handsontable.hooks.execute(instance, "afterValidate", valid, value, cellProperties.row, cellProperties.prop, source);
  926. callback(valid);
  927. });
  928. return value;
  929. }, 0));
  930. } else { //resolve callback even if validator function was not found
  931. cellProperties.valid = true;
  932. callback(true);
  933. }
  934. };
  935. function setDataInputToArray(row, prop_or_col, value) {
  936. if (typeof row === "object") { //is it an array of changes
  937. return row;
  938. }
  939. else {
  940. return [
  941. [row, prop_or_col, value]
  942. ];
  943. }
  944. }
  945. /**
  946. * Set data at given cell
  947. * @public
  948. * @param {Number|Array} row or array of changes in format [[row, col, value], ...]
  949. * @param {Number|String} col or source String
  950. * @param {String} value
  951. * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback)
  952. */
  953. this.setDataAtCell = function (row, col, value, source) {
  954. var input = setDataInputToArray(row, col, value)
  955. , i
  956. , ilen
  957. , changes = []
  958. , prop;
  959. for (i = 0, ilen = input.length; i < ilen; i++) {
  960. if (typeof input[i] !== 'object') {
  961. throw new Error('Method `setDataAtCell` accepts row number or changes array of arrays as its first parameter');
  962. }
  963. if (typeof input[i][1] !== 'number') {
  964. throw new Error('Method `setDataAtCell` accepts row and column number as its parameters. If you want to use object property name, use method `setDataAtRowProp`');
  965. }
  966. prop = datamap.colToProp(input[i][1]);
  967. changes.push([
  968. input[i][0],
  969. prop,
  970. datamap.get(input[i][0], prop),
  971. input[i][2]
  972. ]);
  973. }
  974. if (!source && typeof row === "object") {
  975. source = col;
  976. }
  977. validateChanges(changes, source, function () {
  978. applyChanges(changes, source);
  979. });
  980. };
  981. /**
  982. * Set data at given row property
  983. * @public
  984. * @param {Number|Array} row or array of changes in format [[row, prop, value], ...]
  985. * @param {String} prop or source String
  986. * @param {String} value
  987. * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback)
  988. */
  989. this.setDataAtRowProp = function (row, prop, value, source) {
  990. var input = setDataInputToArray(row, prop, value)
  991. , i
  992. , ilen
  993. , changes = [];
  994. for (i = 0, ilen = input.length; i < ilen; i++) {
  995. changes.push([
  996. input[i][0],
  997. input[i][1],
  998. datamap.get(input[i][0], input[i][1]),
  999. input[i][2]
  1000. ]);
  1001. }
  1002. if (!source && typeof row === "object") {
  1003. source = prop;
  1004. }
  1005. validateChanges(changes, source, function () {
  1006. applyChanges(changes, source);
  1007. });
  1008. };
  1009. /**
  1010. * Listen to document body keyboard input
  1011. */
  1012. this.listen = function () {
  1013. Handsontable.activeGuid = instance.guid;
  1014. if (document.activeElement && document.activeElement !== document.body) {
  1015. document.activeElement.blur();
  1016. }
  1017. else if (!document.activeElement) { //IE
  1018. document.body.focus();
  1019. }
  1020. };
  1021. /**
  1022. * Stop listening to document body keyboard input
  1023. */
  1024. this.unlisten = function () {
  1025. Handsontable.activeGuid = null;
  1026. };
  1027. /**
  1028. * Returns true if current Handsontable instance is listening on document body keyboard input
  1029. */
  1030. this.isListening = function () {
  1031. return Handsontable.activeGuid === instance.guid;
  1032. };
  1033. /**
  1034. * Destroys current editor, renders and selects current cell. If revertOriginal != true, edited data is saved
  1035. * @param {Boolean} revertOriginal
  1036. */
  1037. this.destroyEditor = function (revertOriginal) {
  1038. selection.refreshBorders(revertOriginal);
  1039. };
  1040. /**
  1041. * Populate cells at position with 2d array
  1042. * @param {Number} row Start row
  1043. * @param {Number} col Start column
  1044. * @param {Array} input 2d array
  1045. * @param {Number=} endRow End row (use when you want to cut input when certain row is reached)
  1046. * @param {Number=} endCol End column (use when you want to cut input when certain column is reached)
  1047. * @param {String=} [source="populateFromArray"]
  1048. * @param {String=} [method="overwrite"]
  1049. * @param {String} direction edit (left|right|up|down)
  1050. * @param {Array} deltas array
  1051. * @return {Object|undefined} ending td in pasted area (only if any cell was changed)
  1052. */
  1053. this.populateFromArray = function (row, col, input, endRow, endCol, source, method, direction, deltas) {
  1054. if (!(typeof input === 'object' && typeof input[0] === 'object')) {
  1055. 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
  1056. }
  1057. return grid.populateFromArray(new WalkontableCellCoords(row, col), input, typeof endRow === 'number' ? new WalkontableCellCoords(endRow, endCol) : null, source, method, direction, deltas);
  1058. };
  1059. /**
  1060. * Adds/removes data from the column
  1061. * @param {Number} col Index of column in which do you want to do splice.
  1062. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  1063. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  1064. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  1065. */
  1066. this.spliceCol = function (col, index, amount/*, elements... */) {
  1067. return datamap.spliceCol.apply(datamap, arguments);
  1068. };
  1069. /**
  1070. * Adds/removes data from the row
  1071. * @param {Number} row Index of column in which do you want to do splice.
  1072. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  1073. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  1074. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  1075. */
  1076. this.spliceRow = function (row, index, amount/*, elements... */) {
  1077. return datamap.spliceRow.apply(datamap, arguments);
  1078. };
  1079. /**
  1080. * Returns current selection. Returns undefined if there is no selection.
  1081. * @public
  1082. * @return {Array} [`startRow`, `startCol`, `endRow`, `endCol`]
  1083. */
  1084. this.getSelected = function () { //https://github.com/handsontable/handsontable/issues/44 //cjl
  1085. if (selection.isSelected()) {
  1086. return [priv.selRange.from.row, priv.selRange.from.col, priv.selRange.to.row, priv.selRange.to.col];
  1087. }
  1088. };
  1089. /**
  1090. * Returns current selection as a WalkontableCellRange object. Returns undefined if there is no selection.
  1091. * @public
  1092. * @return {WalkontableCellRange}
  1093. */
  1094. this.getSelectedRange = function () { //https://github.com/handsontable/handsontable/issues/44 //cjl
  1095. if (selection.isSelected()) {
  1096. return priv.selRange;
  1097. }
  1098. };
  1099. /**
  1100. * Render visible data
  1101. * @public
  1102. */
  1103. this.render = function () {
  1104. if (instance.view) {
  1105. instance.forceFullRender = true; //used when data was changed
  1106. selection.refreshBorders(null, true);
  1107. }
  1108. };
  1109. /**
  1110. * Load data from array
  1111. * @public
  1112. * @param {Array} data
  1113. */
  1114. this.loadData = function (data) {
  1115. if (typeof data === 'object' && data !== null) {
  1116. if (!(data.push && data.splice)) { //check if data is array. Must use duck-type check so Backbone Collections also pass it
  1117. //when data is not an array, attempt to make a single-row array of it
  1118. data = [data];
  1119. }
  1120. }
  1121. else if(data === null) {
  1122. data = [];
  1123. var row;
  1124. for (var r = 0, rlen = priv.settings.startRows; r < rlen; r++) {
  1125. row = [];
  1126. for (var c = 0, clen = priv.settings.startCols; c < clen; c++) {
  1127. row.push(null);
  1128. }
  1129. data.push(row);
  1130. }
  1131. }
  1132. else {
  1133. throw new Error("loadData only accepts array of objects or array of arrays (" + typeof data + " given)");
  1134. }
  1135. priv.isPopulated = false;
  1136. GridSettings.prototype.data = data;
  1137. if (Array.isArray(priv.settings.dataSchema) || Array.isArray(data[0])) {
  1138. instance.dataType = 'array';
  1139. }
  1140. else if (typeof priv.settings.dataSchema === 'function') {
  1141. instance.dataType = 'function';
  1142. }
  1143. else {
  1144. instance.dataType = 'object';
  1145. }
  1146. datamap = new Handsontable.DataMap(instance, priv, GridSettings);
  1147. clearCellSettingCache();
  1148. grid.adjustRowsAndCols();
  1149. Handsontable.hooks.run(instance, 'afterLoadData');
  1150. if (priv.firstRun) {
  1151. priv.firstRun = [null, 'loadData'];
  1152. }
  1153. else {
  1154. Handsontable.hooks.run(instance, 'afterChange', null, 'loadData');
  1155. instance.render();
  1156. }
  1157. priv.isPopulated = true;
  1158. function clearCellSettingCache() {
  1159. priv.cellSettings.length = 0;
  1160. }
  1161. };
  1162. /**
  1163. * 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
  1164. * @public
  1165. * @param {Number} r (Optional) From row
  1166. * @param {Number} c (Optional) From col
  1167. * @param {Number} r2 (Optional) To row
  1168. * @param {Number} c2 (Optional) To col
  1169. * @return {Array|Object}
  1170. */
  1171. this.getData = function (r, c, r2, c2) {
  1172. if (typeof r === 'undefined') {
  1173. return datamap.getAll();
  1174. } else {
  1175. return datamap.getRange(new WalkontableCellCoords(r, c), new WalkontableCellCoords(r2, c2), datamap.DESTINATION_RENDERER);
  1176. }
  1177. };
  1178. this.getCopyableData = function (startRow, startCol, endRow, endCol) {
  1179. return datamap.getCopyableText(new WalkontableCellCoords(startRow, startCol), new WalkontableCellCoords(endRow, endCol));
  1180. };
  1181. /**
  1182. * Update settings
  1183. * @public
  1184. */
  1185. this.updateSettings = function (settings, init) {
  1186. var i, clen;
  1187. if (typeof settings.rows !== "undefined") {
  1188. throw new Error("'rows' setting is no longer supported. do you mean startRows, minRows or maxRows?");
  1189. }
  1190. if (typeof settings.cols !== "undefined") {
  1191. throw new Error("'cols' setting is no longer supported. do you mean startCols, minCols or maxCols?");
  1192. }
  1193. for (i in settings) {
  1194. if (i === 'data') {
  1195. continue; //loadData will be triggered later
  1196. }
  1197. else {
  1198. if (Handsontable.hooks.hooks[i] !== void 0 || Handsontable.hooks.legacy[i] !== void 0) {
  1199. if (typeof settings[i] === 'function' || Array.isArray(settings[i])) {
  1200. instance.addHook(i, settings[i]);
  1201. }
  1202. }
  1203. else {
  1204. // Update settings
  1205. if (!init && settings.hasOwnProperty(i)) {
  1206. GridSettings.prototype[i] = settings[i];
  1207. }
  1208. }
  1209. }
  1210. }
  1211. // Load data or create data map
  1212. if (settings.data === void 0 && priv.settings.data === void 0) {
  1213. instance.loadData(null); //data source created just now
  1214. }
  1215. else if (settings.data !== void 0) {
  1216. instance.loadData(settings.data); //data source given as option
  1217. }
  1218. else if (settings.columns !== void 0) {
  1219. datamap.createMap();
  1220. }
  1221. // Init columns constructors configuration
  1222. clen = instance.countCols();
  1223. //Clear cellSettings cache
  1224. priv.cellSettings.length = 0;
  1225. if (clen > 0) {
  1226. var proto, column;
  1227. for (i = 0; i < clen; i++) {
  1228. priv.columnSettings[i] = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts);
  1229. // shortcut for prototype
  1230. proto = priv.columnSettings[i].prototype;
  1231. // Use settings provided by user
  1232. if (GridSettings.prototype.columns) {
  1233. column = GridSettings.prototype.columns[i];
  1234. Handsontable.helper.extend(proto, column);
  1235. Handsontable.helper.extend(proto, expandType(column));
  1236. }
  1237. }
  1238. }
  1239. if (typeof settings.cell !== 'undefined') {
  1240. for(i in settings.cell) {
  1241. var cell = settings.cell[i];
  1242. instance.setCellMetaObject(cell.row, cell.col, cell);
  1243. }
  1244. }
  1245. Handsontable.hooks.run(instance, 'afterCellMetaReset');
  1246. if (typeof settings.className !== "undefined") {
  1247. if (GridSettings.prototype.className) {
  1248. Handsontable.Dom.removeClass(instance.rootElement,GridSettings.prototype.className);
  1249. // instance.rootElement.removeClass(GridSettings.prototype.className);
  1250. }
  1251. if (settings.className) {
  1252. Handsontable.Dom.addClass(instance.rootElement,settings.className);
  1253. // instance.rootElement.addClass(settings.className);
  1254. }
  1255. }
  1256. if (typeof settings.height != 'undefined'){
  1257. var height = settings.height;
  1258. if (typeof height == 'function'){
  1259. height = height();
  1260. }
  1261. instance.rootElement.style.height = height + 'px';
  1262. }
  1263. if (typeof settings.width != 'undefined'){
  1264. var width = settings.width;
  1265. if (typeof width == 'function'){
  1266. width = width();
  1267. }
  1268. instance.rootElement.style.width = width + 'px';
  1269. }
  1270. if (height){
  1271. instance.rootElement.style.overflow = 'auto';
  1272. }
  1273. if (!init) {
  1274. Handsontable.hooks.run(instance, 'afterUpdateSettings');
  1275. }
  1276. grid.adjustRowsAndCols();
  1277. if (instance.view && !priv.firstRun) {
  1278. instance.forceFullRender = true; //used when data was changed
  1279. selection.refreshBorders(null, true);
  1280. }
  1281. };
  1282. this.getValue = function () {
  1283. var sel = instance.getSelected();
  1284. if (GridSettings.prototype.getValue) {
  1285. if (typeof GridSettings.prototype.getValue === 'function') {
  1286. return GridSettings.prototype.getValue.call(instance);
  1287. }
  1288. else if (sel) {
  1289. return instance.getData()[sel[0]][GridSettings.prototype.getValue];
  1290. }
  1291. }
  1292. else if (sel) {
  1293. return instance.getDataAtCell(sel[0], sel[1]);
  1294. }
  1295. };
  1296. function expandType(obj) {
  1297. if (!obj.hasOwnProperty('type')) return; //ignore obj.prototype.type
  1298. var type, expandedType = {};
  1299. if (typeof obj.type === 'object') {
  1300. type = obj.type;
  1301. }
  1302. else if (typeof obj.type === 'string') {
  1303. type = Handsontable.cellTypes[obj.type];
  1304. if (type === void 0) {
  1305. 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');
  1306. }
  1307. }
  1308. for (var i in type) {
  1309. if (type.hasOwnProperty(i) && !obj.hasOwnProperty(i)) {
  1310. expandedType[i] = type[i];
  1311. }
  1312. }
  1313. return expandedType;
  1314. }
  1315. /**
  1316. * Returns current settings object
  1317. * @return {Object}
  1318. */
  1319. this.getSettings = function () {
  1320. return priv.settings;
  1321. };
  1322. /**
  1323. * Clears grid
  1324. * @public
  1325. */
  1326. this.clear = function () {
  1327. selection.selectAll();
  1328. selection.empty();
  1329. };
  1330. /**
  1331. * Inserts or removes rows and columns
  1332. * @param {String} action See grid.alter for possible values
  1333. * @param {Number} index
  1334. * @param {Number} amount
  1335. * @param {String} [source] Optional. Source of hook runner.
  1336. * @param {Boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows.
  1337. * @public
  1338. */
  1339. this.alter = function (action, index, amount, source, keepEmptyRows) {
  1340. grid.alter(action, index, amount, source, keepEmptyRows);
  1341. };
  1342. /**
  1343. * Returns <td> element corresponding to params row, col
  1344. * @param {Number} row
  1345. * @param {Number} col
  1346. * @param {Boolean} topmost
  1347. * @public
  1348. * @return {Element}
  1349. */
  1350. this.getCell = function (row, col, topmost) {
  1351. return instance.view.getCellAtCoords(new WalkontableCellCoords(row, col), topmost);
  1352. };
  1353. /**
  1354. * Returns coordinates for the provided element
  1355. * @param elem
  1356. * @returns {WalkontableCellCoords|*}
  1357. */
  1358. this.getCoords = function(elem) {
  1359. return this.view.wt.wtTable.getCoords.call(this.view.wt.wtTable, elem);
  1360. };
  1361. /**
  1362. * Returns property name associated with column number
  1363. * @param {Number} col
  1364. * @public
  1365. * @return {String}
  1366. */
  1367. this.colToProp = function (col) {
  1368. return datamap.colToProp(col);
  1369. };
  1370. /**
  1371. * Returns column number associated with property name
  1372. * @param {String} prop
  1373. * @public
  1374. * @return {Number}
  1375. */
  1376. this.propToCol = function (prop) {
  1377. return datamap.propToCol(prop);
  1378. };
  1379. /**
  1380. * Return value at `row`, `col`
  1381. * @param {Number} row
  1382. * @param {Number} col
  1383. * @public
  1384. * @return value (mixed data type)
  1385. */
  1386. this.getDataAtCell = function (row, col) {
  1387. return datamap.get(row, datamap.colToProp(col));
  1388. };
  1389. /**
  1390. * Return value at `row`, `prop`
  1391. * @param {Number} row
  1392. * @param {String} prop
  1393. * @public
  1394. * @return value (mixed data type)
  1395. */
  1396. this.getDataAtRowProp = function (row, prop) {
  1397. return datamap.get(row, prop);
  1398. };
  1399. /**
  1400. * Return value at `col`, where `col` is the visible index of the column
  1401. * @param {Number} col
  1402. * @public
  1403. * @return {Array} value (mixed data type)
  1404. */
  1405. this.getDataAtCol = function (col) {
  1406. var out = [];
  1407. return out.concat.apply(out, datamap.getRange(new WalkontableCellCoords(0, col), new WalkontableCellCoords(priv.settings.data.length - 1, col), datamap.DESTINATION_RENDERER));
  1408. };
  1409. /**
  1410. * Return value at `prop`
  1411. * @param {String} prop
  1412. * @public
  1413. * @return {Array} value (mixed data type)
  1414. */
  1415. this.getDataAtProp = function (prop) {
  1416. var out = [];
  1417. return out.concat.apply(out, datamap.getRange(new WalkontableCellCoords(0, datamap.propToCol(prop)), new WalkontableCellCoords(priv.settings.data.length - 1, datamap.propToCol(prop)), datamap.DESTINATION_RENDERER));
  1418. };
  1419. /**
  1420. * Return original source values at 'col'
  1421. * @param {Number} col
  1422. * @public
  1423. * @returns value (mixed data type)
  1424. */
  1425. this.getSourceDataAtCol = function (col) {
  1426. var out = [],
  1427. data = priv.settings.data;
  1428. for (var i = 0; i < data.length; i++) {
  1429. out.push(data[i][col]);
  1430. }
  1431. return out;
  1432. };
  1433. /**
  1434. * Return original source values at 'row'
  1435. * @param {Number} row
  1436. * @public
  1437. * @returns value {mixed data type}
  1438. */
  1439. this.getSourceDataAtRow = function (row) {
  1440. return priv.settings.data[row];
  1441. };
  1442. /**
  1443. * Return value at `row`
  1444. * @param {Number} row
  1445. * @public
  1446. * @return value (mixed data type)
  1447. */
  1448. this.getDataAtRow = function (row) {
  1449. var data = datamap.getRange(new WalkontableCellCoords(row, 0), new WalkontableCellCoords(row, this.countCols() - 1), datamap.DESTINATION_RENDERER);
  1450. return data[0];
  1451. };
  1452. /***
  1453. * Remove "key" property object from cell meta data corresponding to params row,col
  1454. * @param {Number} row
  1455. * @param {Number} col
  1456. * @param {String} key
  1457. */
  1458. this.removeCellMeta = function(row, col, key) {
  1459. var cellMeta = instance.getCellMeta(row, col);
  1460. if(cellMeta[key] != undefined){
  1461. delete priv.cellSettings[row][col][key];
  1462. }
  1463. };
  1464. /**
  1465. * Set cell meta data object to corresponding params row, col
  1466. * @param {Number} row
  1467. * @param {Number} col
  1468. * @param {Object} prop
  1469. */
  1470. this.setCellMetaObject = function (row, col, prop) {
  1471. if (typeof prop === 'object') {
  1472. for (var key in prop) {
  1473. var value = prop[key];
  1474. this.setCellMeta(row, col, key, value);
  1475. }
  1476. }
  1477. };
  1478. /**
  1479. * Sets cell meta data object "key" corresponding to params row, col
  1480. * @param {Number} row
  1481. * @param {Number} col
  1482. * @param {String} key
  1483. * @param {String} val
  1484. *
  1485. */
  1486. this.setCellMeta = function (row, col, key, val) {
  1487. if (!priv.cellSettings[row]) {
  1488. priv.cellSettings[row] = [];
  1489. }
  1490. if (!priv.cellSettings[row][col]) {
  1491. priv.cellSettings[row][col] = new priv.columnSettings[col]();
  1492. }
  1493. priv.cellSettings[row][col][key] = val;
  1494. Handsontable.hooks.run(instance, 'afterSetCellMeta', row, col, key, val);
  1495. };
  1496. /**
  1497. * Returns cell meta data object corresponding to params row, col
  1498. * @param {Number} row
  1499. * @param {Number} col
  1500. * @public
  1501. * @return {Object}
  1502. */
  1503. this.getCellMeta = function (row, col) {
  1504. var prop = datamap.colToProp(col)
  1505. , cellProperties;
  1506. row = translateRowIndex(row);
  1507. col = translateColIndex(col);
  1508. if (!priv.columnSettings[col]) {
  1509. priv.columnSettings[col] = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts);
  1510. }
  1511. if (!priv.cellSettings[row]) {
  1512. priv.cellSettings[row] = [];
  1513. }
  1514. if (!priv.cellSettings[row][col]) {
  1515. priv.cellSettings[row][col] = new priv.columnSettings[col]();
  1516. }
  1517. cellProperties = priv.cellSettings[row][col]; //retrieve cellProperties from cache
  1518. cellProperties.row = row;
  1519. cellProperties.col = col;
  1520. cellProperties.prop = prop;
  1521. cellProperties.instance = instance;
  1522. Handsontable.hooks.run(instance, 'beforeGetCellMeta', row, col, cellProperties);
  1523. Handsontable.helper.extend(cellProperties, expandType(cellProperties)); //for `type` added in beforeGetCellMeta
  1524. if (cellProperties.cells) {
  1525. var settings = cellProperties.cells.call(cellProperties, row, col, prop);
  1526. if (settings) {
  1527. Handsontable.helper.extend(cellProperties, settings);
  1528. Handsontable.helper.extend(cellProperties, expandType(settings)); //for `type` added in cells
  1529. }
  1530. }
  1531. Handsontable.hooks.run(instance, 'afterGetCellMeta', row, col, cellProperties);
  1532. return cellProperties;
  1533. };
  1534. /**
  1535. * If displayed rows order is different than the order of rows stored in memory (i.e. sorting is applied)
  1536. * we need to translate logical (stored) row index to physical (displayed) index.
  1537. * @param row - original row index
  1538. * @returns {int} translated row index
  1539. */
  1540. function translateRowIndex(row){
  1541. return Handsontable.hooks.execute(instance, 'modifyRow', row);
  1542. }
  1543. /**
  1544. * If displayed columns order is different than the order of columns stored in memory (i.e. column were moved using manualColumnMove plugin)
  1545. * we need to translate logical (stored) column index to physical (displayed) index.
  1546. * @param col - original column index
  1547. * @returns {int} - translated column index
  1548. */
  1549. function translateColIndex(col){
  1550. return Handsontable.hooks.execute(instance, 'modifyCol', col); // warning: this must be done after datamap.colToProp
  1551. }
  1552. var rendererLookup = Handsontable.helper.cellMethodLookupFactory('renderer');
  1553. this.getCellRenderer = function (row, col) {
  1554. var renderer = rendererLookup.call(this, row, col);
  1555. return Handsontable.renderers.getRenderer(renderer);
  1556. };
  1557. this.getCellEditor = Handsontable.helper.cellMethodLookupFactory('editor');
  1558. this.getCellValidator = Handsontable.helper.cellMethodLookupFactory('validator');
  1559. /**
  1560. * Validates all cells using their validator functions and calls callback when finished. Does not render the view
  1561. * @param callback
  1562. */
  1563. this.validateCells = function (callback) {
  1564. var waitingForValidator = new ValidatorsQueue();
  1565. waitingForValidator.onQueueEmpty = callback;
  1566. var i = instance.countRows() - 1;
  1567. while (i >= 0) {
  1568. var j = instance.countCols() - 1;
  1569. while (j >= 0) {
  1570. waitingForValidator.addValidatorToQueue();
  1571. instance.validateCell(instance.getDataAtCell(i, j), instance.getCellMeta(i, j), function () {
  1572. waitingForValidator.removeValidatorFormQueue();
  1573. }, 'validateCells');
  1574. j--;
  1575. }
  1576. i--;
  1577. }
  1578. waitingForValidator.checkIfQueueIsEmpty();
  1579. };
  1580. /**
  1581. * Return array of row headers (if they are enabled). If param `row` given, return header at given row as string
  1582. * @param {Number} row (Optional)
  1583. * @return {Array|String}
  1584. */
  1585. this.getRowHeader = function (row) {
  1586. if (row === void 0) {
  1587. var out = [];
  1588. for (var i = 0, ilen = instance.countRows(); i < ilen; i++) {
  1589. out.push(instance.getRowHeader(i));
  1590. }
  1591. return out;
  1592. }
  1593. else if (Array.isArray(priv.settings.rowHeaders) && priv.settings.rowHeaders[row] !== void 0) {
  1594. return priv.settings.rowHeaders[row];
  1595. }
  1596. else if (typeof priv.settings.rowHeaders === 'function') {
  1597. return priv.settings.rowHeaders(row);
  1598. }
  1599. else if (priv.settings.rowHeaders && typeof priv.settings.rowHeaders !== 'string' && typeof priv.settings.rowHeaders !== 'number') {
  1600. return row + 1;
  1601. }
  1602. else {
  1603. return priv.settings.rowHeaders;
  1604. }
  1605. };
  1606. /**
  1607. * Returns information of this table is configured to display row headers
  1608. * @returns {boolean}
  1609. */
  1610. this.hasRowHeaders = function () {
  1611. return !!priv.settings.rowHeaders;
  1612. };
  1613. /**
  1614. * Returns information of this table is configured to display column headers
  1615. * @returns {boolean}
  1616. */
  1617. this.hasColHeaders = function () {
  1618. if (priv.settings.colHeaders !== void 0 && priv.settings.colHeaders !== null) { //Polymer has empty value = null
  1619. return !!priv.settings.colHeaders;
  1620. }
  1621. for (var i = 0, ilen = instance.countCols(); i < ilen; i++) {
  1622. if (instance.getColHeader(i)) {
  1623. return true;
  1624. }
  1625. }
  1626. return false;
  1627. };
  1628. /**
  1629. * Return array of column headers (if they are enabled). If param `col` given, return header at given column as string
  1630. * @param {Number} col (Optional)
  1631. * @return {Array|String}
  1632. */
  1633. this.getColHeader = function (col) {
  1634. if (col === void 0) {
  1635. var out = [];
  1636. for (var i = 0, ilen = instance.countCols(); i < ilen; i++) {
  1637. out.push(instance.getColHeader(i));
  1638. }
  1639. return out;
  1640. }
  1641. else {
  1642. var baseCol = col;
  1643. col = Handsontable.hooks.execute(instance, 'modifyCol', col);
  1644. if (priv.settings.columns && priv.settings.columns[col] && priv.settings.columns[col].title) {
  1645. return priv.settings.columns[col].title;
  1646. }
  1647. else if (Array.isArray(priv.settings.colHeaders) && priv.settings.colHeaders[col] !== void 0) {
  1648. return priv.settings.colHeaders[col];
  1649. }
  1650. else if (typeof priv.settings.colHeaders === 'function') {
  1651. return priv.settings.colHeaders(col);
  1652. }
  1653. else if (priv.settings.colHeaders && typeof priv.settings.colHeaders !== 'string' && typeof priv.settings.colHeaders !== 'number') {
  1654. return Handsontable.helper.spreadsheetColumnLabel(baseCol); //see #1458
  1655. }
  1656. else {
  1657. return priv.settings.colHeaders;
  1658. }
  1659. }
  1660. };
  1661. /**
  1662. * Return column width from settings (no guessing). Private use intended
  1663. * @param {Number} col
  1664. * @return {Number}
  1665. */
  1666. this._getColWidthFromSettings = function (col) {
  1667. var cellProperties = instance.getCellMeta(0, col);
  1668. var width = cellProperties.width;
  1669. if (width === void 0 || width === priv.settings.width) {
  1670. width = cellProperties.colWidths;
  1671. }
  1672. if (width !== void 0 && width !== null) {
  1673. switch (typeof width) {
  1674. case 'object': //array
  1675. width = width[col];
  1676. break;
  1677. case 'function':
  1678. width = width(col);
  1679. break;
  1680. }
  1681. if (typeof width === 'string') {
  1682. width = parseInt(width, 10);
  1683. }
  1684. }
  1685. return width;
  1686. };
  1687. /**
  1688. * Return column width
  1689. * @param {Number} col
  1690. * @return {Number}
  1691. */
  1692. this.getColWidth = function (col) {
  1693. var width = instance._getColWidthFromSettings(col);
  1694. if (!width) {
  1695. width = 50;
  1696. }
  1697. width = Handsontable.hooks.execute(instance, 'modifyColWidth', width, col);
  1698. return width;
  1699. };
  1700. /**
  1701. * Return row height from settings (no guessing). Private use intended
  1702. * @param {Number} row
  1703. * @return {Number}
  1704. */
  1705. this._getRowHeightFromSettings= function (row) {
  1706. /* inefficient
  1707. var cellProperties = instance.getCellMeta(0, row);
  1708. var height = cellProperties.height;
  1709. if (height === void 0 || height === priv.settings.height) {
  1710. height = cellProperties.rowHeights;
  1711. }
  1712. */
  1713. var height = priv.settings.rowHeights; //only uses grid settings
  1714. if (height !== void 0 && height !== null) {
  1715. switch (typeof height) {
  1716. case 'object': //array
  1717. height = height[row];
  1718. break;
  1719. case 'function':
  1720. height = height(row);
  1721. break;
  1722. }
  1723. if (typeof height === 'string') {
  1724. height = parseInt(height, 10);
  1725. }
  1726. }
  1727. return height;
  1728. };
  1729. /**
  1730. * Return row height
  1731. * @param {Number} row
  1732. * @return {Number}
  1733. */
  1734. this.getRowHeight = function (row) {
  1735. var height = instance._getRowHeightFromSettings(row);
  1736. height = Handsontable.hooks.execute(instance, 'modifyRowHeight', height, row);
  1737. return height;
  1738. };
  1739. /**
  1740. * Return total number of rows in grid
  1741. * @return {Number}
  1742. */
  1743. this.countRows = function () {
  1744. return priv.settings.data.length;
  1745. };
  1746. /**
  1747. * Return total number of columns in grid
  1748. * @return {Number}
  1749. */
  1750. this.countCols = function () {
  1751. if (instance.dataType === 'object' || instance.dataType === 'function') {
  1752. if (priv.settings.columns && priv.settings.columns.length) {
  1753. return priv.settings.columns.length;
  1754. }
  1755. else {
  1756. return datamap.colToPropCache.length;
  1757. }
  1758. }
  1759. else if (instance.dataType === 'array') {
  1760. if (priv.settings.columns && priv.settings.columns.length) {
  1761. return priv.settings.columns.length;
  1762. }
  1763. else if (priv.settings.data && priv.settings.data[0] && priv.settings.data[0].length) {
  1764. return priv.settings.data[0].length;
  1765. }
  1766. else {
  1767. return 0;
  1768. }
  1769. }
  1770. };
  1771. /**
  1772. * Return index of first rendered row
  1773. * @return {Number}
  1774. */
  1775. this.rowOffset = function () {
  1776. return instance.view.wt.wtTable.getFirstRenderedRow();
  1777. };
  1778. /**
  1779. * Return index of first visible column
  1780. * @return {Number}
  1781. */
  1782. this.colOffset = function () {
  1783. return instance.view.wt.wtTable.getFirstRenderedColumn();
  1784. };
  1785. /**
  1786. * Return number of rendered rows (including rows partially or fully rendered outside viewport). Returns -1 if table is not visible
  1787. * @return {Number}
  1788. */
  1789. this.countRenderedRows = function () {
  1790. return instance.view.wt.drawn ? instance.view.wt.wtTable.getRenderedRowsCount() : -1;
  1791. };
  1792. /**
  1793. * Return number of visible rows (rendered rows that fully fit inside viewport)). Returns -1 if table is not visible
  1794. * @return {Number}
  1795. */
  1796. this.countVisibleRows = function () {
  1797. return instance.view.wt.drawn ? instance.view.wt.wtTable.getVisibleRowsCount() : -1;
  1798. };
  1799. /**
  1800. * Return number of rendered columns (including columns partially or fully rendered outside viewport). Returns -1 if table is not visible
  1801. * @return {Number}
  1802. */
  1803. this.countRenderedCols = function () {
  1804. return instance.view.wt.drawn ? instance.view.wt.wtTable.getRenderedColumnsCount() : -1;
  1805. };
  1806. /**
  1807. * Return number of visible columns. Returns -1 if table is not visible
  1808. * @return {Number}
  1809. */
  1810. this.countVisibleCols = function () {
  1811. return instance.view.wt.drawn ? instance.view.wt.wtTable.getVisibleColumnsCount() : - 1;
  1812. };
  1813. /**
  1814. * Return number of empty rows
  1815. * @return {Boolean} ending If true, will only count empty rows at the end of the data source
  1816. */
  1817. this.countEmptyRows = function (ending) {
  1818. var i = instance.countRows() - 1
  1819. , empty = 0
  1820. , row;
  1821. while (i >= 0) {
  1822. row = Handsontable.hooks.execute(this, 'modifyRow', i);
  1823. if (instance.isEmptyRow(row)) {
  1824. empty++;
  1825. }
  1826. else if (ending) {
  1827. break;
  1828. }
  1829. i--;
  1830. }
  1831. return empty;
  1832. };
  1833. /**
  1834. * Return number of empty columns
  1835. * @return {Boolean} ending If true, will only count empty columns at the end of the data source row
  1836. */
  1837. this.countEmptyCols = function (ending) {
  1838. if (instance.countRows() < 1) {
  1839. return 0;
  1840. }
  1841. var i = instance.countCols() - 1
  1842. , empty = 0;
  1843. while (i >= 0) {
  1844. if (instance.isEmptyCol(i)) {
  1845. empty++;
  1846. }
  1847. else if (ending) {
  1848. break;
  1849. }
  1850. i--;
  1851. }
  1852. return empty;
  1853. };
  1854. /**
  1855. * Return true if the row at the given index is empty, false otherwise
  1856. * @param {Number} r Row index
  1857. * @return {Boolean}
  1858. */
  1859. this.isEmptyRow = function (r) {
  1860. return priv.settings.isEmptyRow.call(instance, r);
  1861. };
  1862. /**
  1863. * Return true if the column at the given index is empty, false otherwise
  1864. * @param {Number} c Column index
  1865. * @return {Boolean}
  1866. */
  1867. this.isEmptyCol = function (c) {
  1868. return priv.settings.isEmptyCol.call(instance, c);
  1869. };
  1870. /**
  1871. * Selects cell on grid. Optionally selects range to another cell
  1872. * @param {Number} row
  1873. * @param {Number} col
  1874. * @param {Number} [endRow]
  1875. * @param {Number} [endCol]
  1876. * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to the selection
  1877. * @public
  1878. * @return {Boolean}
  1879. */
  1880. this.selectCell = function (row, col, endRow, endCol, scrollToCell) {
  1881. if (typeof row !== 'number' || row < 0 || row >= instance.countRows()) {
  1882. return false;
  1883. }
  1884. if (typeof col !== 'number' || col < 0 || col >= instance.countCols()) {
  1885. return false;
  1886. }
  1887. if (typeof endRow !== "undefined") {
  1888. if (typeof endRow !== 'number' || endRow < 0 || endRow >= instance.countRows()) {
  1889. return false;
  1890. }
  1891. if (typeof endCol !== 'number' || endCol < 0 || endCol >= instance.countCols()) {
  1892. return false;
  1893. }
  1894. }
  1895. var coords = new WalkontableCellCoords(row, col);
  1896. priv.selRange = new WalkontableCellRange(coords, coords, coords);
  1897. if (document.activeElement && document.activeElement !== document.documentElement && document.activeElement !== document.body) {
  1898. document.activeElement.blur(); //needed or otherwise prepare won't focus the cell. selectionSpec tests this (should move focus to selected cell)
  1899. }
  1900. instance.listen();
  1901. if (typeof endRow === "undefined") {
  1902. selection.setRangeEnd(priv.selRange.from, scrollToCell);
  1903. }
  1904. else {
  1905. selection.setRangeEnd(new WalkontableCellCoords(endRow, endCol), scrollToCell);
  1906. }
  1907. instance.selection.finish();
  1908. return true;
  1909. };
  1910. this.selectCellByProp = function (row, prop, endRow, endProp, scrollToCell) {
  1911. arguments[1] = datamap.propToCol(arguments[1]);
  1912. if (typeof arguments[3] !== "undefined") {
  1913. arguments[3] = datamap.propToCol(arguments[3]);
  1914. }
  1915. return instance.selectCell.apply(instance, arguments);
  1916. };
  1917. /**
  1918. * Deselects current sell selection on grid
  1919. * @public
  1920. */
  1921. this.deselectCell = function () {
  1922. selection.deselect();
  1923. };
  1924. /**
  1925. * Remove grid from DOM
  1926. * @public
  1927. */
  1928. this.destroy = function () {
  1929. instance._clearTimeouts();
  1930. if (instance.view) { //in case HT is destroyed before initialization has finished
  1931. instance.view.destroy();
  1932. }
  1933. Handsontable.Dom.empty(instance.rootElement);
  1934. eventManager.clear();
  1935. Handsontable.hooks.run(instance, 'afterDestroy');
  1936. Handsontable.hooks.destroy(instance);
  1937. for (var i in instance) {
  1938. if (instance.hasOwnProperty(i)) {
  1939. //replace instance methods with post mortem
  1940. if (typeof instance[i] === "function") {
  1941. if (i !== "runHooks" && i !== "runHooksAndReturn") {
  1942. instance[i] = postMortem;
  1943. }
  1944. }
  1945. //replace instance properties with null (restores memory)
  1946. //it should not be necessary but this prevents a memory leak side effects that show itself in Jasmine tests
  1947. else if (i !== "guid") {
  1948. instance[i] = null;
  1949. }
  1950. }
  1951. }
  1952. //replace private properties with null (restores memory)
  1953. //it should not be necessary but this prevents a memory leak side effects that show itself in Jasmine tests
  1954. priv = null;
  1955. datamap = null;
  1956. grid = null;
  1957. selection = null;
  1958. editorManager = null;
  1959. instance = null;
  1960. GridSettings = null;
  1961. };
  1962. /**
  1963. * Replacement for all methods after Handsotnable was destroyed
  1964. */
  1965. function postMortem() {
  1966. throw new Error("This method cannot be called because this Handsontable instance has been destroyed");
  1967. }
  1968. /**
  1969. * Returns active editor object
  1970. * @returns {Object}
  1971. */
  1972. this.getActiveEditor = function(){
  1973. return editorManager.getActiveEditor();
  1974. };
  1975. /**
  1976. * Return Handsontable instance
  1977. * @public
  1978. * @return {Object}
  1979. */
  1980. this.getInstance = function () {
  1981. return instance;
  1982. };
  1983. this.addHook = function (key, fn) {
  1984. Handsontable.hooks.add(key, fn, instance);
  1985. };
  1986. this.addHookOnce = function (key, fn) {
  1987. Handsontable.hooks.once(key, fn, instance);
  1988. };
  1989. this.removeHook = function (key, fn) {
  1990. Handsontable.hooks.remove(key, fn, instance);
  1991. };
  1992. this.runHooks = function (key, p1, p2, p3, p4, p5, p6) {
  1993. Handsontable.hooks.run(instance, key, p1, p2, p3, p4, p5, p6);
  1994. };
  1995. this.runHooksAndReturn = function (key, p1, p2, p3, p4, p5, p6) {
  1996. return Handsontable.hooks.execute(instance, key, p1, p2, p3, p4, p5, p6);
  1997. };
  1998. this.timeouts = [];
  1999. /**
  2000. * Sets timeout. Purpose of this method is to clear all known timeouts when `destroy` method is called
  2001. * @public
  2002. */
  2003. this._registerTimeout = function (handle) {
  2004. this.timeouts.push(handle);
  2005. };
  2006. /**
  2007. * Clears all known timeouts
  2008. * @public
  2009. */
  2010. this._clearTimeouts = function () {
  2011. for(var i = 0, ilen = this.timeouts.length; i<ilen; i++) {
  2012. clearTimeout(this.timeouts[i]);
  2013. }
  2014. };
  2015. /**
  2016. * Handsontable version
  2017. */
  2018. this.version = '0.12.3'; //inserted by grunt from package.json
  2019. };
  2020. var DefaultSettings = function () {};
  2021. DefaultSettings.prototype = {
  2022. data: void 0,
  2023. dataSchema: void 0,
  2024. width: void 0,
  2025. height: void 0,
  2026. startRows: 5,
  2027. startCols: 5,
  2028. rowHeaders: null,
  2029. colHeaders: null,
  2030. colWidths: void 0,
  2031. columns: void 0,
  2032. cells: void 0,
  2033. cell: [],
  2034. minRows: 0,
  2035. minCols: 0,
  2036. maxRows: Infinity,
  2037. maxCols: Infinity,
  2038. minSpareRows: 0,
  2039. minSpareCols: 0,
  2040. allowInsertRow:true,
  2041. allowInsertColumn: true,
  2042. allowRemoveRow: true,
  2043. allowRemoveColumn: true,
  2044. multiSelect: true,
  2045. fillHandle: true,
  2046. fixedRowsTop: 0,
  2047. fixedColumnsLeft: 0,
  2048. outsideClickDeselects: true,
  2049. enterBeginsEditing: true,
  2050. enterMoves: {row: 1, col: 0},
  2051. tabMoves: {row: 0, col: 1},
  2052. autoWrapRow: false,
  2053. autoWrapCol: false,
  2054. copyRowsLimit: 1000,
  2055. copyColsLimit: 1000,
  2056. pasteMode: 'overwrite',
  2057. currentRowClassName: void 0,
  2058. currentColClassName: void 0,
  2059. stretchH: 'none',
  2060. isEmptyRow: function (r) {
  2061. var val;
  2062. for (var c = 0, clen = this.countCols(); c < clen; c++) {
  2063. val = this.getDataAtCell(r, c);
  2064. if (val !== '' && val !== null && typeof val !== 'undefined') {
  2065. return false;
  2066. }
  2067. }
  2068. return true;
  2069. },
  2070. isEmptyCol: function (c) {
  2071. var val;
  2072. for (var r = 0, rlen = this.countRows(); r < rlen; r++) {
  2073. val = this.getDataAtCell(r, c);
  2074. if (val !== '' && val !== null && typeof val !== 'undefined') {
  2075. return false;
  2076. }
  2077. }
  2078. return true;
  2079. },
  2080. observeDOMVisibility: true,
  2081. allowInvalid: true,
  2082. invalidCellClassName: 'htInvalid',
  2083. placeholder: false,
  2084. placeholderCellClassName: 'htPlaceholder',
  2085. readOnlyCellClassName: 'htDimmed',
  2086. commentedCellClassName: 'htCommentCell',
  2087. fragmentSelection: false,
  2088. readOnly: false,
  2089. type: 'text',
  2090. copyable: true,
  2091. debug: false, //shows debug overlays in Walkontable
  2092. wordWrap: true,
  2093. noWordWrapClassName: 'htNoWrap',
  2094. contextMenu: void 0,
  2095. undo: void 0,
  2096. columnSorting: void 0,
  2097. manualColumnMove: void 0,
  2098. manualColumnResize: void 0,
  2099. manualRowMove: void 0,
  2100. manualRowResize: void 0,
  2101. viewportRowRenderingOffset: 10, //number of rows to be prerendered before and after the viewport
  2102. viewportColumnRenderingOffset: 10, // number of columns to be prerendered before and after the viewport
  2103. groups: void 0
  2104. };
  2105. Handsontable.DefaultSettings = DefaultSettings;
  2106. (function (window) {
  2107. 'use strict';
  2108. function MultiMap() {
  2109. var map = {
  2110. arrayMap: [],
  2111. weakMap: new WeakMap()
  2112. };
  2113. return {
  2114. 'get': function (key) {
  2115. if (canBeAnArrayMapKey(key)) {
  2116. return map.arrayMap[key];
  2117. } else if (canBeAWeakMapKey(key)) {
  2118. return map.weakMap.get(key);
  2119. }
  2120. },
  2121. 'set': function (key, value) {
  2122. if (canBeAnArrayMapKey(key)) {
  2123. map.arrayMap[key] = value;
  2124. } else if (canBeAWeakMapKey(key)) {
  2125. map.weakMap.set(key, value);
  2126. } else {
  2127. throw new Error('Invalid key type');
  2128. }
  2129. },
  2130. 'delete': function (key) {
  2131. if (canBeAnArrayMapKey(key)) {
  2132. delete map.arrayMap[key];
  2133. } else if (canBeAWeakMapKey(key)) {
  2134. map.weakMap['delete'](key); //Delete must be called using square bracket notation, because IE8 does not handle using `delete` with dot notation
  2135. }
  2136. }
  2137. };
  2138. function canBeAnArrayMapKey(obj){
  2139. return obj !== null && !isNaNSymbol(obj) && (typeof obj == 'string' || typeof obj == 'number');
  2140. }
  2141. function canBeAWeakMapKey(obj){
  2142. return obj !== null && (typeof obj == 'object' || typeof obj == 'function');
  2143. }
  2144. function isNaNSymbol(obj){
  2145. return obj !== obj; // NaN === NaN is always false
  2146. }
  2147. }
  2148. if (!window.MultiMap){
  2149. window.MultiMap = MultiMap;
  2150. }
  2151. })(window);
  2152. /**
  2153. * DOM helper optimized for maximum performance
  2154. * It is recommended for Handsontable plugins and renderers, because it is much faster than jQuery
  2155. * @type {Object}
  2156. */
  2157. if(!window.Handsontable) {
  2158. var Handsontable = {}; //required because Walkontable test suite uses this class directly
  2159. }
  2160. Handsontable.Dom = {};
  2161. Handsontable.Dom.enableImmediatePropagation = function (event) {
  2162. if (event != null && event.isImmediatePropagationEnabled == null) {
  2163. event.stopImmediatePropagation = function () {
  2164. this.isImmediatePropagationEnabled = false;
  2165. this.cancelBubble = true;
  2166. };
  2167. event.isImmediatePropagationEnabled = true;
  2168. event.isImmediatePropagationStopped = function () {
  2169. return !this.isImmediatePropagationEnabled;
  2170. };
  2171. }
  2172. };
  2173. //goes up the DOM tree (including given element) until it finds an element that matches the nodeName
  2174. Handsontable.Dom.closest = function (elem, nodeNames, until) {
  2175. while (elem != null && elem !== until) {
  2176. if (elem.nodeType === 1 && nodeNames.indexOf(elem.nodeName) > -1) {
  2177. return elem;
  2178. }
  2179. elem = elem.parentNode;
  2180. }
  2181. return null;
  2182. };
  2183. /**
  2184. * Goes up the DOM tree and checks if element is child of another element
  2185. * @param child Child element
  2186. * @param {Object|string} parent Parent element OR selector of the parent element. If classname provided, function returns true for the first occurance of element with that class.
  2187. * @returns {boolean}
  2188. */
  2189. Handsontable.Dom.isChildOf = function (child, parent) {
  2190. var node = child.parentNode;
  2191. var queriedParents = [];
  2192. if(typeof parent === "string") {
  2193. queriedParents = Array.prototype.slice.call(document.querySelectorAll(parent), 0);
  2194. } else {
  2195. queriedParents.push(parent);
  2196. }
  2197. while (node != null) {
  2198. if (queriedParents.indexOf(node) > - 1) {
  2199. return true;
  2200. }
  2201. node = node.parentNode;
  2202. }
  2203. return false;
  2204. };
  2205. /**
  2206. * Counts index of element within its parent
  2207. * WARNING: for performance reasons, assumes there are only element nodes (no text nodes). This is true for Walkotnable
  2208. * Otherwise would need to check for nodeType or use previousElementSibling
  2209. * @see http://jsperf.com/sibling-index/10
  2210. * @param {Element} elem
  2211. * @return {Number}
  2212. */
  2213. Handsontable.Dom.index = function (elem) {
  2214. var i = 0;
  2215. if (elem.previousSibling) {
  2216. while (elem = elem.previousSibling) {
  2217. ++i
  2218. }
  2219. }
  2220. return i;
  2221. };
  2222. if (document.documentElement.classList) {
  2223. // HTML5 classList API
  2224. Handsontable.Dom.hasClass = function (ele, cls) {
  2225. return ele.classList.contains(cls);
  2226. };
  2227. Handsontable.Dom.addClass = function (ele, cls) {
  2228. if (cls) {
  2229. ele.classList.add(cls);
  2230. }
  2231. };
  2232. Handsontable.Dom.removeClass = function (ele, cls) {
  2233. ele.classList.remove(cls);
  2234. };
  2235. }
  2236. else {
  2237. //http://snipplr.com/view/3561/addclass-removeclass-hasclass/
  2238. Handsontable.Dom.hasClass = function (ele, cls) {
  2239. return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
  2240. };
  2241. Handsontable.Dom.addClass = function (ele, cls) {
  2242. if(ele.className == "") ele.className = cls;
  2243. else if (!this.hasClass(ele, cls)) ele.className += " " + cls;
  2244. };
  2245. Handsontable.Dom.removeClass = function (ele, cls) {
  2246. if (this.hasClass(ele, cls)) { //is this really needed?
  2247. var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
  2248. ele.className = ele.className.replace(reg, ' ').trim(); //String.prototype.trim is defined in polyfill.js
  2249. }
  2250. };
  2251. }
  2252. Handsontable.Dom.removeTextNodes = function (elem, parent) {
  2253. if (elem.nodeType === 3) {
  2254. parent.removeChild(elem); //bye text nodes!
  2255. }
  2256. else if (['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'].indexOf(elem.nodeName) > -1) {
  2257. var childs = elem.childNodes;
  2258. for (var i = childs.length - 1; i >= 0; i--) {
  2259. this.removeTextNodes(childs[i], elem);
  2260. }
  2261. }
  2262. };
  2263. /**
  2264. * Remove childs function
  2265. * WARNING - this doesn't unload events and data attached by jQuery
  2266. * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/9
  2267. * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/11 - no siginificant improvement with Chrome remove() method
  2268. * @param element
  2269. * @returns {void}
  2270. */
  2271. //
  2272. Handsontable.Dom.empty = function (element) {
  2273. var child;
  2274. while (child = element.lastChild) {
  2275. element.removeChild(child);
  2276. }
  2277. };
  2278. Handsontable.Dom.HTML_CHARACTERS = /(<(.*)>|&(.*);)/;
  2279. /**
  2280. * Insert content into element trying avoid innerHTML method.
  2281. * @return {void}
  2282. */
  2283. Handsontable.Dom.fastInnerHTML = function (element, content) {
  2284. if (this.HTML_CHARACTERS.test(content)) {
  2285. element.innerHTML = content;
  2286. }
  2287. else {
  2288. this.fastInnerText(element, content);
  2289. }
  2290. };
  2291. /**
  2292. * Insert text content into element
  2293. * @return {void}
  2294. */
  2295. if (document.createTextNode('test').textContent) { //STANDARDS
  2296. Handsontable.Dom.fastInnerText = function (element, content) {
  2297. var child = element.firstChild;
  2298. if (child && child.nodeType === 3 && child.nextSibling === null) {
  2299. //fast lane - replace existing text node
  2300. //http://jsperf.com/replace-text-vs-reuse
  2301. child.textContent = content;
  2302. }
  2303. else {
  2304. //slow lane - empty element and insert a text node
  2305. this.empty(element);
  2306. element.appendChild(document.createTextNode(content));
  2307. }
  2308. };
  2309. }
  2310. else { //IE8
  2311. Handsontable.Dom.fastInnerText = function (element, content) {
  2312. var child = element.firstChild;
  2313. if (child && child.nodeType === 3 && child.nextSibling === null) {
  2314. //fast lane - replace existing text node
  2315. //http://jsperf.com/replace-text-vs-reuse
  2316. child.data = content;
  2317. }
  2318. else {
  2319. //slow lane - empty element and insert a text node
  2320. this.empty(element);
  2321. element.appendChild(document.createTextNode(content));
  2322. }
  2323. };
  2324. }
  2325. /**
  2326. * Returns true if element is attached to the DOM and visible, false otherwise
  2327. * @param elem
  2328. * @returns {boolean}
  2329. */
  2330. Handsontable.Dom.isVisible = function (elem) {
  2331. var next = elem;
  2332. while (next !== document.documentElement) { //until <html> reached
  2333. if (next === null) { //parent detached from DOM
  2334. return false;
  2335. }
  2336. else if (next.nodeType === 11) { //nodeType == 1 -> DOCUMENT_FRAGMENT_NODE
  2337. if (next.host) { //this is Web Components Shadow DOM
  2338. //see: http://w3c.github.io/webcomponents/spec/shadow/#encapsulation
  2339. //according to spec, should be if (next.ownerDocument !== window.document), but that doesn't work yet
  2340. if (next.host.impl) { //Chrome 33.0.1723.0 canary (2013-11-29) Web Platform features disabled
  2341. return Handsontable.Dom.isVisible(next.host.impl);
  2342. }
  2343. else if (next.host) { //Chrome 33.0.1723.0 canary (2013-11-29) Web Platform features enabled
  2344. return Handsontable.Dom.isVisible(next.host);
  2345. }
  2346. else {
  2347. throw new Error("Lost in Web Components world");
  2348. }
  2349. }
  2350. else {
  2351. return false; //this is a node detached from document in IE8
  2352. }
  2353. }
  2354. else if (next.style.display === 'none') {
  2355. return false;
  2356. }
  2357. next = next.parentNode;
  2358. }
  2359. return true;
  2360. };
  2361. /**
  2362. * Returns elements top and left offset relative to the document. Function is not compatible with jQuery offset.
  2363. *
  2364. * @param {HTMLElement} elem
  2365. * @return {Object} Returns object with `top` and `left` props
  2366. */
  2367. Handsontable.Dom.offset = function (elem) {
  2368. var offsetLeft,
  2369. offsetTop,
  2370. lastElem,
  2371. docElem,
  2372. box;
  2373. docElem = document.documentElement;
  2374. if (this.hasCaptionProblem() && elem.firstChild && elem.firstChild.nodeName === 'CAPTION') {
  2375. // fixes problem with Firefox ignoring <caption> in TABLE offset (see also Handsontable.Dom.outerHeight)
  2376. // http://jsperf.com/offset-vs-getboundingclientrect/8
  2377. box = elem.getBoundingClientRect();
  2378. return {
  2379. top: box.top + (window.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0),
  2380. left: box.left + (window.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)
  2381. };
  2382. }
  2383. offsetLeft = elem.offsetLeft;
  2384. offsetTop = elem.offsetTop;
  2385. lastElem = elem;
  2386. while (elem = elem.offsetParent) {
  2387. // from my observation, document.body always has scrollLeft/scrollTop == 0
  2388. if (elem === document.body) {
  2389. break;
  2390. }
  2391. offsetLeft += elem.offsetLeft;
  2392. offsetTop += elem.offsetTop;
  2393. lastElem = elem;
  2394. }
  2395. //slow - http://jsperf.com/offset-vs-getboundingclientrect/6
  2396. if (lastElem && lastElem.style.position === 'fixed') {
  2397. //if(lastElem !== document.body) { //faster but does gives false positive in Firefox
  2398. offsetLeft += window.pageXOffset || docElem.scrollLeft;
  2399. offsetTop += window.pageYOffset || docElem.scrollTop;
  2400. }
  2401. return {
  2402. left: offsetLeft,
  2403. top: offsetTop
  2404. };
  2405. };
  2406. Handsontable.Dom.getWindowScrollTop = function () {
  2407. var res = window.scrollY;
  2408. if (res == void 0) { //IE8-11
  2409. res = document.documentElement.scrollTop;
  2410. }
  2411. return res;
  2412. };
  2413. Handsontable.Dom.getWindowScrollLeft = function () {
  2414. var res = window.scrollX;
  2415. if (res == void 0) { //IE8-11
  2416. res = document.documentElement.scrollLeft;
  2417. }
  2418. return res;
  2419. };
  2420. Handsontable.Dom.getScrollTop = function (elem) {
  2421. if (elem === window) {
  2422. return Handsontable.Dom.getWindowScrollTop(elem);
  2423. }
  2424. else {
  2425. return elem.scrollTop;
  2426. }
  2427. };
  2428. Handsontable.Dom.getScrollLeft = function (elem) {
  2429. if (elem === window) {
  2430. return Handsontable.Dom.getWindowScrollLeft(elem);
  2431. }
  2432. else {
  2433. return elem.scrollLeft;
  2434. }
  2435. };
  2436. Handsontable.Dom.getComputedStyle = function (elem) {
  2437. return elem.currentStyle || document.defaultView.getComputedStyle(elem);
  2438. };
  2439. Handsontable.Dom.outerWidth = function (elem) {
  2440. return elem.offsetWidth;
  2441. };
  2442. Handsontable.Dom.outerHeight = function (elem) {
  2443. if (this.hasCaptionProblem() && elem.firstChild && elem.firstChild.nodeName === 'CAPTION') {
  2444. //fixes problem with Firefox ignoring <caption> in TABLE.offsetHeight
  2445. //jQuery (1.10.1) still has this unsolved
  2446. //may be better to just switch to getBoundingClientRect
  2447. //http://bililite.com/blog/2009/03/27/finding-the-size-of-a-table/
  2448. //http://lists.w3.org/Archives/Public/www-style/2009Oct/0089.html
  2449. //http://bugs.jquery.com/ticket/2196
  2450. //http://lists.w3.org/Archives/Public/www-style/2009Oct/0140.html#start140
  2451. return elem.offsetHeight + elem.firstChild.offsetHeight;
  2452. }
  2453. else {
  2454. return elem.offsetHeight;
  2455. }
  2456. };
  2457. Handsontable.Dom.innerHeight = function (elem) {
  2458. return elem.clientHeight || elem.innerHeight;
  2459. };
  2460. Handsontable.Dom.innerWidth = function (elem) {
  2461. return elem.clientWidth || elem.innerWidth;
  2462. };
  2463. Handsontable.Dom.addEvent = function(element, event, callback) {
  2464. if (window.addEventListener) {
  2465. element.addEventListener(event, callback, false)
  2466. } else {
  2467. element.attachEvent('on' + event, callback);
  2468. }
  2469. };
  2470. Handsontable.Dom.removeEvent = function(element, event, callback) {
  2471. if (window.removeEventListener) {
  2472. element.removeEventListener(event, callback, false);
  2473. } else {
  2474. element.detachEvent('on' + event, callback);
  2475. }
  2476. };
  2477. (function () {
  2478. var hasCaptionProblem;
  2479. function detectCaptionProblem() {
  2480. var TABLE = document.createElement('TABLE');
  2481. TABLE.style.borderSpacing = 0;
  2482. TABLE.style.borderWidth = 0;
  2483. TABLE.style.padding = 0;
  2484. var TBODY = document.createElement('TBODY');
  2485. TABLE.appendChild(TBODY);
  2486. TBODY.appendChild(document.createElement('TR'));
  2487. TBODY.firstChild.appendChild(document.createElement('TD'));
  2488. TBODY.firstChild.firstChild.innerHTML = '<tr><td>t<br>t</td></tr>';
  2489. var CAPTION = document.createElement('CAPTION');
  2490. CAPTION.innerHTML = 'c<br>c<br>c<br>c';
  2491. CAPTION.style.padding = 0;
  2492. CAPTION.style.margin = 0;
  2493. TABLE.insertBefore(CAPTION, TBODY);
  2494. document.body.appendChild(TABLE);
  2495. hasCaptionProblem = (TABLE.offsetHeight < 2 * TABLE.lastChild.offsetHeight); //boolean
  2496. document.body.removeChild(TABLE);
  2497. }
  2498. Handsontable.Dom.hasCaptionProblem = function () {
  2499. if (hasCaptionProblem === void 0) {
  2500. detectCaptionProblem();
  2501. }
  2502. return hasCaptionProblem;
  2503. };
  2504. /**
  2505. * Returns caret position in text input
  2506. * @author http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
  2507. * @return {Number}
  2508. */
  2509. Handsontable.Dom.getCaretPosition = function (el) {
  2510. if (el.selectionStart) {
  2511. return el.selectionStart;
  2512. }
  2513. else if (document.selection) { //IE8
  2514. el.focus();
  2515. var r = document.selection.createRange();
  2516. if (r == null) {
  2517. return 0;
  2518. }
  2519. var re = el.createTextRange(),
  2520. rc = re.duplicate();
  2521. re.moveToBookmark(r.getBookmark());
  2522. rc.setEndPoint('EndToStart', re);
  2523. return rc.text.length;
  2524. }
  2525. return 0;
  2526. };
  2527. /**
  2528. * Returns end of the selection in text input
  2529. * @return {Number}
  2530. */
  2531. Handsontable.Dom.getSelectionEndPosition = function (el) {
  2532. if(el.selectionEnd) {
  2533. return el.selectionEnd;
  2534. } else if(document.selection) { //IE8
  2535. var r = document.selection.createRange();
  2536. if(r == null) {
  2537. return 0;
  2538. }
  2539. var re = el.createTextRange();
  2540. return re.text.indexOf(r.text) + r.text.length;
  2541. }
  2542. };
  2543. /**
  2544. * Sets caret position in text input
  2545. * @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/
  2546. * @param {Element} el
  2547. * @param {Number} pos
  2548. * @param {Number} endPos
  2549. */
  2550. Handsontable.Dom.setCaretPosition = function (el, pos, endPos) {
  2551. if (endPos === void 0) {
  2552. endPos = pos;
  2553. }
  2554. if (el.setSelectionRange) {
  2555. el.focus();
  2556. el.setSelectionRange(pos, endPos);
  2557. }
  2558. else if (el.createTextRange) { //IE8
  2559. var range = el.createTextRange();
  2560. range.collapse(true);
  2561. range.moveEnd('character', endPos);
  2562. range.moveStart('character', pos);
  2563. range.select();
  2564. }
  2565. };
  2566. var cachedScrollbarWidth;
  2567. //http://stackoverflow.com/questions/986937/how-can-i-get-the-browsers-scrollbar-sizes
  2568. function walkontableCalculateScrollbarWidth() {
  2569. var inner = document.createElement('p');
  2570. inner.style.width = "100%";
  2571. inner.style.height = "200px";
  2572. var outer = document.createElement('div');
  2573. outer.style.position = "absolute";
  2574. outer.style.top = "0px";
  2575. outer.style.left = "0px";
  2576. outer.style.visibility = "hidden";
  2577. outer.style.width = "200px";
  2578. outer.style.height = "150px";
  2579. outer.style.overflow = "hidden";
  2580. outer.appendChild(inner);
  2581. (document.body || document.documentElement).appendChild(outer);
  2582. var w1 = inner.offsetWidth;
  2583. outer.style.overflow = 'scroll';
  2584. var w2 = inner.offsetWidth;
  2585. if (w1 == w2) w2 = outer.clientWidth;
  2586. (document.body || document.documentElement).removeChild(outer);
  2587. return (w1 - w2);
  2588. }
  2589. /**
  2590. * Returns the computed width of the native browser scroll bar
  2591. * @return {Number} width
  2592. */
  2593. Handsontable.Dom.getScrollbarWidth = function () {
  2594. if (cachedScrollbarWidth === void 0) {
  2595. cachedScrollbarWidth = walkontableCalculateScrollbarWidth();
  2596. }
  2597. return cachedScrollbarWidth;
  2598. };
  2599. var isIE8 = !(document.createTextNode('test').textContent);
  2600. Handsontable.Dom.isIE8 = function () {
  2601. return isIE8;
  2602. };
  2603. var isIE9 = !!(document.documentMode);
  2604. Handsontable.Dom.isIE9 = function () {
  2605. return isIE9;
  2606. };
  2607. var isSafari = (/Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor));
  2608. Handsontable.Dom.isSafari = function () {
  2609. return isSafari;
  2610. };
  2611. /**
  2612. * Sets overlay position depending on it's type and used browser
  2613. */
  2614. Handsontable.Dom.setOverlayPosition = function (overlayElem, left, top) {
  2615. if (isIE8 || isIE9) {
  2616. overlayElem.style.top = top;
  2617. overlayElem.style.left = left;
  2618. } else if (isSafari) {
  2619. overlayElem.style['-webkit-transform'] = 'translate3d(' + left + ',' + top + ',0)';
  2620. } else {
  2621. overlayElem.style['transform'] = 'translate3d(' + left + ',' + top + ',0)';
  2622. }
  2623. };
  2624. Handsontable.Dom.getCssTransform = function (elem) {
  2625. var transform;
  2626. if(elem.style['transform'] && (transform = elem.style['transform']) != "") {
  2627. return ['transform', transform];
  2628. } else if (elem.style['-webkit-transform'] && (transform = elem.style['-webkit-transform']) != "") {
  2629. return ['-webkit-transform', transform];
  2630. } else {
  2631. return -1;
  2632. }
  2633. };
  2634. Handsontable.Dom.resetCssTransform = function (elem) {
  2635. if(elem['transform'] && elem['transform'] != "") {
  2636. elem['transform'] = "";
  2637. } else if(elem['-webkit-transform'] && elem['-webkit-transform'] != "") {
  2638. elem['-webkit-transform'] = "";
  2639. }
  2640. };
  2641. })();
  2642. if(!window.Handsontable){
  2643. var Handsontable = {};
  2644. }
  2645. Handsontable.countEventManagerListeners = 0; //used to debug memory leaks
  2646. Handsontable.eventManager = function (instance) {
  2647. if (!instance) {
  2648. throw new Error ('instance not defined');
  2649. }
  2650. if (!instance.eventListeners) {
  2651. instance.eventListeners = [];
  2652. }
  2653. var addEvent = function (element, event, callback) {
  2654. var callbackProxy = function (event) {
  2655. if(event.target == void 0 && event.srcElement != void 0) {
  2656. if(event.definePoperty) {
  2657. event.definePoperty('target', {
  2658. value: event.srcElement
  2659. });
  2660. } else {
  2661. event.target = event.srcElement;
  2662. }
  2663. }
  2664. if(event.preventDefault == void 0) {
  2665. if(event.definePoperty) {
  2666. event.definePoperty('preventDefault', {
  2667. value: function() {
  2668. this.returnValue = false;
  2669. }
  2670. });
  2671. } else {
  2672. event.preventDefault = function () {
  2673. this.returnValue = false;
  2674. }
  2675. }
  2676. }
  2677. callback.call(this, event);
  2678. };
  2679. instance.eventListeners.push({
  2680. element: element,
  2681. event: event,
  2682. callback: callback,
  2683. callbackProxy: callbackProxy
  2684. });
  2685. if (window.addEventListener) {
  2686. element.addEventListener(event, callbackProxy, false)
  2687. } else {
  2688. element.attachEvent('on' + event, callbackProxy);
  2689. }
  2690. Handsontable.countEventManagerListeners++;
  2691. },
  2692. removeEvent = function (element, event, callback){
  2693. var len = instance.eventListeners.length;
  2694. while (len--) {
  2695. var tmpEv = instance.eventListeners[len];
  2696. if (tmpEv.event == event && tmpEv.element == element) {
  2697. if (callback && callback != tmpEv.callback) {
  2698. continue;
  2699. }
  2700. instance.eventListeners.splice(len, 1);
  2701. if (tmpEv.element.removeEventListener) {
  2702. tmpEv.element.removeEventListener(tmpEv.event, tmpEv.callbackProxy, false);
  2703. } else {
  2704. tmpEv.element.detachEvent('on' + tmpEv.event, tmpEv.callbackProxy);
  2705. }
  2706. Handsontable.countEventManagerListeners--;
  2707. }
  2708. }
  2709. },
  2710. clearEvents = function () {
  2711. var len = instance.eventListeners.length;
  2712. while(len--) {
  2713. var event = instance.eventListeners[len];
  2714. removeEvent(event.element, event.event, event.callback);
  2715. }
  2716. },
  2717. fireEvent = function (element, type) {
  2718. var options = {
  2719. bubbles: true,
  2720. cancelable: (type !== "mousemove"),
  2721. view: window,
  2722. detail: 0,
  2723. screenX: 0,
  2724. screenY: 0,
  2725. clientX: 1,
  2726. clientY: 1,
  2727. ctrlKey: false,
  2728. altKey: false,
  2729. shiftKey: false,
  2730. metaKey: false,
  2731. button: 0,
  2732. relatedTarget: undefined
  2733. };
  2734. var event;
  2735. if ( document.createEvent ) {
  2736. event = document.createEvent("MouseEvents");
  2737. event.initMouseEvent(type, options.bubbles, options.cancelable,
  2738. options.view, options.detail,
  2739. options.screenX, options.screenY, options.clientX, options.clientY,
  2740. options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
  2741. options.button, options.relatedTarget || document.body.parentNode);
  2742. } else {
  2743. event = document.createEventObject();
  2744. }
  2745. if (element.dispatchEvent) {
  2746. element.dispatchEvent(event);
  2747. } else {
  2748. element.fireEvent('on' + type, event);
  2749. }
  2750. };
  2751. return {
  2752. addEventListener: addEvent,
  2753. removeEventListener: removeEvent,
  2754. clear: clearEvents,
  2755. fireEvent: fireEvent
  2756. }
  2757. };
  2758. /**
  2759. * Handsontable TableView constructor
  2760. * @param {Object} instance
  2761. */
  2762. Handsontable.TableView = function (instance) {
  2763. var that = this
  2764. this.eventManager = Handsontable.eventManager(instance);
  2765. this.instance = instance;
  2766. this.settings = instance.getSettings();
  2767. var originalStyle = instance.rootElement.getAttribute('style');
  2768. if(originalStyle) {
  2769. instance.rootElement.setAttribute('data-originalstyle', originalStyle); //needed to retrieve original style in jsFiddle link generator in HT examples. may be removed in future versions
  2770. }
  2771. Handsontable.Dom.addClass(instance.rootElement,'handsontable');
  2772. // instance.rootElement.addClass('handsontable');
  2773. var table = document.createElement('TABLE');
  2774. table.className = 'htCore';
  2775. this.THEAD = document.createElement('THEAD');
  2776. table.appendChild(this.THEAD);
  2777. this.TBODY = document.createElement('TBODY');
  2778. table.appendChild(this.TBODY);
  2779. instance.table = table;
  2780. instance.container.insertBefore(table, instance.container.firstChild);
  2781. this.eventManager.addEventListener(instance.rootElement,'mousedown', function (event) {
  2782. if (!that.isTextSelectionAllowed(event.target)) {
  2783. clearTextSelection();
  2784. event.preventDefault();
  2785. window.focus(); //make sure that window that contains HOT is active. Important when HOT is in iframe.
  2786. }
  2787. });
  2788. this.eventManager.addEventListener(document.documentElement, 'keyup',function (event) {
  2789. if (instance.selection.isInProgress() && !event.shiftKey) {
  2790. instance.selection.finish();
  2791. }
  2792. });
  2793. var isMouseDown;
  2794. this.isMouseDown = function () {
  2795. return isMouseDown;
  2796. };
  2797. this.eventManager.addEventListener(document.documentElement, 'mouseup', function (event) {
  2798. if (instance.selection.isInProgress() && event.which === 1) { //is left mouse button
  2799. instance.selection.finish();
  2800. }
  2801. isMouseDown = false;
  2802. if (Handsontable.helper.isOutsideInput(document.activeElement)) {
  2803. instance.unlisten();
  2804. }
  2805. });
  2806. this.eventManager.addEventListener(document.documentElement, 'mousedown',function (event) {
  2807. var next = event.target;
  2808. if (isMouseDown) {
  2809. return; //it must have been started in a cell
  2810. }
  2811. if (next !== that.wt.wtTable.spreader) { //immediate click on "spreader" means click on the right side of vertical scrollbar
  2812. while (next !== document.documentElement) {
  2813. if (next === null) {
  2814. return; //click on something that was a row but now is detached (possibly because your click triggered a rerender)
  2815. }
  2816. if (next === instance.rootElement) {
  2817. return; //click inside container
  2818. }
  2819. next = next.parentNode;
  2820. }
  2821. }
  2822. //function did not return until here, we have an outside click!
  2823. if (that.settings.outsideClickDeselects) {
  2824. instance.deselectCell();
  2825. }
  2826. else {
  2827. instance.destroyEditor();
  2828. }
  2829. });
  2830. this.eventManager.addEventListener(table, 'selectstart', function (event) {
  2831. if (that.settings.fragmentSelection) {
  2832. return;
  2833. }
  2834. //https://github.com/handsontable/handsontable/issues/160
  2835. //selectstart is IE only event. Prevent text from being selected when performing drag down in IE8
  2836. event.preventDefault();
  2837. });
  2838. var clearTextSelection = function () {
  2839. //http://stackoverflow.com/questions/3169786/clear-text-selection-with-javascript
  2840. if (window.getSelection) {
  2841. if (window.getSelection().empty) { // Chrome
  2842. window.getSelection().empty();
  2843. } else if (window.getSelection().removeAllRanges) { // Firefox
  2844. window.getSelection().removeAllRanges();
  2845. }
  2846. } else if (document.selection) { // IE?
  2847. document.selection.empty();
  2848. }
  2849. };
  2850. var selections = [
  2851. new WalkontableSelection({
  2852. className: 'current',
  2853. border: {
  2854. width: 2,
  2855. color: '#5292F7',
  2856. //style: 'solid', //not used
  2857. cornerVisible: function () {
  2858. return that.settings.fillHandle && !that.isCellEdited() && !instance.selection.isMultiple();
  2859. },
  2860. multipleSelectionHandlesVisible: function () {
  2861. return !that.isCellEdited() && !instance.selection.isMultiple();
  2862. }
  2863. }
  2864. }),
  2865. new WalkontableSelection({
  2866. className: 'area',
  2867. border: {
  2868. width: 1,
  2869. color: '#89AFF9',
  2870. //style: 'solid', // not used
  2871. cornerVisible: function () {
  2872. return that.settings.fillHandle && !that.isCellEdited() && instance.selection.isMultiple()
  2873. },
  2874. multipleSelectionHandlesVisible: function () {
  2875. return !that.isCellEdited() && instance.selection.isMultiple();
  2876. }
  2877. }
  2878. }),
  2879. new WalkontableSelection({
  2880. className: 'highlight',
  2881. highlightRowClassName: that.settings.currentRowClassName,
  2882. highlightColumnClassName: that.settings.currentColClassName
  2883. }),
  2884. new WalkontableSelection({
  2885. className: 'fill',
  2886. border: {
  2887. width: 1,
  2888. color: 'red'
  2889. //style: 'solid' // not used
  2890. }
  2891. })
  2892. ];
  2893. selections.current = selections[0];
  2894. selections.area = selections[1];
  2895. selections.highlight = selections[2];
  2896. selections.fill = selections[3];
  2897. var walkontableConfig = {
  2898. debug: function () {
  2899. return that.settings.debug;
  2900. },
  2901. table: table,
  2902. stretchH: this.settings.stretchH,
  2903. data: instance.getDataAtCell,
  2904. totalRows: instance.countRows,
  2905. totalColumns: instance.countCols,
  2906. fixedColumnsLeft: function () {
  2907. return that.settings.fixedColumnsLeft;
  2908. },
  2909. fixedRowsTop: function () {
  2910. return that.settings.fixedRowsTop;
  2911. },
  2912. renderAllRows: that.settings.renderAllRows,
  2913. rowHeaders: function () {
  2914. var arr = [];
  2915. if(instance.hasRowHeaders()) {
  2916. arr.push(function (index, TH) {
  2917. that.appendRowHeader(index, TH);
  2918. });
  2919. }
  2920. Handsontable.hooks.run(instance, 'afterGetRowHeaderRenderers', arr);
  2921. return arr;
  2922. },
  2923. columnHeaders: function () {
  2924. var arr = [];
  2925. if(instance.hasColHeaders()) {
  2926. arr.push(function (index, TH) {
  2927. that.appendColHeader(index, TH);
  2928. });
  2929. }
  2930. Handsontable.hooks.run(instance, 'afterGetColumnHeaderRenderers', arr);
  2931. return arr;
  2932. },
  2933. columnWidth: instance.getColWidth,
  2934. rowHeight: instance.getRowHeight,
  2935. cellRenderer: function (row, col, TD) {
  2936. var prop = that.instance.colToProp(col)
  2937. , cellProperties = that.instance.getCellMeta(row, col)
  2938. , renderer = that.instance.getCellRenderer(cellProperties);
  2939. var value = that.instance.getDataAtRowProp(row, prop);
  2940. renderer(that.instance, TD, row, col, prop, value, cellProperties);
  2941. Handsontable.hooks.run(that.instance, 'afterRenderer', TD, row, col, prop, value, cellProperties);
  2942. },
  2943. selections: selections,
  2944. hideBorderOnMouseDownOver: function () {
  2945. return that.settings.fragmentSelection;
  2946. },
  2947. onCellMouseDown: function (event, coords, TD, wt) {
  2948. instance.listen();
  2949. that.activeWt = wt;
  2950. isMouseDown = true;
  2951. Handsontable.hooks.run(instance, 'beforeOnCellMouseDown', event, coords, TD);
  2952. Handsontable.Dom.enableImmediatePropagation(event);
  2953. if (!event.isImmediatePropagationStopped()) {
  2954. if (event.button === 2 && instance.selection.inInSelection(coords)) { //right mouse button
  2955. //do nothing
  2956. }
  2957. else if (event.shiftKey) {
  2958. if (coords.row >= 0 && coords.col >= 0) {
  2959. instance.selection.setRangeEnd(coords);
  2960. }
  2961. }
  2962. else {
  2963. if (coords.row < 0 || coords.col < 0) {
  2964. if (coords.row < 0) {
  2965. instance.selectCell(0, coords.col, instance.countRows() - 1, coords.col);
  2966. instance.selection.setSelectedHeaders(false, true);
  2967. }
  2968. if (coords.col < 0) {
  2969. instance.selectCell(coords.row, 0, coords.row, instance.countCols() - 1);
  2970. instance.selection.setSelectedHeaders(true, false);
  2971. }
  2972. }
  2973. else {
  2974. instance.selection.setRangeStart(coords);
  2975. }
  2976. }
  2977. Handsontable.hooks.run(instance, 'afterOnCellMouseDown', event, coords, TD);
  2978. that.activeWt = that.wt;
  2979. }
  2980. },
  2981. /*onCellMouseOut: function (/*event, coords, TD* /) {
  2982. if (isMouseDown && that.settings.fragmentSelection === 'single') {
  2983. clearTextSelection(); //otherwise text selection blinks during multiple cells selection
  2984. }
  2985. },*/
  2986. onCellMouseOver: function (event, coords, TD, wt) {
  2987. that.activeWt = wt;
  2988. if (coords.row >= 0 && coords.col >= 0) { //is not a header
  2989. if (isMouseDown) {
  2990. /*if (that.settings.fragmentSelection === 'single') {
  2991. clearTextSelection(); //otherwise text selection blinks during multiple cells selection
  2992. }*/
  2993. instance.selection.setRangeEnd(coords);
  2994. }
  2995. } else {
  2996. if (isMouseDown) {
  2997. // multi select columns
  2998. if (coords.row < 0) {
  2999. instance.selection.setRangeEnd(new WalkontableCellCoords(instance.countRows() - 1, coords.col));
  3000. instance.selection.setSelectedHeaders(false, true);
  3001. }
  3002. // multi select rows
  3003. if (coords.col < 0) {
  3004. instance.selection.setRangeEnd(new WalkontableCellCoords(coords.row, instance.countCols() - 1));
  3005. instance.selection.setSelectedHeaders(true, false);
  3006. }
  3007. }
  3008. }
  3009. Handsontable.hooks.run(instance, 'afterOnCellMouseOver', event, coords, TD);
  3010. that.activeWt = that.wt;
  3011. },
  3012. onCellCornerMouseDown: function (event) {
  3013. event.preventDefault();
  3014. Handsontable.hooks.run(instance, 'afterOnCellCornerMouseDown', event);
  3015. },
  3016. beforeDraw: function (force) {
  3017. that.beforeRender(force);
  3018. },
  3019. onDraw: function (force) {
  3020. that.onDraw(force);
  3021. },
  3022. onScrollVertically: function () {
  3023. instance.runHooks('afterScrollVertically');
  3024. },
  3025. onScrollHorizontally: function () {
  3026. instance.runHooks('afterScrollHorizontally');
  3027. },
  3028. onBeforeDrawBorders: function (corners, borderClassName) {
  3029. instance.runHooks('beforeDrawBorders', corners, borderClassName);
  3030. },
  3031. onBeforeTouchScroll: function () {
  3032. instance.runHooks('beforeTouchScroll');
  3033. },
  3034. onAfterMomentumScroll: function () {
  3035. instance.runHooks('afterMomentumScroll');
  3036. },
  3037. viewportRowCalculatorOverride: function (calc) {
  3038. if (that.settings.viewportRowRenderingOffset) {
  3039. calc.startRow = Math.max(calc.startRow - that.settings.viewportRowRenderingOffset, 0);
  3040. calc.endRow = Math.min(calc.endRow + that.settings.viewportRowRenderingOffset, instance.countRows() - 1);
  3041. }
  3042. instance.runHooks('afterViewportRowCalculatorOverride', calc);
  3043. },
  3044. viewportColumnCalculatorOverride: function (calc) {
  3045. if (that.settings.viewportColumnRenderingOffset) {
  3046. calc.startColumn = Math.max(calc.startColumn - that.settings.viewportColumnRenderingOffset, 0);
  3047. calc.endColumn = Math.min(calc.endColumn + that.settings.viewportColumnRenderingOffset, instance.countCols() - 1);
  3048. }
  3049. instance.runHooks('afterViewportColumnCalculatorOverride', calc);
  3050. }
  3051. };
  3052. Handsontable.hooks.run(instance, 'beforeInitWalkontable', walkontableConfig);
  3053. this.wt = new Walkontable(walkontableConfig);
  3054. this.activeWt = this.wt;
  3055. this.eventManager.addEventListener(that.wt.wtTable.spreader, 'mousedown', function (event) {
  3056. 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
  3057. Handsontable.helper.stopPropagation(event);
  3058. //event.stopPropagation();
  3059. }
  3060. });
  3061. this.eventManager.addEventListener(that.wt.wtTable.spreader, 'contextmenu', function (event) {
  3062. 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
  3063. Handsontable.helper.stopPropagation(event);
  3064. //event.stopPropagation();
  3065. }
  3066. });
  3067. this.eventManager.addEventListener(document.documentElement, 'click', function () {
  3068. if (that.settings.observeDOMVisibility) {
  3069. if (that.wt.drawInterrupted) {
  3070. that.instance.forceFullRender = true;
  3071. that.render();
  3072. }
  3073. }
  3074. });
  3075. };
  3076. Handsontable.TableView.prototype.isTextSelectionAllowed = function (el) {
  3077. if (Handsontable.helper.isInput(el)) {
  3078. return (true);
  3079. }
  3080. if (this.settings.fragmentSelection && Handsontable.Dom.isChildOf(el, this.TBODY)) {
  3081. return (true);
  3082. }
  3083. return false;
  3084. };
  3085. Handsontable.TableView.prototype.isCellEdited = function () {
  3086. var activeEditor = this.instance.getActiveEditor();
  3087. return activeEditor && activeEditor.isOpened();
  3088. };
  3089. Handsontable.TableView.prototype.beforeRender = function (force) {
  3090. if (force) { //force = did Walkontable decide to do full render
  3091. Handsontable.hooks.run(this.instance, 'beforeRender', this.instance.forceFullRender); //this.instance.forceFullRender = did Handsontable request full render?
  3092. }
  3093. };
  3094. Handsontable.TableView.prototype.onDraw = function (force) {
  3095. if (force) { //force = did Walkontable decide to do full render
  3096. Handsontable.hooks.run(this.instance, 'afterRender', this.instance.forceFullRender); //this.instance.forceFullRender = did Handsontable request full render?
  3097. }
  3098. };
  3099. Handsontable.TableView.prototype.render = function () {
  3100. this.wt.draw(!this.instance.forceFullRender);
  3101. this.instance.forceFullRender = false;
  3102. // this.instance.rootElement.triggerHandler('render.handsontable');
  3103. };
  3104. /**
  3105. * Returns td object given coordinates
  3106. * @param {WalkontableCellCoords} coords
  3107. * @param {Boolean} topmost
  3108. */
  3109. Handsontable.TableView.prototype.getCellAtCoords = function (coords, topmost) {
  3110. var td = this.wt.getCell(coords, topmost);
  3111. //var td = this.wt.wtTable.getCell(coords);
  3112. if (td < 0) { //there was an exit code (cell is out of bounds)
  3113. return null;
  3114. }
  3115. else {
  3116. return td;
  3117. }
  3118. };
  3119. /**
  3120. * Scroll viewport to selection
  3121. * @param {WalkontableCellCoords} coords
  3122. */
  3123. Handsontable.TableView.prototype.scrollViewport = function (coords) {
  3124. this.wt.scrollViewport(coords);
  3125. };
  3126. /**
  3127. * Append row header to a TH element
  3128. * @param row
  3129. * @param TH
  3130. */
  3131. Handsontable.TableView.prototype.appendRowHeader = function (row, TH) {
  3132. var DIV = document.createElement('DIV'),
  3133. SPAN = document.createElement('SPAN');
  3134. DIV.className = 'relative';
  3135. SPAN.className = 'rowHeader';
  3136. if (row > -1) {
  3137. Handsontable.Dom.fastInnerHTML(SPAN, this.instance.getRowHeader(row));
  3138. } else {
  3139. Handsontable.Dom.fastInnerText(SPAN, String.fromCharCode(160)); // workaround for https://github.com/handsontable/handsontable/issues/1946
  3140. }
  3141. DIV.appendChild(SPAN);
  3142. Handsontable.Dom.empty(TH);
  3143. TH.appendChild(DIV);
  3144. Handsontable.hooks.run(this.instance, 'afterGetRowHeader', row, TH);
  3145. };
  3146. /**
  3147. * Append column header to a TH element
  3148. * @param col
  3149. * @param TH
  3150. */
  3151. Handsontable.TableView.prototype.appendColHeader = function (col, TH) {
  3152. var DIV = document.createElement('DIV')
  3153. , SPAN = document.createElement('SPAN');
  3154. DIV.className = 'relative';
  3155. SPAN.className = 'colHeader';
  3156. if (col > -1) {
  3157. Handsontable.Dom.fastInnerHTML(SPAN, this.instance.getColHeader(col));
  3158. } else {
  3159. Handsontable.Dom.fastInnerText(SPAN, String.fromCharCode(160)); // workaround for https://github.com/handsontable/handsontable/issues/1946
  3160. }
  3161. DIV.appendChild(SPAN);
  3162. Handsontable.Dom.empty(TH);
  3163. TH.appendChild(DIV);
  3164. Handsontable.hooks.run(this.instance, 'afterGetColHeader', col, TH);
  3165. };
  3166. /**
  3167. * Given a element's left position relative to the viewport, returns maximum element width until the right edge of the viewport (before scrollbar)
  3168. * @param {Number} leftOffset
  3169. * @return {Number}
  3170. */
  3171. Handsontable.TableView.prototype.maximumVisibleElementWidth = function (leftOffset) {
  3172. var workspaceWidth = this.wt.wtViewport.getWorkspaceWidth();
  3173. var maxWidth = workspaceWidth - leftOffset;
  3174. return maxWidth > 0 ? maxWidth : 0;
  3175. };
  3176. /**
  3177. * Given a element's top position relative to the viewport, returns maximum element height until the bottom edge of the viewport (before scrollbar)
  3178. * @param {Number} topOffset
  3179. * @return {Number}
  3180. */
  3181. Handsontable.TableView.prototype.maximumVisibleElementHeight = function (topOffset) {
  3182. var workspaceHeight = this.wt.wtViewport.getWorkspaceHeight();
  3183. var maxHeight = workspaceHeight - topOffset;
  3184. return maxHeight > 0 ? maxHeight : 0;
  3185. };
  3186. Handsontable.TableView.prototype.mainViewIsActive = function () {
  3187. return this.wt === this.activeWt;
  3188. };
  3189. Handsontable.TableView.prototype.destroy = function () {
  3190. this.wt.destroy();
  3191. this.eventManager.clear();
  3192. };
  3193. /**
  3194. * Utility to register editors and common namespace for keeping reference to all editor classes
  3195. */
  3196. (function (Handsontable) {
  3197. 'use strict';
  3198. function RegisteredEditor(editorClass) {
  3199. var clazz, instances;
  3200. instances = {};
  3201. clazz = editorClass;
  3202. this.getInstance = function (hotInstance) {
  3203. if (!(hotInstance.guid in instances)) {
  3204. instances[hotInstance.guid] = new clazz(hotInstance);
  3205. }
  3206. return instances[hotInstance.guid];
  3207. }
  3208. }
  3209. var registeredEditorNames = {};
  3210. var registeredEditorClasses = new WeakMap();
  3211. Handsontable.editors = {
  3212. /**
  3213. * Registers editor under given name
  3214. * @param {String} editorName
  3215. * @param {Function} editorClass
  3216. */
  3217. registerEditor: function (editorName, editorClass) {
  3218. var editor = new RegisteredEditor(editorClass);
  3219. if (typeof editorName === "string") {
  3220. registeredEditorNames[editorName] = editor;
  3221. }
  3222. registeredEditorClasses.set(editorClass, editor);
  3223. },
  3224. /**
  3225. * Returns instance (singleton) of editor class
  3226. * @param {String|Function} editorName/editorClass
  3227. * @returns {Function} editorClass
  3228. */
  3229. getEditor: function (editorName, hotInstance) {
  3230. var editor;
  3231. if (typeof editorName == 'function') {
  3232. if (!(registeredEditorClasses.get(editorName))) {
  3233. this.registerEditor(null, editorName);
  3234. }
  3235. editor = registeredEditorClasses.get(editorName);
  3236. }
  3237. else if (typeof editorName == 'string') {
  3238. editor = registeredEditorNames[editorName];
  3239. }
  3240. else {
  3241. throw Error('Only strings and functions can be passed as "editor" parameter ');
  3242. }
  3243. if (!editor) {
  3244. throw Error('No editor registered under name "' + editorName + '"');
  3245. }
  3246. return editor.getInstance(hotInstance);
  3247. }
  3248. };
  3249. })(Handsontable);
  3250. (function(Handsontable){
  3251. 'use strict';
  3252. Handsontable.EditorManager = function(instance, priv, selection){
  3253. var that = this;
  3254. var keyCodes = Handsontable.helper.keyCode;
  3255. var destroyed = false;
  3256. var eventManager = Handsontable.eventManager(instance);
  3257. var activeEditor;
  3258. var init = function () {
  3259. function onKeyDown(event) {
  3260. if (!instance.isListening()) {
  3261. return;
  3262. }
  3263. Handsontable.hooks.run(instance, 'beforeKeyDown', event);
  3264. if(destroyed) {
  3265. return;
  3266. }
  3267. Handsontable.Dom.enableImmediatePropagation(event);
  3268. if (!event.isImmediatePropagationStopped()) {
  3269. priv.lastKeyCode = event.keyCode;
  3270. if (selection.isSelected()) {
  3271. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
  3272. if (!activeEditor.isWaiting()) {
  3273. if (!Handsontable.helper.isMetaKey(event.keyCode) && !ctrlDown && !that.isEditorOpened()) {
  3274. that.openEditor("");
  3275. return;
  3276. }
  3277. }
  3278. var rangeModifier = event.shiftKey ? selection.setRangeEnd : selection.setRangeStart;
  3279. switch (event.keyCode) {
  3280. case keyCodes.A:
  3281. if (ctrlDown) {
  3282. selection.selectAll(); //select all cells
  3283. event.preventDefault();
  3284. Handsontable.helper.stopPropagation(event);
  3285. //event.stopPropagation();
  3286. }
  3287. break;
  3288. case keyCodes.ARROW_UP:
  3289. if (that.isEditorOpened() && !activeEditor.isWaiting()){
  3290. that.closeEditorAndSaveChanges(ctrlDown);
  3291. }
  3292. moveSelectionUp(event.shiftKey);
  3293. event.preventDefault();
  3294. Handsontable.helper.stopPropagation(event);
  3295. //event.stopPropagation(); //required by HandsontableEditor
  3296. break;
  3297. case keyCodes.ARROW_DOWN:
  3298. if (that.isEditorOpened() && !activeEditor.isWaiting()){
  3299. that.closeEditorAndSaveChanges(ctrlDown);
  3300. }
  3301. moveSelectionDown(event.shiftKey);
  3302. event.preventDefault();
  3303. Handsontable.helper.stopPropagation(event);
  3304. //event.stopPropagation(); //required by HandsontableEditor
  3305. break;
  3306. case keyCodes.ARROW_RIGHT:
  3307. if(that.isEditorOpened() && !activeEditor.isWaiting()){
  3308. that.closeEditorAndSaveChanges(ctrlDown);
  3309. }
  3310. moveSelectionRight(event.shiftKey);
  3311. event.preventDefault();
  3312. Handsontable.helper.stopPropagation(event);
  3313. //event.stopPropagation(); //required by HandsontableEditor
  3314. break;
  3315. case keyCodes.ARROW_LEFT:
  3316. if(that.isEditorOpened() && !activeEditor.isWaiting()){
  3317. that.closeEditorAndSaveChanges(ctrlDown);
  3318. }
  3319. moveSelectionLeft(event.shiftKey);
  3320. event.preventDefault();
  3321. Handsontable.helper.stopPropagation(event);
  3322. //event.stopPropagation(); //required by HandsontableEditor
  3323. break;
  3324. case keyCodes.TAB:
  3325. var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves;
  3326. if (event.shiftKey) {
  3327. selection.transformStart(-tabMoves.row, -tabMoves.col); //move selection left
  3328. }
  3329. else {
  3330. selection.transformStart(tabMoves.row, tabMoves.col, true); //move selection right (add a new column if needed)
  3331. }
  3332. event.preventDefault();
  3333. Handsontable.helper.stopPropagation(event);
  3334. //event.stopPropagation(); //required by HandsontableEditor
  3335. break;
  3336. case keyCodes.BACKSPACE:
  3337. case keyCodes.DELETE:
  3338. selection.empty(event);
  3339. that.prepareEditor();
  3340. event.preventDefault();
  3341. break;
  3342. case keyCodes.F2: /* F2 */
  3343. that.openEditor();
  3344. event.preventDefault(); //prevent Opera from opening Go to Page dialog
  3345. break;
  3346. case keyCodes.ENTER: /* return/enter */
  3347. if(that.isEditorOpened()){
  3348. if (activeEditor.state !== Handsontable.EditorState.WAITING){
  3349. that.closeEditorAndSaveChanges(ctrlDown);
  3350. }
  3351. moveSelectionAfterEnter(event.shiftKey);
  3352. } else {
  3353. if (instance.getSettings().enterBeginsEditing){
  3354. that.openEditor();
  3355. } else {
  3356. moveSelectionAfterEnter(event.shiftKey);
  3357. }
  3358. }
  3359. event.preventDefault(); //don't add newline to field
  3360. event.stopImmediatePropagation(); //required by HandsontableEditor
  3361. break;
  3362. case keyCodes.ESCAPE:
  3363. if(that.isEditorOpened()){
  3364. that.closeEditorAndRestoreOriginalValue(ctrlDown);
  3365. }
  3366. event.preventDefault();
  3367. break;
  3368. case keyCodes.HOME:
  3369. if (event.ctrlKey || event.metaKey) {
  3370. rangeModifier(new WalkontableCellCoords(0, priv.selRange.from.col));
  3371. }
  3372. else {
  3373. rangeModifier(new WalkontableCellCoords(priv.selRange.from.row, 0));
  3374. }
  3375. event.preventDefault(); //don't scroll the window
  3376. Handsontable.helper.stopPropagation(event);
  3377. //event.stopPropagation(); //required by HandsontableEditor
  3378. break;
  3379. case keyCodes.END:
  3380. if (event.ctrlKey || event.metaKey) {
  3381. rangeModifier(new WalkontableCellCoords(instance.countRows() - 1, priv.selRange.from.col));
  3382. }
  3383. else {
  3384. rangeModifier(new WalkontableCellCoords(priv.selRange.from.row, instance.countCols() - 1));
  3385. }
  3386. event.preventDefault(); //don't scroll the window
  3387. Handsontable.helper.stopPropagation(event);
  3388. //event.stopPropagation(); //required by HandsontableEditor
  3389. break;
  3390. case keyCodes.PAGE_UP:
  3391. selection.transformStart(-instance.countVisibleRows(), 0);
  3392. event.preventDefault(); //don't page up the window
  3393. Handsontable.helper.stopPropagation(event);
  3394. //event.stopPropagation(); //required by HandsontableEditor
  3395. break;
  3396. case keyCodes.PAGE_DOWN:
  3397. selection.transformStart(instance.countVisibleRows(), 0);
  3398. event.preventDefault(); //don't page down the window
  3399. Handsontable.helper.stopPropagation(event);
  3400. //event.stopPropagation(); //required by HandsontableEditor
  3401. break;
  3402. }
  3403. }
  3404. }
  3405. }
  3406. instance.addHook('afterDocumentKeyDown', function(originalEvent){
  3407. onKeyDown(originalEvent);
  3408. });
  3409. eventManager.addEventListener(document, 'keydown', function (ev){
  3410. instance.runHooks('afterDocumentKeyDown', ev);
  3411. });
  3412. function onDblClick(event, coords, elem) {
  3413. if(elem.nodeName == "TD") { //may be TD or TH
  3414. that.openEditor();
  3415. }
  3416. }
  3417. instance.view.wt.update('onCellDblClick', onDblClick);
  3418. instance.addHook('afterDestroy', function(){
  3419. destroyed = true;
  3420. });
  3421. function moveSelectionAfterEnter(shiftKey){
  3422. var enterMoves = typeof priv.settings.enterMoves === 'function' ? priv.settings.enterMoves(event) : priv.settings.enterMoves;
  3423. if (shiftKey) {
  3424. selection.transformStart(-enterMoves.row, -enterMoves.col); //move selection up
  3425. }
  3426. else {
  3427. selection.transformStart(enterMoves.row, enterMoves.col, true); //move selection down (add a new row if needed)
  3428. }
  3429. }
  3430. function moveSelectionUp(shiftKey){
  3431. if (shiftKey) {
  3432. selection.transformEnd(-1, 0);
  3433. }
  3434. else {
  3435. selection.transformStart(-1, 0);
  3436. }
  3437. }
  3438. function moveSelectionDown(shiftKey){
  3439. if (shiftKey) {
  3440. selection.transformEnd(1, 0); //expanding selection down with shift
  3441. }
  3442. else {
  3443. selection.transformStart(1, 0); //move selection down
  3444. }
  3445. }
  3446. function moveSelectionRight(shiftKey){
  3447. if (shiftKey) {
  3448. selection.transformEnd(0, 1);
  3449. }
  3450. else {
  3451. selection.transformStart(0, 1);
  3452. }
  3453. }
  3454. function moveSelectionLeft(shiftKey){
  3455. if (shiftKey) {
  3456. selection.transformEnd(0, -1);
  3457. }
  3458. else {
  3459. selection.transformStart(0, -1);
  3460. }
  3461. }
  3462. };
  3463. /**
  3464. * Destroy current editor, if exists
  3465. * @param {Boolean} revertOriginal
  3466. */
  3467. this.destroyEditor = function (revertOriginal) {
  3468. this.closeEditor(revertOriginal);
  3469. };
  3470. this.getActiveEditor = function () {
  3471. return activeEditor;
  3472. };
  3473. /**
  3474. * Prepare text input to be displayed at given grid cell
  3475. */
  3476. this.prepareEditor = function () {
  3477. if (activeEditor && activeEditor.isWaiting()){
  3478. this.closeEditor(false, false, function(dataSaved){
  3479. if(dataSaved){
  3480. that.prepareEditor();
  3481. }
  3482. });
  3483. return;
  3484. }
  3485. var row = priv.selRange.highlight.row;
  3486. var col = priv.selRange.highlight.col;
  3487. var prop = instance.colToProp(col);
  3488. var td = instance.getCell(row, col);
  3489. var originalValue = instance.getDataAtCell(row, col);
  3490. var cellProperties = instance.getCellMeta(row, col);
  3491. var editorClass = instance.getCellEditor(cellProperties);
  3492. activeEditor = Handsontable.editors.getEditor(editorClass, instance);
  3493. activeEditor.prepare(row, col, prop, td, originalValue, cellProperties);
  3494. };
  3495. this.isEditorOpened = function () {
  3496. return activeEditor.isOpened();
  3497. };
  3498. this.openEditor = function (initialValue) {
  3499. if (!activeEditor.cellProperties.readOnly){
  3500. activeEditor.beginEditing(initialValue);
  3501. }
  3502. };
  3503. this.closeEditor = function (restoreOriginalValue, ctrlDown, callback) {
  3504. if (!activeEditor){
  3505. if(callback) {
  3506. callback(false);
  3507. }
  3508. }
  3509. else {
  3510. activeEditor.finishEditing(restoreOriginalValue, ctrlDown, callback);
  3511. }
  3512. };
  3513. this.closeEditorAndSaveChanges = function(ctrlDown){
  3514. return this.closeEditor(false, ctrlDown);
  3515. };
  3516. this.closeEditorAndRestoreOriginalValue = function(ctrlDown){
  3517. return this.closeEditor(true, ctrlDown);
  3518. };
  3519. init();
  3520. };
  3521. })(Handsontable);
  3522. /**
  3523. * Utility to register renderers and common namespace for keeping reference to all renderers classes
  3524. */
  3525. (function (Handsontable) {
  3526. 'use strict';
  3527. var registeredRenderers = {};
  3528. Handsontable.renderers = {
  3529. /**
  3530. * Registers renderer under given name
  3531. * @param {String} rendererName
  3532. * @param {Function} rendererFunction
  3533. */
  3534. registerRenderer: function (rendererName, rendererFunction) {
  3535. registeredRenderers[rendererName] = rendererFunction
  3536. },
  3537. /**
  3538. * @param {String|Function} rendererName/rendererFunction
  3539. * @returns {Function} rendererFunction
  3540. */
  3541. getRenderer: function (rendererName) {
  3542. if (typeof rendererName == 'function'){
  3543. return rendererName;
  3544. }
  3545. if (typeof rendererName != 'string'){
  3546. throw Error('Only strings and functions can be passed as "renderer" parameter ');
  3547. }
  3548. if (!(rendererName in registeredRenderers)) {
  3549. throw Error('No editor registered under name "' + rendererName + '"');
  3550. }
  3551. return registeredRenderers[rendererName];
  3552. }
  3553. };
  3554. })(Handsontable);
  3555. Handsontable.helper = {};
  3556. /**
  3557. * Returns true if keyCode represents a printable character
  3558. * @param {Number} keyCode
  3559. * @return {Boolean}
  3560. */
  3561. Handsontable.helper.isPrintableChar = function (keyCode) {
  3562. return ((keyCode == 32) || //space
  3563. (keyCode >= 48 && keyCode <= 57) || //0-9
  3564. (keyCode >= 96 && keyCode <= 111) || //numpad
  3565. (keyCode >= 186 && keyCode <= 192) || //;=,-./`
  3566. (keyCode >= 219 && keyCode <= 222) || //[]{}\|"'
  3567. keyCode >= 226 || //special chars (229 for Asian chars)
  3568. (keyCode >= 65 && keyCode <= 90)); //a-z
  3569. };
  3570. Handsontable.helper.isMetaKey = function (keyCode) {
  3571. var keyCodes = Handsontable.helper.keyCode;
  3572. var metaKeys = [
  3573. keyCodes.ARROW_DOWN,
  3574. keyCodes.ARROW_UP,
  3575. keyCodes.ARROW_LEFT,
  3576. keyCodes.ARROW_RIGHT,
  3577. keyCodes.HOME,
  3578. keyCodes.END,
  3579. keyCodes.DELETE,
  3580. keyCodes.BACKSPACE,
  3581. keyCodes.F1,
  3582. keyCodes.F2,
  3583. keyCodes.F3,
  3584. keyCodes.F4,
  3585. keyCodes.F5,
  3586. keyCodes.F6,
  3587. keyCodes.F7,
  3588. keyCodes.F8,
  3589. keyCodes.F9,
  3590. keyCodes.F10,
  3591. keyCodes.F11,
  3592. keyCodes.F12,
  3593. keyCodes.TAB,
  3594. keyCodes.PAGE_DOWN,
  3595. keyCodes.PAGE_UP,
  3596. keyCodes.ENTER,
  3597. keyCodes.ESCAPE,
  3598. keyCodes.SHIFT,
  3599. keyCodes.CAPS_LOCK,
  3600. keyCodes.ALT
  3601. ];
  3602. return metaKeys.indexOf(keyCode) != -1;
  3603. };
  3604. Handsontable.helper.isCtrlKey = function (keyCode) {
  3605. var keys = Handsontable.helper.keyCode;
  3606. return [keys.CONTROL_LEFT, 224, keys.COMMAND_LEFT, keys.COMMAND_RIGHT].indexOf(keyCode) != -1;
  3607. };
  3608. /**
  3609. * Converts a value to string
  3610. * @param value
  3611. * @return {String}
  3612. */
  3613. Handsontable.helper.stringify = function (value) {
  3614. switch (typeof value) {
  3615. case 'string':
  3616. case 'number':
  3617. return value + '';
  3618. case 'object':
  3619. if (value === null) {
  3620. return '';
  3621. }
  3622. else {
  3623. return value.toString();
  3624. }
  3625. case 'undefined':
  3626. return '';
  3627. default:
  3628. return value.toString();
  3629. }
  3630. };
  3631. /**
  3632. * Generates spreadsheet-like column names: A, B, C, ..., Z, AA, AB, etc
  3633. * @param index
  3634. * @returns {String}
  3635. */
  3636. Handsontable.helper.spreadsheetColumnLabel = function (index) {
  3637. var dividend = index + 1;
  3638. var columnLabel = '';
  3639. var modulo;
  3640. while (dividend > 0) {
  3641. modulo = (dividend - 1) % 26;
  3642. columnLabel = String.fromCharCode(65 + modulo) + columnLabel;
  3643. dividend = parseInt((dividend - modulo) / 26, 10);
  3644. }
  3645. return columnLabel;
  3646. };
  3647. /**
  3648. * Creates 2D array of Excel-like values "A1", "A2", ...
  3649. * @param rowCount
  3650. * @param colCount
  3651. * @returns {Array}
  3652. */
  3653. Handsontable.helper.createSpreadsheetData = function(rowCount, colCount) {
  3654. rowCount = typeof rowCount === 'number' ? rowCount : 100;
  3655. colCount = typeof colCount === 'number' ? colCount : 4;
  3656. var rows = []
  3657. , i
  3658. , j;
  3659. for (i = 0; i < rowCount; i++) {
  3660. var row = [];
  3661. for (j = 0; j < colCount; j++) {
  3662. row.push(Handsontable.helper.spreadsheetColumnLabel(j) + (i + 1));
  3663. }
  3664. rows.push(row);
  3665. }
  3666. return rows;
  3667. }
  3668. Handsontable.helper.createSpreadsheetObjectData = function(rowCount, colCount) {
  3669. rowCount = typeof rowCount === 'number' ? rowCount : 100;
  3670. colCount = typeof colCount === 'number' ? colCount : 4;
  3671. var rows = []
  3672. , i
  3673. , j;
  3674. for (i = 0; i < rowCount; i++) {
  3675. var row = {};
  3676. for (j = 0; j < colCount; j++) {
  3677. row['prop' + j] = Handsontable.helper.spreadsheetColumnLabel(j) + (i + 1)
  3678. }
  3679. rows.push(row);
  3680. }
  3681. return rows;
  3682. }
  3683. /**
  3684. * Checks if value of n is a numeric one
  3685. * http://jsperf.com/isnan-vs-isnumeric/4
  3686. * @param n
  3687. * @returns {boolean}
  3688. */
  3689. Handsontable.helper.isNumeric = function (n) {
  3690. var t = typeof n;
  3691. return t == 'number' ? !isNaN(n) && isFinite(n) :
  3692. t == 'string' ? !n.length ? false :
  3693. n.length == 1 ? /\d/.test(n) :
  3694. /^\s*[+-]?\s*(?:(?:\d+(?:\.\d+)?(?:e[+-]?\d+)?)|(?:0x[a-f\d]+))\s*$/i.test(n) :
  3695. t == 'object' ? !!n && typeof n.valueOf() == "number" && !(n instanceof Date) : false;
  3696. };
  3697. /**
  3698. * Generates a random hex string. Used as namespace for Handsontable instance events.
  3699. * @return {String} - 16 character random string: "92b1bfc74ec4"
  3700. */
  3701. Handsontable.helper.randomString = function () {
  3702. return walkontableRandomString();
  3703. };
  3704. /**
  3705. * Inherit without without calling parent constructor, and setting `Child.prototype.constructor` to `Child` instead of `Parent`.
  3706. * Creates temporary dummy function to call it as constructor.
  3707. * Described in ticket: https://github.com/handsontable/handsontable/pull/516
  3708. * @param {Object} Child child class
  3709. * @param {Object} Parent parent class
  3710. * @return {Object} extended Child
  3711. */
  3712. Handsontable.helper.inherit = function (Child, Parent) {
  3713. Parent.prototype.constructor = Parent;
  3714. Child.prototype = new Parent();
  3715. Child.prototype.constructor = Child;
  3716. return Child;
  3717. };
  3718. /**
  3719. * Perform shallow extend of a target object with extension's own properties
  3720. * @param {Object} target An object that will receive the new properties
  3721. * @param {Object} extension An object containing additional properties to merge into the target
  3722. */
  3723. Handsontable.helper.extend = function (target, extension) {
  3724. for (var i in extension) {
  3725. if (extension.hasOwnProperty(i)) {
  3726. target[i] = extension[i];
  3727. }
  3728. }
  3729. };
  3730. /**
  3731. * Perform deep extend of a target object with extension's own properties
  3732. * @param {Object} target An object that will receive the new properties
  3733. * @param {Object} extension An object containing additional properties to merge into the target
  3734. */
  3735. Handsontable.helper.deepExtend = function (target, extension) {
  3736. for (var key in extension) {
  3737. if (extension.hasOwnProperty(key)) {
  3738. if (extension[key] && typeof extension[key] === 'object') {
  3739. if (!target[key]) {
  3740. if (Array.isArray(extension[key])) {
  3741. target[key] = [];
  3742. }
  3743. else {
  3744. target[key] = {};
  3745. }
  3746. }
  3747. Handsontable.helper.deepExtend(target[key], extension[key]);
  3748. }
  3749. else {
  3750. target[key] = extension[key];
  3751. }
  3752. }
  3753. }
  3754. };
  3755. /**
  3756. * Perform deep clone of an object
  3757. * WARNING! Only clones JSON properties. Will cause error when `obj` contains a function, Date, etc
  3758. * @param {Object} obj An object that will be cloned
  3759. * @return {Object}
  3760. */
  3761. Handsontable.helper.deepClone = function (obj) {
  3762. if (typeof obj === "object") {
  3763. return JSON.parse(JSON.stringify(obj));
  3764. }
  3765. else {
  3766. return obj;
  3767. }
  3768. };
  3769. Handsontable.helper.getPrototypeOf = function (obj) {
  3770. var prototype;
  3771. if(typeof obj.__proto__ == "object"){
  3772. prototype = obj.__proto__;
  3773. } else {
  3774. var oldConstructor,
  3775. constructor = obj.constructor;
  3776. if (typeof obj.constructor == "function") {
  3777. oldConstructor = constructor;
  3778. if (delete obj.constructor){
  3779. constructor = obj.constructor; // get real constructor
  3780. obj.constructor = oldConstructor; // restore constructor
  3781. }
  3782. }
  3783. prototype = constructor ? constructor.prototype : null; // needed for IE
  3784. }
  3785. return prototype;
  3786. };
  3787. /**
  3788. * Factory for columns constructors.
  3789. * @param {Object} GridSettings
  3790. * @param {Array} conflictList
  3791. * @return {Object} ColumnSettings
  3792. */
  3793. Handsontable.helper.columnFactory = function (GridSettings, conflictList) {
  3794. function ColumnSettings () {}
  3795. Handsontable.helper.inherit(ColumnSettings, GridSettings);
  3796. // Clear conflict settings
  3797. for (var i = 0, len = conflictList.length; i < len; i++) {
  3798. ColumnSettings.prototype[conflictList[i]] = void 0;
  3799. }
  3800. return ColumnSettings;
  3801. };
  3802. Handsontable.helper.translateRowsToColumns = function (input) {
  3803. var i
  3804. , ilen
  3805. , j
  3806. , jlen
  3807. , output = []
  3808. , olen = 0;
  3809. for (i = 0, ilen = input.length; i < ilen; i++) {
  3810. for (j = 0, jlen = input[i].length; j < jlen; j++) {
  3811. if (j == olen) {
  3812. output.push([]);
  3813. olen++;
  3814. }
  3815. output[j].push(input[i][j])
  3816. }
  3817. }
  3818. return output;
  3819. };
  3820. Handsontable.helper.to2dArray = function (arr) {
  3821. var i = 0
  3822. , ilen = arr.length;
  3823. while (i < ilen) {
  3824. arr[i] = [arr[i]];
  3825. i++;
  3826. }
  3827. };
  3828. Handsontable.helper.extendArray = function (arr, extension) {
  3829. var i = 0
  3830. , ilen = extension.length;
  3831. while (i < ilen) {
  3832. arr.push(extension[i]);
  3833. i++;
  3834. }
  3835. };
  3836. /**
  3837. * Determines if the given DOM element is an input field.
  3838. * Notice: By 'input' we mean input, textarea and select nodes
  3839. * @param element - DOM element
  3840. * @returns {boolean}
  3841. */
  3842. Handsontable.helper.isInput = function (element) {
  3843. var inputs = ['INPUT', 'SELECT', 'TEXTAREA'];
  3844. return inputs.indexOf(element.nodeName) > -1;
  3845. }
  3846. /**
  3847. * Determines if the given DOM element is an input field placed OUTSIDE of HOT.
  3848. * Notice: By 'input' we mean input, textarea and select nodes
  3849. * @param element - DOM element
  3850. * @returns {boolean}
  3851. */
  3852. Handsontable.helper.isOutsideInput = function (element) {
  3853. return Handsontable.helper.isInput(element) && element.className.indexOf('handsontableInput') == -1;
  3854. };
  3855. Handsontable.helper.keyCode = {
  3856. MOUSE_LEFT: 1,
  3857. MOUSE_RIGHT: 3,
  3858. MOUSE_MIDDLE: 2,
  3859. BACKSPACE: 8,
  3860. COMMA: 188,
  3861. INSERT: 45,
  3862. DELETE: 46,
  3863. END: 35,
  3864. ENTER: 13,
  3865. ESCAPE: 27,
  3866. CONTROL_LEFT: 91,
  3867. COMMAND_LEFT: 17,
  3868. COMMAND_RIGHT: 93,
  3869. ALT: 18,
  3870. HOME: 36,
  3871. PAGE_DOWN: 34,
  3872. PAGE_UP: 33,
  3873. PERIOD: 190,
  3874. SPACE: 32,
  3875. SHIFT: 16,
  3876. CAPS_LOCK: 20,
  3877. TAB: 9,
  3878. ARROW_RIGHT: 39,
  3879. ARROW_LEFT: 37,
  3880. ARROW_UP: 38,
  3881. ARROW_DOWN: 40,
  3882. F1: 112,
  3883. F2: 113,
  3884. F3: 114,
  3885. F4: 115,
  3886. F5: 116,
  3887. F6: 117,
  3888. F7: 118,
  3889. F8: 119,
  3890. F9: 120,
  3891. F10: 121,
  3892. F11: 122,
  3893. F12: 123,
  3894. A: 65,
  3895. X: 88,
  3896. C: 67,
  3897. V: 86
  3898. };
  3899. /**
  3900. * Determines whether given object is a plain Object.
  3901. * Note: String and Array are not plain Objects
  3902. * @param {*} obj
  3903. * @returns {boolean}
  3904. */
  3905. Handsontable.helper.isObject = function (obj) {
  3906. return Object.prototype.toString.call(obj) == '[object Object]';
  3907. };
  3908. Handsontable.helper.pivot = function (arr) {
  3909. var pivotedArr = [];
  3910. if(!arr || arr.length == 0 || !arr[0] || arr[0].length == 0){
  3911. return pivotedArr;
  3912. }
  3913. var rowCount = arr.length;
  3914. var colCount = arr[0].length;
  3915. for(var i = 0; i < rowCount; i++){
  3916. for(var j = 0; j < colCount; j++){
  3917. if(!pivotedArr[j]){
  3918. pivotedArr[j] = [];
  3919. }
  3920. pivotedArr[j][i] = arr[i][j];
  3921. }
  3922. }
  3923. return pivotedArr;
  3924. };
  3925. Handsontable.helper.proxy = function (fun, context) {
  3926. return function () {
  3927. return fun.apply(context, arguments);
  3928. };
  3929. };
  3930. /**
  3931. * Factory that produces a function for searching methods (or any properties) which could be defined directly in
  3932. * table configuration or implicitly, within cell type definition.
  3933. *
  3934. * For example: renderer can be defined explicitly using "renderer" property in column configuration or it can be
  3935. * defined implicitly using "type" property.
  3936. *
  3937. * Methods/properties defined explicitly always takes precedence over those defined through "type".
  3938. *
  3939. * If the method/property is not found in an object, searching is continued recursively through prototype chain, until
  3940. * it reaches the Object.prototype.
  3941. *
  3942. *
  3943. * @param methodName {String} name of the method/property to search (i.e. 'renderer', 'validator', 'copyable')
  3944. * @param allowUndefined {Boolean} [optional] if false, the search is continued if methodName has not been found in cell "type"
  3945. * @returns {Function}
  3946. */
  3947. Handsontable.helper.cellMethodLookupFactory = function (methodName, allowUndefined) {
  3948. allowUndefined = typeof allowUndefined == 'undefined' ? true : allowUndefined;
  3949. return function cellMethodLookup (row, col) {
  3950. return (function getMethodFromProperties(properties) {
  3951. if (!properties){
  3952. return; //method not found
  3953. }
  3954. else if (properties.hasOwnProperty(methodName) && properties[methodName] !== void 0) { //check if it is own and is not empty
  3955. return properties[methodName]; //method defined directly
  3956. } else if (properties.hasOwnProperty('type') && properties.type) { //check if it is own and is not empty
  3957. var type;
  3958. if(typeof properties.type != 'string' ){
  3959. throw new Error('Cell type must be a string ');
  3960. }
  3961. type = translateTypeNameToObject(properties.type);
  3962. if (type.hasOwnProperty(methodName)) {
  3963. return type[methodName]; //method defined in type.
  3964. } else if (allowUndefined) {
  3965. return; //method does not defined in type (eg. validator), returns undefined
  3966. }
  3967. }
  3968. return getMethodFromProperties(Handsontable.helper.getPrototypeOf(properties));
  3969. })(typeof row == 'number' ? this.getCellMeta(row, col) : row);
  3970. };
  3971. function translateTypeNameToObject(typeName) {
  3972. var type = Handsontable.cellTypes[typeName];
  3973. if(typeof type == 'undefined'){
  3974. 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');
  3975. }
  3976. return type;
  3977. }
  3978. };
  3979. Handsontable.helper.isMobileBrowser = function (userAgent) {
  3980. if(!userAgent) {
  3981. userAgent = navigator.userAgent;
  3982. }
  3983. return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent));
  3984. // Logic for checking the specific mobile browser
  3985. //
  3986. /* var type = type != void 0 ? type.toLowerCase() : ''
  3987. , result;
  3988. switch(type) {
  3989. case '':
  3990. result = (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
  3991. return result;
  3992. break;
  3993. case 'ipad':
  3994. return navigator.userAgent.indexOf('iPad') > -1;
  3995. break;
  3996. case 'android':
  3997. return navigator.userAgent.indexOf('Android') > -1;
  3998. break;
  3999. case 'windows':
  4000. return navigator.userAgent.indexOf('IEMobile') > -1;
  4001. break;
  4002. default:
  4003. throw new Error('Invalid isMobileBrowser argument');
  4004. break;
  4005. } */
  4006. };
  4007. Handsontable.helper.isTouchSupported = function () {
  4008. return ('ontouchstart' in window);
  4009. };
  4010. Handsontable.helper.stopPropagation = function (event) {
  4011. // ie8
  4012. //http://msdn.microsoft.com/en-us/library/ie/ff975462(v=vs.85).aspx
  4013. if (typeof (event.stopPropagation) === 'function') {
  4014. event.stopPropagation();
  4015. }
  4016. else {
  4017. event.cancelBubble = true;
  4018. }
  4019. };
  4020. Handsontable.helper.pageX = function (event) {
  4021. if (event.pageX) {
  4022. return event.pageX;
  4023. }
  4024. var scrollLeft = Handsontable.Dom.getWindowScrollLeft();
  4025. var cursorX = event.clientX + scrollLeft;
  4026. return cursorX;
  4027. };
  4028. Handsontable.helper.pageY = function (event) {
  4029. if (event.pageY) {
  4030. return event.pageY;
  4031. }
  4032. var scrollTop = Handsontable.Dom.getWindowScrollTop();
  4033. var cursorY = event.clientY + scrollTop;
  4034. return cursorY;
  4035. };
  4036. (function (Handsontable) {
  4037. 'use strict';
  4038. /**
  4039. * Utility class that gets and saves data from/to the data source using mapping of columns numbers to object property names
  4040. * TODO refactor arguments of methods getRange, getText to be numbers (not objects)
  4041. * TODO remove priv, GridSettings from object constructor
  4042. *
  4043. * @param instance
  4044. * @param priv
  4045. * @param GridSettings
  4046. * @constructor
  4047. */
  4048. Handsontable.DataMap = function (instance, priv, GridSettings) {
  4049. this.instance = instance;
  4050. this.priv = priv;
  4051. this.GridSettings = GridSettings;
  4052. this.dataSource = this.instance.getSettings().data;
  4053. if (this.dataSource[0]) {
  4054. this.duckSchema = this.recursiveDuckSchema(this.dataSource[0]);
  4055. }
  4056. else {
  4057. this.duckSchema = {};
  4058. }
  4059. this.createMap();
  4060. };
  4061. Handsontable.DataMap.prototype.DESTINATION_RENDERER = 1;
  4062. Handsontable.DataMap.prototype.DESTINATION_CLIPBOARD_GENERATOR = 2;
  4063. Handsontable.DataMap.prototype.recursiveDuckSchema = function (obj) {
  4064. var schema;
  4065. if (!Array.isArray(obj)){
  4066. schema = {};
  4067. for (var i in obj) {
  4068. if (obj.hasOwnProperty(i)) {
  4069. if (typeof obj[i] === "object" && !Array.isArray(obj[i])) {
  4070. schema[i] = this.recursiveDuckSchema(obj[i]);
  4071. }
  4072. else {
  4073. schema[i] = null;
  4074. }
  4075. }
  4076. }
  4077. }
  4078. else {
  4079. schema = [];
  4080. }
  4081. return schema;
  4082. };
  4083. Handsontable.DataMap.prototype.recursiveDuckColumns = function (schema, lastCol, parent) {
  4084. var prop, i;
  4085. if (typeof lastCol === 'undefined') {
  4086. lastCol = 0;
  4087. parent = '';
  4088. }
  4089. if (typeof schema === "object" && !Array.isArray(schema)) {
  4090. for (i in schema) {
  4091. if (schema.hasOwnProperty(i)) {
  4092. if (schema[i] === null) {
  4093. prop = parent + i;
  4094. this.colToPropCache.push(prop);
  4095. this.propToColCache.set(prop, lastCol);
  4096. lastCol++;
  4097. }
  4098. else {
  4099. lastCol = this.recursiveDuckColumns(schema[i], lastCol, i + '.');
  4100. }
  4101. }
  4102. }
  4103. }
  4104. return lastCol;
  4105. };
  4106. Handsontable.DataMap.prototype.createMap = function () {
  4107. var i, ilen, schema = this.getSchema();
  4108. if (typeof schema === "undefined") {
  4109. throw new Error("trying to create `columns` definition but you didnt' provide `schema` nor `data`");
  4110. }
  4111. this.colToPropCache = [];
  4112. this.propToColCache = new MultiMap();
  4113. var columns = this.instance.getSettings().columns;
  4114. if (columns) {
  4115. for (i = 0, ilen = columns.length; i < ilen; i++) {
  4116. if (typeof columns[i].data != 'undefined'){
  4117. this.colToPropCache[i] = columns[i].data;
  4118. this.propToColCache.set(columns[i].data, i);
  4119. }
  4120. }
  4121. }
  4122. else {
  4123. this.recursiveDuckColumns(schema);
  4124. }
  4125. };
  4126. Handsontable.DataMap.prototype.colToProp = function (col) {
  4127. col = Handsontable.hooks.execute(this.instance, 'modifyCol', col);
  4128. if (this.colToPropCache && typeof this.colToPropCache[col] !== 'undefined') {
  4129. return this.colToPropCache[col];
  4130. }
  4131. else {
  4132. return col;
  4133. }
  4134. };
  4135. Handsontable.DataMap.prototype.propToCol = function (prop) {
  4136. var col;
  4137. if (typeof this.propToColCache.get(prop) !== 'undefined') {
  4138. col = this.propToColCache.get(prop);
  4139. } else {
  4140. col = prop;
  4141. }
  4142. col = Handsontable.hooks.execute(this.instance, 'modifyCol', col);
  4143. return col;
  4144. };
  4145. Handsontable.DataMap.prototype.getSchema = function () {
  4146. var schema = this.instance.getSettings().dataSchema;
  4147. if (schema) {
  4148. if (typeof schema === 'function') {
  4149. return schema();
  4150. }
  4151. return schema;
  4152. }
  4153. return this.duckSchema;
  4154. };
  4155. /**
  4156. * Creates row at the bottom of the data array
  4157. * @param {Number} [index] Optional. Index of the row before which the new row will be inserted
  4158. */
  4159. Handsontable.DataMap.prototype.createRow = function (index, amount, createdAutomatically) {
  4160. var row
  4161. , colCount = this.instance.countCols()
  4162. , numberOfCreatedRows = 0
  4163. , currentIndex;
  4164. if (!amount) {
  4165. amount = 1;
  4166. }
  4167. if (typeof index !== 'number' || index >= this.instance.countRows()) {
  4168. index = this.instance.countRows();
  4169. }
  4170. currentIndex = index;
  4171. var maxRows = this.instance.getSettings().maxRows;
  4172. while (numberOfCreatedRows < amount && this.instance.countRows() < maxRows) {
  4173. if (this.instance.dataType === 'array') {
  4174. row = [];
  4175. for (var c = 0; c < colCount; c++) {
  4176. row.push(null);
  4177. }
  4178. }
  4179. else if (this.instance.dataType === 'function') {
  4180. row = this.instance.getSettings().dataSchema(index);
  4181. }
  4182. else {
  4183. row = {};
  4184. Handsontable.helper.deepExtend(row, this.getSchema());
  4185. }
  4186. if (index === this.instance.countRows()) {
  4187. this.dataSource.push(row);
  4188. }
  4189. else {
  4190. this.dataSource.splice(index, 0, row);
  4191. }
  4192. numberOfCreatedRows++;
  4193. currentIndex++;
  4194. }
  4195. Handsontable.hooks.run(this.instance, 'afterCreateRow', index, numberOfCreatedRows, createdAutomatically);
  4196. this.instance.forceFullRender = true; //used when data was changed
  4197. return numberOfCreatedRows;
  4198. };
  4199. /**
  4200. * Creates col at the right of the data array
  4201. * @param {Number} [index] Optional. Index of the column before which the new column will be inserted
  4202. * * @param {Number} [amount] Optional.
  4203. */
  4204. Handsontable.DataMap.prototype.createCol = function (index, amount, createdAutomatically) {
  4205. if (this.instance.dataType === 'object' || this.instance.getSettings().columns) {
  4206. throw new Error("Cannot create new column. When data source in an object, " +
  4207. "you can only have as much columns as defined in first data row, data schema or in the 'columns' setting." +
  4208. "If you want to be able to add new columns, you have to use array datasource.");
  4209. }
  4210. var rlen = this.instance.countRows()
  4211. , data = this.dataSource
  4212. , constructor
  4213. , numberOfCreatedCols = 0
  4214. , currentIndex;
  4215. if (!amount) {
  4216. amount = 1;
  4217. }
  4218. currentIndex = index;
  4219. var maxCols = this.instance.getSettings().maxCols;
  4220. while (numberOfCreatedCols < amount && this.instance.countCols() < maxCols) {
  4221. constructor = Handsontable.helper.columnFactory(this.GridSettings, this.priv.columnsSettingConflicts);
  4222. if (typeof index !== 'number' || index >= this.instance.countCols()) {
  4223. for (var r = 0; r < rlen; r++) {
  4224. if (typeof data[r] === 'undefined') {
  4225. data[r] = [];
  4226. }
  4227. data[r].push(null);
  4228. }
  4229. // Add new column constructor
  4230. this.priv.columnSettings.push(constructor);
  4231. }
  4232. else {
  4233. for (var r = 0; r < rlen; r++) {
  4234. data[r].splice(currentIndex, 0, null);
  4235. }
  4236. // Add new column constructor at given index
  4237. this.priv.columnSettings.splice(currentIndex, 0, constructor);
  4238. }
  4239. numberOfCreatedCols++;
  4240. currentIndex++;
  4241. }
  4242. Handsontable.hooks.run(this.instance, 'afterCreateCol', index, numberOfCreatedCols, createdAutomatically);
  4243. this.instance.forceFullRender = true; //used when data was changed
  4244. return numberOfCreatedCols;
  4245. };
  4246. /**
  4247. * Removes row from the data array
  4248. * @param {Number} [index] Optional. Index of the row to be removed. If not provided, the last row will be removed
  4249. * @param {Number} [amount] Optional. Amount of the rows to be removed. If not provided, one row will be removed
  4250. */
  4251. Handsontable.DataMap.prototype.removeRow = function (index, amount) {
  4252. if (!amount) {
  4253. amount = 1;
  4254. }
  4255. if (typeof index !== 'number') {
  4256. index = -amount;
  4257. }
  4258. index = (this.instance.countRows() + index) % this.instance.countRows();
  4259. // We have to map the physical row ids to logical and than perform removing with (possibly) new row id
  4260. var logicRows = this.physicalRowsToLogical(index, amount);
  4261. var actionWasNotCancelled = Handsontable.hooks.execute(this.instance, 'beforeRemoveRow', index, amount);
  4262. if (actionWasNotCancelled === false) {
  4263. return;
  4264. }
  4265. var data = this.dataSource;
  4266. var newData = data.filter(function (row, index) {
  4267. return logicRows.indexOf(index) == -1;
  4268. });
  4269. data.length = 0;
  4270. Array.prototype.push.apply(data, newData);
  4271. Handsontable.hooks.run(this.instance, 'afterRemoveRow', index, amount);
  4272. this.instance.forceFullRender = true; //used when data was changed
  4273. };
  4274. /**
  4275. * Removes column from the data array
  4276. * @param {Number} [index] Optional. Index of the column to be removed. If not provided, the last column will be removed
  4277. * @param {Number} [amount] Optional. Amount of the columns to be removed. If not provided, one column will be removed
  4278. */
  4279. Handsontable.DataMap.prototype.removeCol = function (index, amount) {
  4280. if (this.instance.dataType === 'object' || this.instance.getSettings().columns) {
  4281. throw new Error("cannot remove column with object data source or columns option specified");
  4282. }
  4283. if (!amount) {
  4284. amount = 1;
  4285. }
  4286. if (typeof index !== 'number') {
  4287. index = -amount;
  4288. }
  4289. index = (this.instance.countCols() + index) % this.instance.countCols();
  4290. var actionWasNotCancelled = Handsontable.hooks.execute(this.instance, 'beforeRemoveCol', index, amount);
  4291. if (actionWasNotCancelled === false) {
  4292. return;
  4293. }
  4294. var data = this.dataSource;
  4295. for (var r = 0, rlen = this.instance.countRows(); r < rlen; r++) {
  4296. data[r].splice(index, amount);
  4297. }
  4298. this.priv.columnSettings.splice(index, amount);
  4299. Handsontable.hooks.run(this.instance, 'afterRemoveCol', index, amount);
  4300. this.instance.forceFullRender = true; //used when data was changed
  4301. };
  4302. /**
  4303. * Add / removes data from the column
  4304. * @param {Number} col Index of column in which do you want to do splice.
  4305. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  4306. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  4307. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  4308. */
  4309. Handsontable.DataMap.prototype.spliceCol = function (col, index, amount/*, elements...*/) {
  4310. var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
  4311. var colData = this.instance.getDataAtCol(col);
  4312. var removed = colData.slice(index, index + amount);
  4313. var after = colData.slice(index + amount);
  4314. Handsontable.helper.extendArray(elements, after);
  4315. var i = 0;
  4316. while (i < amount) {
  4317. elements.push(null); //add null in place of removed elements
  4318. i++;
  4319. }
  4320. Handsontable.helper.to2dArray(elements);
  4321. this.instance.populateFromArray(index, col, elements, null, null, 'spliceCol');
  4322. return removed;
  4323. };
  4324. /**
  4325. * Add / removes data from the row
  4326. * @param {Number} row Index of row in which do you want to do splice.
  4327. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  4328. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  4329. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  4330. */
  4331. Handsontable.DataMap.prototype.spliceRow = function (row, index, amount/*, elements...*/) {
  4332. var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
  4333. var rowData = this.instance.getSourceDataAtRow(row);
  4334. var removed = rowData.slice(index, index + amount);
  4335. var after = rowData.slice(index + amount);
  4336. Handsontable.helper.extendArray(elements, after);
  4337. var i = 0;
  4338. while (i < amount) {
  4339. elements.push(null); //add null in place of removed elements
  4340. i++;
  4341. }
  4342. this.instance.populateFromArray(row, index, [elements], null, null, 'spliceRow');
  4343. return removed;
  4344. };
  4345. /**
  4346. * Returns single value from the data array
  4347. * @param {Number} row
  4348. * @param {Number} prop
  4349. */
  4350. Handsontable.DataMap.prototype.get = function (row, prop) {
  4351. row = Handsontable.hooks.execute(this.instance, 'modifyRow', row);
  4352. if (typeof prop === 'string' && prop.indexOf('.') > -1) {
  4353. var sliced = prop.split(".");
  4354. var out = this.dataSource[row];
  4355. if (!out) {
  4356. return null;
  4357. }
  4358. for (var i = 0, ilen = sliced.length; i < ilen; i++) {
  4359. out = out[sliced[i]];
  4360. if (typeof out === 'undefined') {
  4361. return null;
  4362. }
  4363. }
  4364. return out;
  4365. }
  4366. else if (typeof prop === 'function') {
  4367. /**
  4368. * allows for interacting with complex structures, for example
  4369. * d3/jQuery getter/setter properties:
  4370. *
  4371. * {columns: [{
  4372. * data: function(row, value){
  4373. * if(arguments.length === 1){
  4374. * return row.property();
  4375. * }
  4376. * row.property(value);
  4377. * }
  4378. * }]}
  4379. */
  4380. return prop(this.dataSource.slice(
  4381. row,
  4382. row + 1
  4383. )[0]);
  4384. }
  4385. else {
  4386. return this.dataSource[row] ? this.dataSource[row][prop] : null;
  4387. }
  4388. };
  4389. var copyableLookup = Handsontable.helper.cellMethodLookupFactory('copyable', false);
  4390. /**
  4391. * Returns single value from the data array (intended for clipboard copy to an external application)
  4392. * @param {Number} row
  4393. * @param {Number} prop
  4394. * @return {String}
  4395. */
  4396. Handsontable.DataMap.prototype.getCopyable = function (row, prop) {
  4397. if (copyableLookup.call(this.instance, row, this.propToCol(prop))) {
  4398. return this.get(row, prop);
  4399. }
  4400. return '';
  4401. };
  4402. /**
  4403. * Saves single value to the data array
  4404. * @param {Number} row
  4405. * @param {Number} prop
  4406. * @param {String} value
  4407. * @param {String} [source] Optional. Source of hook runner.
  4408. */
  4409. Handsontable.DataMap.prototype.set = function (row, prop, value, source) {
  4410. row = Handsontable.hooks.execute(this.instance, 'modifyRow', row, source || "datamapGet");
  4411. if (typeof prop === 'string' && prop.indexOf('.') > -1) {
  4412. var sliced = prop.split(".");
  4413. var out = this.dataSource[row];
  4414. for (var i = 0, ilen = sliced.length - 1; i < ilen; i++) {
  4415. if (typeof out[sliced[i]] === 'undefined'){
  4416. out[sliced[i]] = {};
  4417. }
  4418. out = out[sliced[i]];
  4419. }
  4420. out[sliced[i]] = value;
  4421. }
  4422. else if (typeof prop === 'function') {
  4423. /* see the `function` handler in `get` */
  4424. prop(this.dataSource.slice(
  4425. row,
  4426. row + 1
  4427. )[0], value);
  4428. }
  4429. else {
  4430. this.dataSource[row][prop] = value;
  4431. }
  4432. };
  4433. /**
  4434. * This ridiculous piece of code maps rows Id that are present in table data to those displayed for user.
  4435. * The trick is, the physical row id (stored in settings.data) is not necessary the same
  4436. * as the logical (displayed) row id (e.g. when sorting is applied).
  4437. */
  4438. Handsontable.DataMap.prototype.physicalRowsToLogical = function (index, amount) {
  4439. var totalRows = this.instance.countRows();
  4440. var physicRow = (totalRows + index) % totalRows;
  4441. var logicRows = [];
  4442. var rowsToRemove = amount;
  4443. var row;
  4444. while (physicRow < totalRows && rowsToRemove) {
  4445. row = Handsontable.hooks.execute(this.instance, 'modifyRow', physicRow);
  4446. logicRows.push(row);
  4447. rowsToRemove--;
  4448. physicRow++;
  4449. }
  4450. return logicRows;
  4451. };
  4452. /**
  4453. * Clears the data array
  4454. */
  4455. Handsontable.DataMap.prototype.clear = function () {
  4456. for (var r = 0; r < this.instance.countRows(); r++) {
  4457. for (var c = 0; c < this.instance.countCols(); c++) {
  4458. this.set(r, this.colToProp(c), '');
  4459. }
  4460. }
  4461. };
  4462. /**
  4463. * Returns the data array
  4464. * @return {Array}
  4465. */
  4466. Handsontable.DataMap.prototype.getAll = function () {
  4467. return this.dataSource;
  4468. };
  4469. /**
  4470. * Returns data range as array
  4471. * @param {Object} start Start selection position
  4472. * @param {Object} end End selection position
  4473. * @param {Number} destination Destination of datamap.get
  4474. * @return {Array}
  4475. */
  4476. Handsontable.DataMap.prototype.getRange = function (start, end, destination) {
  4477. var r, rlen, c, clen, output = [], row;
  4478. var getFn = destination === this.DESTINATION_CLIPBOARD_GENERATOR ? this.getCopyable : this.get;
  4479. rlen = Math.max(start.row, end.row);
  4480. clen = Math.max(start.col, end.col);
  4481. for (r = Math.min(start.row, end.row); r <= rlen; r++) {
  4482. row = [];
  4483. for (c = Math.min(start.col, end.col); c <= clen; c++) {
  4484. row.push(getFn.call(this, r, this.colToProp(c)));
  4485. }
  4486. output.push(row);
  4487. }
  4488. return output;
  4489. };
  4490. /**
  4491. * Return data as text (tab separated columns)
  4492. * @param {Object} start (Optional) Start selection position
  4493. * @param {Object} end (Optional) End selection position
  4494. * @return {String}
  4495. */
  4496. Handsontable.DataMap.prototype.getText = function (start, end) {
  4497. return SheetClip.stringify(this.getRange(start, end, this.DESTINATION_RENDERER));
  4498. };
  4499. /**
  4500. * Return data as copyable text (tab separated columns intended for clipboard copy to an external application)
  4501. * @param {Object} start (Optional) Start selection position
  4502. * @param {Object} end (Optional) End selection position
  4503. * @return {String}
  4504. */
  4505. Handsontable.DataMap.prototype.getCopyableText = function (start, end) {
  4506. return SheetClip.stringify(this.getRange(start, end, this.DESTINATION_CLIPBOARD_GENERATOR));
  4507. };
  4508. })(Handsontable);
  4509. (function (Handsontable) {
  4510. 'use strict';
  4511. /*
  4512. Adds appropriate CSS class to table cell, based on cellProperties
  4513. */
  4514. Handsontable.renderers.cellDecorator = function (instance, TD, row, col, prop, value, cellProperties) {
  4515. if (cellProperties.className) {
  4516. if(TD.className) {
  4517. TD.className = TD.className + " " + cellProperties.className;
  4518. } else {
  4519. TD.className = cellProperties.className;
  4520. }
  4521. }
  4522. if (cellProperties.readOnly) {
  4523. Handsontable.Dom.addClass(TD, cellProperties.readOnlyCellClassName);
  4524. }
  4525. if (cellProperties.valid === false && cellProperties.invalidCellClassName) {
  4526. Handsontable.Dom.addClass(TD, cellProperties.invalidCellClassName);
  4527. }
  4528. if (cellProperties.wordWrap === false && cellProperties.noWordWrapClassName) {
  4529. Handsontable.Dom.addClass(TD, cellProperties.noWordWrapClassName);
  4530. }
  4531. if (!value && cellProperties.placeholder) {
  4532. Handsontable.Dom.addClass(TD, cellProperties.placeholderCellClassName);
  4533. }
  4534. }
  4535. })(Handsontable);
  4536. /**
  4537. * Default text renderer
  4538. * @param {Object} instance Handsontable instance
  4539. * @param {Element} TD Table cell where to render
  4540. * @param {Number} row
  4541. * @param {Number} col
  4542. * @param {String|Number} prop Row object property name
  4543. * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
  4544. * @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
  4545. */
  4546. (function (Handsontable) {
  4547. 'use strict';
  4548. var TextRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  4549. Handsontable.renderers.cellDecorator.apply(this, arguments);
  4550. if (!value && cellProperties.placeholder) {
  4551. value = cellProperties.placeholder;
  4552. }
  4553. var escaped = Handsontable.helper.stringify(value);
  4554. if (cellProperties.rendererTemplate) {
  4555. Handsontable.Dom.empty(TD);
  4556. var TEMPLATE = document.createElement('TEMPLATE');
  4557. TEMPLATE.setAttribute('bind', '{{}}');
  4558. TEMPLATE.innerHTML = cellProperties.rendererTemplate;
  4559. HTMLTemplateElement.decorate(TEMPLATE);
  4560. TEMPLATE.model = instance.getSourceDataAtRow(row);
  4561. TD.appendChild(TEMPLATE);
  4562. }
  4563. else {
  4564. Handsontable.Dom.fastInnerText(TD, escaped); //this is faster than innerHTML. See: https://github.com/handsontable/handsontable/wiki/JavaScript-&-DOM-performance-tips
  4565. }
  4566. };
  4567. //Handsontable.TextRenderer = TextRenderer; //Left for backward compatibility
  4568. Handsontable.renderers.TextRenderer = TextRenderer;
  4569. Handsontable.renderers.registerRenderer('text', TextRenderer);
  4570. })(Handsontable);
  4571. (function (Handsontable) {
  4572. var clonableWRAPPER = document.createElement('DIV');
  4573. clonableWRAPPER.className = 'htAutocompleteWrapper';
  4574. var clonableARROW = document.createElement('DIV');
  4575. clonableARROW.className = 'htAutocompleteArrow';
  4576. clonableARROW.appendChild(document.createTextNode(String.fromCharCode(9660))); // workaround for https://github.com/handsontable/handsontable/issues/1946
  4577. //this is faster than innerHTML. See: https://github.com/handsontable/handsontable/wiki/JavaScript-&-DOM-performance-tips
  4578. var wrapTdContentWithWrapper = function(TD, WRAPPER){
  4579. WRAPPER.innerHTML = TD.innerHTML;
  4580. Handsontable.Dom.empty(TD);
  4581. TD.appendChild(WRAPPER);
  4582. };
  4583. /**
  4584. * Autocomplete renderer
  4585. * @param {Object} instance Handsontable instance
  4586. * @param {Element} TD Table cell where to render
  4587. * @param {Number} row
  4588. * @param {Number} col
  4589. * @param {String|Number} prop Row object property name
  4590. * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
  4591. * @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
  4592. */
  4593. var AutocompleteRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  4594. var WRAPPER = clonableWRAPPER.cloneNode(true); //this is faster than createElement
  4595. var ARROW = clonableARROW.cloneNode(true); //this is faster than createElement
  4596. Handsontable.renderers.TextRenderer(instance, TD, row, col, prop, value, cellProperties);
  4597. TD.appendChild(ARROW);
  4598. Handsontable.Dom.addClass(TD, 'htAutocomplete');
  4599. if (!TD.firstChild) { //http://jsperf.com/empty-node-if-needed
  4600. //otherwise empty fields appear borderless in demo/renderers.html (IE)
  4601. TD.appendChild(document.createTextNode(String.fromCharCode(160))); // workaround for https://github.com/handsontable/handsontable/issues/1946
  4602. //this is faster than innerHTML. See: https://github.com/handsontable/handsontable/wiki/JavaScript-&-DOM-performance-tips
  4603. }
  4604. if (!instance.acArrowListener) {
  4605. var eventManager = Handsontable.eventManager(instance);
  4606. //not very elegant but easy and fast
  4607. instance.acArrowListener = function (event) {
  4608. if (Handsontable.Dom.hasClass(event.target,'htAutocompleteArrow')) {
  4609. instance.view.wt.getSetting('onCellDblClick', null, new WalkontableCellCoords(row, col), TD);
  4610. }
  4611. };
  4612. eventManager.addEventListener(instance.rootElement,'mousedown',instance.acArrowListener);
  4613. //We need to unbind the listener after the table has been destroyed
  4614. instance.addHookOnce('afterDestroy', function () {
  4615. eventManager.clear();
  4616. });
  4617. }
  4618. };
  4619. Handsontable.AutocompleteRenderer = AutocompleteRenderer;
  4620. Handsontable.renderers.AutocompleteRenderer = AutocompleteRenderer;
  4621. Handsontable.renderers.registerRenderer('autocomplete', AutocompleteRenderer);
  4622. })(Handsontable);
  4623. /**
  4624. * Checkbox renderer
  4625. * @param {Object} instance Handsontable instance
  4626. * @param {Element} TD Table cell where to render
  4627. * @param {Number} row
  4628. * @param {Number} col
  4629. * @param {String|Number} prop Row object property name
  4630. * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
  4631. * @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
  4632. */
  4633. (function (Handsontable) {
  4634. 'use strict';
  4635. var clonableINPUT = document.createElement('INPUT');
  4636. clonableINPUT.className = 'htCheckboxRendererInput';
  4637. clonableINPUT.type = 'checkbox';
  4638. clonableINPUT.setAttribute('autocomplete', 'off');
  4639. var CheckboxRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  4640. var eventManager = Handsontable.eventManager(instance);
  4641. if (typeof cellProperties.checkedTemplate === "undefined") {
  4642. cellProperties.checkedTemplate = true;
  4643. }
  4644. if (typeof cellProperties.uncheckedTemplate === "undefined") {
  4645. cellProperties.uncheckedTemplate = false;
  4646. }
  4647. Handsontable.Dom.empty(TD); //TODO identify under what circumstances this line can be removed
  4648. var INPUT = clonableINPUT.cloneNode(false); //this is faster than createElement
  4649. if (value === cellProperties.checkedTemplate || value === Handsontable.helper.stringify(cellProperties.checkedTemplate)) {
  4650. INPUT.checked = true;
  4651. TD.appendChild(INPUT);
  4652. }
  4653. else if (value === cellProperties.uncheckedTemplate || value === Handsontable.helper.stringify(cellProperties.uncheckedTemplate)) {
  4654. TD.appendChild(INPUT);
  4655. }
  4656. else if (value === null) { //default value
  4657. INPUT.className += ' noValue';
  4658. TD.appendChild(INPUT);
  4659. }
  4660. else {
  4661. Handsontable.Dom.fastInnerText(TD, '#bad value#'); //this is faster than innerHTML. See: https://github.com/handsontable/handsontable/wiki/JavaScript-&-DOM-performance-tips
  4662. }
  4663. if (cellProperties.readOnly) {
  4664. eventManager.addEventListener(INPUT,'click',function (event) {
  4665. event.preventDefault();
  4666. });
  4667. }
  4668. else {
  4669. eventManager.addEventListener(INPUT,'mousedown',function (event) {
  4670. Handsontable.helper.stopPropagation(event);
  4671. //event.stopPropagation(); //otherwise can confuse cell mousedown handler
  4672. });
  4673. eventManager.addEventListener(INPUT,'mouseup',function (event) {
  4674. Handsontable.helper.stopPropagation(event);
  4675. //event.stopPropagation(); //otherwise can confuse cell dblclick handler
  4676. });
  4677. eventManager.addEventListener(INPUT,'change',function () {
  4678. if (this.checked) {
  4679. instance.setDataAtRowProp(row, prop, cellProperties.checkedTemplate);
  4680. }
  4681. else {
  4682. instance.setDataAtRowProp(row, prop, cellProperties.uncheckedTemplate);
  4683. }
  4684. });
  4685. }
  4686. if(!instance.CheckboxRenderer || !instance.CheckboxRenderer.beforeKeyDownHookBound){
  4687. instance.CheckboxRenderer = {
  4688. beforeKeyDownHookBound : true
  4689. };
  4690. instance.addHook('beforeKeyDown', function(event){
  4691. Handsontable.Dom.enableImmediatePropagation(event);
  4692. if(event.keyCode == Handsontable.helper.keyCode.SPACE || event.keyCode == Handsontable.helper.keyCode.ENTER){
  4693. var cell, checkbox, cellProperties;
  4694. var selRange = instance.getSelectedRange();
  4695. var topLeft = selRange.getTopLeftCorner();
  4696. var bottomRight = selRange.getBottomRightCorner();
  4697. for(var row = topLeft.row; row <= bottomRight.row; row++ ){
  4698. for(var col = topLeft.col; col <= bottomRight.col; col++){
  4699. cell = instance.getCell(row, col);
  4700. cellProperties = instance.getCellMeta(row, col);
  4701. checkbox = cell.querySelectorAll('input[type=checkbox]');
  4702. if(checkbox.length > 0 && !cellProperties.readOnly){
  4703. if(!event.isImmediatePropagationStopped()){
  4704. event.stopImmediatePropagation();
  4705. event.preventDefault();
  4706. }
  4707. for(var i = 0, len = checkbox.length; i < len; i++){
  4708. checkbox[i].checked = !checkbox[i].checked;
  4709. eventManager.fireEvent(checkbox[i], 'change');
  4710. }
  4711. }
  4712. }
  4713. }
  4714. }
  4715. });
  4716. }
  4717. };
  4718. Handsontable.CheckboxRenderer = CheckboxRenderer;
  4719. Handsontable.renderers.CheckboxRenderer = CheckboxRenderer;
  4720. Handsontable.renderers.registerRenderer('checkbox', CheckboxRenderer);
  4721. })(Handsontable);
  4722. /**
  4723. * Numeric cell renderer
  4724. * @param {Object} instance Handsontable instance
  4725. * @param {Element} TD Table cell where to render
  4726. * @param {Number} row
  4727. * @param {Number} col
  4728. * @param {String|Number} prop Row object property name
  4729. * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
  4730. * @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
  4731. */
  4732. (function (Handsontable) {
  4733. 'use strict';
  4734. var NumericRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  4735. if (Handsontable.helper.isNumeric(value)) {
  4736. if (typeof cellProperties.language !== 'undefined') {
  4737. numeral.language(cellProperties.language)
  4738. }
  4739. value = numeral(value).format(cellProperties.format || '0'); //docs: http://numeraljs.com/
  4740. Handsontable.Dom.addClass(TD, 'htNumeric');
  4741. }
  4742. Handsontable.renderers.TextRenderer(instance, TD, row, col, prop, value, cellProperties);
  4743. };
  4744. Handsontable.NumericRenderer = NumericRenderer; //Left for backward compatibility with versions prior 0.10.0
  4745. Handsontable.renderers.NumericRenderer = NumericRenderer;
  4746. Handsontable.renderers.registerRenderer('numeric', NumericRenderer);
  4747. })(Handsontable);
  4748. (function(Handosntable){
  4749. 'use strict';
  4750. var PasswordRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
  4751. Handsontable.renderers.TextRenderer.apply(this, arguments);
  4752. value = TD.innerHTML;
  4753. var hash;
  4754. var hashLength = cellProperties.hashLength || value.length;
  4755. var hashSymbol = cellProperties.hashSymbol || '*';
  4756. for( hash = ''; hash.split(hashSymbol).length - 1 < hashLength; hash += hashSymbol);
  4757. Handsontable.Dom.fastInnerHTML(TD, hash);
  4758. };
  4759. Handosntable.PasswordRenderer = PasswordRenderer;
  4760. Handosntable.renderers.PasswordRenderer = PasswordRenderer;
  4761. Handosntable.renderers.registerRenderer('password', PasswordRenderer);
  4762. })(Handsontable);
  4763. (function (Handsontable) {
  4764. function HtmlRenderer(instance, TD, row, col, prop, value, cellProperties){
  4765. Handsontable.renderers.cellDecorator.apply(this, arguments);
  4766. Handsontable.Dom.fastInnerHTML(TD, value);
  4767. }
  4768. Handsontable.renderers.registerRenderer('html', HtmlRenderer);
  4769. Handsontable.renderers.HtmlRenderer = HtmlRenderer;
  4770. })(Handsontable);
  4771. (function (Handsontable) {
  4772. 'use strict';
  4773. Handsontable.EditorState = {
  4774. VIRGIN: 'STATE_VIRGIN', //before editing
  4775. EDITING: 'STATE_EDITING',
  4776. WAITING: 'STATE_WAITING', //waiting for async validation
  4777. FINISHED: 'STATE_FINISHED'
  4778. };
  4779. function BaseEditor(instance) {
  4780. this.instance = instance;
  4781. this.state = Handsontable.EditorState.VIRGIN;
  4782. this._opened = false;
  4783. this._closeCallback = null;
  4784. this.init();
  4785. }
  4786. BaseEditor.prototype._fireCallbacks = function(result) {
  4787. if(this._closeCallback){
  4788. this._closeCallback(result);
  4789. this._closeCallback = null;
  4790. }
  4791. }
  4792. BaseEditor.prototype.init = function(){};
  4793. BaseEditor.prototype.getValue = function(){
  4794. throw Error('Editor getValue() method unimplemented');
  4795. };
  4796. BaseEditor.prototype.setValue = function(newValue){
  4797. throw Error('Editor setValue() method unimplemented');
  4798. };
  4799. BaseEditor.prototype.open = function(){
  4800. throw Error('Editor open() method unimplemented');
  4801. };
  4802. BaseEditor.prototype.close = function(){
  4803. throw Error('Editor close() method unimplemented');
  4804. };
  4805. BaseEditor.prototype.prepare = function(row, col, prop, td, originalValue, cellProperties){
  4806. this.TD = td;
  4807. this.row = row;
  4808. this.col = col;
  4809. this.prop = prop;
  4810. this.originalValue = originalValue;
  4811. this.cellProperties = cellProperties;
  4812. this.state = Handsontable.EditorState.VIRGIN;
  4813. };
  4814. BaseEditor.prototype.extend = function(){
  4815. var baseClass = this.constructor;
  4816. function Editor(){
  4817. baseClass.apply(this, arguments);
  4818. }
  4819. function inherit(Child, Parent){
  4820. function Bridge() {
  4821. }
  4822. Bridge.prototype = Parent.prototype;
  4823. Child.prototype = new Bridge();
  4824. Child.prototype.constructor = Child;
  4825. return Child;
  4826. }
  4827. return inherit(Editor, baseClass);
  4828. };
  4829. BaseEditor.prototype.saveValue = function (val, ctrlDown) {
  4830. if (ctrlDown) { //if ctrl+enter and multiple cells selected, behave like Excel (finish editing and apply to all cells)
  4831. var sel = this.instance.getSelected()
  4832. , tmp;
  4833. if(sel[0] > sel[2]) {
  4834. tmp = sel[0];
  4835. sel[0] = sel[2];
  4836. sel[2] = tmp;
  4837. }
  4838. if(sel[1] > sel[3]) {
  4839. tmp = sel[1];
  4840. sel[1] = sel[3];
  4841. sel[3] = tmp;
  4842. }
  4843. this.instance.populateFromArray(sel[0], sel[1], val, sel[2], sel[3], 'edit');
  4844. }
  4845. else {
  4846. this.instance.populateFromArray(this.row, this.col, val, null, null, 'edit');
  4847. }
  4848. };
  4849. BaseEditor.prototype.beginEditing = function(initialValue){
  4850. if (this.state != Handsontable.EditorState.VIRGIN) {
  4851. return;
  4852. }
  4853. this.instance.view.scrollViewport(new WalkontableCellCoords(this.row, this.col));
  4854. this.instance.view.render();
  4855. this.state = Handsontable.EditorState.EDITING;
  4856. initialValue = typeof initialValue == 'string' ? initialValue : this.originalValue;
  4857. this.setValue(Handsontable.helper.stringify(initialValue));
  4858. this.open();
  4859. this._opened = true;
  4860. this.focus();
  4861. this.instance.view.render(); //only rerender the selections (FillHandle should disappear when beginediting is triggered)
  4862. };
  4863. BaseEditor.prototype.finishEditing = function (restoreOriginalValue, ctrlDown, callback) {
  4864. if (callback) {
  4865. var previousCloseCallback = this._closeCallback;
  4866. this._closeCallback = function (result) {
  4867. if(previousCloseCallback){
  4868. previousCloseCallback(result);
  4869. }
  4870. callback(result);
  4871. };
  4872. }
  4873. if (this.isWaiting()) {
  4874. return;
  4875. }
  4876. if (this.state == Handsontable.EditorState.VIRGIN) {
  4877. var that = this;
  4878. this.instance._registerTimeout(setTimeout(function () {
  4879. that._fireCallbacks(true);
  4880. }, 0));
  4881. return;
  4882. }
  4883. if (this.state == Handsontable.EditorState.EDITING) {
  4884. if (restoreOriginalValue) {
  4885. this.cancelChanges();
  4886. this.instance.view.render();
  4887. return;
  4888. }
  4889. var val = [
  4890. [String.prototype.trim.call(this.getValue())] //String.prototype.trim is defined in Walkontable polyfill.js
  4891. ];
  4892. this.state = Handsontable.EditorState.WAITING;
  4893. this.saveValue(val, ctrlDown);
  4894. if(this.instance.getCellValidator(this.cellProperties)){
  4895. var that = this;
  4896. this.instance.addHookOnce('afterValidate', function (result) {
  4897. that.state = Handsontable.EditorState.FINISHED;
  4898. that.discardEditor(result);
  4899. });
  4900. } else {
  4901. this.state = Handsontable.EditorState.FINISHED;
  4902. this.discardEditor(true);
  4903. }
  4904. }
  4905. };
  4906. BaseEditor.prototype.cancelChanges = function () {
  4907. this.state = Handsontable.EditorState.FINISHED;
  4908. this.discardEditor();
  4909. };
  4910. BaseEditor.prototype.discardEditor = function (result) {
  4911. if (this.state !== Handsontable.EditorState.FINISHED) {
  4912. return;
  4913. }
  4914. if (result === false && this.cellProperties.allowInvalid !== true) { //validator was defined and failed
  4915. this.instance.selectCell(this.row, this.col);
  4916. this.focus();
  4917. this.state = Handsontable.EditorState.EDITING;
  4918. this._fireCallbacks(false);
  4919. }
  4920. else {
  4921. this.close();
  4922. this._opened = false;
  4923. this.state = Handsontable.EditorState.VIRGIN;
  4924. this._fireCallbacks(true);
  4925. }
  4926. };
  4927. BaseEditor.prototype.isOpened = function(){
  4928. return this._opened;
  4929. };
  4930. BaseEditor.prototype.isWaiting = function () {
  4931. return this.state === Handsontable.EditorState.WAITING;
  4932. };
  4933. Handsontable.editors.BaseEditor = BaseEditor;
  4934. })(Handsontable);
  4935. (function(Handsontable){
  4936. var TextEditor = Handsontable.editors.BaseEditor.prototype.extend();
  4937. TextEditor.prototype.init = function(){
  4938. var that = this;
  4939. this.createElements();
  4940. this.eventManager = new Handsontable.eventManager(this);
  4941. this.bindEvents();
  4942. this.autoResize = autoResize();
  4943. this.instance.addHook('afterDestroy', function () {
  4944. that.destroy();
  4945. });
  4946. };
  4947. TextEditor.prototype.getValue = function(){
  4948. return this.TEXTAREA.value
  4949. };
  4950. TextEditor.prototype.setValue = function(newValue){
  4951. this.TEXTAREA.value = newValue;
  4952. };
  4953. var onBeforeKeyDown = function onBeforeKeyDown(event){
  4954. var instance = this;
  4955. var that = instance.getActiveEditor();
  4956. var keyCodes = Handsontable.helper.keyCode;
  4957. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
  4958. Handsontable.Dom.enableImmediatePropagation(event);
  4959. //Process only events that have been fired in the editor
  4960. if (event.target !== that.TEXTAREA || event.isImmediatePropagationStopped()){
  4961. return;
  4962. }
  4963. if (event.keyCode === 17 || event.keyCode === 224 || event.keyCode === 91 || event.keyCode === 93) {
  4964. //when CTRL or its equivalent is pressed and cell is edited, don't prepare selectable text in textarea
  4965. event.stopImmediatePropagation();
  4966. return;
  4967. }
  4968. switch (event.keyCode) {
  4969. case keyCodes.ARROW_RIGHT:
  4970. if (Handsontable.Dom.getCaretPosition(that.TEXTAREA) !== that.TEXTAREA.value.length) {
  4971. event.stopImmediatePropagation();
  4972. }
  4973. break;
  4974. case keyCodes.ARROW_LEFT: /* arrow left */
  4975. if (Handsontable.Dom.getCaretPosition(that.TEXTAREA) !== 0) {
  4976. event.stopImmediatePropagation();
  4977. }
  4978. break;
  4979. case keyCodes.ENTER:
  4980. var selected = that.instance.getSelected();
  4981. var isMultipleSelection = !(selected[0] === selected[2] && selected[1] === selected[3]);
  4982. if ((ctrlDown && !isMultipleSelection) || event.altKey) { //if ctrl+enter or alt+enter, add new line
  4983. if(that.isOpened()){
  4984. that.setValue(that.getValue() + '\n');
  4985. that.focus();
  4986. } else {
  4987. that.beginEditing(that.originalValue + '\n')
  4988. }
  4989. event.stopImmediatePropagation();
  4990. }
  4991. event.preventDefault(); //don't add newline to field
  4992. break;
  4993. case keyCodes.A:
  4994. case keyCodes.X:
  4995. case keyCodes.C:
  4996. case keyCodes.V:
  4997. if(ctrlDown){
  4998. event.stopImmediatePropagation(); //CTRL+A, CTRL+C, CTRL+V, CTRL+X should only work locally when cell is edited (not in table context)
  4999. }
  5000. break;
  5001. case keyCodes.BACKSPACE:
  5002. case keyCodes.DELETE:
  5003. case keyCodes.HOME:
  5004. case keyCodes.END:
  5005. event.stopImmediatePropagation(); //backspace, delete, home, end should only work locally when cell is edited (not in table context)
  5006. break;
  5007. }
  5008. that.autoResize.resize(String.fromCharCode(event.keyCode));
  5009. };
  5010. TextEditor.prototype.open = function(){
  5011. this.refreshDimensions(); //need it instantly, to prevent https://github.com/handsontable/handsontable/issues/348
  5012. this.instance.addHook('beforeKeyDown', onBeforeKeyDown);
  5013. };
  5014. TextEditor.prototype.close = function(){
  5015. this.textareaParentStyle.display = 'none';
  5016. this.autoResize.unObserve();
  5017. if (document.activeElement === this.TEXTAREA) {
  5018. this.instance.listen(); //don't refocus the table if user focused some cell outside of HT on purpose
  5019. }
  5020. this.instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  5021. };
  5022. TextEditor.prototype.focus = function(){
  5023. this.TEXTAREA.focus();
  5024. Handsontable.Dom.setCaretPosition(this.TEXTAREA, this.TEXTAREA.value.length);
  5025. };
  5026. TextEditor.prototype.createElements = function () {
  5027. // this.$body = $(document.body);
  5028. this.TEXTAREA = document.createElement('TEXTAREA');
  5029. Handsontable.Dom.addClass(this.TEXTAREA, 'handsontableInput');
  5030. this.textareaStyle = this.TEXTAREA.style;
  5031. this.textareaStyle.width = 0;
  5032. this.textareaStyle.height = 0;
  5033. this.TEXTAREA_PARENT = document.createElement('DIV');
  5034. Handsontable.Dom.addClass(this.TEXTAREA_PARENT, 'handsontableInputHolder');
  5035. this.textareaParentStyle = this.TEXTAREA_PARENT.style;
  5036. this.textareaParentStyle.top = 0;
  5037. this.textareaParentStyle.left = 0;
  5038. this.textareaParentStyle.display = 'none';
  5039. this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
  5040. this.instance.rootElement.appendChild(this.TEXTAREA_PARENT);
  5041. var that = this;
  5042. this.instance._registerTimeout(setTimeout(function () {
  5043. that.refreshDimensions();
  5044. }, 0));
  5045. };
  5046. TextEditor.prototype.checkEditorSection = function () {
  5047. if(this.row < this.instance.getSettings().fixedRowsTop) {
  5048. if(this.col < this.instance.getSettings().fixedColumnsLeft) {
  5049. return 'corner';
  5050. } else {
  5051. return 'top';
  5052. }
  5053. } else {
  5054. if(this.col < this.instance.getSettings().fixedColumnsLeft) {
  5055. return 'left';
  5056. }
  5057. }
  5058. };
  5059. TextEditor.prototype.getEditedCell = function () {
  5060. var editorSection = this.checkEditorSection()
  5061. , editedCell;
  5062. switch (editorSection) {
  5063. case 'top':
  5064. editedCell = this.instance.view.wt.wtScrollbars.vertical.clone.wtTable.getCell({row: this.row, col: this.col});
  5065. this.textareaParentStyle.zIndex = 101;
  5066. break;
  5067. case 'corner':
  5068. editedCell = this.instance.view.wt.wtScrollbars.corner.clone.wtTable.getCell({row: this.row, col: this.col});
  5069. this.textareaParentStyle.zIndex = 103;
  5070. break;
  5071. case 'left':
  5072. editedCell = this.instance.view.wt.wtScrollbars.horizontal.clone.wtTable.getCell({row: this.row, col: this.col});
  5073. this.textareaParentStyle.zIndex = 102;
  5074. break;
  5075. default :
  5076. editedCell = this.instance.getCell(this.row, this.col);
  5077. this.textareaParentStyle.zIndex = "";
  5078. break;
  5079. }
  5080. return editedCell != -1 && editedCell != -2 ? editedCell : void 0;
  5081. };
  5082. TextEditor.prototype.refreshDimensions = function () {
  5083. if (this.state !== Handsontable.EditorState.EDITING) {
  5084. return;
  5085. }
  5086. ///start prepare textarea position
  5087. // this.TD = this.instance.getCell(this.row, this.col);
  5088. this.TD = this.getEditedCell();
  5089. if (!this.TD) {
  5090. //TD is outside of the viewport. Otherwise throws exception when scrolling the table while a cell is edited
  5091. return;
  5092. }
  5093. //var $td = $(this.TD); //because old td may have been scrolled out with scrollViewport
  5094. var currentOffset = Handsontable.Dom.offset(this.TD);
  5095. var containerOffset = Handsontable.Dom.offset(this.instance.rootElement);
  5096. var editTop = currentOffset.top - containerOffset.top - 1;
  5097. var editLeft = currentOffset.left - containerOffset.left - 1;
  5098. var settings = this.instance.getSettings();
  5099. var rowHeadersCount = settings.rowHeaders === false ? 0 : 1;
  5100. var colHeadersCount = settings.colHeaders === false ? 0 : 1;
  5101. var editorSection = this.checkEditorSection();
  5102. var cssTransformOffset;
  5103. // TODO: Refactor this to the new instance.getCell method (from #ply-59), after 0.12.1 is released
  5104. switch(editorSection) {
  5105. case 'top':
  5106. cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.vertical.clone.wtTable.holder.parentNode);
  5107. break;
  5108. case 'left':
  5109. cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.horizontal.clone.wtTable.holder.parentNode);
  5110. break;
  5111. case 'corner':
  5112. cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.corner.clone.wtTable.holder.parentNode);
  5113. break;
  5114. }
  5115. if (editTop < 0) {
  5116. editTop = 0;
  5117. }
  5118. if (editLeft < 0) {
  5119. editLeft = 0;
  5120. }
  5121. if (rowHeadersCount > 0 && parseInt(this.TD.style.borderTopWidth, 10) > 0) {
  5122. editTop += 1;
  5123. }
  5124. if (colHeadersCount > 0 && parseInt(this.TD.style.borderLeftWidth, 10) > 0) {
  5125. editLeft += 1;
  5126. }
  5127. if(cssTransformOffset && cssTransformOffset != -1) {
  5128. this.textareaParentStyle[cssTransformOffset[0]] = cssTransformOffset[1];
  5129. } else {
  5130. Handsontable.Dom.resetCssTransform(this.textareaParentStyle);
  5131. }
  5132. this.textareaParentStyle.top = editTop + 'px';
  5133. this.textareaParentStyle.left = editLeft + 'px';
  5134. ///end prepare textarea position
  5135. var cellTopOffset = this.TD.offsetTop - this.instance.view.wt.wtScrollbars.vertical.getScrollPosition(),
  5136. cellLeftOffset = this.TD.offsetLeft - this.instance.view.wt.wtScrollbars.horizontal.getScrollPosition();
  5137. var width = Handsontable.Dom.innerWidth(this.TD) - 8 //$td.width()
  5138. , maxWidth = this.instance.view.maximumVisibleElementWidth(cellLeftOffset) - 10 //10 is TEXTAREAs border and padding
  5139. , height = Handsontable.Dom.outerHeight(this.TD) - 4 //$td.outerHeight() - 4
  5140. , maxHeight = this.instance.view.maximumVisibleElementHeight(cellTopOffset) - 2; //10 is TEXTAREAs border and padding
  5141. if (parseInt(this.TD.style.borderTopWidth, 10) > 0) {
  5142. height -= 1;
  5143. }
  5144. if (parseInt(this.TD.style.borderLeftWidth, 10) > 0) {
  5145. if (rowHeadersCount > 0) {
  5146. width -= 1;
  5147. }
  5148. }
  5149. this.TEXTAREA.style.fontSize = Handsontable.Dom.getComputedStyle(this.TD).fontSize;
  5150. this.TEXTAREA.style.fontFamily = Handsontable.Dom.getComputedStyle(this.TD).fontFamily;
  5151. this.autoResize.init(this.TEXTAREA, {
  5152. minHeight: Math.min(height, maxHeight),
  5153. maxHeight: maxHeight, //TEXTAREA should never be wider than visible part of the viewport (should not cover the scrollbar)
  5154. minWidth: Math.min(width, maxWidth),
  5155. maxWidth: maxWidth //TEXTAREA should never be wider than visible part of the viewport (should not cover the scrollbar)
  5156. }, true);
  5157. this.textareaParentStyle.display = 'block';
  5158. };
  5159. TextEditor.prototype.bindEvents = function () {
  5160. var editor = this;
  5161. this.eventManager.addEventListener(this.TEXTAREA, 'cut',function (event){
  5162. Handsontable.helper.stopPropagation(event);
  5163. //event.stopPropagation();
  5164. });
  5165. this.eventManager.addEventListener(this.TEXTAREA, 'paste', function (event){
  5166. Handsontable.helper.stopPropagation(event);
  5167. //event.stopPropagation();
  5168. });
  5169. this.instance.addHook('afterScrollVertically', function () {
  5170. editor.refreshDimensions();
  5171. });
  5172. this.instance.addHook('afterDestroy', function () {
  5173. editor.eventManager.clear();
  5174. });
  5175. };
  5176. TextEditor.prototype.destroy = function () {
  5177. this.eventManager.clear();
  5178. };
  5179. Handsontable.editors.TextEditor = TextEditor;
  5180. Handsontable.editors.registerEditor('text', Handsontable.editors.TextEditor);
  5181. })(Handsontable);
  5182. (function (Handsontable) {
  5183. var MobileTextEditor = Handsontable.editors.BaseEditor.prototype.extend();
  5184. var domDimensionsCache = {};
  5185. var createControls = function () {
  5186. this.controls = {};
  5187. this.controls.leftButton = document.createElement('DIV');
  5188. this.controls.leftButton.className = 'leftButton';
  5189. this.controls.rightButton = document.createElement('DIV');
  5190. this.controls.rightButton.className = 'rightButton';
  5191. this.controls.upButton = document.createElement('DIV');
  5192. this.controls.upButton.className = 'upButton';
  5193. this.controls.downButton = document.createElement('DIV');
  5194. this.controls.downButton.className = 'downButton';
  5195. for(var button in this.controls) {
  5196. this.positionControls.appendChild(this.controls[button]);
  5197. }
  5198. };
  5199. MobileTextEditor.prototype.valueChanged = function () {
  5200. return this.initValue != this.getValue();
  5201. };
  5202. MobileTextEditor.prototype.init = function () {
  5203. var that = this;
  5204. this.eventManager = new Handsontable.eventManager(this.instance);
  5205. this.createElements();
  5206. this.bindEvents();
  5207. this.instance.addHook('afterDestroy', function () {
  5208. that.destroy();
  5209. });
  5210. };
  5211. MobileTextEditor.prototype.getValue = function () {
  5212. return this.TEXTAREA.value
  5213. };
  5214. MobileTextEditor.prototype.setValue = function (newValue) {
  5215. this.initValue = newValue;
  5216. this.TEXTAREA.value = newValue;
  5217. };
  5218. MobileTextEditor.prototype.createElements = function () {
  5219. this.editorContainer = document.createElement('DIV');
  5220. this.editorContainer.className = "htMobileEditorContainer";
  5221. this.cellPointer = document.createElement('DIV');
  5222. this.cellPointer.className = "cellPointer";
  5223. this.moveHandle = document.createElement('DIV');
  5224. this.moveHandle.className = "moveHandle";
  5225. this.inputPane = document.createElement('DIV');
  5226. this.inputPane.className = "inputs";
  5227. this.positionControls = document.createElement('DIV');
  5228. this.positionControls.className = "positionControls";
  5229. this.TEXTAREA = document.createElement('TEXTAREA');
  5230. Handsontable.Dom.addClass(this.TEXTAREA, 'handsontableInput');
  5231. this.inputPane.appendChild(this.TEXTAREA);
  5232. this.editorContainer.appendChild(this.cellPointer);
  5233. this.editorContainer.appendChild(this.moveHandle);
  5234. this.editorContainer.appendChild(this.inputPane);
  5235. this.editorContainer.appendChild(this.positionControls);
  5236. createControls.call(this);
  5237. document.body.appendChild(this.editorContainer);
  5238. };
  5239. MobileTextEditor.prototype.onBeforeKeyDown = function (event) {
  5240. var instance = this;
  5241. var that = instance.getActiveEditor();
  5242. Handsontable.Dom.enableImmediatePropagation(event);
  5243. if (event.target !== that.TEXTAREA || event.isImmediatePropagationStopped()){
  5244. return;
  5245. }
  5246. var keyCodes = Handsontable.helper.keyCode;
  5247. switch(event.keyCode) {
  5248. case keyCodes.ENTER:
  5249. that.close();
  5250. event.preventDefault(); //don't add newline to field
  5251. break;
  5252. case keyCodes.BACKSPACE:
  5253. event.stopImmediatePropagation(); //backspace, delete, home, end should only work locally when cell is edited (not in table context)
  5254. break;
  5255. }
  5256. };
  5257. MobileTextEditor.prototype.open = function () {
  5258. this.instance.addHook('beforeKeyDown', this.onBeforeKeyDown);
  5259. Handsontable.Dom.addClass(this.editorContainer, 'active');
  5260. //this.updateEditorDimensions();
  5261. //this.scrollToView();
  5262. Handsontable.Dom.removeClass(this.cellPointer, 'hidden');
  5263. this.updateEditorPosition();
  5264. };
  5265. MobileTextEditor.prototype.focus = function(){
  5266. this.TEXTAREA.focus();
  5267. Handsontable.Dom.setCaretPosition(this.TEXTAREA, this.TEXTAREA.value.length);
  5268. };
  5269. MobileTextEditor.prototype.close = function () {
  5270. this.TEXTAREA.blur();
  5271. this.instance.removeHook('beforeKeyDown', this.onBeforeKeyDown);
  5272. Handsontable.Dom.removeClass(this.editorContainer, 'active');
  5273. };
  5274. MobileTextEditor.prototype.scrollToView = function () {
  5275. var coords = this.instance.getSelectedRange().highlight;
  5276. this.instance.view.scrollViewport(coords);
  5277. };
  5278. MobileTextEditor.prototype.hideCellPointer = function () {
  5279. if(!Handsontable.Dom.hasClass(this.cellPointer, 'hidden')) {
  5280. Handsontable.Dom.addClass(this.cellPointer, 'hidden');
  5281. }
  5282. };
  5283. MobileTextEditor.prototype.updateEditorPosition = function (x, y) {
  5284. if(x && y) {
  5285. x = parseInt(x, 10);
  5286. y = parseInt(y, 10);
  5287. this.editorContainer.style.top = y + "px";
  5288. this.editorContainer.style.left = x + "px";
  5289. } else {
  5290. var selection = this.instance.getSelected()
  5291. , selectedCell = this.instance.getCell(selection[0],selection[1]);
  5292. //cache sizes
  5293. if(!domDimensionsCache.cellPointer) {
  5294. domDimensionsCache.cellPointer = {
  5295. height: Handsontable.Dom.outerHeight(this.cellPointer),
  5296. width: Handsontable.Dom.outerWidth(this.cellPointer)
  5297. }
  5298. }
  5299. if(!domDimensionsCache.editorContainer) {
  5300. domDimensionsCache.editorContainer = {
  5301. width: Handsontable.Dom.outerWidth(this.editorContainer)
  5302. }
  5303. }
  5304. if(selectedCell != undefined) {
  5305. var scrollLeft = this.instance.view.wt.wtScrollbars.horizontal.scrollHandler == window ? 0 : Handsontable.Dom.getScrollLeft(this.instance.view.wt.wtScrollbars.horizontal.scrollHandler);
  5306. var scrollTop = this.instance.view.wt.wtScrollbars.vertical.scrollHandler == window ? 0 : Handsontable.Dom.getScrollTop(this.instance.view.wt.wtScrollbars.vertical.scrollHandler);
  5307. var selectedCellOffset = Handsontable.Dom.offset(selectedCell)
  5308. , selectedCellWidth = Handsontable.Dom.outerWidth(selectedCell)
  5309. , currentScrollPosition = {
  5310. x: scrollLeft,
  5311. y: scrollTop
  5312. };
  5313. this.editorContainer.style.top = parseInt(selectedCellOffset.top + Handsontable.Dom.outerHeight(selectedCell) - currentScrollPosition.y + domDimensionsCache.cellPointer.height, 10) + "px";
  5314. this.editorContainer.style.left = parseInt((window.innerWidth / 2) - (domDimensionsCache.editorContainer.width / 2) ,10) + "px";
  5315. if(selectedCellOffset.left + selectedCellWidth / 2 > parseInt(this.editorContainer.style.left,10) + domDimensionsCache.editorContainer.width) {
  5316. this.editorContainer.style.left = window.innerWidth - domDimensionsCache.editorContainer.width + "px";
  5317. } else if(selectedCellOffset.left + selectedCellWidth / 2 < parseInt(this.editorContainer.style.left,10) + 20) {
  5318. this.editorContainer.style.left = 0 + "px";
  5319. }
  5320. this.cellPointer.style.left = parseInt(selectedCellOffset.left - (domDimensionsCache.cellPointer.width / 2) - Handsontable.Dom.offset(this.editorContainer).left + (selectedCellWidth / 2) - currentScrollPosition.x ,10) + "px";
  5321. }
  5322. }
  5323. };
  5324. // For the optional dont-affect-editor-by-zooming feature:
  5325. //MobileTextEditor.prototype.updateEditorDimensions = function () {
  5326. // if(!this.beginningWindowWidth) {
  5327. // this.beginningWindowWidth = window.innerWidth;
  5328. // this.beginningEditorWidth = Handsontable.Dom.outerWidth(this.editorContainer);
  5329. // this.scaleRatio = this.beginningEditorWidth / this.beginningWindowWidth;
  5330. //
  5331. // this.editorContainer.style.width = this.beginningEditorWidth + "px";
  5332. // return;
  5333. // }
  5334. //
  5335. // var currentScaleRatio = this.beginningEditorWidth / window.innerWidth;
  5336. // //if(currentScaleRatio > this.scaleRatio + 0.2 || currentScaleRatio < this.scaleRatio - 0.2) {
  5337. // if(currentScaleRatio != this.scaleRatio) {
  5338. // this.editorContainer.style["zoom"] = (1 - ((currentScaleRatio * this.scaleRatio) - this.scaleRatio)) * 100 + "%";
  5339. // }
  5340. //
  5341. //};
  5342. MobileTextEditor.prototype.updateEditorData = function () {
  5343. var selected = this.instance.getSelected()
  5344. , selectedValue = this.instance.getDataAtCell(selected[0], selected[1]);
  5345. this.row = selected[0];
  5346. this.col = selected[1];
  5347. this.setValue(selectedValue);
  5348. this.updateEditorPosition();
  5349. };
  5350. MobileTextEditor.prototype.prepareAndSave = function () {
  5351. if(!this.valueChanged()) {
  5352. return true;
  5353. }
  5354. var val = [
  5355. [String.prototype.trim.call(this.getValue())]
  5356. ];
  5357. this.saveValue(val);
  5358. };
  5359. MobileTextEditor.prototype.bindEvents = function () {
  5360. var that = this;
  5361. this.eventManager.addEventListener(this.controls.leftButton, "touchend", function (event) {
  5362. that.prepareAndSave();
  5363. that.instance.selection.transformStart(0, -1, null, true);
  5364. that.updateEditorData();
  5365. event.preventDefault();
  5366. });
  5367. this.eventManager.addEventListener(this.controls.rightButton, "touchend", function (event) {
  5368. that.prepareAndSave();
  5369. that.instance.selection.transformStart(0, 1, null, true);
  5370. that.updateEditorData();
  5371. event.preventDefault();
  5372. });
  5373. this.eventManager.addEventListener(this.controls.upButton, "touchend", function (event) {
  5374. that.prepareAndSave();
  5375. that.instance.selection.transformStart(-1, 0, null, true);
  5376. that.updateEditorData();
  5377. event.preventDefault();
  5378. });
  5379. this.eventManager.addEventListener(this.controls.downButton, "touchend", function (event) {
  5380. that.prepareAndSave();
  5381. that.instance.selection.transformStart(1, 0, null, true);
  5382. that.updateEditorData();
  5383. event.preventDefault();
  5384. });
  5385. this.eventManager.addEventListener(this.moveHandle, "touchstart", function (event) {
  5386. if (event.touches.length == 1) {
  5387. var touch = event.touches[0]
  5388. , onTouchPosition = {
  5389. x: that.editorContainer.offsetLeft,
  5390. y: that.editorContainer.offsetTop
  5391. }
  5392. , onTouchOffset = {
  5393. x: touch.pageX - onTouchPosition.x,
  5394. y: touch.pageY - onTouchPosition.y
  5395. };
  5396. that.eventManager.addEventListener(this, "touchmove", function (event) {
  5397. var touch = event.touches[0];
  5398. that.updateEditorPosition(touch.pageX - onTouchOffset.x, touch.pageY - onTouchOffset.y);
  5399. that.hideCellPointer();
  5400. event.preventDefault();
  5401. });
  5402. }
  5403. });
  5404. this.eventManager.addEventListener(document.body, "touchend", function (event) {
  5405. if(!Handsontable.Dom.isChildOf(event.target, that.editorContainer) && !Handsontable.Dom.isChildOf(event.target, that.instance.rootElement)) {
  5406. that.close();
  5407. }
  5408. });
  5409. this.eventManager.addEventListener(this.instance.view.wt.wtScrollbars.horizontal.scrollHandler, "scroll", function (event) {
  5410. if(that.instance.view.wt.wtScrollbars.horizontal.scrollHandler != window) {
  5411. that.hideCellPointer();
  5412. }
  5413. });
  5414. this.eventManager.addEventListener(this.instance.view.wt.wtScrollbars.vertical.scrollHandler, "scroll", function (event) {
  5415. if(that.instance.view.wt.wtScrollbars.vertical.scrollHandler != window) {
  5416. that.hideCellPointer();
  5417. }
  5418. });
  5419. };
  5420. MobileTextEditor.prototype.destroy = function () {
  5421. this.eventManager.clear();
  5422. this.editorContainer.parentNode.removeChild(this.editorContainer);
  5423. };
  5424. Handsontable.editors.MobileTextEditor = MobileTextEditor;
  5425. Handsontable.editors.registerEditor('mobile', Handsontable.editors.MobileTextEditor);
  5426. })(Handsontable);
  5427. (function(Handsontable){
  5428. //Blank editor, because all the work is done by renderer
  5429. var CheckboxEditor = Handsontable.editors.BaseEditor.prototype.extend();
  5430. CheckboxEditor.prototype.beginEditing = function () {
  5431. var checkbox = this.TD.querySelector('input[type="checkbox"]');
  5432. if (checkbox) {
  5433. checkbox.click();
  5434. }
  5435. };
  5436. CheckboxEditor.prototype.finishEditing = function () {};
  5437. CheckboxEditor.prototype.init = function () {};
  5438. CheckboxEditor.prototype.open = function () {};
  5439. CheckboxEditor.prototype.close = function () {};
  5440. CheckboxEditor.prototype.getValue = function () {};
  5441. CheckboxEditor.prototype.setValue = function () {};
  5442. CheckboxEditor.prototype.focus = function () {};
  5443. Handsontable.editors.CheckboxEditor = CheckboxEditor;
  5444. Handsontable.editors.registerEditor('checkbox', CheckboxEditor);
  5445. })(Handsontable);
  5446. (function (Handsontable) {
  5447. var DateEditor = Handsontable.editors.TextEditor.prototype.extend();
  5448. var $;
  5449. DateEditor.prototype.init = function () {
  5450. if (typeof jQuery != 'undefined') {
  5451. $ = jQuery;
  5452. } else {
  5453. throw new Error("You need to include jQuery to your project in order to use the jQuery UI Datepicker.");
  5454. }
  5455. if (!$.datepicker) {
  5456. throw new Error("jQuery UI Datepicker dependency not found. Did you forget to include jquery-ui.custom.js or its substitute?");
  5457. }
  5458. Handsontable.editors.TextEditor.prototype.init.apply(this, arguments);
  5459. this.isCellEdited = false;
  5460. var that = this;
  5461. this.instance.addHook('afterDestroy', function () {
  5462. that.destroyElements();
  5463. })
  5464. };
  5465. DateEditor.prototype.createElements = function () {
  5466. Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
  5467. this.datePicker = document.createElement('DIV');
  5468. Handsontable.Dom.addClass(this.datePicker, 'htDatepickerHolder');
  5469. this.datePickerStyle = this.datePicker.style;
  5470. this.datePickerStyle.position = 'absolute';
  5471. this.datePickerStyle.top = 0;
  5472. this.datePickerStyle.left = 0;
  5473. this.datePickerStyle.zIndex = 99;
  5474. document.body.appendChild(this.datePicker);
  5475. this.$datePicker = $(this.datePicker);
  5476. var that = this;
  5477. var defaultOptions = {
  5478. dateFormat: "yy-mm-dd",
  5479. showButtonPanel: true,
  5480. changeMonth: true,
  5481. changeYear: true,
  5482. onSelect: function (dateStr) {
  5483. that.setValue(dateStr);
  5484. that.finishEditing(false);
  5485. }
  5486. };
  5487. this.$datePicker.datepicker(defaultOptions);
  5488. var eventManager = Handsontable.eventManager(this);
  5489. /**
  5490. * Prevent recognizing clicking on jQuery Datepicker as clicking outside of table
  5491. */
  5492. eventManager.addEventListener(this.datePicker, 'mousedown', function (event) {
  5493. Handsontable.helper.stopPropagation(event);
  5494. //event.stopPropagation();
  5495. });
  5496. this.hideDatepicker();
  5497. };
  5498. DateEditor.prototype.destroyElements = function () {
  5499. this.$datePicker.datepicker('destroy');
  5500. this.$datePicker.remove();
  5501. //var eventManager = Handsontable.eventManager(this);
  5502. //eventManager.removeEventListener(this.datePicker, 'mousedown');
  5503. };
  5504. DateEditor.prototype.open = function () {
  5505. Handsontable.editors.TextEditor.prototype.open.call(this);
  5506. this.showDatepicker();
  5507. };
  5508. DateEditor.prototype.finishEditing = function (isCancelled, ctrlDown) {
  5509. this.hideDatepicker();
  5510. Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments);
  5511. };
  5512. DateEditor.prototype.showDatepicker = function () {
  5513. var offset = this.TD.getBoundingClientRect(),
  5514. DatepickerSettings,
  5515. datepickerSettings;
  5516. this.datePickerStyle.top = (window.pageYOffset + offset.top + Handsontable.Dom.outerHeight(this.TD)) + 'px';
  5517. this.datePickerStyle.left = (window.pageXOffset + offset.left) + 'px';
  5518. DatepickerSettings = function () {};
  5519. DatepickerSettings.prototype = this.cellProperties;
  5520. datepickerSettings = new DatepickerSettings();
  5521. datepickerSettings.defaultDate = this.originalValue || void 0;
  5522. this.$datePicker.datepicker('option', datepickerSettings);
  5523. if (this.originalValue) {
  5524. this.$datePicker.datepicker('setDate', this.originalValue);
  5525. }
  5526. this.datePickerStyle.display = 'block';
  5527. };
  5528. DateEditor.prototype.hideDatepicker = function () {
  5529. this.datePickerStyle.display = 'none';
  5530. };
  5531. Handsontable.editors.DateEditor = DateEditor;
  5532. Handsontable.editors.registerEditor('date', DateEditor);
  5533. })(Handsontable);
  5534. /**
  5535. * This is inception. Using Handsontable as Handsontable editor
  5536. */
  5537. (function (Handsontable) {
  5538. "use strict";
  5539. var HandsontableEditor = Handsontable.editors.TextEditor.prototype.extend();
  5540. HandsontableEditor.prototype.createElements = function () {
  5541. Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
  5542. var DIV = document.createElement('DIV');
  5543. DIV.className = 'handsontableEditor';
  5544. this.TEXTAREA_PARENT.appendChild(DIV);
  5545. this.htContainer = DIV;
  5546. this.htEditor = new Handsontable(DIV);
  5547. this.assignHooks();
  5548. };
  5549. HandsontableEditor.prototype.prepare = function (td, row, col, prop, value, cellProperties) {
  5550. Handsontable.editors.TextEditor.prototype.prepare.apply(this, arguments);
  5551. var parent = this;
  5552. var options = {
  5553. startRows: 0,
  5554. startCols: 0,
  5555. minRows: 0,
  5556. minCols: 0,
  5557. className: 'listbox',
  5558. copyPaste: false,
  5559. cells: function () {
  5560. return {
  5561. readOnly: true
  5562. }
  5563. },
  5564. fillHandle: false,
  5565. afterOnCellMouseDown: function () {
  5566. var value = this.getValue();
  5567. if (value !== void 0) { //if the value is undefined then it means we don't want to set the value
  5568. parent.setValue(value);
  5569. }
  5570. parent.instance.destroyEditor();
  5571. }
  5572. };
  5573. if (this.cellProperties.handsontable) {
  5574. Handsontable.helper.extend(options, cellProperties.handsontable);
  5575. }
  5576. if (this.htEditor) {
  5577. this.htEditor.destroy();
  5578. }
  5579. this.htEditor = new Handsontable(this.htContainer, options);
  5580. //this.$htContainer.handsontable('destroy');
  5581. //this.$htContainer.handsontable(options);
  5582. };
  5583. var onBeforeKeyDown = function (event) {
  5584. if (event != null && event.isImmediatePropagationEnabled == null) {
  5585. event.stopImmediatePropagation = function () {
  5586. this.isImmediatePropagationEnabled = false;
  5587. this.cancelBubble = true;
  5588. };
  5589. event.isImmediatePropagationEnabled = true;
  5590. event.isImmediatePropagationStopped = function () {
  5591. return !this.isImmediatePropagationEnabled;
  5592. };
  5593. }
  5594. if (event.isImmediatePropagationStopped()) {
  5595. return;
  5596. }
  5597. var editor = this.getActiveEditor();
  5598. var innerHOT = editor.htEditor.getInstance(); //Handsontable.tmpHandsontable(editor.htContainer, 'getInstance');
  5599. var rowToSelect;
  5600. if (event.keyCode == Handsontable.helper.keyCode.ARROW_DOWN) {
  5601. if (!innerHOT.getSelected()) {
  5602. rowToSelect = 0;
  5603. }
  5604. else {
  5605. var selectedRow = innerHOT.getSelected()[0];
  5606. var lastRow = innerHOT.countRows() - 1;
  5607. rowToSelect = Math.min(lastRow, selectedRow + 1);
  5608. }
  5609. }
  5610. else if (event.keyCode == Handsontable.helper.keyCode.ARROW_UP) {
  5611. if (innerHOT.getSelected()) {
  5612. var selectedRow = innerHOT.getSelected()[0];
  5613. rowToSelect = selectedRow - 1;
  5614. }
  5615. }
  5616. if (rowToSelect !== void 0) {
  5617. if (rowToSelect < 0) {
  5618. innerHOT.deselectCell();
  5619. }
  5620. else {
  5621. innerHOT.selectCell(rowToSelect, 0);
  5622. }
  5623. event.preventDefault();
  5624. event.stopImmediatePropagation();
  5625. editor.instance.listen();
  5626. editor.TEXTAREA.focus();
  5627. }
  5628. };
  5629. HandsontableEditor.prototype.open = function () {
  5630. this.instance.addHook('beforeKeyDown', onBeforeKeyDown);
  5631. Handsontable.editors.TextEditor.prototype.open.apply(this, arguments);
  5632. //this.$htContainer.handsontable('render');
  5633. //Handsontable.tmpHandsontable(this.htContainer, 'render');
  5634. this.htEditor.render();
  5635. if (this.cellProperties.strict) {
  5636. this.htEditor.selectCell(0,0);
  5637. this.TEXTAREA.style.visibility = 'hidden';
  5638. } else {
  5639. this.htEditor.deselectCell();
  5640. this.TEXTAREA.style.visibility = 'visible';
  5641. }
  5642. Handsontable.Dom.setCaretPosition(this.TEXTAREA, 0, this.TEXTAREA.value.length);
  5643. };
  5644. HandsontableEditor.prototype.close = function () {
  5645. this.instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  5646. this.instance.listen();
  5647. Handsontable.editors.TextEditor.prototype.close.apply(this, arguments);
  5648. };
  5649. HandsontableEditor.prototype.focus = function () {
  5650. this.instance.listen();
  5651. Handsontable.editors.TextEditor.prototype.focus.apply(this, arguments);
  5652. };
  5653. HandsontableEditor.prototype.beginEditing = function (initialValue) {
  5654. var onBeginEditing = this.instance.getSettings().onBeginEditing;
  5655. if (onBeginEditing && onBeginEditing() === false) {
  5656. return;
  5657. }
  5658. Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
  5659. };
  5660. HandsontableEditor.prototype.finishEditing = function (isCancelled, ctrlDown) {
  5661. if (this.htEditor.isListening()) { //if focus is still in the HOT editor
  5662. //if (Handsontable.tmpHandsontable(this.htContainer,'isListening')) { //if focus is still in the HOT editor
  5663. //if (this.$htContainer.handsontable('isListening')) { //if focus is still in the HOT editor
  5664. this.instance.listen(); //return the focus to the parent HOT instance
  5665. }
  5666. if(this.htEditor.getSelected()){
  5667. //if (Handsontable.tmpHandsontable(this.htContainer,'getSelected')) {
  5668. //if (this.$htContainer.handsontable('getSelected')) {
  5669. // var value = this.$htContainer.handsontable('getInstance').getValue();
  5670. var value = this.htEditor.getInstance().getValue();
  5671. //var value = Handsontable.tmpHandsontable(this.htContainer,'getInstance').getValue();
  5672. if (value !== void 0) { //if the value is undefined then it means we don't want to set the value
  5673. this.setValue(value);
  5674. }
  5675. }
  5676. return Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments);
  5677. };
  5678. HandsontableEditor.prototype.assignHooks = function () {
  5679. var that = this;
  5680. this.instance.addHook('afterDestroy', function () {
  5681. if (that.htEditor) {
  5682. that.htEditor.destroy();
  5683. }
  5684. });
  5685. };
  5686. Handsontable.editors.HandsontableEditor = HandsontableEditor;
  5687. Handsontable.editors.registerEditor('handsontable', HandsontableEditor);
  5688. })(Handsontable);
  5689. (function (Handsontable) {
  5690. var AutocompleteEditor = Handsontable.editors.HandsontableEditor.prototype.extend();
  5691. AutocompleteEditor.prototype.init = function () {
  5692. Handsontable.editors.HandsontableEditor.prototype.init.apply(this, arguments);
  5693. // set choices list initial height, so Walkontable can assign it's scroll handler
  5694. var choicesListHot = this.htEditor.getInstance();
  5695. choicesListHot.updateSettings({
  5696. height: 1
  5697. });
  5698. this.query = null;
  5699. this.choices = [];
  5700. };
  5701. AutocompleteEditor.prototype.createElements = function(){
  5702. Handsontable.editors.HandsontableEditor.prototype.createElements.apply(this, arguments);
  5703. var getSystemSpecificPaddingClass = function () {
  5704. if(window.navigator.platform.indexOf('Mac') != -1) {
  5705. return "htMacScroll";
  5706. } else {
  5707. return "";
  5708. }
  5709. };
  5710. Handsontable.Dom.addClass(this.htContainer, 'autocompleteEditor');
  5711. Handsontable.Dom.addClass(this.htContainer, getSystemSpecificPaddingClass());
  5712. //this.$htContainer.addClass('autocompleteEditor');
  5713. //this.$htContainer.addClass(getSystemSpecificPaddingClass());
  5714. };
  5715. var skipOne = false;
  5716. var onBeforeKeyDown = function (event) {
  5717. skipOne = false;
  5718. var editor = this.getActiveEditor();
  5719. var keyCodes = Handsontable.helper.keyCode;
  5720. if (Handsontable.helper.isPrintableChar(event.keyCode) || event.keyCode === keyCodes.BACKSPACE || event.keyCode === keyCodes.DELETE || event.keyCode === keyCodes.INSERT) {
  5721. var timeOffset = 0;
  5722. // on ctl+c / cmd+c don't update suggestion list
  5723. if(event.keyCode === keyCodes.C && (event.ctrlKey || event.metaKey)) {
  5724. return;
  5725. }
  5726. if(!editor.isOpened()) {
  5727. timeOffset += 10;
  5728. }
  5729. editor.instance._registerTimeout(setTimeout(function () {
  5730. editor.queryChoices(editor.TEXTAREA.value);
  5731. skipOne = true;
  5732. }, timeOffset));
  5733. }
  5734. };
  5735. AutocompleteEditor.prototype.prepare = function () {
  5736. this.instance.addHook('beforeKeyDown', onBeforeKeyDown);
  5737. Handsontable.editors.HandsontableEditor.prototype.prepare.apply(this, arguments);
  5738. };
  5739. AutocompleteEditor.prototype.open = function () {
  5740. Handsontable.editors.HandsontableEditor.prototype.open.apply(this, arguments);
  5741. this.TEXTAREA.style.visibility = 'visible';
  5742. this.focus();
  5743. var choicesListHot = this.htEditor.getInstance();
  5744. var that = this;
  5745. choicesListHot.updateSettings({
  5746. 'colWidths': [Handsontable.Dom.outerWidth(this.TEXTAREA) - 2],
  5747. afterRenderer: function (TD, row, col, prop, value) {
  5748. var caseSensitive = this.getCellMeta(row, col).filteringCaseSensitive === true;
  5749. if(value){
  5750. var indexOfMatch = caseSensitive ? value.indexOf(this.query) : value.toLowerCase().indexOf(that.query.toLowerCase());
  5751. if(indexOfMatch != -1){
  5752. var match = value.substr(indexOfMatch, that.query.length);
  5753. TD.innerHTML = value.replace(match, '<strong>' + match + '</strong>');
  5754. }
  5755. }
  5756. }
  5757. });
  5758. if(skipOne) {
  5759. skipOne = false;
  5760. }
  5761. that.instance._registerTimeout(setTimeout(function () {
  5762. that.queryChoices(that.TEXTAREA.value);
  5763. }, 0));
  5764. };
  5765. AutocompleteEditor.prototype.close = function () {
  5766. Handsontable.editors.HandsontableEditor.prototype.close.apply(this, arguments);
  5767. };
  5768. AutocompleteEditor.prototype.queryChoices = function(query){
  5769. this.query = query;
  5770. if (typeof this.cellProperties.source == 'function'){
  5771. var that = this;
  5772. this.cellProperties.source(query, function(choices){
  5773. that.updateChoicesList(choices);
  5774. });
  5775. } else if (Array.isArray(this.cellProperties.source)) {
  5776. var choices;
  5777. if(!query || this.cellProperties.filter === false){
  5778. choices = this.cellProperties.source;
  5779. } else {
  5780. var filteringCaseSensitive = this.cellProperties.filteringCaseSensitive === true;
  5781. var lowerCaseQuery = query.toLowerCase();
  5782. choices = this.cellProperties.source.filter(function(choice){
  5783. if (filteringCaseSensitive) {
  5784. return choice.indexOf(query) != -1;
  5785. } else {
  5786. return choice.toLowerCase().indexOf(lowerCaseQuery) != -1;
  5787. }
  5788. });
  5789. }
  5790. this.updateChoicesList(choices);
  5791. } else {
  5792. this.updateChoicesList([]);
  5793. }
  5794. };
  5795. AutocompleteEditor.prototype.updateChoicesList = function (choices) {
  5796. var pos = Handsontable.Dom.getCaretPosition(this.TEXTAREA),
  5797. endPos = Handsontable.Dom.getSelectionEndPosition(this.TEXTAREA);
  5798. var orderByRelevance = AutocompleteEditor.sortByRelevance(this.getValue(), choices, this.cellProperties.filteringCaseSensitive);
  5799. var highlightIndex;
  5800. if (this.cellProperties.filter != false) {
  5801. var sorted = [];
  5802. for(var i = 0, choicesCount = orderByRelevance.length; i < choicesCount; i++) {
  5803. sorted.push(choices[orderByRelevance[i]]);
  5804. }
  5805. highlightIndex = 0;
  5806. choices = sorted;
  5807. }
  5808. else {
  5809. highlightIndex = orderByRelevance[0];
  5810. }
  5811. this.choices = choices;
  5812. this.htEditor.loadData(Handsontable.helper.pivot([choices]));
  5813. this.htEditor.updateSettings({height: this.getDropdownHeight()});
  5814. //Handsontable.tmpHandsontable(this.htContainer,'loadData', Handsontable.helper.pivot([choices]));
  5815. //Handsontable.tmpHandsontable(this.htContainer,'updateSettings', {height: this.getDropdownHeight()});
  5816. if (this.cellProperties.strict === true) {
  5817. this.highlightBestMatchingChoice(highlightIndex);
  5818. }
  5819. this.instance.listen();
  5820. this.TEXTAREA.focus();
  5821. Handsontable.Dom.setCaretPosition(this.TEXTAREA, pos, (pos != endPos ? endPos : void 0));
  5822. };
  5823. AutocompleteEditor.prototype.finishEditing = function (restoreOriginalValue) {
  5824. if (!restoreOriginalValue) {
  5825. this.instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  5826. }
  5827. Handsontable.editors.HandsontableEditor.prototype.finishEditing.apply(this, arguments);
  5828. };
  5829. AutocompleteEditor.prototype.highlightBestMatchingChoice = function (index) {
  5830. if (typeof index === "number") {
  5831. this.htEditor.selectCell(index, 0);
  5832. } else {
  5833. this.htEditor.deselectCell();
  5834. }
  5835. };
  5836. /**
  5837. * Filters and sorts by relevance
  5838. * @param value
  5839. * @param choices
  5840. * @param caseSensitive
  5841. * @returns {Array} array of indexes in original choices array
  5842. */
  5843. AutocompleteEditor.sortByRelevance = function(value, choices, caseSensitive) {
  5844. var choicesRelevance = []
  5845. , currentItem
  5846. , valueLength = value.length
  5847. , valueIndex
  5848. , charsLeft
  5849. , result = []
  5850. , i
  5851. , choicesCount;
  5852. if(valueLength === 0) {
  5853. for(i = 0, choicesCount = choices.length; i < choicesCount; i++) {
  5854. result.push(i);
  5855. }
  5856. return result;
  5857. }
  5858. for(i = 0, choicesCount = choices.length; i < choicesCount; i++) {
  5859. currentItem = choices[i];
  5860. if(caseSensitive) {
  5861. valueIndex = currentItem.indexOf(value);
  5862. } else {
  5863. valueIndex = currentItem.toLowerCase().indexOf(value.toLowerCase());
  5864. }
  5865. if(valueIndex == -1) { continue; }
  5866. charsLeft = currentItem.length - valueIndex - valueLength;
  5867. choicesRelevance.push({
  5868. baseIndex: i,
  5869. index: valueIndex,
  5870. charsLeft: charsLeft,
  5871. value: currentItem
  5872. });
  5873. }
  5874. choicesRelevance.sort(function(a, b) {
  5875. if(b.index === -1) return -1;
  5876. if(a.index === -1) return 1;
  5877. if(a.index < b.index) {
  5878. return -1;
  5879. } else if(b.index < a.index) {
  5880. return 1;
  5881. } else if(a.index === b.index) {
  5882. if(a.charsLeft < b.charsLeft) {
  5883. return -1;
  5884. } else if(a.charsLeft > b.charsLeft) {
  5885. return 1;
  5886. } else {
  5887. return 0;
  5888. }
  5889. }
  5890. });
  5891. for(i = 0, choicesCount = choicesRelevance.length; i < choicesCount; i++) {
  5892. result.push(choicesRelevance[i].baseIndex);
  5893. }
  5894. return result;
  5895. };
  5896. AutocompleteEditor.prototype.getDropdownHeight = function(){
  5897. //var firstRowHeight = this.$htContainer.handsontable('getInstance').getRowHeight(0) || 23;
  5898. var firstRowHeight = this.htEditor.getInstance().getRowHeight(0) || 23;
  5899. //var firstRowHeight = Handsontable.tmpHandsontable(this.htContainer,'getInstance').getRowHeight(0) || 23;
  5900. return this.choices.length >= 10 ? 10 * firstRowHeight : this.choices.length * firstRowHeight + 8;
  5901. //return 10 * this.$htContainer.handsontable('getInstance').getRowHeight(0);
  5902. //sorry, we can't measure row height before it was rendered. Let's use fixed height for now
  5903. return 230;
  5904. };
  5905. Handsontable.editors.AutocompleteEditor = AutocompleteEditor;
  5906. Handsontable.editors.registerEditor('autocomplete', AutocompleteEditor);
  5907. })(Handsontable);
  5908. (function(Handsontable){
  5909. var PasswordEditor = Handsontable.editors.TextEditor.prototype.extend();
  5910. PasswordEditor.prototype.createElements = function () {
  5911. Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
  5912. this.TEXTAREA = document.createElement('input');
  5913. this.TEXTAREA.setAttribute('type', 'password');
  5914. this.TEXTAREA.className = 'handsontableInput';
  5915. this.textareaStyle = this.TEXTAREA.style;
  5916. this.textareaStyle.width = 0;
  5917. this.textareaStyle.height = 0;
  5918. Handsontable.Dom.empty(this.TEXTAREA_PARENT);
  5919. this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
  5920. };
  5921. Handsontable.editors.PasswordEditor = PasswordEditor;
  5922. Handsontable.editors.registerEditor('password', PasswordEditor);
  5923. })(Handsontable);
  5924. (function (Handsontable) {
  5925. var SelectEditor = Handsontable.editors.BaseEditor.prototype.extend();
  5926. SelectEditor.prototype.init = function(){
  5927. this.select = document.createElement('SELECT');
  5928. Handsontable.Dom.addClass(this.select, 'htSelectEditor');
  5929. this.select.style.display = 'none';
  5930. this.instance.rootElement.appendChild(this.select);
  5931. };
  5932. SelectEditor.prototype.prepare = function(){
  5933. Handsontable.editors.BaseEditor.prototype.prepare.apply(this, arguments);
  5934. var selectOptions = this.cellProperties.selectOptions;
  5935. var options;
  5936. if (typeof selectOptions == 'function'){
  5937. options = this.prepareOptions(selectOptions(this.row, this.col, this.prop))
  5938. } else {
  5939. options = this.prepareOptions(selectOptions);
  5940. }
  5941. Handsontable.Dom.empty(this.select);
  5942. for (var option in options){
  5943. if (options.hasOwnProperty(option)){
  5944. var optionElement = document.createElement('OPTION');
  5945. optionElement.value = option;
  5946. Handsontable.Dom.fastInnerHTML(optionElement, options[option]);
  5947. this.select.appendChild(optionElement);
  5948. }
  5949. }
  5950. };
  5951. SelectEditor.prototype.prepareOptions = function(optionsToPrepare){
  5952. var preparedOptions = {};
  5953. if (Array.isArray(optionsToPrepare)){
  5954. for(var i = 0, len = optionsToPrepare.length; i < len; i++){
  5955. preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i];
  5956. }
  5957. }
  5958. else if (typeof optionsToPrepare == 'object') {
  5959. preparedOptions = optionsToPrepare;
  5960. }
  5961. return preparedOptions;
  5962. };
  5963. SelectEditor.prototype.getValue = function () {
  5964. return this.select.value;
  5965. };
  5966. SelectEditor.prototype.setValue = function (value) {
  5967. this.select.value = value;
  5968. };
  5969. var onBeforeKeyDown = function (event) {
  5970. var instance = this;
  5971. var editor = instance.getActiveEditor();
  5972. switch (event.keyCode){
  5973. case Handsontable.helper.keyCode.ARROW_UP:
  5974. var previousOption = editor.select.find('option:selected').prev();
  5975. if (previousOption.length == 1){
  5976. previousOption.prop('selected', true);
  5977. }
  5978. event.stopImmediatePropagation();
  5979. event.preventDefault();
  5980. break;
  5981. case Handsontable.helper.keyCode.ARROW_DOWN:
  5982. var nextOption = editor.select.find('option:selected').next();
  5983. if (nextOption.length == 1){
  5984. nextOption.prop('selected', true);
  5985. }
  5986. event.stopImmediatePropagation();
  5987. event.preventDefault();
  5988. break;
  5989. }
  5990. };
  5991. // TODO: Refactor this with the use of new getCell() after 0.12.1
  5992. SelectEditor.prototype.checkEditorSection = function () {
  5993. if(this.row < this.instance.getSettings().fixedRowsTop) {
  5994. if(this.col < this.instance.getSettings().fixedColumnsLeft) {
  5995. return 'corner';
  5996. } else {
  5997. return 'top';
  5998. }
  5999. } else {
  6000. if(this.col < this.instance.getSettings().fixedColumnsLeft) {
  6001. return 'left';
  6002. }
  6003. }
  6004. };
  6005. SelectEditor.prototype.open = function () {
  6006. var width = Handsontable.Dom.outerWidth(this.TD); //important - group layout reads together for better performance
  6007. var height = Handsontable.Dom.outerHeight(this.TD);
  6008. var rootOffset = Handsontable.Dom.offset(this.instance.rootElement);
  6009. var tdOffset = Handsontable.Dom.offset(this.TD);
  6010. var editorSection = this.checkEditorSection();
  6011. var cssTransformOffset;
  6012. switch(editorSection) {
  6013. case 'top':
  6014. cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.vertical.clone.wtTable.holder.parentNode);
  6015. break;
  6016. case 'left':
  6017. cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.horizontal.clone.wtTable.holder.parentNode);
  6018. break;
  6019. case 'corner':
  6020. cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.corner.clone.wtTable.holder.parentNode);
  6021. break;
  6022. }
  6023. var selectStyle = this.select.style;
  6024. if(cssTransformOffset && cssTransformOffset != -1) {
  6025. selectStyle[cssTransformOffset[0]] = cssTransformOffset[1];
  6026. } else {
  6027. Handsontable.Dom.resetCssTransform(this.select);
  6028. }
  6029. selectStyle.height = height + 'px';
  6030. selectStyle.minWidth = width + 'px';
  6031. selectStyle.top = tdOffset.top - rootOffset.top + 'px';
  6032. selectStyle.left = tdOffset.left - rootOffset.left + 'px';
  6033. selectStyle.margin = '0px';
  6034. selectStyle.display = '';
  6035. this.instance.addHook('beforeKeyDown', onBeforeKeyDown);
  6036. };
  6037. SelectEditor.prototype.close = function () {
  6038. this.select.style.display = 'none';
  6039. this.instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  6040. };
  6041. SelectEditor.prototype.focus = function () {
  6042. this.select.focus();
  6043. };
  6044. Handsontable.editors.SelectEditor = SelectEditor;
  6045. Handsontable.editors.registerEditor('select', SelectEditor);
  6046. })(Handsontable);
  6047. (function (Handsontable) {
  6048. var DropdownEditor = Handsontable.editors.AutocompleteEditor.prototype.extend();
  6049. DropdownEditor.prototype.prepare = function () {
  6050. Handsontable.editors.AutocompleteEditor.prototype.prepare.apply(this, arguments);
  6051. this.cellProperties.filter = false;
  6052. this.cellProperties.strict = true;
  6053. };
  6054. Handsontable.editors.DropdownEditor = DropdownEditor;
  6055. Handsontable.editors.registerEditor('dropdown', DropdownEditor);
  6056. })(Handsontable);
  6057. (function (Handsontable) {
  6058. 'use strict';
  6059. var NumericEditor = Handsontable.editors.TextEditor.prototype.extend();
  6060. NumericEditor.prototype.beginEditing = function (initialValue) {
  6061. var BaseEditor = Handsontable.editors.TextEditor.prototype;
  6062. if (typeof (initialValue) === 'undefined' && this.originalValue) {
  6063. var value = '' + this.originalValue;
  6064. if (typeof this.cellProperties.language !== 'undefined') {
  6065. numeral.language(this.cellProperties.language)
  6066. }
  6067. var decimalDelimiter = numeral.languageData().delimiters.decimal;
  6068. value = value.replace('.', decimalDelimiter);
  6069. BaseEditor.beginEditing.apply(this, [value]);
  6070. } else {
  6071. BaseEditor.beginEditing.apply(this, arguments);
  6072. }
  6073. };
  6074. Handsontable.editors.NumericEditor = NumericEditor;
  6075. Handsontable.editors.registerEditor('numeric', NumericEditor);
  6076. })(Handsontable);
  6077. /**
  6078. * Numeric cell validator
  6079. * @param {*} value - Value of edited cell
  6080. * @param {*} callback - Callback called with validation result
  6081. */
  6082. Handsontable.NumericValidator = function (value, callback) {
  6083. if (value === null) {
  6084. value = '';
  6085. }
  6086. callback(/^-?\d*(\.|\,)?\d*$/.test(value));
  6087. };
  6088. /**
  6089. * Function responsible for validation of autocomplete value
  6090. * @param {*} value - Value of edited cell
  6091. * @param {*} calback - Callback called with validation result
  6092. */
  6093. var process = function (value, callback) {
  6094. var originalVal = value;
  6095. var lowercaseVal = typeof originalVal === 'string' ? originalVal.toLowerCase() : null;
  6096. return function (source) {
  6097. var found = false;
  6098. for (var s = 0, slen = source.length; s < slen; s++) {
  6099. if (originalVal === source[s]) {
  6100. found = true; //perfect match
  6101. break;
  6102. }
  6103. else if (lowercaseVal === source[s].toLowerCase()) {
  6104. // changes[i][3] = source[s]; //good match, fix the case << TODO?
  6105. found = true;
  6106. break;
  6107. }
  6108. }
  6109. callback(found);
  6110. }
  6111. };
  6112. /**
  6113. * Autocomplete cell validator
  6114. * @param {*} value - Value of edited cell
  6115. * @param {*} calback - Callback called with validation result
  6116. */
  6117. Handsontable.AutocompleteValidator = function (value, callback) {
  6118. if (this.strict && this.source) {
  6119. typeof this.source === 'function' ? this.source(value, process(value, callback)) : process(value, callback)(this.source);
  6120. } else {
  6121. callback(true);
  6122. }
  6123. };
  6124. /**
  6125. * Cell type is just a shortcut for setting bunch of cellProperties (used in getCellMeta)
  6126. */
  6127. Handsontable.mobileBrowser = Handsontable.helper.isMobileBrowser(); // check if viewed on a mobile device
  6128. Handsontable.AutocompleteCell = {
  6129. editor: Handsontable.editors.AutocompleteEditor,
  6130. renderer: Handsontable.renderers.AutocompleteRenderer,
  6131. validator: Handsontable.AutocompleteValidator
  6132. };
  6133. Handsontable.CheckboxCell = {
  6134. editor: Handsontable.editors.CheckboxEditor,
  6135. renderer: Handsontable.renderers.CheckboxRenderer
  6136. };
  6137. Handsontable.TextCell = {
  6138. editor: Handsontable.mobileBrowser ? Handsontable.editors.MobileTextEditor : Handsontable.editors.TextEditor,
  6139. renderer: Handsontable.renderers.TextRenderer
  6140. };
  6141. Handsontable.NumericCell = {
  6142. editor: Handsontable.editors.NumericEditor,
  6143. renderer: Handsontable.renderers.NumericRenderer,
  6144. validator: Handsontable.NumericValidator,
  6145. dataType: 'number'
  6146. };
  6147. Handsontable.DateCell = {
  6148. editor: Handsontable.editors.DateEditor,
  6149. renderer: Handsontable.renderers.AutocompleteRenderer //displays small gray arrow on right side of the cell
  6150. };
  6151. Handsontable.HandsontableCell = {
  6152. editor: Handsontable.editors.HandsontableEditor,
  6153. renderer: Handsontable.renderers.AutocompleteRenderer //displays small gray arrow on right side of the cell
  6154. };
  6155. Handsontable.PasswordCell = {
  6156. editor: Handsontable.editors.PasswordEditor,
  6157. renderer: Handsontable.renderers.PasswordRenderer,
  6158. copyable: false
  6159. };
  6160. Handsontable.DropdownCell = {
  6161. editor: Handsontable.editors.DropdownEditor,
  6162. renderer: Handsontable.renderers.AutocompleteRenderer, //displays small gray arrow on right side of the cell
  6163. validator: Handsontable.AutocompleteValidator
  6164. };
  6165. //here setup the friendly aliases that are used by cellProperties.type
  6166. Handsontable.cellTypes = {
  6167. text: Handsontable.TextCell,
  6168. date: Handsontable.DateCell,
  6169. numeric: Handsontable.NumericCell,
  6170. checkbox: Handsontable.CheckboxCell,
  6171. autocomplete: Handsontable.AutocompleteCell,
  6172. handsontable: Handsontable.HandsontableCell,
  6173. password: Handsontable.PasswordCell,
  6174. dropdown: Handsontable.DropdownCell
  6175. };
  6176. //here setup the friendly aliases that are used by cellProperties.renderer and cellProperties.editor
  6177. Handsontable.cellLookup = {
  6178. validator: {
  6179. numeric: Handsontable.NumericValidator,
  6180. autocomplete: Handsontable.AutocompleteValidator
  6181. }
  6182. };
  6183. /**
  6184. * autoResize - resizes a DOM element to the width and height of another DOM element
  6185. *
  6186. * Copyright 2014, Marcin Warpechowski
  6187. * Licensed under the MIT license
  6188. */
  6189. var autoResize = function () {
  6190. var defaults = {
  6191. minHeight: 200,
  6192. maxHeight: 300,
  6193. minWidth: 100,
  6194. maxWidth: 300
  6195. },
  6196. el,
  6197. body = document.body,
  6198. text = document.createTextNode(''),
  6199. span = document.createElement('SPAN'),
  6200. observe = function (element, event, handler) {
  6201. if (window.attachEvent) {
  6202. element.attachEvent('on' + event, handler);
  6203. } else {
  6204. element.addEventListener(event, handler, false);
  6205. }
  6206. },
  6207. unObserve = function (element, event, handler) {
  6208. if (window.removeEventListener) {
  6209. element.removeEventListener(event, handler, false);
  6210. } else {
  6211. element.detachEvent('on' + event, handler);
  6212. }
  6213. },
  6214. resize = function (newChar) {
  6215. if(!newChar) {
  6216. newChar = "";
  6217. } else if (!/^[a-zA-Z \.,\\\/\|0-9]$/.test(newChar)) {
  6218. newChar = ".";
  6219. }
  6220. if (text.textContent !== void 0) {
  6221. text.textContent = el.value + newChar;
  6222. }
  6223. else {
  6224. text.data = el.value + newChar; //IE8
  6225. }
  6226. span.style.fontSize = Handsontable.Dom.getComputedStyle(el).fontSize;
  6227. span.style.fontFamily = Handsontable.Dom.getComputedStyle(el).fontFamily;
  6228. span.style['white-space'] = "pre";
  6229. body.appendChild(span);
  6230. var width = span.clientWidth + 2;
  6231. body.removeChild(span);
  6232. el.style.height = defaults.minHeight + 'px';
  6233. if (defaults.minWidth > width) {
  6234. el.style.width = defaults.minWidth + 'px';
  6235. } else if (width > defaults.maxWidth) {
  6236. el.style.width = defaults.maxWidth + 'px';
  6237. } else {
  6238. el.style.width = width + 'px';
  6239. }
  6240. var scrollHeight = el.scrollHeight;
  6241. if (defaults.minHeight > scrollHeight) {
  6242. el.style.height = defaults.minHeight + 'px';
  6243. } else if (defaults.maxHeight < scrollHeight) {
  6244. el.style.height = defaults.maxHeight + 'px';
  6245. el.style.overflowY = 'visible';
  6246. } else {
  6247. el.style.height = scrollHeight + 'px';
  6248. }
  6249. },
  6250. delayedResize = function () {
  6251. window.setTimeout(resize, 0);
  6252. },
  6253. extendDefaults = function (config) {
  6254. if (config && config.minHeight) {
  6255. if (config.minHeight == 'inherit') {
  6256. defaults.minHeight = el.clientHeight;
  6257. } else {
  6258. var minHeight = parseInt(config.minHeight);
  6259. if (!isNaN(minHeight)) {
  6260. defaults.minHeight = minHeight
  6261. }
  6262. }
  6263. }
  6264. if (config && config.maxHeight) {
  6265. if (config.maxHeight == 'inherit') {
  6266. defaults.maxHeight = el.clientHeight;
  6267. } else {
  6268. var maxHeight = parseInt(config.maxHeight);
  6269. if (!isNaN(maxHeight)) {
  6270. defaults.maxHeight = maxHeight
  6271. }
  6272. }
  6273. }
  6274. if (config && config.minWidth) {
  6275. if (config.minWidth == 'inherit') {
  6276. defaults.minWidth = el.clientWidth;
  6277. } else {
  6278. var minWidth = parseInt(config.minWidth);
  6279. if (!isNaN(minWidth)) {
  6280. defaults.minWidth = minWidth
  6281. }
  6282. }
  6283. }
  6284. if (config && config.maxWidth) {
  6285. if (config.maxWidth == 'inherit') {
  6286. defaults.maxWidth = el.clientWidth;
  6287. } else {
  6288. var maxWidth = parseInt(config.maxWidth);
  6289. if (!isNaN(maxWidth)) {
  6290. defaults.maxWidth = maxWidth
  6291. }
  6292. }
  6293. }
  6294. if(!span.firstChild) {
  6295. span.className = "autoResize";
  6296. span.style.display = 'inline-block';
  6297. span.appendChild(text);
  6298. }
  6299. },
  6300. init = function (el_, config, doObserve) {
  6301. el = el_;
  6302. extendDefaults(config);
  6303. if (el.nodeName == 'TEXTAREA') {
  6304. el.style.resize = 'none';
  6305. el.style.overflowY = '';
  6306. el.style.height = defaults.minHeight + 'px';
  6307. el.style.minWidth = defaults.minWidth + 'px';
  6308. el.style.maxWidth = defaults.maxWidth + 'px';
  6309. el.style.overflowY = 'hidden';
  6310. }
  6311. if(doObserve) {
  6312. observe(el, 'change', resize);
  6313. observe(el, 'cut', delayedResize);
  6314. observe(el, 'paste', delayedResize);
  6315. observe(el, 'drop', delayedResize);
  6316. observe(el, 'keydown', delayedResize);
  6317. }
  6318. resize();
  6319. };
  6320. return {
  6321. init: function (el_, config, doObserve) {
  6322. init(el_, config, doObserve);
  6323. },
  6324. unObserve: function () {
  6325. unObserve(el, 'change', resize);
  6326. unObserve(el, 'cut', delayedResize);
  6327. unObserve(el, 'paste', delayedResize);
  6328. unObserve(el, 'drop', delayedResize);
  6329. unObserve(el, 'keydown', delayedResize);
  6330. },
  6331. resize: resize
  6332. }
  6333. };
  6334. /**
  6335. * SheetClip - Spreadsheet Clipboard Parser
  6336. * version 0.2
  6337. *
  6338. * This tiny library transforms JavaScript arrays to strings that are pasteable by LibreOffice, OpenOffice,
  6339. * Google Docs and Microsoft Excel.
  6340. *
  6341. * Copyright 2012, Marcin Warpechowski
  6342. * Licensed under the MIT license.
  6343. * http://github.com/warpech/sheetclip/
  6344. */
  6345. /*jslint white: true*/
  6346. (function (global) {
  6347. "use strict";
  6348. function countQuotes(str) {
  6349. return str.split('"').length - 1;
  6350. }
  6351. global.SheetClip = {
  6352. parse: function (str) {
  6353. var r, rlen, rows, arr = [], a = 0, c, clen, multiline, last;
  6354. rows = str.split('\n');
  6355. if (rows.length > 1 && rows[rows.length - 1] === '') {
  6356. rows.pop();
  6357. }
  6358. for (r = 0, rlen = rows.length; r < rlen; r += 1) {
  6359. rows[r] = rows[r].split('\t');
  6360. for (c = 0, clen = rows[r].length; c < clen; c += 1) {
  6361. if (!arr[a]) {
  6362. arr[a] = [];
  6363. }
  6364. if (multiline && c === 0) {
  6365. last = arr[a].length - 1;
  6366. arr[a][last] = arr[a][last] + '\n' + rows[r][0];
  6367. if (multiline && (countQuotes(rows[r][0]) & 1)) { //& 1 is a bitwise way of performing mod 2
  6368. multiline = false;
  6369. arr[a][last] = arr[a][last].substring(0, arr[a][last].length - 1).replace(/""/g, '"');
  6370. }
  6371. }
  6372. else {
  6373. if (c === clen - 1 && rows[r][c].indexOf('"') === 0) {
  6374. arr[a].push(rows[r][c].substring(1).replace(/""/g, '"'));
  6375. multiline = true;
  6376. }
  6377. else {
  6378. arr[a].push(rows[r][c].replace(/""/g, '"'));
  6379. multiline = false;
  6380. }
  6381. }
  6382. }
  6383. if (!multiline) {
  6384. a += 1;
  6385. }
  6386. }
  6387. return arr;
  6388. },
  6389. stringify: function (arr) {
  6390. var r, rlen, c, clen, str = '', val;
  6391. for (r = 0, rlen = arr.length; r < rlen; r += 1) {
  6392. for (c = 0, clen = arr[r].length; c < clen; c += 1) {
  6393. if (c > 0) {
  6394. str += '\t';
  6395. }
  6396. val = arr[r][c];
  6397. if (typeof val === 'string') {
  6398. if (val.indexOf('\n') > -1) {
  6399. str += '"' + val.replace(/"/g, '""') + '"';
  6400. }
  6401. else {
  6402. str += val;
  6403. }
  6404. }
  6405. else if (val === null || val === void 0) { //void 0 resolves to undefined
  6406. str += '';
  6407. }
  6408. else {
  6409. str += val;
  6410. }
  6411. }
  6412. if (r < rlen - 1) {
  6413. str += '\n';
  6414. }
  6415. }
  6416. return str;
  6417. }
  6418. };
  6419. }(window));
  6420. /**
  6421. * CopyPaste.js
  6422. * Creates a textarea that stays hidden on the page and gets focused when user presses CTRL while not having a form input focused
  6423. * In future we may implement a better driver when better APIs are available
  6424. * @constructor
  6425. */
  6426. var CopyPaste = (function () {
  6427. var instance;
  6428. return {
  6429. getInstance: function () {
  6430. if (!instance) {
  6431. instance = new CopyPasteClass();
  6432. } else if (instance.hasBeenDestroyed()){
  6433. instance.init();
  6434. }
  6435. instance.refCounter++;
  6436. return instance;
  6437. }
  6438. };
  6439. })();
  6440. function CopyPasteClass() {
  6441. this.refCounter = 0;
  6442. this.init();
  6443. }
  6444. CopyPasteClass.prototype.init = function () {
  6445. var that = this
  6446. , style
  6447. , parent;
  6448. this.copyCallbacks = [];
  6449. this.cutCallbacks = [];
  6450. this.pasteCallbacks = [];
  6451. this._eventManager = Handsontable.eventManager(this);
  6452. // this.listenerElement = document.documentElement;
  6453. parent = document.body;
  6454. if (document.getElementById('CopyPasteDiv')) {
  6455. this.elDiv = document.getElementById('CopyPasteDiv');
  6456. this.elTextarea = this.elDiv.firstChild;
  6457. }
  6458. else {
  6459. this.elDiv = document.createElement('DIV');
  6460. this.elDiv.id = 'CopyPasteDiv';
  6461. style = this.elDiv.style;
  6462. style.position = 'fixed';
  6463. style.top = '-10000px';
  6464. style.left = '-10000px';
  6465. parent.appendChild(this.elDiv);
  6466. this.elTextarea = document.createElement('TEXTAREA');
  6467. this.elTextarea.className = 'copyPaste';
  6468. this.elTextarea.onpaste = function (event) {
  6469. if('WebkitAppearance' in document.documentElement.style) { // chrome and safari
  6470. this.value = event.clipboardData.getData("Text");
  6471. return false;
  6472. }
  6473. };
  6474. style = this.elTextarea.style;
  6475. style.width = '10000px';
  6476. style.height = '10000px';
  6477. style.overflow = 'hidden';
  6478. this.elDiv.appendChild(this.elTextarea);
  6479. if (typeof style.opacity !== 'undefined') {
  6480. style.opacity = 0;
  6481. }
  6482. else {
  6483. /*@cc_on @if (@_jscript)
  6484. if(typeof style.filter === 'string') {
  6485. style.filter = 'alpha(opacity=0)';
  6486. }
  6487. @end @*/
  6488. }
  6489. }
  6490. this.keydownListener = function (event) {
  6491. var isCtrlDown = false;
  6492. if (event.metaKey) { //mac
  6493. isCtrlDown = true;
  6494. }
  6495. else if (event.ctrlKey && navigator.userAgent.indexOf('Mac') === -1) { //pc
  6496. isCtrlDown = true;
  6497. }
  6498. if (isCtrlDown) {
  6499. if (document.activeElement !== that.elTextarea && (that.getSelectionText() != '' || ['INPUT', 'SELECT', 'TEXTAREA'].indexOf(document.activeElement.nodeName) != -1)) {
  6500. return; //this is needed by fragmentSelection in Handsontable. Ignore copypaste.js behavior if fragment of cell text is selected
  6501. }
  6502. that.selectNodeText(that.elTextarea);
  6503. setTimeout(function () {
  6504. that.selectNodeText(that.elTextarea);
  6505. }, 0);
  6506. }
  6507. /* 67 = c
  6508. * 86 = v
  6509. * 88 = x
  6510. */
  6511. if (isCtrlDown && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88)) {
  6512. // that.selectNodeText(that.elTextarea);
  6513. if (event.keyCode === 88) { //works in all browsers, incl. Opera < 12.12
  6514. setTimeout(function () {
  6515. that.triggerCut(event);
  6516. }, 0);
  6517. }
  6518. else if (event.keyCode === 86) {
  6519. setTimeout(function () {
  6520. that.triggerPaste(event);
  6521. }, 0);
  6522. }
  6523. }
  6524. };
  6525. this._eventManager.addEventListener(document.documentElement,'keydown',this.keydownListener, false);
  6526. // this._bindEvent(this.listenerElement, 'keydown', this.keydownListener);
  6527. };
  6528. //http://jsperf.com/textara-selection
  6529. //http://stackoverflow.com/questions/1502385/how-can-i-make-this-code-work-in-ie
  6530. CopyPasteClass.prototype.selectNodeText = function (el) {
  6531. if (el) {
  6532. el.select();
  6533. }
  6534. };
  6535. //http://stackoverflow.com/questions/5379120/get-the-highlighted-selected-text
  6536. CopyPasteClass.prototype.getSelectionText = function () {
  6537. var text = "";
  6538. if (window.getSelection) {
  6539. text = window.getSelection().toString();
  6540. } else if (document.selection && document.selection.type != "Control") {
  6541. text = document.selection.createRange().text;
  6542. }
  6543. return text;
  6544. };
  6545. CopyPasteClass.prototype.copyable = function (str) {
  6546. if (typeof str !== 'string' && str.toString === void 0) {
  6547. throw new Error('copyable requires string parameter');
  6548. }
  6549. this.elTextarea.value = str;
  6550. };
  6551. /*CopyPasteClass.prototype.onCopy = function (fn) {
  6552. this.copyCallbacks.push(fn);
  6553. };*/
  6554. CopyPasteClass.prototype.onCut = function (fn) {
  6555. this.cutCallbacks.push(fn);
  6556. };
  6557. CopyPasteClass.prototype.onPaste = function (fn) {
  6558. this.pasteCallbacks.push(fn);
  6559. };
  6560. CopyPasteClass.prototype.removeCallback = function (fn) {
  6561. var i, ilen;
  6562. for (i = 0, ilen = this.copyCallbacks.length; i < ilen; i++) {
  6563. if (this.copyCallbacks[i] === fn) {
  6564. this.copyCallbacks.splice(i, 1);
  6565. return true;
  6566. }
  6567. }
  6568. for (i = 0, ilen = this.cutCallbacks.length; i < ilen; i++) {
  6569. if (this.cutCallbacks[i] === fn) {
  6570. this.cutCallbacks.splice(i, 1);
  6571. return true;
  6572. }
  6573. }
  6574. for (i = 0, ilen = this.pasteCallbacks.length; i < ilen; i++) {
  6575. if (this.pasteCallbacks[i] === fn) {
  6576. this.pasteCallbacks.splice(i, 1);
  6577. return true;
  6578. }
  6579. }
  6580. return false;
  6581. };
  6582. CopyPasteClass.prototype.triggerCut = function (event) {
  6583. var that = this;
  6584. if (that.cutCallbacks) {
  6585. setTimeout(function () {
  6586. for (var i = 0, ilen = that.cutCallbacks.length; i < ilen; i++) {
  6587. that.cutCallbacks[i](event);
  6588. }
  6589. }, 50);
  6590. }
  6591. };
  6592. CopyPasteClass.prototype.triggerPaste = function (event, str) {
  6593. var that = this;
  6594. if (that.pasteCallbacks) {
  6595. setTimeout(function () {
  6596. var val = (str || that.elTextarea.value).replace(/\n$/, ''); //remove trailing newline
  6597. for (var i = 0, ilen = that.pasteCallbacks.length; i < ilen; i++) {
  6598. that.pasteCallbacks[i](val, event);
  6599. }
  6600. }, 50);
  6601. }
  6602. };
  6603. CopyPasteClass.prototype.destroy = function () {
  6604. if(!this.hasBeenDestroyed() && --this.refCounter == 0){
  6605. if (this.elDiv && this.elDiv.parentNode) {
  6606. this.elDiv.parentNode.removeChild(this.elDiv);
  6607. this.elDiv = null;
  6608. this.elTextarea = null;
  6609. }
  6610. this._eventManager.removeEventListener(document.documentElement, 'keydown', this.keydownListener, false);
  6611. // this._unbindEvent(this.listenerElement, 'keydown', this.keydownListener);
  6612. }
  6613. };
  6614. CopyPasteClass.prototype.hasBeenDestroyed = function () {
  6615. return !this.refCounter;
  6616. };
  6617. // json-patch-duplex.js 0.3.6
  6618. // (c) 2013 Joachim Wester
  6619. // MIT license
  6620. var jsonpatch;
  6621. (function (jsonpatch) {
  6622. var objOps = {
  6623. add: function (obj, key) {
  6624. obj[key] = this.value;
  6625. return true;
  6626. },
  6627. remove: function (obj, key) {
  6628. delete obj[key];
  6629. return true;
  6630. },
  6631. replace: function (obj, key) {
  6632. obj[key] = this.value;
  6633. return true;
  6634. },
  6635. move: function (obj, key, tree) {
  6636. var temp = { op: "_get", path: this.from };
  6637. apply(tree, [temp]);
  6638. apply(tree, [
  6639. { op: "remove", path: this.from }
  6640. ]);
  6641. apply(tree, [
  6642. { op: "add", path: this.path, value: temp.value }
  6643. ]);
  6644. return true;
  6645. },
  6646. copy: function (obj, key, tree) {
  6647. var temp = { op: "_get", path: this.from };
  6648. apply(tree, [temp]);
  6649. apply(tree, [
  6650. { op: "add", path: this.path, value: temp.value }
  6651. ]);
  6652. return true;
  6653. },
  6654. test: function (obj, key) {
  6655. return (JSON.stringify(obj[key]) === JSON.stringify(this.value));
  6656. },
  6657. _get: function (obj, key) {
  6658. this.value = obj[key];
  6659. }
  6660. };
  6661. var arrOps = {
  6662. add: function (arr, i) {
  6663. arr.splice(i, 0, this.value);
  6664. return true;
  6665. },
  6666. remove: function (arr, i) {
  6667. arr.splice(i, 1);
  6668. return true;
  6669. },
  6670. replace: function (arr, i) {
  6671. arr[i] = this.value;
  6672. return true;
  6673. },
  6674. move: objOps.move,
  6675. copy: objOps.copy,
  6676. test: objOps.test,
  6677. _get: objOps._get
  6678. };
  6679. var observeOps = {
  6680. add: function (patches, path) {
  6681. var patch = {
  6682. op: "add",
  6683. path: path + escapePathComponent(this.name),
  6684. value: this.object[this.name]
  6685. };
  6686. patches.push(patch);
  6687. },
  6688. 'delete': function (patches, path) {
  6689. var patch = {
  6690. op: "remove",
  6691. path: path + escapePathComponent(this.name)
  6692. };
  6693. patches.push(patch);
  6694. },
  6695. update: function (patches, path) {
  6696. var patch = {
  6697. op: "replace",
  6698. path: path + escapePathComponent(this.name),
  6699. value: this.object[this.name]
  6700. };
  6701. patches.push(patch);
  6702. }
  6703. };
  6704. function escapePathComponent(str) {
  6705. if (str.indexOf('/') === -1 && str.indexOf('~') === -1)
  6706. return str;
  6707. return str.replace(/~/g, '~0').replace(/\//g, '~1');
  6708. }
  6709. function _getPathRecursive(root, obj) {
  6710. var found;
  6711. for (var key in root) {
  6712. if (root.hasOwnProperty(key)) {
  6713. if (root[key] === obj) {
  6714. return escapePathComponent(key) + '/';
  6715. } else if (typeof root[key] === 'object') {
  6716. found = _getPathRecursive(root[key], obj);
  6717. if (found != '') {
  6718. return escapePathComponent(key) + '/' + found;
  6719. }
  6720. }
  6721. }
  6722. }
  6723. return '';
  6724. }
  6725. function getPath(root, obj) {
  6726. if (root === obj) {
  6727. return '/';
  6728. }
  6729. var path = _getPathRecursive(root, obj);
  6730. if (path === '') {
  6731. throw new Error("Object not found in root");
  6732. }
  6733. return '/' + path;
  6734. }
  6735. var beforeDict = [];
  6736. jsonpatch.intervals;
  6737. var Mirror = (function () {
  6738. function Mirror(obj) {
  6739. this.observers = [];
  6740. this.obj = obj;
  6741. }
  6742. return Mirror;
  6743. })();
  6744. var ObserverInfo = (function () {
  6745. function ObserverInfo(callback, observer) {
  6746. this.callback = callback;
  6747. this.observer = observer;
  6748. }
  6749. return ObserverInfo;
  6750. })();
  6751. function getMirror(obj) {
  6752. for (var i = 0, ilen = beforeDict.length; i < ilen; i++) {
  6753. if (beforeDict[i].obj === obj) {
  6754. return beforeDict[i];
  6755. }
  6756. }
  6757. }
  6758. function getObserverFromMirror(mirror, callback) {
  6759. for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) {
  6760. if (mirror.observers[j].callback === callback) {
  6761. return mirror.observers[j].observer;
  6762. }
  6763. }
  6764. }
  6765. function removeObserverFromMirror(mirror, observer) {
  6766. for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) {
  6767. if (mirror.observers[j].observer === observer) {
  6768. mirror.observers.splice(j, 1);
  6769. return;
  6770. }
  6771. }
  6772. }
  6773. function unobserve(root, observer) {
  6774. generate(observer);
  6775. if (Object.observe) {
  6776. _unobserve(observer, root);
  6777. } else {
  6778. clearTimeout(observer.next);
  6779. }
  6780. var mirror = getMirror(root);
  6781. removeObserverFromMirror(mirror, observer);
  6782. }
  6783. jsonpatch.unobserve = unobserve;
  6784. function observe(obj, callback) {
  6785. var patches = [];
  6786. var root = obj;
  6787. var observer;
  6788. var mirror = getMirror(obj);
  6789. if (!mirror) {
  6790. mirror = new Mirror(obj);
  6791. beforeDict.push(mirror);
  6792. } else {
  6793. observer = getObserverFromMirror(mirror, callback);
  6794. }
  6795. if (observer) {
  6796. return observer;
  6797. }
  6798. if (Object.observe) {
  6799. observer = function (arr) {
  6800. //This "refresh" is needed to begin observing new object properties
  6801. _unobserve(observer, obj);
  6802. _observe(observer, obj);
  6803. var a = 0, alen = arr.length;
  6804. while (a < alen) {
  6805. if (!(arr[a].name === 'length' && _isArray(arr[a].object)) && !(arr[a].name === '__Jasmine_been_here_before__')) {
  6806. var type = arr[a].type;
  6807. switch (type) {
  6808. case 'new':
  6809. type = 'add';
  6810. break;
  6811. case 'deleted':
  6812. type = 'delete';
  6813. break;
  6814. case 'updated':
  6815. type = 'update';
  6816. break;
  6817. }
  6818. observeOps[type].call(arr[a], patches, getPath(root, arr[a].object));
  6819. }
  6820. a++;
  6821. }
  6822. if (patches) {
  6823. if (callback) {
  6824. callback(patches);
  6825. }
  6826. }
  6827. observer.patches = patches;
  6828. patches = [];
  6829. };
  6830. } else {
  6831. observer = {};
  6832. mirror.value = JSON.parse(JSON.stringify(obj));
  6833. if (callback) {
  6834. //callbacks.push(callback); this has no purpose
  6835. observer.callback = callback;
  6836. observer.next = null;
  6837. var intervals = this.intervals || [100, 1000, 10000, 60000];
  6838. var currentInterval = 0;
  6839. var dirtyCheck = function () {
  6840. generate(observer);
  6841. };
  6842. var fastCheck = function () {
  6843. clearTimeout(observer.next);
  6844. observer.next = setTimeout(function () {
  6845. dirtyCheck();
  6846. currentInterval = 0;
  6847. observer.next = setTimeout(slowCheck, intervals[currentInterval++]);
  6848. }, 0);
  6849. };
  6850. var slowCheck = function () {
  6851. dirtyCheck();
  6852. if (currentInterval == intervals.length)
  6853. currentInterval = intervals.length - 1;
  6854. observer.next = setTimeout(slowCheck, intervals[currentInterval++]);
  6855. };
  6856. if (typeof window !== 'undefined') {
  6857. if (window.addEventListener) {
  6858. window.addEventListener('mousedown', fastCheck);
  6859. window.addEventListener('mouseup', fastCheck);
  6860. window.addEventListener('keydown', fastCheck);
  6861. } else {
  6862. window.attachEvent('onmousedown', fastCheck);
  6863. window.attachEvent('onmouseup', fastCheck);
  6864. window.attachEvent('onkeydown', fastCheck);
  6865. }
  6866. }
  6867. observer.next = setTimeout(slowCheck, intervals[currentInterval++]);
  6868. }
  6869. }
  6870. observer.patches = patches;
  6871. observer.object = obj;
  6872. mirror.observers.push(new ObserverInfo(callback, observer));
  6873. return _observe(observer, obj);
  6874. }
  6875. jsonpatch.observe = observe;
  6876. /// Listen to changes on an object tree, accumulate patches
  6877. function _observe(observer, obj) {
  6878. if (Object.observe) {
  6879. Object.observe(obj, observer);
  6880. for (var key in obj) {
  6881. if (obj.hasOwnProperty(key)) {
  6882. var v = obj[key];
  6883. if (v && typeof (v) === "object") {
  6884. _observe(observer, v);
  6885. }
  6886. }
  6887. }
  6888. }
  6889. return observer;
  6890. }
  6891. function _unobserve(observer, obj) {
  6892. if (Object.observe) {
  6893. Object.unobserve(obj, observer);
  6894. for (var key in obj) {
  6895. if (obj.hasOwnProperty(key)) {
  6896. var v = obj[key];
  6897. if (v && typeof (v) === "object") {
  6898. _unobserve(observer, v);
  6899. }
  6900. }
  6901. }
  6902. }
  6903. return observer;
  6904. }
  6905. function generate(observer) {
  6906. if (Object.observe) {
  6907. Object.deliverChangeRecords(observer);
  6908. } else {
  6909. var mirror;
  6910. for (var i = 0, ilen = beforeDict.length; i < ilen; i++) {
  6911. if (beforeDict[i].obj === observer.object) {
  6912. mirror = beforeDict[i];
  6913. break;
  6914. }
  6915. }
  6916. _generate(mirror.value, observer.object, observer.patches, "");
  6917. }
  6918. var temp = observer.patches;
  6919. if (temp.length > 0) {
  6920. observer.patches = [];
  6921. if (observer.callback) {
  6922. observer.callback(temp);
  6923. }
  6924. }
  6925. return temp;
  6926. }
  6927. jsonpatch.generate = generate;
  6928. var _objectKeys;
  6929. if (Object.keys) {
  6930. _objectKeys = Object.keys;
  6931. } else {
  6932. _objectKeys = function (obj) {
  6933. var keys = [];
  6934. for (var o in obj) {
  6935. if (obj.hasOwnProperty(o)) {
  6936. keys.push(o);
  6937. }
  6938. }
  6939. return keys;
  6940. };
  6941. }
  6942. // Dirty check if obj is different from mirror, generate patches and update mirror
  6943. function _generate(mirror, obj, patches, path) {
  6944. var newKeys = _objectKeys(obj);
  6945. var oldKeys = _objectKeys(mirror);
  6946. var changed = false;
  6947. var deleted = false;
  6948. for (var t = oldKeys.length - 1; t >= 0; t--) {
  6949. var key = oldKeys[t];
  6950. var oldVal = mirror[key];
  6951. if (obj.hasOwnProperty(key)) {
  6952. var newVal = obj[key];
  6953. if (oldVal instanceof Object) {
  6954. _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key));
  6955. } else {
  6956. if (oldVal != newVal) {
  6957. changed = true;
  6958. patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: newVal });
  6959. mirror[key] = newVal;
  6960. }
  6961. }
  6962. } else {
  6963. patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) });
  6964. delete mirror[key];
  6965. deleted = true;
  6966. }
  6967. }
  6968. if (!deleted && newKeys.length == oldKeys.length) {
  6969. return;
  6970. }
  6971. for (var t = 0; t < newKeys.length; t++) {
  6972. var key = newKeys[t];
  6973. if (!mirror.hasOwnProperty(key)) {
  6974. patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: obj[key] });
  6975. mirror[key] = JSON.parse(JSON.stringify(obj[key]));
  6976. }
  6977. }
  6978. }
  6979. var _isArray;
  6980. if (Array.isArray) {
  6981. _isArray = Array.isArray;
  6982. } else {
  6983. _isArray = function (obj) {
  6984. return obj.push && typeof obj.length === 'number';
  6985. };
  6986. }
  6987. /// Apply a json-patch operation on an object tree
  6988. function apply(tree, patches) {
  6989. var result = false, p = 0, plen = patches.length, patch;
  6990. while (p < plen) {
  6991. patch = patches[p];
  6992. // Find the object
  6993. var keys = patch.path.split('/');
  6994. var obj = tree;
  6995. var t = 1;
  6996. var len = keys.length;
  6997. while (true) {
  6998. if (_isArray(obj)) {
  6999. var index = parseInt(keys[t], 10);
  7000. t++;
  7001. if (t >= len) {
  7002. result = arrOps[patch.op].call(patch, obj, index, tree);
  7003. break;
  7004. }
  7005. obj = obj[index];
  7006. } else {
  7007. var key = keys[t];
  7008. if (key.indexOf('~') != -1)
  7009. key = key.replace(/~1/g, '/').replace(/~0/g, '~');
  7010. t++;
  7011. if (t >= len) {
  7012. result = objOps[patch.op].call(patch, obj, key, tree);
  7013. break;
  7014. }
  7015. obj = obj[key];
  7016. }
  7017. }
  7018. p++;
  7019. }
  7020. return result;
  7021. }
  7022. jsonpatch.apply = apply;
  7023. })(jsonpatch || (jsonpatch = {}));
  7024. if (typeof exports !== "undefined") {
  7025. exports.apply = jsonpatch.apply;
  7026. exports.observe = jsonpatch.observe;
  7027. exports.unobserve = jsonpatch.unobserve;
  7028. exports.generate = jsonpatch.generate;
  7029. }
  7030. Handsontable.PluginHookClass = (function () {
  7031. var Hooks = function () {
  7032. return {
  7033. // Hooks
  7034. beforeInitWalkontable: [],
  7035. beforeInit: [],
  7036. beforeRender: [],
  7037. beforeSetRangeEnd: [],
  7038. beforeDrawBorders: [],
  7039. beforeChange: [],
  7040. beforeChangeRender: [],
  7041. beforeRemoveCol: [],
  7042. beforeRemoveRow: [],
  7043. beforeValidate: [],
  7044. beforeGetCellMeta: [],
  7045. beforeAutofill: [],
  7046. beforeKeyDown: [],
  7047. beforeOnCellMouseDown: [],
  7048. beforeTouchScroll: [],
  7049. afterInit : [],
  7050. afterLoadData : [],
  7051. afterUpdateSettings: [],
  7052. afterRender : [],
  7053. afterRenderer : [],
  7054. afterChange : [],
  7055. afterValidate: [],
  7056. afterGetCellMeta: [],
  7057. afterSetCellMeta: [],
  7058. afterGetColHeader: [],
  7059. afterGetRowHeader: [],
  7060. afterDestroy: [],
  7061. afterRemoveRow: [],
  7062. afterCreateRow: [],
  7063. afterRemoveCol: [],
  7064. afterCreateCol: [],
  7065. afterDeselect: [],
  7066. afterSelection: [],
  7067. afterSelectionByProp: [],
  7068. afterSelectionEnd: [],
  7069. afterSelectionEndByProp: [],
  7070. afterOnCellMouseDown: [],
  7071. afterOnCellMouseOver: [],
  7072. afterOnCellCornerMouseDown: [],
  7073. afterScrollVertically: [],
  7074. afterScrollHorizontally: [],
  7075. afterCellMetaReset: [],
  7076. afterIsMultipleSelectionCheck: [],
  7077. afterDocumentKeyDown: [],
  7078. afterMomentumScroll: [],
  7079. // Modifiers
  7080. modifyColWidth: [],
  7081. modifyRowHeight: [],
  7082. modifyRow: [],
  7083. modifyCol: []
  7084. }
  7085. };
  7086. var legacy = {
  7087. onBeforeChange: "beforeChange",
  7088. onChange: "afterChange",
  7089. onCreateRow: "afterCreateRow",
  7090. onCreateCol: "afterCreateCol",
  7091. onSelection: "afterSelection",
  7092. onCopyLimit: "afterCopyLimit",
  7093. onSelectionEnd: "afterSelectionEnd",
  7094. onSelectionByProp: "afterSelectionByProp",
  7095. onSelectionEndByProp: "afterSelectionEndByProp"
  7096. };
  7097. function PluginHookClass() {
  7098. this.hooks = Hooks();
  7099. this.globalBucket = {};
  7100. this.legacy = legacy;
  7101. }
  7102. PluginHookClass.prototype.getBucket = function (instance) {
  7103. if(instance) {
  7104. if(!instance.pluginHookBucket) {
  7105. instance.pluginHookBucket = {};
  7106. }
  7107. return instance.pluginHookBucket;
  7108. }
  7109. return this.globalBucket;
  7110. };
  7111. PluginHookClass.prototype.add = function (key, fn, instance) {
  7112. //if fn is array, run this for all the array items
  7113. if (Array.isArray(fn)) {
  7114. for (var i = 0, len = fn.length; i < len; i++) {
  7115. this.add(key, fn[i]);
  7116. }
  7117. }
  7118. else {
  7119. // provide support for old versions of HOT
  7120. if (key in legacy) {
  7121. key = legacy[key];
  7122. }
  7123. var bucket = this.getBucket(instance);
  7124. if (typeof bucket[key] === "undefined") {
  7125. bucket[key] = [];
  7126. }
  7127. fn.skip = false;
  7128. if (bucket[key].indexOf(fn) == -1) {
  7129. bucket[key].push(fn); //only add a hook if it has not already been added (adding the same hook twice is now silently ignored)
  7130. }
  7131. }
  7132. return this;
  7133. };
  7134. PluginHookClass.prototype.once = function(key, fn, instance){
  7135. if(Array.isArray(fn)){
  7136. for(var i = 0, len = fn.length; i < len; i++){
  7137. fn[i].runOnce = true;
  7138. this.add(key, fn[i], instance);
  7139. }
  7140. } else {
  7141. fn.runOnce = true;
  7142. this.add(key, fn, instance);
  7143. }
  7144. };
  7145. PluginHookClass.prototype.remove = function (key, fn, instance) {
  7146. var status = false;
  7147. // provide support for old versions of HOT
  7148. if (key in legacy) {
  7149. key = legacy[key];
  7150. }
  7151. var bucket = this.getBucket(instance);
  7152. if (typeof bucket[key] !== 'undefined') {
  7153. for (var i = 0, leni = bucket[key].length; i < leni; i++) {
  7154. if (bucket[key][i] == fn) {
  7155. bucket[key][i].skip = true;
  7156. status = true;
  7157. break;
  7158. }
  7159. }
  7160. }
  7161. return status;
  7162. };
  7163. PluginHookClass.prototype.run = function (instance, key, p1, p2, p3, p4, p5, p6) {
  7164. // provide support for old versions of HOT
  7165. if (key in legacy) {
  7166. key = legacy[key];
  7167. }
  7168. this._runBucket(this.globalBucket, instance, key, p1, p2, p3, p4, p5, p6);
  7169. this._runBucket(this.getBucket(instance), instance, key, p1, p2, p3, p4, p5, p6);
  7170. };
  7171. PluginHookClass.prototype._runBucket = function (bucket, instance, key, p1, p2, p3, p4, p5, p6) {
  7172. var handlers = bucket[key];
  7173. if (handlers) {
  7174. for (var i = 0, leni = handlers.length; i < leni; i++) {
  7175. if (!handlers[i].skip) {
  7176. handlers[i].call(instance, p1, p2, p3, p4, p5, p6);
  7177. if (handlers[i].runOnce) {
  7178. this.remove(key, handlers[i], bucket === this.globalBucket ? null : instance);
  7179. }
  7180. }
  7181. }
  7182. }
  7183. };
  7184. PluginHookClass.prototype.destroy = function (instance) {
  7185. var bucket = this.getBucket(instance);
  7186. for (var key in bucket) {
  7187. if (bucket.hasOwnProperty(key)) {
  7188. for (var i = 0, leni = bucket[key].length; i < leni; i++) {
  7189. this.remove(key, bucket[key], instance);
  7190. }
  7191. }
  7192. }
  7193. };
  7194. PluginHookClass.prototype.execute = function (instance, key, p1, p2, p3, p4, p5, p6) {
  7195. // provide support for old versions of HOT
  7196. if (key in legacy) {
  7197. key = legacy[key];
  7198. }
  7199. p1 = this._executeBucket(this.globalBucket, instance, key, p1, p2, p3, p4, p5, p6);
  7200. p1 = this._executeBucket(this.getBucket(instance), instance, key, p1, p2, p3, p4, p5, p6);
  7201. return p1;
  7202. };
  7203. PluginHookClass.prototype._executeBucket = function (bucket, instance, key, p1, p2, p3, p4, p5, p6) {
  7204. var res,
  7205. handlers = bucket[key];
  7206. //performance considerations - http://jsperf.com/call-vs-apply-for-a-plugin-architecture
  7207. if (handlers) {
  7208. for (var i = 0, leni = handlers.length; i < leni; i++) {
  7209. if (!handlers[i].skip) {
  7210. res = handlers[i].call(instance, p1, p2, p3, p4, p5, p6);
  7211. if (res !== void 0) {
  7212. p1 = res;
  7213. }
  7214. if (handlers[i].runOnce) {
  7215. this.remove(key, handlers[i], bucket === this.globalBucket ? null : instance);
  7216. }
  7217. if (res === false) { //if any handler returned false
  7218. return false; //event has been cancelled and further execution of handler queue is being aborted
  7219. }
  7220. }
  7221. }
  7222. }
  7223. return p1;
  7224. };
  7225. /**
  7226. * Registers a hook name (adds it to the list of the known hook names). Used by plugins. It is not neccessary to call,
  7227. * register, but if you use it, your plugin hook will be used returned by getRegistered
  7228. * (which itself is used in the demo http://handsontable.com/demo/callbacks.html)
  7229. * @param key {String}
  7230. */
  7231. PluginHookClass.prototype.register = function (key) {
  7232. if (!this.isRegistered(key)) {
  7233. this.hooks[key] = [];
  7234. }
  7235. };
  7236. /**
  7237. * Deregisters a hook name (removes it from the list of known hook names)
  7238. * @param key {String}
  7239. */
  7240. PluginHookClass.prototype.deregister = function (key) {
  7241. delete this.hooks[key];
  7242. };
  7243. /**
  7244. * Returns boolean information if a hook by such name has been registered
  7245. * @param key {String}
  7246. */
  7247. PluginHookClass.prototype.isRegistered = function (key) {
  7248. return (typeof this.hooks[key] !== "undefined");
  7249. };
  7250. /**
  7251. * Returns an array of registered hooks
  7252. * @returns {Array}
  7253. */
  7254. PluginHookClass.prototype.getRegistered = function () {
  7255. return Object.keys(this.hooks);
  7256. };
  7257. return PluginHookClass;
  7258. })();
  7259. Handsontable.hooks = new Handsontable.PluginHookClass();
  7260. Handsontable.PluginHooks = Handsontable.hooks; //in future move this line to legacy.js
  7261. (function (Handsontable) {
  7262. function HandsontableAutoColumnSize() {
  7263. var plugin = this
  7264. , sampleCount = 5; //number of samples to take of each value length
  7265. this.beforeInit = function () {
  7266. var instance = this;
  7267. instance.autoColumnWidths = [];
  7268. if (instance.getSettings().autoColumnSize !== false) {
  7269. if (!instance.autoColumnSizeTmp) {
  7270. instance.autoColumnSizeTmp = {
  7271. table: null,
  7272. tableStyle: null,
  7273. theadTh: null,
  7274. tbody: null,
  7275. container: null,
  7276. containerStyle: null,
  7277. determineBeforeNextRender: true
  7278. };
  7279. instance.addHook('beforeRender', htAutoColumnSize.determineIfChanged);
  7280. instance.addHook('modifyColWidth', htAutoColumnSize.modifyColWidth);
  7281. instance.addHook('afterDestroy', htAutoColumnSize.afterDestroy);
  7282. instance.determineColumnWidth = plugin.determineColumnWidth;
  7283. }
  7284. } else {
  7285. if (instance.autoColumnSizeTmp) {
  7286. instance.removeHook('beforeRender', htAutoColumnSize.determineIfChanged);
  7287. instance.removeHook('modifyColWidth', htAutoColumnSize.modifyColWidth);
  7288. instance.removeHook('afterDestroy', htAutoColumnSize.afterDestroy);
  7289. delete instance.determineColumnWidth;
  7290. plugin.afterDestroy.call(instance);
  7291. }
  7292. }
  7293. };
  7294. this.determineIfChanged = function (force) {
  7295. if (force) {
  7296. htAutoColumnSize.determineColumnsWidth.apply(this, arguments);
  7297. }
  7298. };
  7299. this.determineColumnWidth = function (col) {
  7300. var instance = this
  7301. , tmp = instance.autoColumnSizeTmp;
  7302. if (!tmp.container) {
  7303. createTmpContainer.call(tmp, instance);
  7304. }
  7305. tmp.container.className = instance.rootElement.className + ' htAutoColumnSize';
  7306. tmp.table.className = instance.table.className;
  7307. var rows = instance.countRows();
  7308. var samples = {};
  7309. var maxLen = 0;
  7310. for (var r = 0; r < rows; r++) {
  7311. var value = Handsontable.helper.stringify(instance.getDataAtCell(r, col));
  7312. var len = value.length;
  7313. if (len > maxLen) {
  7314. maxLen = len;
  7315. }
  7316. if (!samples[len]) {
  7317. samples[len] = {
  7318. needed: sampleCount,
  7319. strings: []
  7320. };
  7321. }
  7322. if (samples[len].needed) {
  7323. samples[len].strings.push({value: value, row: r});
  7324. samples[len].needed--;
  7325. }
  7326. }
  7327. var settings = instance.getSettings();
  7328. if (settings.colHeaders) {
  7329. instance.view.appendColHeader(col, tmp.theadTh); //TH innerHTML
  7330. }
  7331. Handsontable.Dom.empty(tmp.tbody);
  7332. for (var i in samples) {
  7333. if (samples.hasOwnProperty(i)) {
  7334. for (var j = 0, jlen = samples[i].strings.length; j < jlen; j++) {
  7335. var row = samples[i].strings[j].row;
  7336. var cellProperties = instance.getCellMeta(row, col);
  7337. cellProperties.col = col;
  7338. cellProperties.row = row;
  7339. var renderer = instance.getCellRenderer(cellProperties);
  7340. var tr = document.createElement('tr');
  7341. var td = document.createElement('td');
  7342. renderer(instance, td, row, col, instance.colToProp(col), samples[i].strings[j].value, cellProperties);
  7343. r++;
  7344. tr.appendChild(td);
  7345. tmp.tbody.appendChild(tr);
  7346. }
  7347. }
  7348. }
  7349. var parent = instance.rootElement.parentNode;
  7350. parent.appendChild(tmp.container);
  7351. var width = Handsontable.Dom.outerWidth(tmp.table);
  7352. parent.removeChild(tmp.container);
  7353. return width;
  7354. };
  7355. this.determineColumnsWidth = function () {
  7356. var instance = this;
  7357. var settings = this.getSettings();
  7358. if (settings.autoColumnSize || !settings.colWidths) {
  7359. var cols = this.countCols();
  7360. for (var c = 0; c < cols; c++) {
  7361. if (!instance._getColWidthFromSettings(c)) {
  7362. this.autoColumnWidths[c] = plugin.determineColumnWidth.call(instance, c);
  7363. }
  7364. }
  7365. }
  7366. };
  7367. this.modifyColWidth = function (width, col) {
  7368. if (this.autoColumnWidths[col] && this.autoColumnWidths[col] > width) {
  7369. return this.autoColumnWidths[col];
  7370. }
  7371. return width;
  7372. };
  7373. this.afterDestroy = function () {
  7374. var instance = this;
  7375. if (instance.autoColumnSizeTmp && instance.autoColumnSizeTmp.container && instance.autoColumnSizeTmp.container.parentNode) {
  7376. instance.autoColumnSizeTmp.container.parentNode.removeChild(instance.autoColumnSizeTmp.container);
  7377. }
  7378. instance.autoColumnSizeTmp = null;
  7379. };
  7380. function createTmpContainer(instance) {
  7381. var d = document
  7382. , tmp = this;
  7383. tmp.table = d.createElement('table');
  7384. tmp.theadTh = d.createElement('th');
  7385. tmp.table.appendChild(d.createElement('thead')).appendChild(d.createElement('tr')).appendChild(tmp.theadTh);
  7386. tmp.tableStyle = tmp.table.style;
  7387. tmp.tableStyle.tableLayout = 'auto';
  7388. tmp.tableStyle.width = 'auto';
  7389. tmp.tbody = d.createElement('tbody');
  7390. tmp.table.appendChild(tmp.tbody);
  7391. tmp.container = d.createElement('div');
  7392. tmp.container.className = instance.rootElement.className + ' hidden';
  7393. // tmp.container.className = instance.rootElement[0].className + ' hidden';
  7394. tmp.containerStyle = tmp.container.style;
  7395. tmp.container.appendChild(tmp.table);
  7396. }
  7397. }
  7398. var htAutoColumnSize = new HandsontableAutoColumnSize();
  7399. Handsontable.hooks.add('beforeInit', htAutoColumnSize.beforeInit);
  7400. Handsontable.hooks.add('afterUpdateSettings', htAutoColumnSize.beforeInit);
  7401. })(Handsontable);
  7402. /**
  7403. * This plugin sorts the view by a column (but does not sort the data source!)
  7404. * @constructor
  7405. */
  7406. function HandsontableColumnSorting() {
  7407. var plugin = this;
  7408. this.init = function (source) {
  7409. var instance = this;
  7410. var sortingSettings = instance.getSettings().columnSorting;
  7411. var sortingColumn, sortingOrder;
  7412. instance.sortingEnabled = !!(sortingSettings);
  7413. if (instance.sortingEnabled) {
  7414. instance.sortIndex = [];
  7415. var loadedSortingState = loadSortingState.call(instance);
  7416. if (typeof loadedSortingState != 'undefined') {
  7417. sortingColumn = loadedSortingState.sortColumn;
  7418. sortingOrder = loadedSortingState.sortOrder;
  7419. } else {
  7420. sortingColumn = sortingSettings.column;
  7421. sortingOrder = sortingSettings.sortOrder;
  7422. }
  7423. plugin.sortByColumn.call(instance, sortingColumn, sortingOrder);
  7424. instance.sort = function(){
  7425. var args = Array.prototype.slice.call(arguments);
  7426. return plugin.sortByColumn.apply(instance, args)
  7427. };
  7428. if (typeof instance.getSettings().observeChanges == 'undefined'){
  7429. enableObserveChangesPlugin.call(instance);
  7430. }
  7431. if (source == 'afterInit') {
  7432. bindColumnSortingAfterClick.call(instance);
  7433. instance.addHook('afterCreateRow', plugin.afterCreateRow);
  7434. instance.addHook('afterRemoveRow', plugin.afterRemoveRow);
  7435. instance.addHook('afterLoadData', plugin.init);
  7436. }
  7437. } else {
  7438. delete instance.sort;
  7439. instance.removeHook('afterCreateRow', plugin.afterCreateRow);
  7440. instance.removeHook('afterRemoveRow', plugin.afterRemoveRow);
  7441. instance.removeHook('afterLoadData', plugin.init);
  7442. }
  7443. };
  7444. this.setSortingColumn = function (col, order) {
  7445. var instance = this;
  7446. if (typeof col == 'undefined') {
  7447. delete instance.sortColumn;
  7448. delete instance.sortOrder;
  7449. return;
  7450. } else if (instance.sortColumn === col && typeof order == 'undefined') {
  7451. instance.sortOrder = !instance.sortOrder;
  7452. } else {
  7453. instance.sortOrder = typeof order != 'undefined' ? order : true;
  7454. }
  7455. instance.sortColumn = col;
  7456. };
  7457. this.sortByColumn = function (col, order) {
  7458. var instance = this;
  7459. plugin.setSortingColumn.call(instance, col, order);
  7460. if(typeof instance.sortColumn == 'undefined'){
  7461. return;
  7462. }
  7463. Handsontable.hooks.run(instance, 'beforeColumnSort', instance.sortColumn, instance.sortOrder);
  7464. plugin.sort.call(instance);
  7465. instance.render();
  7466. saveSortingState.call(instance);
  7467. Handsontable.hooks.run(instance, 'afterColumnSort', instance.sortColumn, instance.sortOrder);
  7468. };
  7469. var saveSortingState = function () {
  7470. var instance = this;
  7471. var sortingState = {};
  7472. if (typeof instance.sortColumn != 'undefined') {
  7473. sortingState.sortColumn = instance.sortColumn;
  7474. }
  7475. if (typeof instance.sortOrder != 'undefined') {
  7476. sortingState.sortOrder = instance.sortOrder;
  7477. }
  7478. if (sortingState.hasOwnProperty('sortColumn') || sortingState.hasOwnProperty('sortOrder')) {
  7479. Handsontable.hooks.run(instance, 'persistentStateSave', 'columnSorting', sortingState);
  7480. }
  7481. };
  7482. var loadSortingState = function () {
  7483. var instance = this;
  7484. var storedState = {};
  7485. Handsontable.hooks.run(instance, 'persistentStateLoad', 'columnSorting', storedState);
  7486. return storedState.value;
  7487. };
  7488. var bindColumnSortingAfterClick = function () {
  7489. var instance = this;
  7490. var eventManager = Handsontable.eventManager(instance);
  7491. eventManager.addEventListener(instance.rootElement, 'click', function (e){
  7492. if(Handsontable.Dom.hasClass(e.target, 'columnSorting')) {
  7493. var col = getColumn(e.target);
  7494. plugin.sortByColumn.call(instance, col);
  7495. }
  7496. });
  7497. function countRowHeaders() {
  7498. var THs = instance.view.TBODY.querySelector('tr').querySelectorAll('th');
  7499. return THs.length;
  7500. }
  7501. function getColumn(target) {
  7502. var TH = Handsontable.Dom.closest(target, 'TH');
  7503. return Handsontable.Dom.index(TH) - countRowHeaders();
  7504. }
  7505. };
  7506. function enableObserveChangesPlugin () {
  7507. var instance = this;
  7508. instance._registerTimeout(setTimeout(function(){
  7509. instance.updateSettings({
  7510. observeChanges: true
  7511. });
  7512. }, 0));
  7513. }
  7514. function defaultSort(sortOrder) {
  7515. return function (a, b) {
  7516. if(typeof a[1] == "string") a[1] = a[1].toLowerCase();
  7517. if(typeof b[1] == "string") b[1] = b[1].toLowerCase();
  7518. if (a[1] === b[1]) {
  7519. return 0;
  7520. }
  7521. if (a[1] === null || a[1] === "") {
  7522. return 1;
  7523. }
  7524. if (b[1] === null || b[1] === "") {
  7525. return -1;
  7526. }
  7527. if (a[1] < b[1]) return sortOrder ? -1 : 1;
  7528. if (a[1] > b[1]) return sortOrder ? 1 : -1;
  7529. return 0;
  7530. }
  7531. }
  7532. function dateSort(sortOrder) {
  7533. return function (a, b) {
  7534. if (a[1] === b[1]) {
  7535. return 0;
  7536. }
  7537. if (a[1] === null) {
  7538. return 1;
  7539. }
  7540. if (b[1] === null) {
  7541. return -1;
  7542. }
  7543. var aDate = new Date(a[1]);
  7544. var bDate = new Date(b[1]);
  7545. if (aDate < bDate) return sortOrder ? -1 : 1;
  7546. if (aDate > bDate) return sortOrder ? 1 : -1;
  7547. return 0;
  7548. }
  7549. }
  7550. this.sort = function () {
  7551. var instance = this;
  7552. if (typeof instance.sortOrder == 'undefined') {
  7553. return;
  7554. }
  7555. instance.sortingEnabled = false; //this is required by translateRow plugin hook
  7556. instance.sortIndex.length = 0;
  7557. var colOffset = this.colOffset();
  7558. for (var i = 0, ilen = this.countRows() - instance.getSettings()['minSpareRows']; i < ilen; i++) {
  7559. this.sortIndex.push([i, instance.getDataAtCell(i, this.sortColumn + colOffset)]);
  7560. }
  7561. var colMeta = instance.getCellMeta(0, instance.sortColumn);
  7562. var sortFunction;
  7563. switch (colMeta.type) {
  7564. case 'date':
  7565. sortFunction = dateSort;
  7566. break;
  7567. default:
  7568. sortFunction = defaultSort;
  7569. }
  7570. this.sortIndex.sort(sortFunction(instance.sortOrder));
  7571. //Append spareRows
  7572. for(var i = this.sortIndex.length; i < instance.countRows(); i++){
  7573. this.sortIndex.push([i, instance.getDataAtCell(i, this.sortColumn + colOffset)]);
  7574. }
  7575. instance.sortingEnabled = true; //this is required by translateRow plugin hook
  7576. };
  7577. this.translateRow = function (row) {
  7578. var instance = this;
  7579. if (instance.sortingEnabled && instance.sortIndex && instance.sortIndex.length && instance.sortIndex[row]) {
  7580. return instance.sortIndex[row][0];
  7581. }
  7582. return row;
  7583. };
  7584. this.untranslateRow = function (row) {
  7585. var instance = this;
  7586. if (instance.sortingEnabled && instance.sortIndex && instance.sortIndex.length) {
  7587. for (var i = 0; i < instance.sortIndex.length; i++) {
  7588. if (instance.sortIndex[i][0] == row) {
  7589. return i;
  7590. }
  7591. }
  7592. }
  7593. };
  7594. this.getColHeader = function (col, TH) {
  7595. if (this.getSettings().columnSorting && col >= 0) {
  7596. Handsontable.Dom.addClass(TH.querySelector('.colHeader'), 'columnSorting');
  7597. }
  7598. };
  7599. function isSorted(instance){
  7600. return typeof instance.sortColumn != 'undefined';
  7601. }
  7602. this.afterCreateRow = function(index, amount){
  7603. var instance = this;
  7604. if(!isSorted(instance)){
  7605. return;
  7606. }
  7607. for(var i = 0; i < instance.sortIndex.length; i++){
  7608. if (instance.sortIndex[i][0] >= index){
  7609. instance.sortIndex[i][0] += amount;
  7610. }
  7611. }
  7612. for(var i=0; i < amount; i++){
  7613. instance.sortIndex.splice(index+i, 0, [index+i, instance.getData()[index+i][instance.sortColumn + instance.colOffset()]]);
  7614. }
  7615. saveSortingState.call(instance);
  7616. };
  7617. this.afterRemoveRow = function(index, amount){
  7618. var instance = this;
  7619. if(!isSorted(instance)){
  7620. return;
  7621. }
  7622. var physicalRemovedIndex = plugin.translateRow.call(instance, index);
  7623. instance.sortIndex.splice(index, amount);
  7624. for(var i = 0; i < instance.sortIndex.length; i++){
  7625. if (instance.sortIndex[i][0] > physicalRemovedIndex){
  7626. instance.sortIndex[i][0] -= amount;
  7627. }
  7628. }
  7629. saveSortingState.call(instance);
  7630. };
  7631. this.afterChangeSort = function (changes/*, source*/) {
  7632. var instance = this;
  7633. var sortColumnChanged = false;
  7634. var selection = {};
  7635. if (!changes) {
  7636. return;
  7637. }
  7638. for (var i = 0; i < changes.length; i++) {
  7639. if (changes[i][1] == instance.sortColumn) {
  7640. sortColumnChanged = true;
  7641. selection.row = plugin.translateRow.call(instance, changes[i][0]);
  7642. selection.col = changes[i][1];
  7643. break;
  7644. }
  7645. }
  7646. if (sortColumnChanged) {
  7647. instance._registerTimeout(setTimeout(function () {
  7648. plugin.sort.call(instance);
  7649. instance.render();
  7650. instance.selectCell(plugin.untranslateRow.call(instance, selection.row), selection.col);
  7651. }, 0));
  7652. }
  7653. };
  7654. }
  7655. var htSortColumn = new HandsontableColumnSorting();
  7656. Handsontable.hooks.add('afterInit', function () {
  7657. htSortColumn.init.call(this, 'afterInit')
  7658. });
  7659. Handsontable.hooks.add('afterUpdateSettings', function () {
  7660. htSortColumn.init.call(this, 'afterUpdateSettings')
  7661. });
  7662. Handsontable.hooks.add('modifyRow', htSortColumn.translateRow);
  7663. Handsontable.hooks.add('afterGetColHeader', htSortColumn.getColHeader);
  7664. Handsontable.hooks.register('beforeColumnSort');
  7665. Handsontable.hooks.register('afterColumnSort');
  7666. (function (Handsontable) {
  7667. 'use strict';
  7668. function prepareVerticalAlignClass(className, alignment) {
  7669. if (className.indexOf(alignment) != -1) {
  7670. return className;
  7671. }
  7672. className = className
  7673. .replace('htTop', '')
  7674. .replace('htMiddle', '')
  7675. .replace('htBottom', '')
  7676. .replace(' ', '');
  7677. className += " " + alignment;
  7678. return className;
  7679. }
  7680. function prepareHorizontalAlignClass(className, alignment) {
  7681. if (className.indexOf(alignment) != -1) {
  7682. return className;
  7683. }
  7684. className = className
  7685. .replace('htLeft', '')
  7686. .replace('htCenter', '')
  7687. .replace('htRight', '')
  7688. .replace('htJustify', '')
  7689. .replace(' ', '');
  7690. className += " " + alignment;
  7691. return className;
  7692. }
  7693. function doAlign(row, col, type, alignment) {
  7694. var cellMeta = this.getCellMeta(row, col),
  7695. className = alignment;
  7696. if (cellMeta.className) {
  7697. if (type === 'vertical') {
  7698. className = prepareVerticalAlignClass(cellMeta.className, alignment);
  7699. } else {
  7700. className = prepareHorizontalAlignClass(cellMeta.className, alignment);
  7701. }
  7702. }
  7703. this.setCellMeta(row, col, 'className', className);
  7704. this.render();
  7705. }
  7706. function align(range, type, alignment) {
  7707. if (range.from.row == range.to.row && range.from.col == range.to.col) {
  7708. doAlign.call(this, range.from.row, range.from.col, type, alignment);
  7709. } else {
  7710. for (var row = range.from.row; row <= range.to.row; row++) {
  7711. for (var col = range.from.col; col <= range.to.col; col++) {
  7712. doAlign.call(this, row, col, type, alignment);
  7713. }
  7714. }
  7715. }
  7716. }
  7717. function ContextMenu(instance, customOptions) {
  7718. this.instance = instance;
  7719. var contextMenu = this;
  7720. contextMenu.menus = [];
  7721. contextMenu.htMenus = {};
  7722. contextMenu.triggerRows = [];
  7723. contextMenu.eventManager = Handsontable.eventManager(contextMenu);
  7724. this.enabled = true;
  7725. this.instance.addHook('afterDestroy', function () {
  7726. contextMenu.destroy();
  7727. });
  7728. this.defaultOptions = {
  7729. items: [
  7730. {
  7731. key: 'row_above',
  7732. name: 'Insert row above',
  7733. callback: function (key, selection) {
  7734. this.alter("insert_row", selection.start.row);
  7735. },
  7736. disabled: function () {
  7737. var selected = this.getSelected(),
  7738. entireColumnSelection = [0, selected[1], this.countRows() - 1, selected[1]],
  7739. columnSelected = entireColumnSelection.join(',') == selected.join(',');
  7740. return selected[0] < 0 || this.countRows() >= this.getSettings().maxRows || columnSelected;
  7741. }
  7742. },
  7743. {
  7744. key: 'row_below',
  7745. name: 'Insert row below',
  7746. callback: function (key, selection) {
  7747. this.alter("insert_row", selection.end.row + 1);
  7748. },
  7749. disabled: function () {
  7750. var selected = this.getSelected(),
  7751. entireColumnSelection = [0, selected[1], this.countRows() - 1, selected[1]],
  7752. columnSelected = entireColumnSelection.join(',') == selected.join(',');
  7753. return this.getSelected()[0] < 0 || this.countRows() >= this.getSettings().maxRows || columnSelected;
  7754. }
  7755. },
  7756. ContextMenu.SEPARATOR,
  7757. {
  7758. key: 'col_left',
  7759. name: 'Insert column on the left',
  7760. callback: function (key, selection) {
  7761. this.alter("insert_col", selection.start.col);
  7762. },
  7763. disabled: function () {
  7764. var selected = this.getSelected(),
  7765. entireRowSelection = [selected[0], 0, selected[0], this.countCols() - 1],
  7766. rowSelected = entireRowSelection.join(',') == selected.join(',');
  7767. return this.getSelected()[1] < 0 || this.countCols() >= this.getSettings().maxCols || rowSelected;
  7768. }
  7769. },
  7770. {
  7771. key: 'col_right',
  7772. name: 'Insert column on the right',
  7773. callback: function (key, selection) {
  7774. this.alter("insert_col", selection.end.col + 1);
  7775. },
  7776. disabled: function () {
  7777. var selected = this.getSelected(),
  7778. entireRowSelection = [selected[0], 0, selected[0], this.countCols() - 1],
  7779. rowSelected = entireRowSelection.join(',') == selected.join(',');
  7780. return selected[1] < 0 || this.countCols() >= this.getSettings().maxCols || rowSelected;
  7781. }
  7782. },
  7783. ContextMenu.SEPARATOR,
  7784. {
  7785. key: 'remove_row',
  7786. name: 'Remove row',
  7787. callback: function (key, selection) {
  7788. var amount = selection.end.row - selection.start.row + 1;
  7789. this.alter("remove_row", selection.start.row, amount);
  7790. },
  7791. disabled: function () {
  7792. var selected = this.getSelected(),
  7793. entireColumnSelection = [0, selected[1], this.countRows() - 1, selected[1]],
  7794. columnSelected = entireColumnSelection.join(',') == selected.join(',');
  7795. return (selected[0] < 0 || columnSelected);
  7796. }
  7797. },
  7798. {
  7799. key: 'remove_col',
  7800. name: 'Remove column',
  7801. callback: function (key, selection) {
  7802. var amount = selection.end.col - selection.start.col + 1;
  7803. this.alter("remove_col", selection.start.col, amount);
  7804. },
  7805. disabled: function () {
  7806. var selected = this.getSelected(),
  7807. entireRowSelection = [selected[0], 0, selected[0], this.countCols() - 1],
  7808. rowSelected = entireRowSelection.join(',') == selected.join(',');
  7809. return (selected[1] < 0 || rowSelected);
  7810. }
  7811. },
  7812. ContextMenu.SEPARATOR,
  7813. {
  7814. key: 'undo',
  7815. name: 'Undo',
  7816. callback: function () {
  7817. this.undo();
  7818. },
  7819. disabled: function () {
  7820. return this.undoRedo && !this.undoRedo.isUndoAvailable();
  7821. }
  7822. },
  7823. {
  7824. key: 'redo',
  7825. name: 'Redo',
  7826. callback: function () {
  7827. this.redo();
  7828. },
  7829. disabled: function () {
  7830. return this.undoRedo && !this.undoRedo.isRedoAvailable();
  7831. }
  7832. },
  7833. ContextMenu.SEPARATOR,
  7834. {
  7835. key: 'make_read_only',
  7836. name: function () {
  7837. var label = "Read only";
  7838. var atLeastOneReadOnly = contextMenu.checkSelectionReadOnlyConsistency(this);
  7839. if (atLeastOneReadOnly) {
  7840. label = contextMenu.markSelected(label);
  7841. }
  7842. return label;
  7843. },
  7844. callback: function () {
  7845. var atLeastOneReadOnly = contextMenu.checkSelectionReadOnlyConsistency(this);
  7846. var that = this;
  7847. this.getSelectedRange().forAll(function (r, c) {
  7848. that.getCellMeta(r, c).readOnly = atLeastOneReadOnly ? false : true;
  7849. });
  7850. this.render();
  7851. }
  7852. },
  7853. ContextMenu.SEPARATOR,
  7854. {
  7855. key: 'alignment',
  7856. name: 'Alignment',
  7857. submenu: {
  7858. items: [
  7859. {
  7860. name: function () {
  7861. var label = "Left";
  7862. var hasClass = contextMenu.checkSelectionAlignment(this, 'htLeft');
  7863. if (hasClass) {
  7864. label = contextMenu.markSelected(label);
  7865. }
  7866. return label;
  7867. },
  7868. callback: function () {
  7869. align.call(this, this.getSelectedRange(), 'horizontal', 'htLeft');
  7870. },
  7871. disabled: false
  7872. },
  7873. {
  7874. name: function () {
  7875. var label = "Center";
  7876. var hasClass = contextMenu.checkSelectionAlignment(this, 'htCenter');
  7877. if (hasClass) {
  7878. label = contextMenu.markSelected(label);
  7879. }
  7880. return label;
  7881. },
  7882. callback: function () {
  7883. align.call(this, this.getSelectedRange(), 'horizontal', 'htCenter');
  7884. },
  7885. disabled: false
  7886. },
  7887. {
  7888. name: function () {
  7889. var label = "Right";
  7890. var hasClass = contextMenu.checkSelectionAlignment(this, 'htRight');
  7891. if (hasClass) {
  7892. label = contextMenu.markSelected(label);
  7893. }
  7894. return label;
  7895. },
  7896. callback: function () {
  7897. align.call(this, this.getSelectedRange(), 'horizontal', 'htRight');
  7898. },
  7899. disabled: false
  7900. },
  7901. {
  7902. name: function () {
  7903. var label = "Justify";
  7904. var hasClass = contextMenu.checkSelectionAlignment(this, 'htJustify');
  7905. if (hasClass) {
  7906. label = contextMenu.markSelected(label);
  7907. }
  7908. return label;
  7909. },
  7910. callback: function () {
  7911. align.call(this, this.getSelectedRange(), 'horizontal', 'htJustify');
  7912. },
  7913. disabled: false
  7914. },
  7915. ContextMenu.SEPARATOR,
  7916. {
  7917. name: function () {
  7918. var label = "Top";
  7919. var hasClass = contextMenu.checkSelectionAlignment(this, 'htTop');
  7920. if (hasClass) {
  7921. label = contextMenu.markSelected(label);
  7922. }
  7923. return label;
  7924. },
  7925. callback: function () {
  7926. align.call(this, this.getSelectedRange(), 'vertical', 'htTop');
  7927. },
  7928. disabled: false
  7929. },
  7930. {
  7931. name: function () {
  7932. var label = "Middle";
  7933. var hasClass = contextMenu.checkSelectionAlignment(this, 'htMiddle');
  7934. if (hasClass) {
  7935. label = contextMenu.markSelected(label);
  7936. }
  7937. return label;
  7938. },
  7939. callback: function () {
  7940. align.call(this, this.getSelectedRange(), 'vertical', 'htMiddle');
  7941. },
  7942. disabled: false
  7943. },
  7944. {
  7945. name: function () {
  7946. var label = "Bottom";
  7947. var hasClass = contextMenu.checkSelectionAlignment(this, 'htBottom');
  7948. if (hasClass) {
  7949. label = contextMenu.markSelected(label);
  7950. }
  7951. return label;
  7952. },
  7953. callback: function () {
  7954. align.call(this, this.getSelectedRange(), 'vertical', 'htBottom');
  7955. },
  7956. disabled: false
  7957. }
  7958. ]
  7959. }
  7960. }
  7961. ]
  7962. };
  7963. contextMenu.options = {};
  7964. Handsontable.helper.extend(contextMenu.options, this.options);
  7965. this.bindMouseEvents();
  7966. this.markSelected = function (label) {
  7967. return "<span class='selected'>" + String.fromCharCode(10003) + "</span>" + label; // workaround for https://github.com/handsontable/handsontable/issues/1946
  7968. };
  7969. this.checkSelectionAlignment = function (hot, className) {
  7970. var hasAlignment = false;
  7971. hot.getSelectedRange().forAll(function (r, c) {
  7972. var metaClassName = hot.getCellMeta(r, c).className;
  7973. if (metaClassName && metaClassName.indexOf(className) != -1) {
  7974. hasAlignment = true;
  7975. return false;
  7976. }
  7977. });
  7978. return hasAlignment;
  7979. };
  7980. if(!this.instance.getSettings().allowInsertRow) {
  7981. var rowAboveIndex = findIndexByKey(this.defaultOptions.items, 'row_above');
  7982. this.defaultOptions.items.splice(rowAboveIndex,1);
  7983. var rowBelowIndex = findIndexByKey(this.defaultOptions.items, 'row_above');
  7984. this.defaultOptions.items.splice(rowBelowIndex,1);
  7985. this.defaultOptions.items.splice(rowBelowIndex,1); // FOR SEPARATOR
  7986. }
  7987. if(!this.instance.getSettings().allowInsertColumn) {
  7988. var colLeftIndex = findIndexByKey(this.defaultOptions.items, 'col_left');
  7989. this.defaultOptions.items.splice(colLeftIndex,1);
  7990. var colRightIndex = findIndexByKey(this.defaultOptions.items, 'col_right');
  7991. this.defaultOptions.items.splice(colRightIndex,1);
  7992. this.defaultOptions.items.splice(colRightIndex,1); // FOR SEPARATOR
  7993. }
  7994. var removeRow = false;
  7995. var removeCol = false;
  7996. var removeRowIndex, removeColumnIndex;
  7997. if(!this.instance.getSettings().allowRemoveRow) {
  7998. removeRowIndex = findIndexByKey(this.defaultOptions.items, 'remove_row');
  7999. this.defaultOptions.items.splice(removeRowIndex,1);
  8000. removeRow = true;
  8001. }
  8002. if(!this.instance.getSettings().allowRemoveColumn) {
  8003. removeColumnIndex = findIndexByKey(this.defaultOptions.items, 'remove_col');
  8004. this.defaultOptions.items.splice(removeColumnIndex,1);
  8005. removeCol = true;
  8006. }
  8007. if (removeRow && removeCol) {
  8008. this.defaultOptions.items.splice(removeColumnIndex,1); // SEPARATOR
  8009. }
  8010. this.checkSelectionReadOnlyConsistency = function (hot) {
  8011. var atLeastOneReadOnly = false;
  8012. hot.getSelectedRange().forAll(function (r, c) {
  8013. if (hot.getCellMeta(r, c).readOnly) {
  8014. atLeastOneReadOnly = true;
  8015. return false; //breaks forAll
  8016. }
  8017. });
  8018. return atLeastOneReadOnly;
  8019. };
  8020. Handsontable.hooks.run(instance, 'afterContextMenuDefaultOptions', this.defaultOptions);
  8021. }
  8022. /***
  8023. * Create DOM instance of contextMenu
  8024. * @param menuName
  8025. * @param row
  8026. * @return {*}
  8027. */
  8028. ContextMenu.prototype.createMenu = function (menuName, row) {
  8029. if (menuName) {
  8030. menuName = menuName.replace(/ /g, '_'); // replace all spaces in name
  8031. menuName = 'htContextSubMenu_' + menuName;
  8032. }
  8033. var menu;
  8034. if (menuName) {
  8035. menu = document.querySelector('.htContextMenu.' + menuName);
  8036. } else {
  8037. menu = document.querySelector('.htContextMenu');
  8038. }
  8039. if (!menu) {
  8040. menu = document.createElement('DIV');
  8041. Handsontable.Dom.addClass(menu, 'htContextMenu');
  8042. if (menuName) {
  8043. Handsontable.Dom.addClass(menu, menuName);
  8044. }
  8045. document.getElementsByTagName('body')[0].appendChild(menu);
  8046. }
  8047. if (this.menus.indexOf(menu) < 0) {
  8048. this.menus.push(menu);
  8049. row = row || 0;
  8050. this.triggerRows.push(row);
  8051. }
  8052. return menu;
  8053. };
  8054. ContextMenu.prototype.bindMouseEvents = function () {
  8055. function contextMenuOpenListener(event) {
  8056. var settings = this.instance.getSettings();
  8057. this.closeAll();
  8058. event.preventDefault();
  8059. Handsontable.helper.stopPropagation(event);
  8060. var showRowHeaders = this.instance.getSettings().rowHeaders,
  8061. showColHeaders = this.instance.getSettings().colHeaders;
  8062. if (!(showRowHeaders || showColHeaders)) {
  8063. if (event.target.nodeName != 'TD' && !(Handsontable.Dom.hasClass(event.target, 'current') && Handsontable.Dom.hasClass(event.target, 'wtBorder'))) {
  8064. return;
  8065. }
  8066. }
  8067. var menu = this.createMenu();
  8068. var items = this.getItems(settings.contextMenu);
  8069. this.show(menu, items);
  8070. this.setMenuPosition(event, menu);
  8071. this.eventManager.addEventListener(document.documentElement, 'mousedown', Handsontable.helper.proxy(ContextMenu.prototype.closeAll, this));
  8072. }
  8073. var eventManager = Handsontable.eventManager(this.instance);
  8074. eventManager.addEventListener(this.instance.rootElement, 'contextmenu', Handsontable.helper.proxy(contextMenuOpenListener, this));
  8075. };
  8076. ContextMenu.prototype.bindTableEvents = function () {
  8077. this._afterScrollCallback = function () {};
  8078. this.instance.addHook('afterScrollVertically', this._afterScrollCallback);
  8079. this.instance.addHook('afterScrollHorizontally', this._afterScrollCallback);
  8080. };
  8081. ContextMenu.prototype.unbindTableEvents = function () {
  8082. if (this._afterScrollCallback) {
  8083. this.instance.removeHook('afterScrollVertically', this._afterScrollCallback);
  8084. this.instance.removeHook('afterScrollHorizontally', this._afterScrollCallback);
  8085. this._afterScrollCallback = null;
  8086. }
  8087. };
  8088. ContextMenu.prototype.performAction = function (event, hot) {
  8089. var contextMenu = this;
  8090. var selectedItemIndex = hot.getSelected()[0];
  8091. var selectedItem = hot.getData()[selectedItemIndex];
  8092. if (selectedItem.disabled === true || (typeof selectedItem.disabled == 'function' && selectedItem.disabled.call(this.instance) === true)) {
  8093. return;
  8094. }
  8095. if (!selectedItem.hasOwnProperty('submenu')) {
  8096. if (typeof selectedItem.callback != 'function') {
  8097. return;
  8098. }
  8099. var selRange = this.instance.getSelectedRange();
  8100. var normalizedSelection = ContextMenu.utils.normalizeSelection(selRange);
  8101. selectedItem.callback.call(this.instance, selectedItem.key, normalizedSelection, event);
  8102. contextMenu.closeAll();
  8103. }
  8104. };
  8105. ContextMenu.prototype.unbindMouseEvents = function () {
  8106. this.eventManager.clear();
  8107. var eventManager = Handsontable.eventManager(this.instance);
  8108. eventManager.removeEventListener(this.instance.rootElement, 'contextmenu');
  8109. };
  8110. ContextMenu.prototype.show = function (menu, items) {
  8111. var that = this;
  8112. menu.removeAttribute('style');
  8113. menu.style.display = 'block';
  8114. var settings = {
  8115. data: items,
  8116. colHeaders: false,
  8117. colWidths: [200],
  8118. readOnly: true,
  8119. copyPaste: false,
  8120. columns: [
  8121. {
  8122. data: 'name',
  8123. renderer: Handsontable.helper.proxy(this.renderer, this)
  8124. }
  8125. ],
  8126. renderAllRows: true,
  8127. beforeKeyDown: function (event) {
  8128. that.onBeforeKeyDown(event, htContextMenu);
  8129. },
  8130. afterOnCellMouseOver: function (event, coords, TD) {
  8131. that.onCellMouseOver(event, coords, TD, htContextMenu);
  8132. }
  8133. };
  8134. var htContextMenu = new Handsontable(menu, settings);
  8135. this.eventManager.removeEventListener(menu, 'mousedown');
  8136. this.eventManager.addEventListener(menu,'mousedown', function (event) {
  8137. that.performAction(event, htContextMenu)
  8138. });
  8139. this.bindTableEvents();
  8140. htContextMenu.listen();
  8141. this.htMenus[htContextMenu.guid] = htContextMenu;
  8142. };
  8143. ContextMenu.prototype.close = function (menu) {
  8144. this.hide(menu);
  8145. this.eventManager.clear();
  8146. this.unbindTableEvents();
  8147. this.instance.listen();
  8148. };
  8149. ContextMenu.prototype.closeAll = function () {
  8150. while (this.menus.length > 0) {
  8151. var menu = this.menus.pop();
  8152. if (menu) {
  8153. this.close(menu);
  8154. }
  8155. }
  8156. this.triggerRows = [];
  8157. };
  8158. ContextMenu.prototype.closeLastOpenedSubMenu = function () {
  8159. var menu = this.menus.pop();
  8160. if (menu) {
  8161. this.hide(menu);
  8162. }
  8163. };
  8164. ContextMenu.prototype.hide = function (menu) {
  8165. menu.style.display = 'none';
  8166. var instance =this.htMenus[menu.id];
  8167. instance.destroy();
  8168. delete this.htMenus[menu.id];
  8169. };
  8170. ContextMenu.prototype.renderer = function (instance, TD, row, col, prop, value) {
  8171. var contextMenu = this;
  8172. var item = instance.getData()[row];
  8173. var wrapper = document.createElement('DIV');
  8174. if (typeof value === 'function') {
  8175. value = value.call(this.instance);
  8176. }
  8177. Handsontable.Dom.empty(TD);
  8178. TD.appendChild(wrapper);
  8179. if (itemIsSeparator(item)) {
  8180. Handsontable.Dom.addClass(TD, 'htSeparator');
  8181. } else {
  8182. Handsontable.Dom.fastInnerHTML(wrapper, value);
  8183. }
  8184. if (itemIsDisabled(item)) {
  8185. Handsontable.Dom.addClass(TD, 'htDisabled');
  8186. this.eventManager.addEventListener(wrapper, 'mouseenter', function () {
  8187. instance.deselectCell();
  8188. });
  8189. } else {
  8190. if (isSubMenu(item)) {
  8191. Handsontable.Dom.addClass(TD, 'htSubmenu');
  8192. this.eventManager.addEventListener(wrapper, 'mouseenter', function () {
  8193. instance.selectCell(row, col);
  8194. });
  8195. } else {
  8196. Handsontable.Dom.removeClass(TD, 'htSubmenu');
  8197. Handsontable.Dom.removeClass(TD, 'htDisabled');
  8198. this.eventManager.addEventListener(wrapper, 'mouseenter', function () {
  8199. instance.selectCell(row, col);
  8200. });
  8201. }
  8202. }
  8203. function isSubMenu(item) {
  8204. return item.hasOwnProperty('submenu');
  8205. }
  8206. function itemIsSeparator(item) {
  8207. return new RegExp(ContextMenu.SEPARATOR.name, 'i').test(item.name);
  8208. }
  8209. function itemIsDisabled(item) {
  8210. return item.disabled === true || (typeof item.disabled == 'function' && item.disabled.call(contextMenu.instance) === true);
  8211. }
  8212. };
  8213. ContextMenu.prototype.onCellMouseOver = function (event, coords, TD, hot) {
  8214. var menusLength = this.menus.length;
  8215. if (menusLength > 0) {
  8216. var lastMenu = this.menus[menusLength - 1];
  8217. if (lastMenu.id != hot.guid) {
  8218. this.closeLastOpenedSubMenu();
  8219. }
  8220. } else {
  8221. this.closeLastOpenedSubMenu();
  8222. }
  8223. if (TD.className.indexOf('htSubmenu') != -1) {
  8224. var selectedItem = hot.getData()[coords.row];
  8225. var items = this.getItems(selectedItem.submenu);
  8226. var subMenu = this.createMenu(selectedItem.name, coords.row);
  8227. var tdCoords = TD.getBoundingClientRect();
  8228. this.show(subMenu, items);
  8229. this.setSubMenuPosition(tdCoords, subMenu);
  8230. }
  8231. };
  8232. ContextMenu.prototype.onBeforeKeyDown = function (event, instance) {
  8233. Handsontable.Dom.enableImmediatePropagation(event);
  8234. var contextMenu = this;
  8235. var selection = instance.getSelected();
  8236. switch (event.keyCode) {
  8237. case Handsontable.helper.keyCode.ESCAPE:
  8238. contextMenu.closeAll();
  8239. event.preventDefault();
  8240. event.stopImmediatePropagation();
  8241. break;
  8242. case Handsontable.helper.keyCode.ENTER:
  8243. if (selection) {
  8244. contextMenu.performAction(event, instance);
  8245. }
  8246. break;
  8247. case Handsontable.helper.keyCode.ARROW_DOWN:
  8248. if (!selection) {
  8249. selectFirstCell(instance, contextMenu);
  8250. } else {
  8251. selectNextCell(selection[0], selection[1], instance, contextMenu);
  8252. }
  8253. event.preventDefault();
  8254. event.stopImmediatePropagation();
  8255. break;
  8256. case Handsontable.helper.keyCode.ARROW_UP:
  8257. if (!selection) {
  8258. selectLastCell(instance, contextMenu);
  8259. } else {
  8260. selectPrevCell(selection[0], selection[1], instance, contextMenu);
  8261. }
  8262. event.preventDefault();
  8263. event.stopImmediatePropagation();
  8264. break;
  8265. case Handsontable.helper.keyCode.ARROW_RIGHT:
  8266. if (selection) {
  8267. var row = selection[0];
  8268. var cell = instance.getCell(selection[0], 0);
  8269. if (ContextMenu.utils.hasSubMenu(cell)) {
  8270. openSubMenu(instance, contextMenu, cell, row);
  8271. }
  8272. }
  8273. event.preventDefault();
  8274. event.stopImmediatePropagation();
  8275. break;
  8276. case Handsontable.helper.keyCode.ARROW_LEFT:
  8277. if (selection) {
  8278. if (instance.rootElement.className.indexOf('htContextSubMenu_') != -1) {
  8279. contextMenu.closeLastOpenedSubMenu();
  8280. var index = contextMenu.menus.length;
  8281. if (index > 0) {
  8282. var menu = contextMenu.menus[index - 1];
  8283. var triggerRow = contextMenu.triggerRows.pop();
  8284. instance = this.htMenus[menu.id];
  8285. instance.selectCell(triggerRow, 0);
  8286. }
  8287. }
  8288. event.preventDefault();
  8289. event.stopImmediatePropagation();
  8290. }
  8291. break;
  8292. }
  8293. function selectFirstCell(instance) {
  8294. var firstCell = instance.getCell(0, 0);
  8295. if (ContextMenu.utils.isSeparator(firstCell) || ContextMenu.utils.isDisabled(firstCell)) {
  8296. selectNextCell(0, 0, instance);
  8297. } else {
  8298. instance.selectCell(0, 0);
  8299. }
  8300. }
  8301. function selectLastCell(instance) {
  8302. var lastRow = instance.countRows() - 1;
  8303. var lastCell = instance.getCell(lastRow, 0);
  8304. if (ContextMenu.utils.isSeparator(lastCell) || ContextMenu.utils.isDisabled(lastCell)) {
  8305. selectPrevCell(lastRow, 0, instance);
  8306. } else {
  8307. instance.selectCell(lastRow, 0);
  8308. }
  8309. }
  8310. function selectNextCell(row, col, instance) {
  8311. var nextRow = row + 1;
  8312. var nextCell = nextRow < instance.countRows() ? instance.getCell(nextRow, col) : null;
  8313. if (!nextCell) {
  8314. return;
  8315. }
  8316. if (ContextMenu.utils.isSeparator(nextCell) || ContextMenu.utils.isDisabled(nextCell)) {
  8317. selectNextCell(nextRow, col, instance);
  8318. } else {
  8319. instance.selectCell(nextRow, col);
  8320. }
  8321. }
  8322. function selectPrevCell(row, col, instance) {
  8323. var prevRow = row - 1;
  8324. var prevCell = prevRow >= 0 ? instance.getCell(prevRow, col) : null;
  8325. if (!prevCell) {
  8326. return;
  8327. }
  8328. if (ContextMenu.utils.isSeparator(prevCell) || ContextMenu.utils.isDisabled(prevCell)) {
  8329. selectPrevCell(prevRow, col, instance);
  8330. } else {
  8331. instance.selectCell(prevRow, col);
  8332. }
  8333. }
  8334. function openSubMenu(instance, contextMenu, cell, row) {
  8335. var selectedItem = instance.getData()[row];
  8336. var items = contextMenu.getItems(selectedItem.submenu);
  8337. var subMenu = contextMenu.createMenu(selectedItem.name, row);
  8338. var coords = cell.getBoundingClientRect();
  8339. var subMenuInstance = contextMenu.show(subMenu, items);
  8340. contextMenu.setSubMenuPosition(coords, subMenu);
  8341. subMenuInstance.selectCell(0, 0);
  8342. }
  8343. };
  8344. function findByKey(items, key) {
  8345. for (var i = 0, ilen = items.length; i < ilen; i++) {
  8346. if (items[i].key === key) {
  8347. return items[i];
  8348. }
  8349. }
  8350. }
  8351. function findIndexByKey(items, key) {
  8352. for (var i = 0, ilen = items.length; i < ilen; i++) {
  8353. if (items[i].key === key) {
  8354. return i;
  8355. }
  8356. }
  8357. }
  8358. ContextMenu.prototype.getItems = function (items) {
  8359. var menu, item;
  8360. function ContextMenuItem(rawItem) {
  8361. if (typeof rawItem == 'string') {
  8362. this.name = rawItem;
  8363. } else {
  8364. Handsontable.helper.extend(this, rawItem);
  8365. }
  8366. }
  8367. ContextMenuItem.prototype = items;
  8368. if (items && items.items) {
  8369. items = items.items;
  8370. }
  8371. if (items === true) {
  8372. items = this.defaultOptions.items;
  8373. }
  8374. if (1 == 1) {
  8375. menu = [];
  8376. for (var key in items) {
  8377. if (items.hasOwnProperty(key)) {
  8378. if (typeof items[key] === 'string') {
  8379. item = findByKey(this.defaultOptions.items, items[key]);
  8380. }
  8381. else {
  8382. item = findByKey(this.defaultOptions.items, key);
  8383. }
  8384. if (!item) {
  8385. item = items[key];
  8386. }
  8387. item = new ContextMenuItem(item);
  8388. if (typeof items[key] === 'object') {
  8389. Handsontable.helper.extend(item, items[key]);
  8390. }
  8391. if (!item.key) {
  8392. item.key = key;
  8393. }
  8394. menu.push(item);
  8395. }
  8396. }
  8397. }
  8398. return menu;
  8399. };
  8400. ContextMenu.prototype.setSubMenuPosition = function (coords, menu) {
  8401. var scrollTop = Handsontable.Dom.getWindowScrollTop();
  8402. var scrollLeft = Handsontable.Dom.getWindowScrollLeft();
  8403. var cursor = {
  8404. top: scrollTop + coords.top,
  8405. topRelative: coords.top,
  8406. left: coords.left,
  8407. leftRelative: coords.left - scrollLeft,
  8408. scrollTop: scrollTop,
  8409. scrollLeft: scrollLeft,
  8410. cellHeight: coords.height,
  8411. cellWidth: coords.width
  8412. };
  8413. if (this.menuFitsBelowCursor(cursor, menu, document.body.clientWidth)) {
  8414. this.positionMenuBelowCursor(cursor, menu, true);
  8415. } else {
  8416. if (this.menuFitsAboveCursor(cursor, menu)) {
  8417. this.positionMenuAboveCursor(cursor, menu, true);
  8418. } else {
  8419. this.positionMenuBelowCursor(cursor, menu, true);
  8420. }
  8421. }
  8422. if (this.menuFitsOnRightOfCursor(cursor, menu, document.body.clientWidth)) {
  8423. this.positionMenuOnRightOfCursor(cursor, menu, true);
  8424. } else {
  8425. this.positionMenuOnLeftOfCursor(cursor, menu, true);
  8426. }
  8427. };
  8428. ContextMenu.prototype.setMenuPosition = function (event, menu) {
  8429. // for ie8
  8430. // http://msdn.microsoft.com/en-us/library/ie/ff974655(v=vs.85).aspx
  8431. var scrollTop = Handsontable.Dom.getWindowScrollTop();
  8432. var scrollLeft = Handsontable.Dom.getWindowScrollLeft();
  8433. var cursorY = event.pageY || (event.clientY + scrollTop);
  8434. var cursorX = event.pageX || (event.clientX + scrollLeft);
  8435. var cursor = {
  8436. top: cursorY,
  8437. topRelative: cursorY - scrollTop,
  8438. left: cursorX,
  8439. leftRelative: cursorX - scrollLeft,
  8440. scrollTop: scrollTop,
  8441. scrollLeft: scrollLeft,
  8442. cellHeight: event.target.clientHeight,
  8443. cellWidth: event.target.clientWidth
  8444. };
  8445. if (this.menuFitsBelowCursor(cursor, menu, document.body.clientHeight)) {
  8446. this.positionMenuBelowCursor(cursor, menu);
  8447. } else {
  8448. if (this.menuFitsAboveCursor(cursor, menu)) {
  8449. this.positionMenuAboveCursor(cursor, menu);
  8450. } else {
  8451. this.positionMenuBelowCursor(cursor, menu);
  8452. }
  8453. }
  8454. if (this.menuFitsOnRightOfCursor(cursor, menu, document.body.clientWidth)) {
  8455. this.positionMenuOnRightOfCursor(cursor, menu);
  8456. } else {
  8457. this.positionMenuOnLeftOfCursor(cursor, menu);
  8458. }
  8459. };
  8460. ContextMenu.prototype.menuFitsAboveCursor = function (cursor, menu) {
  8461. return cursor.topRelative >= menu.offsetHeight;
  8462. };
  8463. ContextMenu.prototype.menuFitsBelowCursor = function (cursor, menu, viewportHeight) {
  8464. return cursor.topRelative + menu.offsetHeight <= viewportHeight;
  8465. };
  8466. ContextMenu.prototype.menuFitsOnRightOfCursor = function (cursor, menu, viewportHeight) {
  8467. return cursor.leftRelative + menu.offsetWidth <= viewportHeight;
  8468. };
  8469. ContextMenu.prototype.positionMenuBelowCursor = function (cursor, menu) {
  8470. menu.style.top = cursor.top + 'px';
  8471. };
  8472. ContextMenu.prototype.positionMenuAboveCursor = function (cursor, menu, subMenu) {
  8473. if (subMenu) {
  8474. menu.style.top = (cursor.top + cursor.cellHeight - menu.offsetHeight) + 'px';
  8475. } else {
  8476. menu.style.top = (cursor.top - menu.offsetHeight) + 'px';
  8477. }
  8478. };
  8479. ContextMenu.prototype.positionMenuOnRightOfCursor = function (cursor, menu, subMenu) {
  8480. if (subMenu) {
  8481. menu.style.left = 1 + cursor.left + cursor.cellWidth + 'px';
  8482. } else {
  8483. menu.style.left = 1 + cursor.left + 'px';
  8484. }
  8485. };
  8486. ContextMenu.prototype.positionMenuOnLeftOfCursor = function (cursor, menu, subMenu) {
  8487. if (subMenu) {
  8488. menu.style.left = (cursor.left - menu.offsetWidth) + 'px';
  8489. } else {
  8490. menu.style.left = (cursor.left - menu.offsetWidth) + 'px';
  8491. }
  8492. };
  8493. ContextMenu.utils = {};
  8494. ContextMenu.utils.normalizeSelection = function (selRange) {
  8495. return {
  8496. start: selRange.getTopLeftCorner(),
  8497. end: selRange.getBottomRightCorner()
  8498. }
  8499. };
  8500. ContextMenu.utils.isSeparator = function (cell) {
  8501. return Handsontable.Dom.hasClass(cell, 'htSeparator');
  8502. };
  8503. ContextMenu.utils.hasSubMenu = function (cell) {
  8504. return Handsontable.Dom.hasClass(cell, 'htSubmenu');
  8505. };
  8506. ContextMenu.utils.isDisabled = function (cell) {
  8507. return Handsontable.Dom.hasClass(cell, 'htDisabled');
  8508. };
  8509. ContextMenu.prototype.enable = function () {
  8510. if (!this.enabled) {
  8511. this.enabled = true;
  8512. this.bindMouseEvents();
  8513. }
  8514. };
  8515. ContextMenu.prototype.disable = function () {
  8516. if (this.enabled) {
  8517. this.enabled = false;
  8518. this.closeAll();
  8519. this.unbindMouseEvents();
  8520. this.unbindTableEvents();
  8521. }
  8522. };
  8523. ContextMenu.prototype.destroy = function () {
  8524. this.closeAll();
  8525. while (this.menus.length > 0) {
  8526. var menu = this.menus.pop();
  8527. this.triggerRows.pop();
  8528. if (menu) {
  8529. this.close(menu);
  8530. if (!this.isMenuEnabledByOtherHotInstance()) {
  8531. this.removeMenu(menu);
  8532. }
  8533. }
  8534. }
  8535. this.unbindMouseEvents();
  8536. this.unbindTableEvents();
  8537. };
  8538. ContextMenu.prototype.isMenuEnabledByOtherHotInstance = function () {
  8539. var hotContainers = document.querySelectorAll('.handsontable');
  8540. var menuEnabled = false;
  8541. for (var i = 0, len = hotContainers.length; i < len; i++) {
  8542. var instance = this.htMenus[hotContainers[i].id];
  8543. if (instance && instance.getSettings().contextMenu) {
  8544. menuEnabled = true;
  8545. break;
  8546. }
  8547. }
  8548. return menuEnabled;
  8549. };
  8550. ContextMenu.prototype.removeMenu = function (menu) {
  8551. if (menu.parentNode) {
  8552. this.menu.parentNode.removeChild(menu);
  8553. }
  8554. };
  8555. ContextMenu.SEPARATOR = {name: "---------"};
  8556. function updateHeight() {
  8557. if (this.rootElement.className.indexOf('htContextMenu')) {
  8558. return;
  8559. }
  8560. var realSeparatorHeight = 0,
  8561. realEntrySize = 0,
  8562. dataSize = this.getSettings().data.length;
  8563. for (var i = 0; i < dataSize; i++) {
  8564. if (this.getSettings().data[i].name == ContextMenu.SEPARATOR.name) {
  8565. realSeparatorHeight += 2;
  8566. } else {
  8567. realEntrySize += 26;
  8568. }
  8569. }
  8570. this.view.wt.wtScrollbars.vertical.fixedContainer.style.height = realEntrySize + realSeparatorHeight + "px";
  8571. }
  8572. function init() {
  8573. var instance = this;
  8574. var contextMenuSetting = instance.getSettings().contextMenu;
  8575. var customOptions = Handsontable.helper.isObject(contextMenuSetting) ? contextMenuSetting : {};
  8576. if (contextMenuSetting) {
  8577. if (!instance.contextMenu) {
  8578. instance.contextMenu = new ContextMenu(instance, customOptions);
  8579. }
  8580. instance.contextMenu.enable();
  8581. } else if (instance.contextMenu) {
  8582. instance.contextMenu.destroy();
  8583. delete instance.contextMenu;
  8584. }
  8585. }
  8586. Handsontable.hooks.add('afterInit', init);
  8587. Handsontable.hooks.add('afterUpdateSettings', init);
  8588. Handsontable.hooks.add('afterInit', updateHeight);
  8589. Handsontable.PluginHooks.register('afterContextMenuDefaultOptions');
  8590. Handsontable.ContextMenu = ContextMenu;
  8591. })(Handsontable);
  8592. function Comments(instance) {
  8593. var eventManager = Handsontable.eventManager(instance),
  8594. doSaveComment = function (row, col, comment, instance) {
  8595. instance.setCellMeta(row, col, 'comment', comment);
  8596. instance.render();
  8597. },
  8598. saveComment = function (range, comment, instance) {
  8599. //LIKE IN EXCEL (TOP LEFT CELL)
  8600. doSaveComment(range.from.row, range.from.col, comment, instance);
  8601. },
  8602. hideCommentTextArea = function () {
  8603. var commentBox = createCommentBox();
  8604. commentBox.style.display = 'none';
  8605. commentBox.value = '';
  8606. },
  8607. bindMouseEvent = function (range) {
  8608. function commentsListener(event) {
  8609. eventManager.removeEventListener(document, 'mouseover');
  8610. if (!(event.target.className == 'htCommentTextArea' || event.target.innerHTML.indexOf('Comment') != -1)) {
  8611. var value = document.querySelector('.htCommentTextArea').value;
  8612. if (value.trim().length > 1) {
  8613. saveComment(range, value, instance);
  8614. }
  8615. unBindMouseEvent();
  8616. hideCommentTextArea();
  8617. }
  8618. }
  8619. eventManager.addEventListener(document, 'mousedown',Handsontable.helper.proxy(commentsListener));
  8620. },
  8621. unBindMouseEvent = function () {
  8622. eventManager.removeEventListener(document, 'mousedown');
  8623. eventManager.addEventListener(document, 'mousedown', Handsontable.helper.proxy(commentsMouseOverListener));
  8624. },
  8625. placeCommentBox = function (range, commentBox) {
  8626. var TD = instance.view.wt.wtTable.getCell(range.from),
  8627. offset = Handsontable.Dom.offset(TD),
  8628. lastColWidth = instance.getColWidth(range.from.col);
  8629. commentBox.style.position = 'absolute';
  8630. commentBox.style.left = offset.left + lastColWidth + 'px';
  8631. commentBox.style.top = offset.top + 'px';
  8632. commentBox.style.zIndex = 2;
  8633. bindMouseEvent(range, commentBox);
  8634. },
  8635. createCommentBox = function (value) {
  8636. var comments = document.querySelector('.htComments');
  8637. if (!comments) {
  8638. comments = document.createElement('DIV');
  8639. var textArea = document.createElement('TEXTAREA');
  8640. Handsontable.Dom.addClass(textArea, 'htCommentTextArea');
  8641. comments.appendChild(textArea);
  8642. Handsontable.Dom.addClass(comments, 'htComments');
  8643. document.getElementsByTagName('body')[0].appendChild(comments);
  8644. }
  8645. value = value ||'';
  8646. document.querySelector('.htCommentTextArea').value = value;
  8647. //var tA = document.getElementsByClassName('htCommentTextArea')[0];
  8648. //tA.focus();
  8649. return comments;
  8650. },
  8651. commentsMouseOverListener = function (event) {
  8652. if(event.target.className.indexOf('htCommentCell') != -1) {
  8653. unBindMouseEvent();
  8654. var coords = instance.view.wt.wtTable.getCoords(event.target);
  8655. var range = {
  8656. from: new WalkontableCellCoords(coords.row, coords.col)
  8657. };
  8658. Handsontable.Comments.showComment(range);
  8659. }
  8660. else if(event.target.className !='htCommentTextArea'){
  8661. hideCommentTextArea();
  8662. }
  8663. };
  8664. return {
  8665. init: function () {
  8666. eventManager.addEventListener(document, 'mouseover', Handsontable.helper.proxy(commentsMouseOverListener));
  8667. },
  8668. showComment: function (range) {
  8669. var meta = instance.getCellMeta(range.from.row, range.from.col),
  8670. value = '';
  8671. if (meta.comment) {
  8672. value = meta.comment;
  8673. }
  8674. var commentBox = createCommentBox(value);
  8675. commentBox.style.display = 'block';
  8676. placeCommentBox(range, commentBox);
  8677. },
  8678. removeComment: function (row, col) {
  8679. instance.removeCellMeta(row, col, 'comment');
  8680. instance.render();
  8681. },
  8682. checkSelectionCommentsConsistency : function () {
  8683. var hasComment = false;
  8684. // IN EXCEL THERE IS COMMENT ONLY FOR TOP LEFT CELL IN SELECTION
  8685. var cell = instance.getSelectedRange().from;
  8686. if(instance.getCellMeta(cell.row,cell.col).comment) {
  8687. hasComment = true;
  8688. }
  8689. return hasComment;
  8690. }
  8691. };
  8692. }
  8693. var init = function () {
  8694. var instance = this;
  8695. var commentsSetting = instance.getSettings().comments;
  8696. if (commentsSetting) {
  8697. Handsontable.Comments = new Comments(instance);
  8698. Handsontable.Comments.init();
  8699. }
  8700. },
  8701. afterRenderer = function (TD, row, col, prop, value, cellProperties) {
  8702. if(cellProperties.comment) {
  8703. Handsontable.Dom.addClass(TD, cellProperties.commentedCellClassName);
  8704. }
  8705. },
  8706. addCommentsActionsToContextMenu = function (defaultOptions) {
  8707. var instance = this;
  8708. if (!instance.getSettings().comments) {
  8709. return;
  8710. }
  8711. defaultOptions.items.push(Handsontable.ContextMenu.SEPARATOR);
  8712. defaultOptions.items.push({
  8713. key: 'commentsAddEdit',
  8714. name: function () {
  8715. var hasComment = Handsontable.Comments.checkSelectionCommentsConsistency();
  8716. return hasComment ? "Edit Comment" : "Add Comment";
  8717. },
  8718. callback: function (key, selection, event) {
  8719. Handsontable.Comments.showComment(this.getSelectedRange());
  8720. },
  8721. disabled: function () {
  8722. return false;
  8723. }
  8724. });
  8725. defaultOptions.items.push({
  8726. key: 'commentsRemove',
  8727. name: function () {
  8728. return "Delete Comment"
  8729. },
  8730. callback: function (key, selection, event) {
  8731. Handsontable.Comments.removeComment(selection.start.row, selection.start.col);
  8732. },
  8733. disabled: function () {
  8734. var hasComment = Handsontable.Comments.checkSelectionCommentsConsistency();
  8735. return !hasComment;
  8736. }
  8737. });
  8738. };
  8739. Handsontable.hooks.add('beforeInit', init);
  8740. Handsontable.hooks.add('afterContextMenuDefaultOptions', addCommentsActionsToContextMenu);
  8741. Handsontable.hooks.add('afterRenderer', afterRenderer);
  8742. /**
  8743. * HandsontableManualColumnMove
  8744. *
  8745. * Has 2 UI components:
  8746. * - handle - the draggable element that sets the desired position of the column
  8747. * - guide - the helper guide that shows the desired position as a vertical guide
  8748. *
  8749. * Warning! Whenever you make a change in this file, make an analogous change in manualRowMove.js
  8750. * @constructor
  8751. */
  8752. (function (Handsontable) {
  8753. function HandsontableManualColumnMove() {
  8754. var startCol
  8755. , endCol
  8756. , startX
  8757. , startOffset
  8758. , currentCol
  8759. , instance
  8760. , currentTH
  8761. , handle = document.createElement('DIV')
  8762. , guide = document.createElement('DIV')
  8763. , eventManager = Handsontable.eventManager(this);
  8764. handle.className = 'manualColumnMover';
  8765. guide.className = 'manualColumnMoverGuide';
  8766. var saveManualColumnPositions = function () {
  8767. var instance = this;
  8768. Handsontable.hooks.run(instance, 'persistentStateSave', 'manualColumnPositions', instance.manualColumnPositions);
  8769. };
  8770. var loadManualColumnPositions = function () {
  8771. var instance = this;
  8772. var storedState = {};
  8773. Handsontable.hooks.run(instance, 'persistentStateLoad', 'manualColumnPositions', storedState);
  8774. return storedState.value;
  8775. };
  8776. function setupHandlePosition(TH) {
  8777. instance = this;
  8778. currentTH = TH;
  8779. var col = this.view.wt.wtTable.getCoords(TH).col; //getCoords returns WalkontableCellCoords
  8780. if (col >= 0) { //if not row header
  8781. currentCol = col;
  8782. var box = currentTH.getBoundingClientRect();
  8783. startOffset = box.left;
  8784. handle.style.top = box.top + 'px';
  8785. handle.style.left = startOffset + 'px';
  8786. instance.rootElement.appendChild(handle);
  8787. }
  8788. }
  8789. function refreshHandlePosition(TH, delta) {
  8790. var box = TH.getBoundingClientRect();
  8791. var handleWidth = 6;
  8792. if (delta > 0) {
  8793. handle.style.left = (box.left + box.width - handleWidth) + 'px';
  8794. }
  8795. else {
  8796. handle.style.left = box.left + 'px';
  8797. }
  8798. }
  8799. function setupGuidePosition() {
  8800. var instance = this;
  8801. Handsontable.Dom.addClass(handle, 'active');
  8802. Handsontable.Dom.addClass(guide, 'active');
  8803. var box = currentTH.getBoundingClientRect();
  8804. guide.style.width = box.width + 'px';
  8805. guide.style.height = instance.view.maximumVisibleElementHeight(0) + 'px';
  8806. guide.style.top = handle.style.top;
  8807. guide.style.left = startOffset + 'px';
  8808. instance.rootElement.appendChild(guide);
  8809. }
  8810. function refreshGuidePosition(diff) {
  8811. guide.style.left = startOffset + diff + 'px';
  8812. }
  8813. function hideHandleAndGuide() {
  8814. Handsontable.Dom.removeClass(handle, 'active');
  8815. Handsontable.Dom.removeClass(guide, 'active');
  8816. }
  8817. var checkColumnHeader = function (element) {
  8818. if (element.tagName != 'BODY') {
  8819. if (element.parentNode.tagName == 'THEAD') {
  8820. return true;
  8821. } else {
  8822. element = element.parentNode;
  8823. return checkColumnHeader(element);
  8824. }
  8825. }
  8826. return false;
  8827. };
  8828. var getTHFromTargetElement = function (element) {
  8829. if (element.tagName != 'TABLE') {
  8830. if (element.tagName == 'TH') {
  8831. return element;
  8832. } else {
  8833. return getTHFromTargetElement(element.parentNode);
  8834. }
  8835. }
  8836. return null;
  8837. };
  8838. var bindEvents = function () {
  8839. var instance = this;
  8840. var pressed;
  8841. eventManager.addEventListener(instance.rootElement,'mouseover',function (e) {
  8842. if (checkColumnHeader(e.target)){
  8843. var th = getTHFromTargetElement(e.target);
  8844. if (th) {
  8845. if (pressed) {
  8846. var col = instance.view.wt.wtTable.getCoords(th).col;
  8847. if(col >= 0) { //not TH above row header
  8848. endCol = col;
  8849. refreshHandlePosition(e.target, endCol - startCol);
  8850. }
  8851. }
  8852. else {
  8853. setupHandlePosition.call(instance, th);
  8854. }
  8855. }
  8856. }
  8857. });
  8858. eventManager.addEventListener(instance.rootElement,'mousedown', function (e) {
  8859. if (Handsontable.Dom.hasClass(e.target, 'manualColumnMover')){
  8860. startX = Handsontable.helper.pageX(e);
  8861. setupGuidePosition.call(instance);
  8862. pressed = instance;
  8863. startCol = currentCol;
  8864. endCol = currentCol;
  8865. }
  8866. });
  8867. eventManager.addEventListener(window,'mousemove',function (e) {
  8868. if (pressed) {
  8869. refreshGuidePosition(Handsontable.helper.pageX(e) - startX);
  8870. }
  8871. });
  8872. eventManager.addEventListener(window,'mouseup',function (e) {
  8873. if (pressed) {
  8874. hideHandleAndGuide();
  8875. pressed = false;
  8876. createPositionData(instance.manualColumnPositions, instance.countCols());
  8877. instance.manualColumnPositions.splice(endCol, 0, instance.manualColumnPositions.splice(startCol, 1)[0]);
  8878. instance.forceFullRender = true;
  8879. instance.view.render(); //updates all
  8880. saveManualColumnPositions.call(instance);
  8881. Handsontable.hooks.run(instance, 'afterColumnMove', startCol, endCol);
  8882. setupHandlePosition.call(instance, currentTH);
  8883. }
  8884. });
  8885. instance.addHook('afterDestroy', unbindEvents);
  8886. };
  8887. var unbindEvents = function(){
  8888. eventManager.clear();
  8889. };
  8890. var createPositionData = function (positionArr, len) {
  8891. if (positionArr.length < len) {
  8892. for (var i = positionArr.length; i < len; i++) {
  8893. positionArr[i] = i;
  8894. }
  8895. }
  8896. };
  8897. this.beforeInit = function () {
  8898. this.manualColumnPositions = [];
  8899. };
  8900. this.init = function (source) {
  8901. var instance = this;
  8902. var manualColMoveEnabled = !!(this.getSettings().manualColumnMove);
  8903. if (manualColMoveEnabled) {
  8904. var initialManualColumnPositions = this.getSettings().manualColumnMove;
  8905. var loadedManualColumnPositions = loadManualColumnPositions.call(instance);
  8906. if (typeof loadedManualColumnPositions != 'undefined') {
  8907. this.manualColumnPositions = loadedManualColumnPositions;
  8908. } else if (Array.isArray(initialManualColumnPositions)) {
  8909. this.manualColumnPositions = initialManualColumnPositions;
  8910. } else {
  8911. this.manualColumnPositions = [];
  8912. }
  8913. if (source == 'afterInit') {
  8914. bindEvents.call(this);
  8915. if (this.manualColumnPositions.length > 0) {
  8916. this.forceFullRender = true;
  8917. this.render();
  8918. }
  8919. }
  8920. } else {
  8921. unbindEvents.call(this);
  8922. this.manualColumnPositions = [];
  8923. }
  8924. };
  8925. this.modifyCol = function (col) {
  8926. //TODO test performance: http://jsperf.com/object-wrapper-vs-primitive/2
  8927. if (this.getSettings().manualColumnMove) {
  8928. if (typeof this.manualColumnPositions[col] === 'undefined') {
  8929. createPositionData(this.manualColumnPositions, col + 1);
  8930. }
  8931. return this.manualColumnPositions[col];
  8932. }
  8933. return col;
  8934. };
  8935. // need to reconstruct manualcolpositions after removing columns
  8936. this.afterRemoveCol = function (index, amount) {
  8937. if (!this.getSettings().manualColumnMove) return;
  8938. var rmindx,
  8939. colpos = this.manualColumnPositions;
  8940. // We have removed columns, we also need to remove the indicies from manual column array
  8941. rmindx = colpos.splice(index, amount);
  8942. // We need to remap manualColPositions so it remains constant linear from 0->ncols
  8943. colpos = colpos.map(function (colpos) {
  8944. var i, newpos = colpos;
  8945. for (i = 0; i < rmindx.length; i++) {
  8946. if (colpos > rmindx[i]) newpos--;
  8947. }
  8948. return newpos;
  8949. });
  8950. this.manualColumnPositions = colpos;
  8951. };
  8952. // need to reconstruct manualcolpositions after adding columns
  8953. this.afterCreateCol = function (index, amount) {
  8954. if (!this.getSettings().manualColumnMove) return;
  8955. var colpos = this.manualColumnPositions;
  8956. if (!colpos.length) return;
  8957. var addindx = [];
  8958. for (var i = 0; i < amount; i++) {
  8959. addindx.push(index + i);
  8960. }
  8961. if (index >= colpos.length) {
  8962. colpos.concat(addindx);
  8963. }
  8964. else {
  8965. // We need to remap manualColPositions so it remains constant linear from 0->ncols
  8966. colpos = colpos.map(function (colpos) {
  8967. return (colpos >= index) ? (colpos + amount) : colpos;
  8968. });
  8969. // We have added columns, we also need to add new indicies to manualcolumn position array
  8970. colpos.splice.apply(colpos, [index, 0].concat(addindx));
  8971. }
  8972. this.manualColumnPositions = colpos;
  8973. };
  8974. }
  8975. var htManualColumnMove = new HandsontableManualColumnMove();
  8976. Handsontable.hooks.add('beforeInit', htManualColumnMove.beforeInit);
  8977. Handsontable.hooks.add('afterInit', function () {
  8978. htManualColumnMove.init.call(this, 'afterInit')
  8979. });
  8980. Handsontable.hooks.add('afterUpdateSettings', function () {
  8981. htManualColumnMove.init.call(this, 'afterUpdateSettings')
  8982. });
  8983. Handsontable.hooks.add('modifyCol', htManualColumnMove.modifyCol);
  8984. Handsontable.hooks.add('afterRemoveCol', htManualColumnMove.afterRemoveCol);
  8985. Handsontable.hooks.add('afterCreateCol', htManualColumnMove.afterCreateCol);
  8986. Handsontable.hooks.register('afterColumnMove');
  8987. })(Handsontable);
  8988. /**
  8989. * HandsontableManualColumnResize
  8990. *
  8991. * Has 2 UI components:
  8992. * - handle - the draggable element that sets the desired width of the column
  8993. * - guide - the helper guide that shows the desired width as a vertical guide
  8994. *
  8995. * Warning! Whenever you make a change in this file, make an analogous change in manualRowResize.js
  8996. * @constructor
  8997. */
  8998. (function (Handsontable) {
  8999. function HandsontableManualColumnResize() {
  9000. var currentTH
  9001. , currentCol
  9002. , currentWidth
  9003. , instance
  9004. , newSize
  9005. , startX
  9006. , startWidth
  9007. , startOffset
  9008. , handle = document.createElement('DIV')
  9009. , guide = document.createElement('DIV')
  9010. , eventManager = Handsontable.eventManager(this);
  9011. handle.className = 'manualColumnResizer';
  9012. guide.className = 'manualColumnResizerGuide';
  9013. var saveManualColumnWidths = function () {
  9014. var instance = this;
  9015. Handsontable.hooks.run(instance, 'persistentStateSave', 'manualColumnWidths', instance.manualColumnWidths);
  9016. };
  9017. var loadManualColumnWidths = function () {
  9018. var instance = this;
  9019. var storedState = {};
  9020. Handsontable.hooks.run(instance, 'persistentStateLoad', 'manualColumnWidths', storedState);
  9021. return storedState.value;
  9022. };
  9023. function setupHandlePosition(TH) {
  9024. instance = this;
  9025. currentTH = TH;
  9026. var col = this.view.wt.wtTable.getCoords(TH).col; //getCoords returns WalkontableCellCoords
  9027. if (col >= 0) { //if not row header
  9028. currentCol = col;
  9029. var box = currentTH.getBoundingClientRect();
  9030. startOffset = box.left - 6;
  9031. startWidth = parseInt(box.width, 10);
  9032. handle.style.top = box.top + 'px';
  9033. handle.style.left = startOffset + startWidth + 'px';
  9034. instance.rootElement.appendChild(handle);
  9035. }
  9036. }
  9037. function refreshHandlePosition() {
  9038. handle.style.left = startOffset + currentWidth + 'px';
  9039. }
  9040. function setupGuidePosition() {
  9041. var instance = this;
  9042. Handsontable.Dom.addClass(handle, 'active');
  9043. Handsontable.Dom.addClass(guide, 'active');
  9044. guide.style.top = handle.style.top;
  9045. guide.style.left = handle.style.left;
  9046. guide.style.height = instance.view.maximumVisibleElementHeight(0) + 'px';
  9047. instance.rootElement.appendChild(guide);
  9048. }
  9049. function refreshGuidePosition() {
  9050. guide.style.left = handle.style.left;
  9051. }
  9052. function hideHandleAndGuide() {
  9053. Handsontable.Dom.removeClass(handle, 'active');
  9054. Handsontable.Dom.removeClass(guide, 'active');
  9055. }
  9056. var checkColumnHeader = function (element) {
  9057. if (element.tagName != 'BODY') {
  9058. if (element.parentNode.tagName == 'THEAD') {
  9059. return true;
  9060. } else {
  9061. element = element.parentNode;
  9062. return checkColumnHeader(element);
  9063. }
  9064. }
  9065. return false;
  9066. };
  9067. var getTHFromTargetElement = function (element) {
  9068. if (element.tagName != 'TABLE') {
  9069. if (element.tagName == 'TH') {
  9070. return element;
  9071. } else {
  9072. return getTHFromTargetElement(element.parentNode);
  9073. }
  9074. }
  9075. return null;
  9076. };
  9077. var bindEvents = function () {
  9078. var instance = this;
  9079. var pressed;
  9080. var dblclick = 0;
  9081. var autoresizeTimeout = null;
  9082. eventManager.addEventListener(instance.rootElement, 'mouseover',function (e) {
  9083. if (checkColumnHeader(e.target)) {
  9084. var th = getTHFromTargetElement(e.target);
  9085. if (th) {
  9086. if (!pressed) {
  9087. setupHandlePosition.call(instance, th);
  9088. }
  9089. }
  9090. }
  9091. });
  9092. eventManager.addEventListener(instance.rootElement,'mousedown', function (e) {
  9093. if (Handsontable.Dom.hasClass(e.target, 'manualColumnResizer')) {
  9094. setupGuidePosition.call(instance);
  9095. pressed = instance;
  9096. if (autoresizeTimeout == null) {
  9097. autoresizeTimeout = setTimeout(function () {
  9098. if (dblclick >= 2) {
  9099. newSize = instance.determineColumnWidth.call(instance, currentCol);
  9100. setManualSize(currentCol, newSize);
  9101. instance.forceFullRender = true;
  9102. instance.view.render(); //updates all
  9103. Handsontable.hooks.run(instance, 'afterColumnResize', currentCol, newSize);
  9104. }
  9105. dblclick = 0;
  9106. autoresizeTimeout = null;
  9107. }, 500);
  9108. instance._registerTimeout(autoresizeTimeout);
  9109. }
  9110. dblclick++;
  9111. startX = Handsontable.helper.pageX(e);
  9112. newSize = startWidth;
  9113. }
  9114. });
  9115. eventManager.addEventListener(window,'mousemove', function (e) {
  9116. if (pressed) {
  9117. currentWidth = startWidth + (Handsontable.helper.pageX(e) - startX);
  9118. newSize = setManualSize(currentCol, currentWidth); //save col width
  9119. refreshHandlePosition();
  9120. refreshGuidePosition();
  9121. }
  9122. });
  9123. eventManager.addEventListener(window, 'mouseup', function (){
  9124. if (pressed) {
  9125. hideHandleAndGuide();
  9126. pressed = false;
  9127. if(newSize != startWidth){
  9128. instance.forceFullRender = true;
  9129. instance.view.render(); //updates all
  9130. saveManualColumnWidths.call(instance);
  9131. Handsontable.hooks.run(instance, 'afterColumnResize', currentCol, newSize);
  9132. }
  9133. setupHandlePosition.call(instance, currentTH);
  9134. }
  9135. });
  9136. instance.addHook('afterDestroy', unbindEvents);
  9137. };
  9138. var unbindEvents = function(){
  9139. eventManager.clear();
  9140. };
  9141. this.beforeInit = function () {
  9142. this.manualColumnWidths = [];
  9143. };
  9144. this.init = function (source) {
  9145. var instance = this;
  9146. var manualColumnWidthEnabled = !!(this.getSettings().manualColumnResize);
  9147. if (manualColumnWidthEnabled) {
  9148. var initialColumnWidths = this.getSettings().manualColumnResize;
  9149. var loadedManualColumnWidths = loadManualColumnWidths.call(instance);
  9150. if (typeof loadedManualColumnWidths != 'undefined') {
  9151. this.manualColumnWidths = loadedManualColumnWidths;
  9152. } else if (Array.isArray(initialColumnWidths)) {
  9153. this.manualColumnWidths = initialColumnWidths;
  9154. } else {
  9155. this.manualColumnWidths = [];
  9156. }
  9157. if (source == 'afterInit') {
  9158. bindEvents.call(this);
  9159. if (this.manualColumnWidths.length > 0) {
  9160. this.forceFullRender = true;
  9161. this.render();
  9162. }
  9163. }
  9164. }
  9165. else {
  9166. unbindEvents.call(this);
  9167. this.manualColumnWidths = [];
  9168. }
  9169. };
  9170. var setManualSize = function (col, width) {
  9171. width = Math.max(width, 20);
  9172. /**
  9173. * We need to run col through modifyCol hook, in case the order of displayed columns is different than the order
  9174. * in data source. For instance, this order can be modified by manualColumnMove plugin.
  9175. */
  9176. col = Handsontable.hooks.execute(instance, 'modifyCol', col);
  9177. instance.manualColumnWidths[col] = width;
  9178. return width;
  9179. };
  9180. this.modifyColWidth = function (width, col) {
  9181. col = this.runHooksAndReturn('modifyCol', col);
  9182. if (this.getSettings().manualColumnResize && this.manualColumnWidths[col]) {
  9183. return this.manualColumnWidths[col];
  9184. }
  9185. return width;
  9186. };
  9187. }
  9188. var htManualColumnResize = new HandsontableManualColumnResize();
  9189. Handsontable.hooks.add('beforeInit', htManualColumnResize.beforeInit);
  9190. Handsontable.hooks.add('afterInit', function () {
  9191. htManualColumnResize.init.call(this, 'afterInit')
  9192. });
  9193. Handsontable.hooks.add('afterUpdateSettings', function () {
  9194. htManualColumnResize.init.call(this, 'afterUpdateSettings')
  9195. });
  9196. Handsontable.hooks.add('modifyColWidth', htManualColumnResize.modifyColWidth);
  9197. Handsontable.hooks.register('afterColumnResize');
  9198. })(Handsontable);
  9199. /**
  9200. * HandsontableManualRowResize
  9201. *
  9202. * Has 2 UI components:
  9203. * - handle - the draggable element that sets the desired height of the row
  9204. * - guide - the helper guide that shows the desired height as a horizontal guide
  9205. *
  9206. * Warning! Whenever you make a change in this file, make an analogous change in manualRowResize.js
  9207. * @constructor
  9208. */
  9209. (function (Handsontable) {
  9210. function HandsontableManualRowResize () {
  9211. var currentTH
  9212. , currentRow
  9213. , currentHeight
  9214. , instance
  9215. , newSize
  9216. , startY
  9217. , startHeight
  9218. , startOffset
  9219. , handle = document.createElement('DIV')
  9220. , guide = document.createElement('DIV')
  9221. , eventManager = Handsontable.eventManager(this);
  9222. handle.className = 'manualRowResizer';
  9223. guide.className = 'manualRowResizerGuide';
  9224. var saveManualRowHeights = function () {
  9225. var instance = this;
  9226. Handsontable.hooks.run(instance, 'persistentStateSave', 'manualRowHeights', instance.manualRowHeights);
  9227. };
  9228. var loadManualRowHeights = function () {
  9229. var instance = this
  9230. , storedState = {};
  9231. Handsontable.hooks.run(instance, 'persistentStateLoad', 'manualRowHeights', storedState);
  9232. return storedState.value;
  9233. };
  9234. function setupHandlePosition(TH) {
  9235. instance = this;
  9236. currentTH = TH;
  9237. var row = this.view.wt.wtTable.getCoords(TH).row; //getCoords returns WalkontableCellCoords
  9238. if (row >= 0) { //if not col header
  9239. currentRow = row;
  9240. var box = currentTH.getBoundingClientRect();
  9241. startOffset = box.top - 6;
  9242. startHeight = parseInt(box.height, 10);
  9243. handle.style.left = box.left + 'px';
  9244. handle.style.top = startOffset + startHeight + 'px';
  9245. instance.rootElement.appendChild(handle);
  9246. }
  9247. }
  9248. function refreshHandlePosition() {
  9249. handle.style.top = startOffset + currentHeight + 'px';
  9250. }
  9251. function setupGuidePosition() {
  9252. var instance = this;
  9253. Handsontable.Dom.addClass(handle, 'active');
  9254. Handsontable.Dom.addClass(guide, 'active');
  9255. guide.style.top = handle.style.top;
  9256. guide.style.left = handle.style.left;
  9257. guide.style.width = instance.view.maximumVisibleElementWidth(0) + 'px';
  9258. instance.rootElement.appendChild(guide);
  9259. }
  9260. function refreshGuidePosition() {
  9261. guide.style.top = handle.style.top;
  9262. }
  9263. function hideHandleAndGuide() {
  9264. Handsontable.Dom.removeClass(handle, 'active');
  9265. Handsontable.Dom.removeClass(guide, 'active');
  9266. }
  9267. var checkRowHeader = function (element) {
  9268. if (element.tagName != 'BODY') {
  9269. if (element.parentNode.tagName == 'TBODY') {
  9270. return true;
  9271. } else {
  9272. element = element.parentNode;
  9273. return checkRowHeader(element);
  9274. }
  9275. }
  9276. return false;
  9277. };
  9278. var getTHFromTargetElement = function (element) {
  9279. if (element.tagName != 'TABLE') {
  9280. if (element.tagName == 'TH') {
  9281. return element;
  9282. } else {
  9283. return getTHFromTargetElement(element.parentNode);
  9284. }
  9285. }
  9286. return null;
  9287. };
  9288. var bindEvents = function () {
  9289. var instance = this;
  9290. var pressed;
  9291. var dblclick = 0;
  9292. var autoresizeTimeout = null;
  9293. eventManager.addEventListener(instance.rootElement,'mouseover', function (e){
  9294. if(checkRowHeader(e.target)) {
  9295. var th = getTHFromTargetElement(e.target)
  9296. if (th) {
  9297. if (!pressed) {
  9298. setupHandlePosition.call(instance, th);
  9299. }
  9300. }
  9301. }
  9302. });
  9303. eventManager.addEventListener(instance.rootElement,'mousedown', function (e) {
  9304. if (Handsontable.Dom.hasClass(e.target, 'manualRowResizer')) {
  9305. setupGuidePosition.call(instance);
  9306. pressed = instance;
  9307. if (autoresizeTimeout == null) {
  9308. autoresizeTimeout = setTimeout(function () {
  9309. if (dblclick >= 2) {
  9310. setManualSize(currentRow, null); //double click sets auto row size
  9311. instance.forceFullRender = true;
  9312. instance.view.render(); //updates all
  9313. Handsontable.hooks.run(instance, 'afterRowResize', currentRow, newSize);
  9314. }
  9315. dblclick = 0;
  9316. autoresizeTimeout = null;
  9317. }, 500);
  9318. instance._registerTimeout(autoresizeTimeout);
  9319. }
  9320. dblclick++;
  9321. startY = Handsontable.helper.pageY(e);
  9322. newSize = startHeight;
  9323. }
  9324. });
  9325. eventManager.addEventListener(window,'mousemove',function (e) {
  9326. if (pressed) {
  9327. currentHeight = startHeight + (Handsontable.helper.pageY(e) - startY);
  9328. newSize = setManualSize(currentRow, currentHeight);
  9329. refreshHandlePosition();
  9330. refreshGuidePosition();
  9331. }
  9332. });
  9333. eventManager.addEventListener(window,'mouseup',function (e) {
  9334. if (pressed) {
  9335. hideHandleAndGuide();
  9336. pressed = false;
  9337. if(newSize != startHeight){
  9338. instance.forceFullRender = true;
  9339. instance.view.render(); //updates all
  9340. saveManualRowHeights.call(instance);
  9341. Handsontable.hooks.run(instance, 'afterRowResize', currentRow, newSize);
  9342. }
  9343. setupHandlePosition.call(instance, currentTH);
  9344. }
  9345. });
  9346. instance.addHook('afterDestroy', unbindEvents);
  9347. };
  9348. var unbindEvents = function(){
  9349. eventManager.clear();
  9350. };
  9351. this.beforeInit = function () {
  9352. this.manualRowHeights = [];
  9353. };
  9354. this.init = function (source) {
  9355. var instance = this;
  9356. var manualColumnHeightEnabled = !!(this.getSettings().manualRowResize);
  9357. if (manualColumnHeightEnabled) {
  9358. var initialRowHeights = this.getSettings().manualRowResize;
  9359. var loadedManualRowHeights = loadManualRowHeights.call(instance);
  9360. if (typeof loadedManualRowHeights != 'undefined') {
  9361. this.manualRowHeights = loadedManualRowHeights;
  9362. } else if (Array.isArray(initialRowHeights)) {
  9363. this.manualRowHeights = initialRowHeights;
  9364. } else {
  9365. this.manualRowHeights = [];
  9366. }
  9367. if (source === 'afterInit') {
  9368. bindEvents.call(this);
  9369. if (this.manualRowHeights.length > 0) {
  9370. this.forceFullRender = true;
  9371. this.render();
  9372. }
  9373. }
  9374. else {
  9375. this.forceFullRender = true;
  9376. this.render();
  9377. }
  9378. }
  9379. else {
  9380. unbindEvents.call(this);
  9381. this.manualRowHeights = [];
  9382. }
  9383. };
  9384. var setManualSize = function (row, height) {
  9385. row = Handsontable.hooks.execute(instance, 'modifyRow', row);
  9386. instance.manualRowHeights[row] = height;
  9387. return height;
  9388. };
  9389. this.modifyRowHeight = function (height, row) {
  9390. if (this.getSettings().manualRowResize) {
  9391. row = this.runHooksAndReturn('modifyRow', row);
  9392. if (this.manualRowHeights[row] !== void 0) {
  9393. return this.manualRowHeights[row];
  9394. }
  9395. }
  9396. return height;
  9397. };
  9398. }
  9399. var htManualRowResize = new HandsontableManualRowResize();
  9400. Handsontable.hooks.add('beforeInit', htManualRowResize.beforeInit);
  9401. Handsontable.hooks.add('afterInit', function () {
  9402. htManualRowResize.init.call(this, 'afterInit');
  9403. });
  9404. Handsontable.hooks.add('afterUpdateSettings', function () {
  9405. htManualRowResize.init.call(this, 'afterUpdateSettings')
  9406. });
  9407. Handsontable.hooks.add('modifyRowHeight', htManualRowResize.modifyRowHeight);
  9408. Handsontable.hooks.register('afterRowResize');
  9409. })(Handsontable);
  9410. (function HandsontableObserveChanges() {
  9411. Handsontable.hooks.add('afterLoadData', init);
  9412. Handsontable.hooks.add('afterUpdateSettings', init);
  9413. Handsontable.hooks.register('afterChangesObserved');
  9414. function init() {
  9415. var instance = this;
  9416. var pluginEnabled = instance.getSettings().observeChanges;
  9417. if (pluginEnabled) {
  9418. if(instance.observer) {
  9419. destroy.call(instance); //destroy observer for old data object
  9420. }
  9421. createObserver.call(instance);
  9422. bindEvents.call(instance);
  9423. } else if (!pluginEnabled){
  9424. destroy.call(instance);
  9425. }
  9426. }
  9427. function createObserver(){
  9428. var instance = this;
  9429. instance.observeChangesActive = true;
  9430. instance.pauseObservingChanges = function(){
  9431. instance.observeChangesActive = false;
  9432. };
  9433. instance.resumeObservingChanges = function(){
  9434. instance.observeChangesActive = true;
  9435. };
  9436. instance.observedData = instance.getData();
  9437. instance.observer = jsonpatch.observe(instance.observedData, function (patches) {
  9438. if(instance.observeChangesActive){
  9439. runHookForOperation.call(instance, patches);
  9440. instance.render();
  9441. }
  9442. instance.runHooks('afterChangesObserved');
  9443. });
  9444. }
  9445. function runHookForOperation(rawPatches){
  9446. var instance = this;
  9447. var patches = cleanPatches(rawPatches);
  9448. for(var i = 0, len = patches.length; i < len; i++){
  9449. var patch = patches[i];
  9450. var parsedPath = parsePath(patch.path);
  9451. switch(patch.op){
  9452. case 'add':
  9453. if(isNaN(parsedPath.col)){
  9454. instance.runHooks('afterCreateRow', parsedPath.row);
  9455. } else {
  9456. instance.runHooks('afterCreateCol', parsedPath.col);
  9457. }
  9458. break;
  9459. case 'remove':
  9460. if(isNaN(parsedPath.col)){
  9461. instance.runHooks('afterRemoveRow', parsedPath.row, 1);
  9462. } else {
  9463. instance.runHooks('afterRemoveCol', parsedPath.col, 1);
  9464. }
  9465. break;
  9466. case 'replace':
  9467. instance.runHooks('afterChange', [parsedPath.row, parsedPath.col, null, patch.value], 'external');
  9468. break;
  9469. }
  9470. }
  9471. function cleanPatches(rawPatches){
  9472. var patches;
  9473. patches = removeLengthRelatedPatches(rawPatches);
  9474. patches = removeMultipleAddOrRemoveColPatches(patches);
  9475. return patches;
  9476. }
  9477. /**
  9478. * Removing or adding column will produce one patch for each table row.
  9479. * This function leaves only one patch for each column add/remove operation
  9480. */
  9481. function removeMultipleAddOrRemoveColPatches(rawPatches){
  9482. var newOrRemovedColumns = [];
  9483. return rawPatches.filter(function(patch){
  9484. var parsedPath = parsePath(patch.path);
  9485. if(['add', 'remove'].indexOf(patch.op) != -1 && !isNaN(parsedPath.col)){
  9486. if(newOrRemovedColumns.indexOf(parsedPath.col) != -1){
  9487. return false;
  9488. } else {
  9489. newOrRemovedColumns.push(parsedPath.col);
  9490. }
  9491. }
  9492. return true;
  9493. });
  9494. }
  9495. /**
  9496. * If observeChanges uses native Object.observe method, then it produces patches for length property.
  9497. * This function removes them.
  9498. */
  9499. function removeLengthRelatedPatches(rawPatches){
  9500. return rawPatches.filter(function(patch){
  9501. return !/[/]length/ig.test(patch.path);
  9502. })
  9503. }
  9504. function parsePath(path){
  9505. var match = path.match(/^\/(\d+)\/?(.*)?$/);
  9506. return {
  9507. row: parseInt(match[1], 10),
  9508. col: /^\d*$/.test(match[2]) ? parseInt(match[2], 10) : match[2]
  9509. }
  9510. }
  9511. }
  9512. function destroy(){
  9513. var instance = this;
  9514. if (instance.observer){
  9515. destroyObserver.call(instance);
  9516. unbindEvents.call(instance);
  9517. }
  9518. }
  9519. function destroyObserver(){
  9520. var instance = this;
  9521. jsonpatch.unobserve(instance.observedData, instance.observer);
  9522. delete instance.observeChangesActive;
  9523. delete instance.pauseObservingChanges;
  9524. delete instance.resumeObservingChanges;
  9525. }
  9526. function bindEvents(){
  9527. var instance = this;
  9528. instance.addHook('afterDestroy', destroy);
  9529. instance.addHook('afterCreateRow', afterTableAlter);
  9530. instance.addHook('afterRemoveRow', afterTableAlter);
  9531. instance.addHook('afterCreateCol', afterTableAlter);
  9532. instance.addHook('afterRemoveCol', afterTableAlter);
  9533. instance.addHook('afterChange', function(changes, source){
  9534. if(source != 'loadData'){
  9535. afterTableAlter.call(this);
  9536. }
  9537. });
  9538. }
  9539. function unbindEvents(){
  9540. var instance = this;
  9541. instance.removeHook('afterDestroy', destroy);
  9542. instance.removeHook('afterCreateRow', afterTableAlter);
  9543. instance.removeHook('afterRemoveRow', afterTableAlter);
  9544. instance.removeHook('afterCreateCol', afterTableAlter);
  9545. instance.removeHook('afterRemoveCol', afterTableAlter);
  9546. instance.removeHook('afterChange', afterTableAlter);
  9547. }
  9548. function afterTableAlter(){
  9549. var instance = this;
  9550. instance.pauseObservingChanges();
  9551. instance.addHookOnce('afterChangesObserved', function(){
  9552. instance.resumeObservingChanges();
  9553. });
  9554. }
  9555. })();
  9556. /*
  9557. *
  9558. * Plugin enables saving table state
  9559. *
  9560. * */
  9561. function Storage(prefix) {
  9562. var savedKeys;
  9563. var saveSavedKeys = function () {
  9564. window.localStorage[prefix + '__' + 'persistentStateKeys'] = JSON.stringify(savedKeys);
  9565. };
  9566. var loadSavedKeys = function () {
  9567. var keysJSON = window.localStorage[prefix + '__' + 'persistentStateKeys'];
  9568. var keys = typeof keysJSON == 'string' ? JSON.parse(keysJSON) : void 0;
  9569. savedKeys = keys ? keys : [];
  9570. };
  9571. var clearSavedKeys = function () {
  9572. savedKeys = [];
  9573. saveSavedKeys();
  9574. };
  9575. loadSavedKeys();
  9576. this.saveValue = function (key, value) {
  9577. window.localStorage[prefix + '_' + key] = JSON.stringify(value);
  9578. if (savedKeys.indexOf(key) == -1) {
  9579. savedKeys.push(key);
  9580. saveSavedKeys();
  9581. }
  9582. };
  9583. this.loadValue = function (key, defaultValue) {
  9584. key = typeof key != 'undefined' ? key : defaultValue;
  9585. var value = window.localStorage[prefix + '_' + key];
  9586. return typeof value == "undefined" ? void 0 : JSON.parse(value);
  9587. };
  9588. this.reset = function (key) {
  9589. window.localStorage.removeItem(prefix + '_' + key);
  9590. };
  9591. this.resetAll = function () {
  9592. for (var index = 0; index < savedKeys.length; index++) {
  9593. window.localStorage.removeItem(prefix + '_' + savedKeys[index]);
  9594. }
  9595. clearSavedKeys();
  9596. };
  9597. }
  9598. (function (StorageClass) {
  9599. function HandsontablePersistentState() {
  9600. var plugin = this;
  9601. this.init = function () {
  9602. var instance = this,
  9603. pluginSettings = instance.getSettings()['persistentState'];
  9604. plugin.enabled = !!(pluginSettings);
  9605. if (!plugin.enabled) {
  9606. removeHooks.call(instance);
  9607. return;
  9608. }
  9609. if (!instance.storage) {
  9610. instance.storage = new StorageClass(instance.rootElement.id);
  9611. }
  9612. instance.resetState = plugin.resetValue;
  9613. addHooks.call(instance);
  9614. };
  9615. this.saveValue = function (key, value) {
  9616. var instance = this;
  9617. instance.storage.saveValue(key, value);
  9618. };
  9619. this.loadValue = function (key, saveTo) {
  9620. var instance = this;
  9621. saveTo.value = instance.storage.loadValue(key);
  9622. };
  9623. this.resetValue = function (key) {
  9624. var instance = this;
  9625. if (typeof key != 'undefined') {
  9626. instance.storage.reset(key);
  9627. } else {
  9628. instance.storage.resetAll();
  9629. }
  9630. };
  9631. var hooks = {
  9632. 'persistentStateSave': plugin.saveValue,
  9633. 'persistentStateLoad': plugin.loadValue,
  9634. 'persistentStateReset': plugin.resetValue
  9635. };
  9636. for (var hookName in hooks) {
  9637. if (hooks.hasOwnProperty(hookName)) {
  9638. Handsontable.hooks.register(hookName);
  9639. }
  9640. }
  9641. function addHooks() {
  9642. var instance = this;
  9643. for (var hookName in hooks) {
  9644. if (hooks.hasOwnProperty(hookName)) {
  9645. instance.addHook(hookName, hooks[hookName]);
  9646. }
  9647. }
  9648. }
  9649. function removeHooks() {
  9650. var instance = this;
  9651. for (var hookName in hooks) {
  9652. if (hooks.hasOwnProperty(hookName)) {
  9653. instance.removeHook(hookName, hooks[hookName]);
  9654. }
  9655. }
  9656. }
  9657. }
  9658. var htPersistentState = new HandsontablePersistentState();
  9659. Handsontable.hooks.add('beforeInit', htPersistentState.init);
  9660. Handsontable.hooks.add('afterUpdateSettings', htPersistentState.init);
  9661. })(Storage);
  9662. /**
  9663. * Handsontable UndoRedo class
  9664. */
  9665. (function(Handsontable){
  9666. Handsontable.UndoRedo = function (instance) {
  9667. var plugin = this;
  9668. this.instance = instance;
  9669. this.doneActions = [];
  9670. this.undoneActions = [];
  9671. this.ignoreNewActions = false;
  9672. instance.addHook("afterChange", function (changes, origin) {
  9673. if(changes){
  9674. var action = new Handsontable.UndoRedo.ChangeAction(changes);
  9675. plugin.done(action);
  9676. }
  9677. });
  9678. instance.addHook("afterCreateRow", function (index, amount, createdAutomatically) {
  9679. if (createdAutomatically) {
  9680. return;
  9681. }
  9682. var action = new Handsontable.UndoRedo.CreateRowAction(index, amount);
  9683. plugin.done(action);
  9684. });
  9685. instance.addHook("beforeRemoveRow", function (index, amount) {
  9686. var originalData = plugin.instance.getData();
  9687. index = ( originalData.length + index ) % originalData.length;
  9688. var removedData = originalData.slice(index, index + amount);
  9689. var action = new Handsontable.UndoRedo.RemoveRowAction(index, removedData);
  9690. plugin.done(action);
  9691. });
  9692. instance.addHook("afterCreateCol", function (index, amount, createdAutomatically) {
  9693. if (createdAutomatically) {
  9694. return;
  9695. }
  9696. var action = new Handsontable.UndoRedo.CreateColumnAction(index, amount);
  9697. plugin.done(action);
  9698. });
  9699. instance.addHook("beforeRemoveCol", function (index, amount) {
  9700. var originalData = plugin.instance.getData();
  9701. index = ( plugin.instance.countCols() + index ) % plugin.instance.countCols();
  9702. var removedData = [];
  9703. for (var i = 0, len = originalData.length; i < len; i++) {
  9704. removedData[i] = originalData[i].slice(index, index + amount);
  9705. }
  9706. var headers;
  9707. if(Array.isArray(instance.getSettings().colHeaders)){
  9708. headers = instance.getSettings().colHeaders.slice(index, index + removedData.length);
  9709. }
  9710. var action = new Handsontable.UndoRedo.RemoveColumnAction(index, removedData, headers);
  9711. plugin.done(action);
  9712. });
  9713. };
  9714. Handsontable.UndoRedo.prototype.done = function (action) {
  9715. if (!this.ignoreNewActions) {
  9716. this.doneActions.push(action);
  9717. this.undoneActions.length = 0;
  9718. }
  9719. };
  9720. /**
  9721. * Undo operation from current revision
  9722. */
  9723. Handsontable.UndoRedo.prototype.undo = function () {
  9724. if (this.isUndoAvailable()) {
  9725. var action = this.doneActions.pop();
  9726. this.ignoreNewActions = true;
  9727. var that = this;
  9728. action.undo(this.instance, function () {
  9729. that.ignoreNewActions = false;
  9730. that.undoneActions.push(action);
  9731. });
  9732. }
  9733. };
  9734. /**
  9735. * Redo operation from current revision
  9736. */
  9737. Handsontable.UndoRedo.prototype.redo = function () {
  9738. if (this.isRedoAvailable()) {
  9739. var action = this.undoneActions.pop();
  9740. this.ignoreNewActions = true;
  9741. var that = this;
  9742. action.redo(this.instance, function () {
  9743. that.ignoreNewActions = false;
  9744. that.doneActions.push(action);
  9745. });
  9746. }
  9747. };
  9748. /**
  9749. * Returns true if undo point is available
  9750. * @return {Boolean}
  9751. */
  9752. Handsontable.UndoRedo.prototype.isUndoAvailable = function () {
  9753. return this.doneActions.length > 0;
  9754. };
  9755. /**
  9756. * Returns true if redo point is available
  9757. * @return {Boolean}
  9758. */
  9759. Handsontable.UndoRedo.prototype.isRedoAvailable = function () {
  9760. return this.undoneActions.length > 0;
  9761. };
  9762. /**
  9763. * Clears undo history
  9764. */
  9765. Handsontable.UndoRedo.prototype.clear = function () {
  9766. this.doneActions.length = 0;
  9767. this.undoneActions.length = 0;
  9768. };
  9769. Handsontable.UndoRedo.Action = function () {
  9770. };
  9771. Handsontable.UndoRedo.Action.prototype.undo = function () {
  9772. };
  9773. Handsontable.UndoRedo.Action.prototype.redo = function () {
  9774. };
  9775. Handsontable.UndoRedo.ChangeAction = function (changes) {
  9776. this.changes = changes;
  9777. };
  9778. Handsontable.helper.inherit(Handsontable.UndoRedo.ChangeAction, Handsontable.UndoRedo.Action);
  9779. Handsontable.UndoRedo.ChangeAction.prototype.undo = function (instance, undoneCallback) {
  9780. var data = Handsontable.helper.deepClone(this.changes),
  9781. emptyRowsAtTheEnd = instance.countEmptyRows(true),
  9782. emptyColsAtTheEnd = instance.countEmptyCols(true);
  9783. for (var i = 0, len = data.length; i < len; i++) {
  9784. data[i].splice(3, 1);
  9785. }
  9786. instance.addHookOnce('afterChange', undoneCallback);
  9787. instance.setDataAtRowProp(data, null, null, 'undo');
  9788. for (var i = 0, len = data.length; i < len; i++) {
  9789. if(instance.getSettings().minSpareRows &&
  9790. data[i][0] + 1 + instance.getSettings().minSpareRows === instance.countRows()
  9791. && emptyRowsAtTheEnd == instance.getSettings().minSpareRows) {
  9792. instance.alter('remove_row', parseInt(data[i][0]+1,10), instance.getSettings().minSpareRows);
  9793. instance.undoRedo.doneActions.pop();
  9794. }
  9795. if (instance.getSettings().minSpareCols &&
  9796. data[i][1] + 1 + instance.getSettings().minSpareCols === instance.countCols()
  9797. && emptyColsAtTheEnd == instance.getSettings().minSpareCols) {
  9798. instance.alter('remove_col', parseInt(data[i][1]+1,10), instance.getSettings().minSpareCols);
  9799. instance.undoRedo.doneActions.pop();
  9800. }
  9801. }
  9802. };
  9803. Handsontable.UndoRedo.ChangeAction.prototype.redo = function (instance, onFinishCallback) {
  9804. var data = Handsontable.helper.deepClone(this.changes);
  9805. for (var i = 0, len = data.length; i < len; i++) {
  9806. data[i].splice(2, 1);
  9807. }
  9808. instance.addHookOnce('afterChange', onFinishCallback);
  9809. instance.setDataAtRowProp(data, null, null, 'redo');
  9810. };
  9811. Handsontable.UndoRedo.CreateRowAction = function (index, amount) {
  9812. this.index = index;
  9813. this.amount = amount;
  9814. };
  9815. Handsontable.helper.inherit(Handsontable.UndoRedo.CreateRowAction, Handsontable.UndoRedo.Action);
  9816. Handsontable.UndoRedo.CreateRowAction.prototype.undo = function (instance, undoneCallback) {
  9817. instance.addHookOnce('afterRemoveRow', undoneCallback);
  9818. instance.alter('remove_row', this.index, this.amount);
  9819. };
  9820. Handsontable.UndoRedo.CreateRowAction.prototype.redo = function (instance, redoneCallback) {
  9821. instance.addHookOnce('afterCreateRow', redoneCallback);
  9822. instance.alter('insert_row', this.index + 1, this.amount);
  9823. };
  9824. Handsontable.UndoRedo.RemoveRowAction = function (index, data) {
  9825. this.index = index;
  9826. this.data = data;
  9827. };
  9828. Handsontable.helper.inherit(Handsontable.UndoRedo.RemoveRowAction, Handsontable.UndoRedo.Action);
  9829. Handsontable.UndoRedo.RemoveRowAction.prototype.undo = function (instance, undoneCallback) {
  9830. var spliceArgs = [this.index, 0];
  9831. Array.prototype.push.apply(spliceArgs, this.data);
  9832. Array.prototype.splice.apply(instance.getData(), spliceArgs);
  9833. instance.addHookOnce('afterRender', undoneCallback);
  9834. instance.render();
  9835. };
  9836. Handsontable.UndoRedo.RemoveRowAction.prototype.redo = function (instance, redoneCallback) {
  9837. instance.addHookOnce('afterRemoveRow', redoneCallback);
  9838. instance.alter('remove_row', this.index, this.data.length);
  9839. };
  9840. Handsontable.UndoRedo.CreateColumnAction = function (index, amount) {
  9841. this.index = index;
  9842. this.amount = amount;
  9843. };
  9844. Handsontable.helper.inherit(Handsontable.UndoRedo.CreateColumnAction, Handsontable.UndoRedo.Action);
  9845. Handsontable.UndoRedo.CreateColumnAction.prototype.undo = function (instance, undoneCallback) {
  9846. instance.addHookOnce('afterRemoveCol', undoneCallback);
  9847. instance.alter('remove_col', this.index, this.amount);
  9848. };
  9849. Handsontable.UndoRedo.CreateColumnAction.prototype.redo = function (instance, redoneCallback) {
  9850. instance.addHookOnce('afterCreateCol', redoneCallback);
  9851. instance.alter('insert_col', this.index + 1, this.amount);
  9852. };
  9853. Handsontable.UndoRedo.RemoveColumnAction = function (index, data, headers) {
  9854. this.index = index;
  9855. this.data = data;
  9856. this.amount = this.data[0].length;
  9857. this.headers = headers;
  9858. };
  9859. Handsontable.helper.inherit(Handsontable.UndoRedo.RemoveColumnAction, Handsontable.UndoRedo.Action);
  9860. Handsontable.UndoRedo.RemoveColumnAction.prototype.undo = function (instance, undoneCallback) {
  9861. var row, spliceArgs;
  9862. for (var i = 0, len = instance.getData().length; i < len; i++) {
  9863. row = instance.getSourceDataAtRow(i);
  9864. spliceArgs = [this.index, 0];
  9865. Array.prototype.push.apply(spliceArgs, this.data[i]);
  9866. Array.prototype.splice.apply(row, spliceArgs);
  9867. }
  9868. if(typeof this.headers != 'undefined'){
  9869. spliceArgs = [this.index, 0];
  9870. Array.prototype.push.apply(spliceArgs, this.headers);
  9871. Array.prototype.splice.apply(instance.getSettings().colHeaders, spliceArgs);
  9872. }
  9873. instance.addHookOnce('afterRender', undoneCallback);
  9874. instance.render();
  9875. };
  9876. Handsontable.UndoRedo.RemoveColumnAction.prototype.redo = function (instance, redoneCallback) {
  9877. instance.addHookOnce('afterRemoveCol', redoneCallback);
  9878. instance.alter('remove_col', this.index, this.amount);
  9879. };
  9880. })(Handsontable);
  9881. (function(Handsontable){
  9882. function init(){
  9883. var instance = this;
  9884. var pluginEnabled = typeof instance.getSettings().undo == 'undefined' || instance.getSettings().undo;
  9885. if(pluginEnabled){
  9886. if(!instance.undoRedo){
  9887. instance.undoRedo = new Handsontable.UndoRedo(instance);
  9888. exposeUndoRedoMethods(instance);
  9889. instance.addHook('beforeKeyDown', onBeforeKeyDown);
  9890. instance.addHook('afterChange', onAfterChange);
  9891. }
  9892. } else {
  9893. if(instance.undoRedo){
  9894. delete instance.undoRedo;
  9895. removeExposedUndoRedoMethods(instance);
  9896. instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  9897. instance.removeHook('afterChange', onAfterChange);
  9898. }
  9899. }
  9900. }
  9901. function onBeforeKeyDown(event){
  9902. var instance = this;
  9903. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey;
  9904. if(ctrlDown){
  9905. if (event.keyCode === 89 || (event.shiftKey && event.keyCode === 90)) { //CTRL + Y or CTRL + SHIFT + Z
  9906. instance.undoRedo.redo();
  9907. event.stopImmediatePropagation();
  9908. }
  9909. else if (event.keyCode === 90) { //CTRL + Z
  9910. instance.undoRedo.undo();
  9911. event.stopImmediatePropagation();
  9912. }
  9913. }
  9914. }
  9915. function onAfterChange(changes, source){
  9916. var instance = this;
  9917. if (source == 'loadData'){
  9918. return instance.undoRedo.clear();
  9919. }
  9920. }
  9921. function exposeUndoRedoMethods(instance){
  9922. instance.undo = function(){
  9923. return instance.undoRedo.undo();
  9924. };
  9925. instance.redo = function(){
  9926. return instance.undoRedo.redo();
  9927. };
  9928. instance.isUndoAvailable = function(){
  9929. return instance.undoRedo.isUndoAvailable();
  9930. };
  9931. instance.isRedoAvailable = function(){
  9932. return instance.undoRedo.isRedoAvailable();
  9933. };
  9934. instance.clearUndo = function(){
  9935. return instance.undoRedo.clear();
  9936. };
  9937. }
  9938. function removeExposedUndoRedoMethods(instance){
  9939. delete instance.undo;
  9940. delete instance.redo;
  9941. delete instance.isUndoAvailable;
  9942. delete instance.isRedoAvailable;
  9943. delete instance.clearUndo;
  9944. }
  9945. Handsontable.hooks.add('afterInit', init);
  9946. Handsontable.hooks.add('afterUpdateSettings', init);
  9947. })(Handsontable);
  9948. /**
  9949. * Plugin used to scroll Handsontable by selecting a cell and dragging outside of visible viewport
  9950. * @constructor
  9951. */
  9952. function DragToScroll() {
  9953. this.boundaries = null;
  9954. this.callback = null;
  9955. }
  9956. /**
  9957. * @param boundaries {Object} compatible with getBoundingClientRect
  9958. */
  9959. DragToScroll.prototype.setBoundaries = function (boundaries) {
  9960. this.boundaries = boundaries;
  9961. };
  9962. /**
  9963. * @param callback {Function}
  9964. */
  9965. DragToScroll.prototype.setCallback = function (callback) {
  9966. this.callback = callback;
  9967. };
  9968. /**
  9969. * Check if mouse position (x, y) is outside of the viewport
  9970. * @param x
  9971. * @param y
  9972. */
  9973. DragToScroll.prototype.check = function (x, y) {
  9974. var diffX = 0;
  9975. var diffY = 0;
  9976. if (y < this.boundaries.top) {
  9977. //y is less than top
  9978. diffY = y - this.boundaries.top;
  9979. }
  9980. else if (y > this.boundaries.bottom) {
  9981. //y is more than bottom
  9982. diffY = y - this.boundaries.bottom;
  9983. }
  9984. if (x < this.boundaries.left) {
  9985. //x is less than left
  9986. diffX = x - this.boundaries.left;
  9987. }
  9988. else if (x > this.boundaries.right) {
  9989. //x is more than right
  9990. diffX = x - this.boundaries.right;
  9991. }
  9992. this.callback(diffX, diffY);
  9993. };
  9994. var dragToScroll;
  9995. var instance;
  9996. if (typeof Handsontable !== 'undefined') {
  9997. var setupListening = function (instance) {
  9998. instance.dragToScrollListening = false;
  9999. var scrollHandler = instance.view.wt.wtScrollbars.vertical.scrollHandler; //native scroll
  10000. dragToScroll = new DragToScroll();
  10001. if (scrollHandler === window) {
  10002. //not much we can do currently
  10003. return;
  10004. }
  10005. else {
  10006. dragToScroll.setBoundaries(scrollHandler.getBoundingClientRect());
  10007. }
  10008. dragToScroll.setCallback(function (scrollX, scrollY) {
  10009. if (scrollX < 0) {
  10010. scrollHandler.scrollLeft -= 50;
  10011. }
  10012. else if (scrollX > 0) {
  10013. scrollHandler.scrollLeft += 50;
  10014. }
  10015. if (scrollY < 0) {
  10016. scrollHandler.scrollTop -= 20;
  10017. }
  10018. else if (scrollY > 0) {
  10019. scrollHandler.scrollTop += 20;
  10020. }
  10021. });
  10022. instance.dragToScrollListening = true;
  10023. };
  10024. Handsontable.hooks.add('afterInit', function () {
  10025. var instance = this;
  10026. var eventManager = Handsontable.eventManager(this);
  10027. eventManager.addEventListener(document,'mouseup', function () {
  10028. instance.dragToScrollListening = false;
  10029. });
  10030. eventManager.addEventListener(document,'mousemove', function () {
  10031. if (instance.dragToScrollListening) {
  10032. dragToScroll.check(event.clientX, event.clientY);
  10033. }
  10034. });
  10035. });
  10036. Handsontable.hooks.add('afterDestroy', function () {
  10037. var eventManager = Handsontable.eventManager(this);
  10038. eventManager.clear();
  10039. });
  10040. Handsontable.hooks.add('afterOnCellMouseDown', function () {
  10041. setupListening(this);
  10042. });
  10043. Handsontable.hooks.add('afterOnCellCornerMouseDown', function () {
  10044. setupListening(this);
  10045. });
  10046. Handsontable.plugins.DragToScroll = DragToScroll;
  10047. }
  10048. (function (Handsontable, CopyPaste, SheetClip) {
  10049. function CopyPastePlugin(instance) {
  10050. this.copyPasteInstance = CopyPaste.getInstance();
  10051. this.copyPasteInstance.onCut(onCut);
  10052. this.copyPasteInstance.onPaste(onPaste);
  10053. var plugin = this;
  10054. instance.addHook('beforeKeyDown', onBeforeKeyDown);
  10055. function onCut() {
  10056. if (!instance.isListening()) {
  10057. return;
  10058. }
  10059. instance.selection.empty();
  10060. }
  10061. function onPaste(str) {
  10062. if (!instance.isListening() || !instance.selection.isSelected()) {
  10063. return;
  10064. }
  10065. var input = str.replace(/^[\r\n]*/g, '').replace(/[\r\n]*$/g, '') //remove newline from the start and the end of the input
  10066. , inputArray = SheetClip.parse(input)
  10067. , selected = instance.getSelected()
  10068. , coordsFrom = new WalkontableCellCoords(selected[0], selected[1])
  10069. , coordsTo = new WalkontableCellCoords(selected[2], selected[3])
  10070. , cellRange = new WalkontableCellRange(coordsFrom, coordsFrom, coordsTo)
  10071. , topLeftCorner = cellRange.getTopLeftCorner()
  10072. , bottomRightCorner = cellRange.getBottomRightCorner()
  10073. , areaStart = topLeftCorner
  10074. , areaEnd = new WalkontableCellCoords(
  10075. Math.max(bottomRightCorner.row, inputArray.length - 1 + topLeftCorner.row),
  10076. Math.max(bottomRightCorner.col, inputArray[0].length - 1 + topLeftCorner.col)
  10077. );
  10078. instance.addHookOnce('afterChange', function (changes, source) {
  10079. if (changes && changes.length) {
  10080. this.selectCell(areaStart.row, areaStart.col, areaEnd.row, areaEnd.col);
  10081. }
  10082. });
  10083. instance.populateFromArray(areaStart.row, areaStart.col, inputArray, areaEnd.row, areaEnd.col, 'paste', instance.getSettings().pasteMode);
  10084. };
  10085. function onBeforeKeyDown (event) {
  10086. if (instance.getSelected()) {
  10087. if (Handsontable.helper.isCtrlKey(event.keyCode)) {
  10088. //when CTRL is pressed, prepare selectable text in textarea
  10089. //http://stackoverflow.com/questions/3902635/how-does-one-capture-a-macs-command-key-via-javascript
  10090. plugin.setCopyableText();
  10091. event.stopImmediatePropagation();
  10092. return;
  10093. }
  10094. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
  10095. if (event.keyCode == Handsontable.helper.keyCode.A && ctrlDown) {
  10096. instance._registerTimeout(setTimeout(Handsontable.helper.proxy(plugin.setCopyableText, plugin), 0));
  10097. }
  10098. }
  10099. }
  10100. this.destroy = function () {
  10101. this.copyPasteInstance.removeCallback(onCut);
  10102. this.copyPasteInstance.removeCallback(onPaste);
  10103. this.copyPasteInstance.destroy();
  10104. instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  10105. };
  10106. instance.addHook('afterDestroy', Handsontable.helper.proxy(this.destroy, this));
  10107. this.triggerPaste = Handsontable.helper.proxy(this.copyPasteInstance.triggerPaste, this.copyPasteInstance);
  10108. this.triggerCut = Handsontable.helper.proxy(this.copyPasteInstance.triggerCut, this.copyPasteInstance);
  10109. /**
  10110. * Prepares copyable text in the invisible textarea
  10111. */
  10112. this.setCopyableText = function () {
  10113. var settings = instance.getSettings();
  10114. var copyRowsLimit = settings.copyRowsLimit;
  10115. var copyColsLimit = settings.copyColsLimit;
  10116. var selRange = instance.getSelectedRange();
  10117. var topLeft = selRange.getTopLeftCorner();
  10118. var bottomRight = selRange.getBottomRightCorner();
  10119. var startRow = topLeft.row;
  10120. var startCol = topLeft.col;
  10121. var endRow = bottomRight.row;
  10122. var endCol = bottomRight.col;
  10123. var finalEndRow = Math.min(endRow, startRow + copyRowsLimit - 1);
  10124. var finalEndCol = Math.min(endCol, startCol + copyColsLimit - 1);
  10125. instance.copyPaste.copyPasteInstance.copyable(instance.getCopyableData(startRow, startCol, finalEndRow, finalEndCol));
  10126. if (endRow !== finalEndRow || endCol !== finalEndCol) {
  10127. Handsontable.hooks.run(instance, "afterCopyLimit", endRow - startRow + 1, endCol - startCol + 1, copyRowsLimit, copyColsLimit);
  10128. }
  10129. };
  10130. }
  10131. function init() {
  10132. var instance = this;
  10133. var pluginEnabled = instance.getSettings().copyPaste !== false;
  10134. if(pluginEnabled && !instance.copyPaste){
  10135. instance.copyPaste = new CopyPastePlugin(instance);
  10136. } else if (!pluginEnabled && instance.copyPaste) {
  10137. instance.copyPaste.destroy();
  10138. delete instance.copyPaste;
  10139. }
  10140. }
  10141. Handsontable.hooks.add('afterInit', init);
  10142. Handsontable.hooks.add('afterUpdateSettings', init);
  10143. Handsontable.hooks.register('afterCopyLimit');
  10144. })(Handsontable, CopyPaste, SheetClip);
  10145. (function (Handsontable) {
  10146. 'use strict';
  10147. Handsontable.Search = function Search(instance) {
  10148. this.query = function (queryStr, callback, queryMethod) {
  10149. var rowCount = instance.countRows();
  10150. var colCount = instance.countCols();
  10151. var queryResult = [];
  10152. if (!callback) {
  10153. callback = Handsontable.Search.global.getDefaultCallback();
  10154. }
  10155. if (!queryMethod) {
  10156. queryMethod = Handsontable.Search.global.getDefaultQueryMethod();
  10157. }
  10158. for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) {
  10159. for (var colIndex = 0; colIndex < colCount; colIndex++) {
  10160. var cellData = instance.getDataAtCell(rowIndex, colIndex);
  10161. var cellProperties = instance.getCellMeta(rowIndex, colIndex);
  10162. var cellCallback = cellProperties.search.callback || callback;
  10163. var cellQueryMethod = cellProperties.search.queryMethod || queryMethod;
  10164. var testResult = cellQueryMethod(queryStr, cellData);
  10165. if (testResult) {
  10166. var singleResult = {
  10167. row: rowIndex,
  10168. col: colIndex,
  10169. data: cellData
  10170. };
  10171. queryResult.push(singleResult);
  10172. }
  10173. if (cellCallback) {
  10174. cellCallback(instance, rowIndex, colIndex, cellData, testResult);
  10175. }
  10176. }
  10177. }
  10178. return queryResult;
  10179. };
  10180. };
  10181. Handsontable.Search.DEFAULT_CALLBACK = function (instance, row, col, data, testResult) {
  10182. instance.getCellMeta(row, col).isSearchResult = testResult;
  10183. };
  10184. Handsontable.Search.DEFAULT_QUERY_METHOD = function (query, value) {
  10185. if (typeof query == 'undefined' || query == null || !query.toLowerCase || query.length == 0){
  10186. return false;
  10187. }
  10188. if(typeof value == 'undefined' || value == null) {
  10189. return false;
  10190. }
  10191. return value.toString().toLowerCase().indexOf(query.toLowerCase()) != -1;
  10192. };
  10193. Handsontable.Search.DEFAULT_SEARCH_RESULT_CLASS = 'htSearchResult';
  10194. Handsontable.Search.global = (function () {
  10195. var defaultCallback = Handsontable.Search.DEFAULT_CALLBACK;
  10196. var defaultQueryMethod = Handsontable.Search.DEFAULT_QUERY_METHOD;
  10197. var defaultSearchResultClass = Handsontable.Search.DEFAULT_SEARCH_RESULT_CLASS;
  10198. return {
  10199. getDefaultCallback: function () {
  10200. return defaultCallback;
  10201. },
  10202. setDefaultCallback: function (newDefaultCallback) {
  10203. defaultCallback = newDefaultCallback;
  10204. },
  10205. getDefaultQueryMethod: function () {
  10206. return defaultQueryMethod;
  10207. },
  10208. setDefaultQueryMethod: function (newDefaultQueryMethod) {
  10209. defaultQueryMethod = newDefaultQueryMethod;
  10210. },
  10211. getDefaultSearchResultClass: function () {
  10212. return defaultSearchResultClass;
  10213. },
  10214. setDefaultSearchResultClass: function (newSearchResultClass) {
  10215. defaultSearchResultClass = newSearchResultClass;
  10216. }
  10217. }
  10218. })();
  10219. Handsontable.SearchCellDecorator = function (instance, TD, row, col, prop, value, cellProperties) {
  10220. var searchResultClass = (typeof cellProperties.search == 'object' && cellProperties.search.searchResultClass) || Handsontable.Search.global.getDefaultSearchResultClass();
  10221. if(cellProperties.isSearchResult){
  10222. Handsontable.Dom.addClass(TD, searchResultClass);
  10223. } else {
  10224. Handsontable.Dom.removeClass(TD, searchResultClass);
  10225. }
  10226. };
  10227. var originalDecorator = Handsontable.renderers.cellDecorator;
  10228. Handsontable.renderers.cellDecorator = function (instance, TD, row, col, prop, value, cellProperties) {
  10229. originalDecorator.apply(this, arguments);
  10230. Handsontable.SearchCellDecorator.apply(this, arguments);
  10231. };
  10232. function init() {
  10233. var instance = this;
  10234. var pluginEnabled = !!instance.getSettings().search;
  10235. if (pluginEnabled) {
  10236. instance.search = new Handsontable.Search(instance);
  10237. } else {
  10238. delete instance.search;
  10239. }
  10240. }
  10241. Handsontable.hooks.add('afterInit', init);
  10242. Handsontable.hooks.add('afterUpdateSettings', init);
  10243. })(Handsontable);
  10244. function CellInfoCollection() {
  10245. var collection = [];
  10246. collection.getInfo = function (row, col) {
  10247. for (var i = 0, ilen = this.length; i < ilen; i++) {
  10248. if (this[i].row <= row && this[i].row + this[i].rowspan - 1 >= row && this[i].col <= col && this[i].col + this[i].colspan - 1 >= col) {
  10249. return this[i];
  10250. }
  10251. }
  10252. };
  10253. collection.setInfo = function (info) {
  10254. for (var i = 0, ilen = this.length; i < ilen; i++) {
  10255. if (this[i].row === info.row && this[i].col === info.col) {
  10256. this[i] = info;
  10257. return;
  10258. }
  10259. }
  10260. this.push(info);
  10261. };
  10262. collection.removeInfo = function (row, col) {
  10263. for (var i = 0, ilen = this.length; i < ilen; i++) {
  10264. if (this[i].row === row && this[i].col === col) {
  10265. this.splice(i, 1);
  10266. break;
  10267. }
  10268. }
  10269. };
  10270. return collection;
  10271. }
  10272. /**
  10273. * Plugin used to merge cells in Handsontable
  10274. * @constructor
  10275. */
  10276. function MergeCells(mergeCellsSetting) {
  10277. this.mergedCellInfoCollection = new CellInfoCollection();
  10278. if (Array.isArray(mergeCellsSetting)) {
  10279. for (var i = 0, ilen = mergeCellsSetting.length; i < ilen; i++) {
  10280. this.mergedCellInfoCollection.setInfo(mergeCellsSetting[i]);
  10281. }
  10282. }
  10283. }
  10284. /**
  10285. * @param cellRange (WalkontableCellRange)
  10286. */
  10287. MergeCells.prototype.canMergeRange = function (cellRange) {
  10288. //is more than one cell selected
  10289. return !cellRange.isSingle();
  10290. };
  10291. MergeCells.prototype.mergeRange = function (cellRange) {
  10292. if (!this.canMergeRange(cellRange)) {
  10293. return;
  10294. }
  10295. //normalize top left corner
  10296. var topLeft = cellRange.getTopLeftCorner();
  10297. var bottomRight = cellRange.getBottomRightCorner();
  10298. var mergeParent = {};
  10299. mergeParent.row = topLeft.row;
  10300. mergeParent.col = topLeft.col;
  10301. mergeParent.rowspan = bottomRight.row - topLeft.row + 1; //TD has rowspan == 1 by default. rowspan == 2 means spread over 2 cells
  10302. mergeParent.colspan = bottomRight.col - topLeft.col + 1;
  10303. this.mergedCellInfoCollection.setInfo(mergeParent);
  10304. };
  10305. MergeCells.prototype.mergeOrUnmergeSelection = function (cellRange) {
  10306. var info = this.mergedCellInfoCollection.getInfo(cellRange.from.row, cellRange.from.col);
  10307. if (info) {
  10308. //unmerge
  10309. this.unmergeSelection(cellRange.from);
  10310. }
  10311. else {
  10312. //merge
  10313. this.mergeSelection(cellRange);
  10314. }
  10315. };
  10316. MergeCells.prototype.mergeSelection = function (cellRange) {
  10317. this.mergeRange(cellRange);
  10318. };
  10319. MergeCells.prototype.unmergeSelection = function (cellRange) {
  10320. var info = this.mergedCellInfoCollection.getInfo(cellRange.row, cellRange.col);
  10321. this.mergedCellInfoCollection.removeInfo(info.row, info.col);
  10322. };
  10323. MergeCells.prototype.applySpanProperties = function (TD, row, col) {
  10324. var info = this.mergedCellInfoCollection.getInfo(row, col);
  10325. if (info) {
  10326. if (info.row === row && info.col === col) {
  10327. TD.setAttribute('rowspan', info.rowspan);
  10328. TD.setAttribute('colspan', info.colspan);
  10329. }
  10330. else {
  10331. TD.removeAttribute('rowspan');
  10332. TD.removeAttribute('colspan');
  10333. TD.style.display = "none";
  10334. }
  10335. }
  10336. else {
  10337. TD.removeAttribute('rowspan');
  10338. TD.removeAttribute('colspan');
  10339. }
  10340. };
  10341. MergeCells.prototype.modifyTransform = function (hook, currentSelectedRange, delta) {
  10342. var sameRowspan = function (merged, coords) {
  10343. if (coords.row >= merged.row && coords.row <= (merged.row + merged.rowspan - 1)) {
  10344. return true;
  10345. }
  10346. return false;
  10347. }
  10348. , sameColspan = function (merged, coords) {
  10349. if (coords.col >= merged.col && coords.col <= (merged.col + merged.colspan - 1)) {
  10350. return true;
  10351. }
  10352. return false;
  10353. }
  10354. , getNextPosition = function (newDelta) {
  10355. return new WalkontableCellCoords(currentSelectedRange.to.row + newDelta.row, currentSelectedRange.to.col + newDelta.col);
  10356. };
  10357. var newDelta = {
  10358. row: delta.row,
  10359. col: delta.col
  10360. };
  10361. if (hook == 'modifyTransformStart') {
  10362. if (!this.lastDesiredCoords) {
  10363. this.lastDesiredCoords = new WalkontableCellCoords(null, null);
  10364. }
  10365. var currentPosition = new WalkontableCellCoords(currentSelectedRange.highlight.row, currentSelectedRange.highlight.col)
  10366. , mergedParent = this.mergedCellInfoCollection.getInfo(currentPosition.row, currentPosition.col)// if current position's parent is a merged range, returns it
  10367. , currentRangeContainsMerge; // if current range contains a merged range
  10368. for (var i = 0, mergesLength = this.mergedCellInfoCollection.length; i < mergesLength; i++) {
  10369. var range = this.mergedCellInfoCollection[i];
  10370. range = new WalkontableCellCoords(range.row + range.rowspan - 1, range.col + range.colspan - 1);
  10371. if (currentSelectedRange.includes(range)) {
  10372. currentRangeContainsMerge = true;
  10373. break;
  10374. }
  10375. }
  10376. if (mergedParent) { // only merge selected
  10377. var mergeTopLeft = new WalkontableCellCoords(mergedParent.row, mergedParent.col)
  10378. , mergeBottomRight = new WalkontableCellCoords(mergedParent.row + mergedParent.rowspan - 1, mergedParent.col + mergedParent.colspan - 1)
  10379. , mergeRange = new WalkontableCellRange(mergeTopLeft, mergeTopLeft, mergeBottomRight);
  10380. if (!mergeRange.includes(this.lastDesiredCoords)) {
  10381. this.lastDesiredCoords = new WalkontableCellCoords(null, null); // reset outdated version of lastDesiredCoords
  10382. }
  10383. newDelta.row = this.lastDesiredCoords.row ? this.lastDesiredCoords.row - currentPosition.row : newDelta.row;
  10384. newDelta.col = this.lastDesiredCoords.col ? this.lastDesiredCoords.col - currentPosition.col : newDelta.col;
  10385. if (delta.row > 0) { // moving down
  10386. newDelta.row = mergedParent.row + mergedParent.rowspan - 1 - currentPosition.row + delta.row;
  10387. } else if (delta.row < 0) { //moving up
  10388. newDelta.row = currentPosition.row - mergedParent.row + delta.row;
  10389. }
  10390. if (delta.col > 0) { // moving right
  10391. newDelta.col = mergedParent.col + mergedParent.colspan - 1 - currentPosition.col + delta.col;
  10392. } else if (delta.col < 0) { // moving left
  10393. newDelta.col = currentPosition.col - mergedParent.col + delta.col;
  10394. }
  10395. }
  10396. var nextPosition = new WalkontableCellCoords(currentSelectedRange.highlight.row + newDelta.row, currentSelectedRange.highlight.col + newDelta.col)
  10397. , nextParentIsMerged = this.mergedCellInfoCollection.getInfo(nextPosition.row, nextPosition.col);
  10398. if (nextParentIsMerged) { // skipping the invisible cells in the merge range
  10399. this.lastDesiredCoords = nextPosition;
  10400. newDelta = {
  10401. row: nextParentIsMerged.row - currentPosition.row,
  10402. col: nextParentIsMerged.col - currentPosition.col
  10403. }
  10404. }
  10405. } else if (hook == 'modifyTransformEnd') {
  10406. for (var i = 0, mergesLength = this.mergedCellInfoCollection.length; i < mergesLength; i++) {
  10407. var currentMerge = this.mergedCellInfoCollection[i]
  10408. , mergeTopLeft = new WalkontableCellCoords(currentMerge.row, currentMerge.col)
  10409. , mergeBottomRight = new WalkontableCellCoords(currentMerge.row + currentMerge.rowspan - 1, currentMerge.col + currentMerge.colspan - 1)
  10410. , mergedRange = new WalkontableCellRange(mergeTopLeft, mergeTopLeft, mergeBottomRight)
  10411. , sharedBorders = currentSelectedRange.getBordersSharedWith(mergedRange);
  10412. if (mergedRange.isEqual(currentSelectedRange)) { // only the merged range is selected
  10413. currentSelectedRange.setDirection("NW-SE");
  10414. }
  10415. else if (sharedBorders.length > 0) {
  10416. var mergeHighlighted = (currentSelectedRange.highlight.isEqual(mergedRange.from));
  10417. if (sharedBorders.indexOf('top') > -1) { // if range shares a border with the merged section, change range direction accordingly
  10418. if (currentSelectedRange.to.isSouthEastOf(mergedRange.from) && mergeHighlighted) {
  10419. currentSelectedRange.setDirection("NW-SE");
  10420. } else if (currentSelectedRange.to.isSouthWestOf(mergedRange.from) && mergeHighlighted) {
  10421. currentSelectedRange.setDirection("NE-SW");
  10422. }
  10423. } else if (sharedBorders.indexOf('bottom') > -1) {
  10424. if (currentSelectedRange.to.isNorthEastOf(mergedRange.from) && mergeHighlighted) {
  10425. currentSelectedRange.setDirection("SW-NE");
  10426. } else if (currentSelectedRange.to.isNorthWestOf(mergedRange.from) && mergeHighlighted) {
  10427. currentSelectedRange.setDirection("SE-NW");
  10428. }
  10429. }
  10430. }
  10431. var nextPosition = getNextPosition(newDelta)
  10432. , withinRowspan = sameRowspan(currentMerge, nextPosition)
  10433. , withinColspan = sameColspan(currentMerge, nextPosition);
  10434. if (currentSelectedRange.includesRange(mergedRange) && (mergedRange.includes(nextPosition) || withinRowspan || withinColspan)) { // if next step overlaps a merged range, jump past it
  10435. if (withinRowspan) {
  10436. if (newDelta.row < 0) {
  10437. newDelta.row -= currentMerge.rowspan - 1;
  10438. } else if (newDelta.row > 0) {
  10439. newDelta.row += currentMerge.rowspan - 1;
  10440. }
  10441. }
  10442. if (withinColspan) {
  10443. if (newDelta.col < 0) {
  10444. newDelta.col -= currentMerge.colspan - 1;
  10445. } else if (newDelta.col > 0) {
  10446. newDelta.col += currentMerge.colspan - 1;
  10447. }
  10448. }
  10449. }
  10450. }
  10451. }
  10452. if (newDelta.row != 0) delta.row = newDelta.row;
  10453. if (newDelta.col != 0) delta.col = newDelta.col;
  10454. };
  10455. if (typeof Handsontable == 'undefined') {
  10456. throw new Error('Handsontable is not defined');
  10457. }
  10458. var beforeInit = function () {
  10459. var instance = this;
  10460. var mergeCellsSetting = instance.getSettings().mergeCells;
  10461. if (mergeCellsSetting) {
  10462. if (!instance.mergeCells) {
  10463. instance.mergeCells = new MergeCells(mergeCellsSetting);
  10464. }
  10465. }
  10466. };
  10467. var afterInit = function () {
  10468. var instance = this;
  10469. if (instance.mergeCells) {
  10470. /**
  10471. * Monkey patch WalkontableTable.prototype.getCell to return TD for merged cell parent if asked for TD of a cell that is
  10472. * invisible due to the merge. This is not the cleanest solution but there is a test case for it (merged cells scroll) so feel free to refactor it!
  10473. */
  10474. instance.view.wt.wtTable.getCell = function (coords) {
  10475. if (instance.getSettings().mergeCells) {
  10476. var mergeParent = instance.mergeCells.mergedCellInfoCollection.getInfo(coords.row, coords.col);
  10477. if (mergeParent) {
  10478. coords = mergeParent;
  10479. }
  10480. }
  10481. return WalkontableTable.prototype.getCell.call(this, coords);
  10482. };
  10483. }
  10484. };
  10485. var onBeforeKeyDown = function (event) {
  10486. if (!this.mergeCells) {
  10487. return;
  10488. }
  10489. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey;
  10490. if (ctrlDown) {
  10491. if (event.keyCode === 77) { //CTRL + M
  10492. this.mergeCells.mergeOrUnmergeSelection(this.getSelectedRange());
  10493. this.render();
  10494. event.stopImmediatePropagation();
  10495. }
  10496. }
  10497. };
  10498. var addMergeActionsToContextMenu = function (defaultOptions) {
  10499. if (!this.getSettings().mergeCells) {
  10500. return;
  10501. }
  10502. defaultOptions.items.push(Handsontable.ContextMenu.SEPARATOR);
  10503. defaultOptions.items.push({
  10504. key: 'mergeCells',
  10505. name: function () {
  10506. var sel = this.getSelected();
  10507. var info = this.mergeCells.mergedCellInfoCollection.getInfo(sel[0], sel[1]);
  10508. if (info) {
  10509. return 'Unmerge cells';
  10510. }
  10511. else {
  10512. return 'Merge cells';
  10513. }
  10514. },
  10515. callback: function () {
  10516. this.mergeCells.mergeOrUnmergeSelection(this.getSelectedRange());
  10517. this.render();
  10518. },
  10519. disabled: function () {
  10520. return false;
  10521. }
  10522. });
  10523. };
  10524. var afterRenderer = function (TD, row, col, prop, value, cellProperties) {
  10525. if (this.mergeCells) {
  10526. this.mergeCells.applySpanProperties(TD, row, col);
  10527. }
  10528. };
  10529. var modifyTransformFactory = function (hook) {
  10530. return function (delta) {
  10531. var mergeCellsSetting = this.getSettings().mergeCells;
  10532. if (mergeCellsSetting) {
  10533. var currentSelectedRange = this.getSelectedRange();
  10534. this.mergeCells.modifyTransform(hook, currentSelectedRange, delta);
  10535. if (hook === "modifyTransformEnd") {
  10536. //sanitize "from" (core.js will sanitize to)
  10537. var totalRows = this.countRows();
  10538. var totalCols = this.countCols();
  10539. if (currentSelectedRange.from.row < 0) {
  10540. currentSelectedRange.from.row = 0;
  10541. }
  10542. else if (currentSelectedRange.from.row > 0 && currentSelectedRange.from.row >= totalRows) {
  10543. currentSelectedRange.from.row = currentSelectedRange.from - 1;
  10544. }
  10545. if (currentSelectedRange.from.col < 0) {
  10546. currentSelectedRange.from.col = 0;
  10547. }
  10548. else if (currentSelectedRange.from.col > 0 && currentSelectedRange.from.col >= totalCols) {
  10549. currentSelectedRange.from.col = totalCols - 1;
  10550. }
  10551. }
  10552. }
  10553. }
  10554. };
  10555. /**
  10556. * While selecting cells with keyboard or mouse, make sure that rectangular area is expanded to the extent of the merged cell
  10557. * @param coords
  10558. */
  10559. var beforeSetRangeEnd = function (coords) {
  10560. this.lastDesiredCoords = null; //unset lastDesiredCoords when selection is changed with mouse
  10561. var mergeCellsSetting = this.getSettings().mergeCells;
  10562. if (mergeCellsSetting) {
  10563. var selRange = this.getSelectedRange();
  10564. selRange.highlight = new WalkontableCellCoords(selRange.highlight.row, selRange.highlight.col); //clone in case we will modify its reference
  10565. selRange.to = coords;
  10566. var rangeExpanded = false;
  10567. do {
  10568. rangeExpanded = false;
  10569. for (var i = 0, ilen = this.mergeCells.mergedCellInfoCollection.length; i < ilen; i++) {
  10570. var cellInfo = this.mergeCells.mergedCellInfoCollection[i];
  10571. var mergedCellTopLeft = new WalkontableCellCoords(cellInfo.row, cellInfo.col);
  10572. var mergedCellBottomRight = new WalkontableCellCoords(cellInfo.row + cellInfo.rowspan - 1, cellInfo.col + cellInfo.colspan - 1);
  10573. var mergedCellRange = new WalkontableCellRange(mergedCellTopLeft, mergedCellTopLeft, mergedCellBottomRight);
  10574. if (selRange.expandByRange(mergedCellRange)) {
  10575. coords.row = selRange.to.row;
  10576. coords.col = selRange.to.col;
  10577. rangeExpanded = true;
  10578. }
  10579. }
  10580. } while (rangeExpanded);
  10581. }
  10582. };
  10583. /**
  10584. * Returns correct coordinates for merged start / end cells in selection for area borders
  10585. * @param corners
  10586. * @param className
  10587. */
  10588. var beforeDrawAreaBorders = function (corners, className) {
  10589. if (className && className == 'area') {
  10590. var mergeCellsSetting = this.getSettings().mergeCells;
  10591. if (mergeCellsSetting) {
  10592. var selRange = this.getSelectedRange();
  10593. var startRange = new WalkontableCellRange(selRange.from, selRange.from, selRange.from);
  10594. var stopRange = new WalkontableCellRange(selRange.to, selRange.to, selRange.to);
  10595. for (var i = 0, ilen = this.mergeCells.mergedCellInfoCollection.length; i < ilen; i++) {
  10596. var cellInfo = this.mergeCells.mergedCellInfoCollection[i];
  10597. var mergedCellTopLeft = new WalkontableCellCoords(cellInfo.row, cellInfo.col);
  10598. var mergedCellBottomRight = new WalkontableCellCoords(cellInfo.row + cellInfo.rowspan - 1, cellInfo.col + cellInfo.colspan - 1);
  10599. var mergedCellRange = new WalkontableCellRange(mergedCellTopLeft, mergedCellTopLeft, mergedCellBottomRight);
  10600. if (startRange.expandByRange(mergedCellRange)) {
  10601. corners[0] = startRange.from.row;
  10602. corners[1] = startRange.from.col;
  10603. }
  10604. if (stopRange.expandByRange(mergedCellRange)) {
  10605. corners[2] = stopRange.from.row;
  10606. corners[3] = stopRange.from.col;
  10607. }
  10608. }
  10609. }
  10610. }
  10611. };
  10612. var afterGetCellMeta = function (row, col, cellProperties) {
  10613. var mergeCellsSetting = this.getSettings().mergeCells;
  10614. if (mergeCellsSetting) {
  10615. var mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(row, col);
  10616. if (mergeParent && (mergeParent.row != row || mergeParent.col != col)) {
  10617. cellProperties.copyable = false;
  10618. }
  10619. }
  10620. };
  10621. var afterViewportRowCalculatorOverride = function (calc) {
  10622. var mergeCellsSetting = this.getSettings().mergeCells;
  10623. if (mergeCellsSetting) {
  10624. var colCount = this.countCols();
  10625. var mergeParent;
  10626. for (var c = 0; c < colCount; c++) {
  10627. mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(calc.startRow, c);
  10628. if (mergeParent) {
  10629. if (mergeParent.row < calc.startRow) {
  10630. calc.startRow = mergeParent.row;
  10631. return afterViewportRowCalculatorOverride.call(this, calc); //recursively search upwards
  10632. }
  10633. }
  10634. mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(calc.endRow, c);
  10635. if (mergeParent) {
  10636. var mergeEnd = mergeParent.row + mergeParent.rowspan - 1;
  10637. if (mergeEnd > calc.endRow) {
  10638. calc.endRow = mergeEnd;
  10639. return afterViewportRowCalculatorOverride.call(this, calc); //recursively search upwards
  10640. }
  10641. }
  10642. }
  10643. }
  10644. };
  10645. var afterViewportColumnCalculatorOverride = function (calc) {
  10646. var mergeCellsSetting = this.getSettings().mergeCells;
  10647. if (mergeCellsSetting) {
  10648. var rowCount = this.countRows();
  10649. var mergeParent;
  10650. for (var r = 0; r < rowCount; r++) {
  10651. mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(r, calc.startColumn);
  10652. if (mergeParent) {
  10653. if (mergeParent.col < calc.startColumn) {
  10654. calc.startColumn = mergeParent.col;
  10655. return afterViewportColumnCalculatorOverride.call(this, calc); //recursively search upwards
  10656. }
  10657. }
  10658. mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(r, calc.endColumn);
  10659. if (mergeParent) {
  10660. var mergeEnd = mergeParent.col + mergeParent.colspan - 1;
  10661. if (mergeEnd > calc.endColumn) {
  10662. calc.endColumn = mergeEnd;
  10663. return afterViewportColumnCalculatorOverride.call(this, calc); //recursively search upwards
  10664. }
  10665. }
  10666. }
  10667. }
  10668. };
  10669. var isMultipleSelection = function (isMultiple) {
  10670. if (isMultiple && this.mergeCells) {
  10671. var mergedCells = this.mergeCells.mergedCellInfoCollection
  10672. , selectionRange = this.getSelectedRange();
  10673. for (var group in mergedCells) {
  10674. if (selectionRange.highlight.row == mergedCells[group].row && selectionRange.highlight.col == mergedCells[group].col
  10675. && selectionRange.to.row == mergedCells[group].row + mergedCells[group].rowspan - 1
  10676. && selectionRange.to.col == mergedCells[group].col + mergedCells[group].colspan - 1) {
  10677. return false;
  10678. }
  10679. }
  10680. }
  10681. return isMultiple;
  10682. };
  10683. Handsontable.hooks.add('beforeInit', beforeInit);
  10684. Handsontable.hooks.add('afterInit', afterInit);
  10685. Handsontable.hooks.add('beforeKeyDown', onBeforeKeyDown);
  10686. Handsontable.hooks.add('modifyTransformStart', modifyTransformFactory('modifyTransformStart'));
  10687. Handsontable.hooks.add('modifyTransformEnd', modifyTransformFactory('modifyTransformEnd'));
  10688. Handsontable.hooks.add('beforeSetRangeEnd', beforeSetRangeEnd);
  10689. Handsontable.hooks.add('beforeDrawBorders', beforeDrawAreaBorders);
  10690. Handsontable.hooks.add('afterIsMultipleSelection', isMultipleSelection);
  10691. Handsontable.hooks.add('afterRenderer', afterRenderer);
  10692. Handsontable.hooks.add('afterContextMenuDefaultOptions', addMergeActionsToContextMenu);
  10693. Handsontable.hooks.add('afterGetCellMeta', afterGetCellMeta);
  10694. Handsontable.hooks.add('afterViewportRowCalculatorOverride', afterViewportRowCalculatorOverride);
  10695. Handsontable.hooks.add('afterViewportColumnCalculatorOverride', afterViewportColumnCalculatorOverride);
  10696. Handsontable.MergeCells = MergeCells;
  10697. (function () {
  10698. function CustomBorders () {
  10699. }
  10700. // /***
  10701. // * Array for all custom border objects (for redraw)
  10702. // * @type {{}}
  10703. // */
  10704. // var bordersArray = {},
  10705. /***
  10706. * Current instance (table where borders should be placed)
  10707. */
  10708. var instance;
  10709. /***
  10710. * Check if plugin should be enabled
  10711. */
  10712. var checkEnable = function (customBorders) {
  10713. if(typeof customBorders === "boolean"){
  10714. if (customBorders == true){
  10715. return true;
  10716. }
  10717. }
  10718. if(typeof customBorders === "object"){
  10719. if(customBorders.length > 0) {
  10720. return true;
  10721. }
  10722. }
  10723. return false;
  10724. };
  10725. /***
  10726. * Initialize plugin
  10727. */
  10728. var init = function () {
  10729. if(checkEnable(this.getSettings().customBorders)){
  10730. if(!this.customBorders){
  10731. instance = this;
  10732. this.customBorders = new CustomBorders();
  10733. }
  10734. }
  10735. };
  10736. /***
  10737. * get index of border setting
  10738. * @param className
  10739. * @returns {number}
  10740. */
  10741. var getSettingIndex = function (className) {
  10742. for (var i = 0; i < instance.view.wt.selections.length; i++){
  10743. if (instance.view.wt.selections[i].settings.className == className){
  10744. return i;
  10745. }
  10746. }
  10747. return -1;
  10748. };
  10749. /***
  10750. * Insert WalkontableSelection instance into Walkontable.settings
  10751. * @param border
  10752. */
  10753. var insertBorderIntoSettings = function (border) {
  10754. var coordinates = {
  10755. row: border.row,
  10756. col: border.col
  10757. };
  10758. var selection = new WalkontableSelection(border, new WalkontableCellRange(coordinates, coordinates, coordinates));
  10759. var index = getSettingIndex(border.className);
  10760. if(index >=0) {
  10761. instance.view.wt.selections[index] = selection;
  10762. } else {
  10763. instance.view.wt.selections.push(selection);
  10764. }
  10765. };
  10766. /***
  10767. * Prepare borders from setting (single cell)
  10768. *
  10769. * @param row
  10770. * @param col
  10771. * @param borderObj
  10772. */
  10773. var prepareBorderFromCustomAdded = function (row, col, borderObj){
  10774. var border = createEmptyBorders(row, col);
  10775. border = extendDefaultBorder(border, borderObj);
  10776. this.setCellMeta(row, col, 'borders', border);
  10777. insertBorderIntoSettings(border);
  10778. };
  10779. /***
  10780. * Prepare borders from setting (object)
  10781. * @param rowObj
  10782. */
  10783. var prepareBorderFromCustomAddedRange = function (rowObj) {
  10784. var range = rowObj.range;
  10785. for (var row = range.from.row; row <= range.to.row; row ++) {
  10786. for (var col = range.from.col; col<= range.to.col; col++){
  10787. var border = createEmptyBorders(row, col);
  10788. var add = 0;
  10789. if(row == range.from.row) {
  10790. add++;
  10791. if(rowObj.hasOwnProperty('top')){
  10792. border.top = rowObj.top;
  10793. }
  10794. }
  10795. if(row == range.to.row){
  10796. add++;
  10797. if(rowObj.hasOwnProperty('bottom')){
  10798. border.bottom = rowObj.bottom;
  10799. }
  10800. }
  10801. if(col == range.from.col) {
  10802. add++;
  10803. if(rowObj.hasOwnProperty('left')){
  10804. border.left = rowObj.left;
  10805. }
  10806. }
  10807. if (col == range.to.col) {
  10808. add++;
  10809. if(rowObj.hasOwnProperty('right')){
  10810. border.right = rowObj.right;
  10811. }
  10812. }
  10813. if(add>0){
  10814. this.setCellMeta(row, col, 'borders', border);
  10815. insertBorderIntoSettings(border);
  10816. }
  10817. }
  10818. }
  10819. };
  10820. /***
  10821. * Create separated class name for borders for each cell
  10822. * @param row
  10823. * @param col
  10824. * @returns {string}
  10825. */
  10826. var createClassName = function (row, col) {
  10827. return "border_row" + row + "col" + col;
  10828. };
  10829. /***
  10830. * Create default single border for each position (top/right/bottom/left)
  10831. * @returns {{width: number, color: string}}
  10832. */
  10833. var createDefaultCustomBorder = function () {
  10834. return {
  10835. width: 1,
  10836. color: '#000'
  10837. };
  10838. };
  10839. /***
  10840. * Create default object for empty border
  10841. * @returns {{hide: boolean}}
  10842. */
  10843. var createSingleEmptyBorder = function () {
  10844. return {
  10845. hide: true
  10846. }
  10847. };
  10848. /***
  10849. * Create default Handsontable border object
  10850. * @returns {{width: number, color: string, cornerVisible: boolean}}
  10851. */
  10852. var createDefaultHtBorder = function () {
  10853. return {
  10854. width: 1,
  10855. color: '#000',
  10856. cornerVisible: false
  10857. }
  10858. };
  10859. /***
  10860. * Prepare empty border for each cell with all custom borders hidden
  10861. *
  10862. * @param row
  10863. * @param col
  10864. * @returns {{className: *, border: *, row: *, col: *, top: {hide: boolean}, right: {hide: boolean}, bottom: {hide: boolean}, left: {hide: boolean}}}
  10865. */
  10866. var createEmptyBorders = function (row, col){
  10867. return {
  10868. className: createClassName(row, col),
  10869. border: createDefaultHtBorder(),
  10870. row: row,
  10871. col: col,
  10872. top: createSingleEmptyBorder(),
  10873. right: createSingleEmptyBorder(),
  10874. bottom: createSingleEmptyBorder(),
  10875. left: createSingleEmptyBorder()
  10876. }
  10877. };
  10878. var extendDefaultBorder = function (defaultBorder, customBorder){
  10879. if(customBorder.hasOwnProperty('border')){
  10880. defaultBorder.border = customBorder.border;
  10881. }
  10882. if(customBorder.hasOwnProperty('top')){
  10883. defaultBorder.top = customBorder.top;
  10884. }
  10885. if(customBorder.hasOwnProperty('right')){
  10886. defaultBorder.right = customBorder.right;
  10887. }
  10888. if(customBorder.hasOwnProperty('bottom')){
  10889. defaultBorder.bottom = customBorder.bottom;
  10890. }
  10891. if(customBorder.hasOwnProperty('left')){
  10892. defaultBorder.left = customBorder.left;
  10893. }
  10894. return defaultBorder;
  10895. };
  10896. /***
  10897. * Remove borders divs from DOM
  10898. *
  10899. * @param borderClassName
  10900. */
  10901. var removeBordersFromDom = function (borderClassName) {
  10902. var borders = document.querySelectorAll("." + borderClassName);
  10903. for(var i = 0; i< borders.length; i++) {
  10904. if (borders[i]) {
  10905. if(borders[i].nodeName != 'TD') {
  10906. var parent = borders[i].parentNode;
  10907. if(parent.parentNode) {
  10908. parent.parentNode.removeChild(parent);
  10909. }
  10910. }
  10911. }
  10912. }
  10913. };
  10914. /***
  10915. * Remove border (triggered from context menu)
  10916. *
  10917. * @param row
  10918. * @param col
  10919. */
  10920. var removeAllBorders = function(row,col) {
  10921. var borderClassName = createClassName(row,col);
  10922. removeBordersFromDom(borderClassName);
  10923. this.removeCellMeta(row, col, 'borders');
  10924. };
  10925. /***
  10926. * Set borders for each cell re. to border position
  10927. *
  10928. * @param row
  10929. * @param col
  10930. * @param place
  10931. * @param remove
  10932. */
  10933. var setBorder = function (row, col,place, remove){
  10934. var bordersMeta = this.getCellMeta(row, col).borders;
  10935. if (!bordersMeta || bordersMeta.border == undefined){
  10936. bordersMeta = createEmptyBorders(row, col);
  10937. }
  10938. if (remove) {
  10939. bordersMeta[place] = createSingleEmptyBorder();
  10940. } else {
  10941. bordersMeta[place] = createDefaultCustomBorder();
  10942. }
  10943. this.setCellMeta(row, col, 'borders', bordersMeta);
  10944. var borderClassName = createClassName(row,col);
  10945. removeBordersFromDom(borderClassName);
  10946. insertBorderIntoSettings(bordersMeta);
  10947. this.render();
  10948. };
  10949. /***
  10950. * Prepare borders based on cell and border position
  10951. *
  10952. * @param range
  10953. * @param place
  10954. * @param remove
  10955. */
  10956. var prepareBorder = function (range, place, remove) {
  10957. if (range.from.row == range.to.row && range.from.col == range.to.col){
  10958. if(place == "noBorders"){
  10959. removeAllBorders.call(this, range.from.row, range.from.col);
  10960. } else {
  10961. setBorder.call(this, range.from.row, range.from.col, place, remove);
  10962. }
  10963. } else {
  10964. switch (place) {
  10965. case "noBorders":
  10966. for(var column = range.from.col; column <= range.to.col; column++){
  10967. for(var row = range.from.row; row <= range.to.row; row++) {
  10968. removeAllBorders.call(this, row, column);
  10969. }
  10970. }
  10971. break;
  10972. case "top":
  10973. for(var topCol = range.from.col; topCol <= range.to.col; topCol++){
  10974. setBorder.call(this, range.from.row, topCol, place, remove);
  10975. }
  10976. break;
  10977. case "right":
  10978. for(var rowRight = range.from.row; rowRight <=range.to.row; rowRight++){
  10979. setBorder.call(this,rowRight, range.to.col, place);
  10980. }
  10981. break;
  10982. case "bottom":
  10983. for(var bottomCol = range.from.col; bottomCol <= range.to.col; bottomCol++){
  10984. setBorder.call(this, range.to.row, bottomCol, place);
  10985. }
  10986. break;
  10987. case "left":
  10988. for(var rowLeft = range.from.row; rowLeft <=range.to.row; rowLeft++){
  10989. setBorder.call(this,rowLeft, range.from.col, place);
  10990. }
  10991. break;
  10992. }
  10993. }
  10994. };
  10995. /***
  10996. * Check if selection has border by className
  10997. *
  10998. * @param hot
  10999. * @param direction
  11000. */
  11001. var checkSelectionBorders = function (hot, direction) {
  11002. var atLeastOneHasBorder = false;
  11003. hot.getSelectedRange().forAll(function(r, c) {
  11004. var metaBorders = hot.getCellMeta(r,c).borders;
  11005. if (metaBorders) {
  11006. if(direction) {
  11007. if (!metaBorders[direction].hasOwnProperty('hide')){
  11008. atLeastOneHasBorder = true;
  11009. return false; //breaks forAll
  11010. }
  11011. } else {
  11012. atLeastOneHasBorder = true;
  11013. return false; //breaks forAll
  11014. }
  11015. }
  11016. });
  11017. return atLeastOneHasBorder;
  11018. };
  11019. /***
  11020. * Mark label in contextMenu as selected
  11021. *
  11022. * @param label
  11023. * @returns {string}
  11024. */
  11025. var markSelected = function (label) {
  11026. return "<span class='selected'>" + String.fromCharCode(10003) + "</span>" + label; // workaround for https://github.com/handsontable/handsontable/issues/1946
  11027. };
  11028. /***
  11029. * Add border options to context menu
  11030. *
  11031. * @param defaultOptions
  11032. */
  11033. var addBordersOptionsToContextMenu = function (defaultOptions) {
  11034. if(!this.getSettings().customBorders){
  11035. return;
  11036. }
  11037. defaultOptions.items.push(Handsontable.ContextMenu.SEPARATOR);
  11038. defaultOptions.items.push({
  11039. key: 'borders',
  11040. name: 'Borders',
  11041. submenu: {
  11042. items: {
  11043. top: {
  11044. name: function () {
  11045. var label = "Top";
  11046. var hasBorder = checkSelectionBorders(this, 'top');
  11047. if(hasBorder) {
  11048. label = markSelected(label);
  11049. }
  11050. return label;
  11051. },
  11052. callback: function () {
  11053. var hasBorder = checkSelectionBorders(this, 'top');
  11054. prepareBorder.call(this, this.getSelectedRange(), 'top', hasBorder);
  11055. },
  11056. disabled: false
  11057. },
  11058. right: {
  11059. name: function () {
  11060. var label = 'Right';
  11061. var hasBorder = checkSelectionBorders(this, 'right');
  11062. if(hasBorder) {
  11063. label = markSelected(label);
  11064. }
  11065. return label;
  11066. },
  11067. callback: function () {
  11068. var hasBorder = checkSelectionBorders(this, 'right');
  11069. prepareBorder.call(this, this.getSelectedRange(), 'right', hasBorder);
  11070. },
  11071. disabled: false
  11072. },
  11073. bottom: {
  11074. name: function () {
  11075. var label = 'Bottom';
  11076. var hasBorder = checkSelectionBorders(this, 'bottom');
  11077. if(hasBorder) {
  11078. label = markSelected(label);
  11079. }
  11080. return label;
  11081. },
  11082. callback: function () {
  11083. var hasBorder = checkSelectionBorders(this, 'bottom');
  11084. prepareBorder.call(this, this.getSelectedRange(), 'bottom', hasBorder);
  11085. },
  11086. disabled: false
  11087. },
  11088. left: {
  11089. name: function () {
  11090. var label = 'Left';
  11091. var hasBorder = checkSelectionBorders(this, 'left');
  11092. if(hasBorder) {
  11093. label = markSelected(label);
  11094. }
  11095. return label
  11096. },
  11097. callback: function () {
  11098. var hasBorder = checkSelectionBorders(this, 'left');
  11099. prepareBorder.call(this, this.getSelectedRange(), 'left', hasBorder);
  11100. },
  11101. disabled: false
  11102. },
  11103. remove: {
  11104. name: 'Remove border(s)',
  11105. callback: function () {
  11106. prepareBorder.call(this, this.getSelectedRange(), 'noBorders');
  11107. },
  11108. disabled: function () {
  11109. return !checkSelectionBorders(this);
  11110. }
  11111. }
  11112. }
  11113. }
  11114. });
  11115. };
  11116. Handsontable.hooks.add('beforeInit', init);
  11117. Handsontable.hooks.add('afterContextMenuDefaultOptions', addBordersOptionsToContextMenu);
  11118. Handsontable.hooks.add('afterInit', function () {
  11119. var customBorders = this.getSettings().customBorders;
  11120. if (customBorders){
  11121. for(var i = 0; i< customBorders.length; i++) {
  11122. if(customBorders[i].range){
  11123. prepareBorderFromCustomAddedRange.call(this,customBorders[i]);
  11124. } else {
  11125. prepareBorderFromCustomAdded.call(this,customBorders[i].row, customBorders[i].col, customBorders[i]);
  11126. }
  11127. }
  11128. this.render();
  11129. this.view.wt.draw(true);
  11130. }
  11131. });
  11132. Handsontable.CustomBorders = CustomBorders;
  11133. }());
  11134. /**
  11135. * HandsontableManualRowMove
  11136. *
  11137. * Has 2 UI components:
  11138. * - handle - the draggable element that sets the desired position of the row
  11139. * - guide - the helper guide that shows the desired position as a horizontal guide
  11140. *
  11141. * Warning! Whenever you make a change in this file, make an analogous change in manualRowMove.js
  11142. * @constructor
  11143. */
  11144. (function (Handsontable) {
  11145. function HandsontableManualRowMove() {
  11146. var startRow,
  11147. endRow,
  11148. startY,
  11149. startOffset,
  11150. currentRow,
  11151. currentTH,
  11152. handle = document.createElement('DIV'),
  11153. guide = document.createElement('DIV'),
  11154. eventManager = Handsontable.eventManager(this);
  11155. handle.className = 'manualRowMover';
  11156. guide.className = 'manualRowMoverGuide';
  11157. var saveManualRowPositions = function () {
  11158. var instance = this;
  11159. Handsontable.hooks.run(instance, 'persistentStateSave', 'manualRowPositions', instance.manualRowPositions);
  11160. };
  11161. var loadManualRowPositions = function () {
  11162. var instance = this,
  11163. storedState = {};
  11164. Handsontable.hooks.run(instance, 'persistentStateLoad', 'manualRowPositions', storedState);
  11165. return storedState.value;
  11166. };
  11167. function setupHandlePosition(TH) {
  11168. instance = this;
  11169. currentTH = TH;
  11170. var row = this.view.wt.wtTable.getCoords(TH).row; //getCoords returns WalkontableCellCoords
  11171. if (row >= 0) { //if not row header
  11172. currentRow = row;
  11173. var box = currentTH.getBoundingClientRect();
  11174. startOffset = box.top;
  11175. handle.style.top = startOffset + 'px';
  11176. handle.style.left = box.left + 'px';
  11177. instance.rootElement.appendChild(handle);
  11178. }
  11179. }
  11180. function refreshHandlePosition(TH, delta) {
  11181. var box = TH.getBoundingClientRect();
  11182. var handleHeight = 6;
  11183. if (delta > 0) {
  11184. handle.style.top = (box.top + box.height - handleHeight) + 'px';
  11185. }
  11186. else {
  11187. handle.style.top = box.top + 'px';
  11188. }
  11189. }
  11190. function setupGuidePosition() {
  11191. var instance = this;
  11192. Handsontable.Dom.addClass(handle, 'active');
  11193. Handsontable.Dom.addClass(guide, 'active');
  11194. var box = currentTH.getBoundingClientRect();
  11195. guide.style.width = instance.view.maximumVisibleElementWidth(0) + 'px';
  11196. guide.style.height = box.height + 'px';
  11197. guide.style.top = startOffset + 'px';
  11198. guide.style.left = handle.style.left;
  11199. instance.rootElement.appendChild(guide);
  11200. }
  11201. function refreshGuidePosition(diff) {
  11202. guide.style.top = startOffset + diff + 'px';
  11203. }
  11204. function hideHandleAndGuide() {
  11205. Handsontable.Dom.removeClass(handle, 'active');
  11206. Handsontable.Dom.removeClass(guide, 'active');
  11207. }
  11208. var checkRowHeader = function (element) {
  11209. if (element.tagName != 'BODY') {
  11210. if (element.parentNode.tagName == 'TBODY') {
  11211. return true;
  11212. } else {
  11213. element = element.parentNode;
  11214. return checkRowHeader(element);
  11215. }
  11216. }
  11217. return false;
  11218. };
  11219. var getTHFromTargetElement = function (element) {
  11220. if (element.tagName != 'TABLE') {
  11221. if (element.tagName == 'TH') {
  11222. return element;
  11223. } else {
  11224. return getTHFromTargetElement(element.parentNode);
  11225. }
  11226. }
  11227. return null;
  11228. };
  11229. var bindEvents = function () {
  11230. var instance = this;
  11231. var pressed;
  11232. eventManager.addEventListener(instance.rootElement,'mouseover', function (e){
  11233. if(checkRowHeader(e.target)){
  11234. var th = getTHFromTargetElement(e.target)
  11235. if (th) {
  11236. if (pressed) {
  11237. endRow = instance.view.wt.wtTable.getCoords(th).row;
  11238. refreshHandlePosition(th, endRow - startRow);
  11239. }
  11240. else {
  11241. setupHandlePosition.call(instance, th);
  11242. }
  11243. }
  11244. }
  11245. });
  11246. eventManager.addEventListener(instance.rootElement,'mousedown', function (e) {
  11247. if (Handsontable.Dom.hasClass(e.target, 'manualRowMover')) {
  11248. startY = Handsontable.helper.pageY(e);
  11249. setupGuidePosition.call(instance);
  11250. pressed = instance;
  11251. startRow = currentRow;
  11252. endRow = currentRow;
  11253. }
  11254. });
  11255. eventManager.addEventListener(window,'mousemove',function (e) {
  11256. if (pressed) {
  11257. refreshGuidePosition(Handsontable.helper.pageY(e) - startY);
  11258. }
  11259. });
  11260. eventManager.addEventListener(window,'mouseup',function (e) {
  11261. if (pressed) {
  11262. hideHandleAndGuide();
  11263. pressed = false;
  11264. createPositionData(instance.manualRowPositions, instance.countRows());
  11265. instance.manualRowPositions.splice(endRow, 0, instance.manualRowPositions.splice(startRow, 1)[0]);
  11266. instance.forceFullRender = true;
  11267. instance.view.render(); //updates all
  11268. saveManualRowPositions.call(instance);
  11269. Handsontable.hooks.run(instance, 'afterRowMove', startRow, endRow);
  11270. setupHandlePosition.call(instance, currentTH);
  11271. }
  11272. });
  11273. instance.addHook('afterDestroy', unbindEvents);
  11274. };
  11275. var unbindEvents = function () {
  11276. eventManager.clear();
  11277. };
  11278. var createPositionData = function (positionArr, len) {
  11279. if (positionArr.length < len) {
  11280. for (var i = positionArr.length; i < len; i++) {
  11281. positionArr[i] = i;
  11282. }
  11283. }
  11284. };
  11285. this.beforeInit = function () {
  11286. this.manualRowPositions = [];
  11287. };
  11288. this.init = function (source) {
  11289. var instance = this;
  11290. var manualRowMoveEnabled = !!(instance.getSettings().manualRowMove);
  11291. if (manualRowMoveEnabled) {
  11292. var initialManualRowPositions = instance.getSettings().manualRowMove;
  11293. var loadedManualRowPostions = loadManualRowPositions.call(instance);
  11294. if (typeof loadedManualRowPostions != 'undefined') {
  11295. this.manualRowPositions = loadedManualRowPostions;
  11296. } else if(Array.isArray(initialManualRowPositions)) {
  11297. this.manualRowPositions = initialManualRowPositions;
  11298. } else {
  11299. this.manualRowPositions = [];
  11300. }
  11301. if (source === 'afterInit') {
  11302. bindEvents.call(this);
  11303. if (this.manualRowPositions.length > 0) {
  11304. instance.forceFullRender = true;
  11305. instance.render();
  11306. }
  11307. }
  11308. } else {
  11309. unbindEvents.call(this);
  11310. instance.manualRowPositions = [];
  11311. }
  11312. };
  11313. this.modifyRow = function (row) {
  11314. var instance = this;
  11315. if (instance.getSettings().manualRowMove) {
  11316. if (typeof instance.manualRowPositions[row] === 'undefined') {
  11317. createPositionData(this.manualRowPositions, row + 1);
  11318. }
  11319. return instance.manualRowPositions[row];
  11320. }
  11321. return row;
  11322. };
  11323. }
  11324. var htManualRowMove = new HandsontableManualRowMove();
  11325. Handsontable.hooks.add('beforeInit', htManualRowMove.beforeInit);
  11326. Handsontable.hooks.add('afterInit', function () {
  11327. htManualRowMove.init.call(this, 'afterInit');
  11328. });
  11329. Handsontable.hooks.add('afterUpdateSettings', function () {
  11330. htManualRowMove.init.call(this, 'afterUpdateSettings');
  11331. });
  11332. Handsontable.hooks.add('modifyRow', htManualRowMove.modifyRow);
  11333. Handsontable.hooks.register('afterRowMove');
  11334. })(Handsontable);
  11335. /**
  11336. * This plugin provides "drag-down" and "copy-down" functionalities, both operated
  11337. * using the small square in the right bottom of the cell selection.
  11338. *
  11339. * "Drag-down" expands the value of the selected cells to the neighbouring
  11340. * cells when you drag the small square in the corner.
  11341. *
  11342. * "Copy-down" copies the value of the selection to all empty cells
  11343. * below when you double click the small square.
  11344. */
  11345. (function (Handsontable) {
  11346. 'use strict';
  11347. function Autofill(instance) {
  11348. this.instance = instance;
  11349. this.addingStarted = false;
  11350. var wtOnCellCornerMouseDown,
  11351. wtOnCellMouseOver,
  11352. mouseDownOnCellCorner = false,
  11353. plugin = this,
  11354. eventManager = Handsontable.eventManager(instance);
  11355. var mouseUpCallback = function (event) {
  11356. if (!instance.autofill) {
  11357. return true;
  11358. }
  11359. if (instance.autofill.handle && instance.autofill.handle.isDragged) {
  11360. if (instance.autofill.handle.isDragged > 1) {
  11361. instance.autofill.apply();
  11362. }
  11363. instance.autofill.handle.isDragged = 0;
  11364. mouseDownOnCellCorner = false;
  11365. }
  11366. };
  11367. eventManager.addEventListener(document, 'mouseup', function (event) {
  11368. mouseUpCallback(event);
  11369. });
  11370. eventManager.addEventListener(document,'mousemove', function (event){
  11371. if (!plugin.instance.autofill) {
  11372. return 0;
  11373. }
  11374. var tableBottom = Handsontable.Dom.offset(plugin.instance.table).top - (window.pageYOffset || document.documentElement.scrollTop) + Handsontable.Dom.outerHeight(plugin.instance.table)
  11375. , tableRight = Handsontable.Dom.offset(plugin.instance.table).left - (window.pageXOffset || document.documentElement.scrollLeft) + Handsontable.Dom.outerWidth(plugin.instance.table);
  11376. if (plugin.addingStarted === false && plugin.instance.autofill.handle.isDragged > 0 && event.clientY > tableBottom && event.clientX <= tableRight) { // dragged outside bottom
  11377. this.mouseDragOutside = true;
  11378. plugin.addingStarted = true;
  11379. } else {
  11380. this.mouseDragOutside = false;
  11381. }
  11382. if (this.mouseDragOutside) {
  11383. setTimeout(function () {
  11384. plugin.addingStarted = false;
  11385. plugin.instance.alter('insert_row');
  11386. }, 200);
  11387. }
  11388. });
  11389. /*
  11390. * Appeding autofill-specific methods to walkontable event settings
  11391. */
  11392. wtOnCellCornerMouseDown = this.instance.view.wt.wtSettings.settings.onCellCornerMouseDown;
  11393. this.instance.view.wt.wtSettings.settings.onCellCornerMouseDown = function (event) {
  11394. instance.autofill.handle.isDragged = 1;
  11395. mouseDownOnCellCorner = true;
  11396. wtOnCellCornerMouseDown(event);
  11397. };
  11398. wtOnCellMouseOver = this.instance.view.wt.wtSettings.settings.onCellMouseOver;
  11399. this.instance.view.wt.wtSettings.settings.onCellMouseOver = function (event, coords, TD, wt) {
  11400. if (instance.autofill && (mouseDownOnCellCorner && !instance.view.isMouseDown() && instance.autofill.handle && instance.autofill.handle.isDragged)) {
  11401. instance.autofill.handle.isDragged++;
  11402. instance.autofill.showBorder(coords);
  11403. instance.autofill.checkIfNewRowNeeded();
  11404. }
  11405. wtOnCellMouseOver(event, coords, TD, wt);
  11406. };
  11407. this.instance.view.wt.wtSettings.settings.onCellCornerDblClick = function () {
  11408. instance.autofill.selectAdjacent();
  11409. };
  11410. }
  11411. /**
  11412. * Create fill handle and fill border objects
  11413. */
  11414. Autofill.prototype.init = function () {
  11415. this.handle = {};
  11416. },
  11417. /**
  11418. * Hide fill handle and fill border permanently
  11419. */
  11420. Autofill.prototype.disable = function () {
  11421. this.handle.disabled = true;
  11422. },
  11423. /**
  11424. * Selects cells down to the last row in the left column, then fills down to that cell
  11425. */
  11426. Autofill.prototype.selectAdjacent = function () {
  11427. var select, data, r, maxR, c;
  11428. if (this.instance.selection.isMultiple()) {
  11429. select = this.instance.view.wt.selections.area.getCorners();
  11430. }
  11431. else {
  11432. select = this.instance.view.wt.selections.current.getCorners();
  11433. }
  11434. data = this.instance.getData();
  11435. rows : for (r = select[2] + 1; r < this.instance.countRows(); r++) {
  11436. for (c = select[1]; c <= select[3]; c++) {
  11437. if (data[r][c]) {
  11438. break rows;
  11439. }
  11440. }
  11441. if (!!data[r][select[1] - 1] || !!data[r][select[3] + 1]) {
  11442. maxR = r;
  11443. }
  11444. }
  11445. if (maxR) {
  11446. this.instance.view.wt.selections.fill.clear();
  11447. this.instance.view.wt.selections.fill.add(new WalkontableCellCoords(select[0], select[1]));
  11448. this.instance.view.wt.selections.fill.add(new WalkontableCellCoords(maxR, select[3]));
  11449. this.apply();
  11450. }
  11451. },
  11452. /**
  11453. * Apply fill values to the area in fill border, omitting the selection border
  11454. */
  11455. Autofill.prototype.apply = function () {
  11456. var drag, select, start, end, _data;
  11457. this.handle.isDragged = 0;
  11458. drag = this.instance.view.wt.selections.fill.getCorners();
  11459. if (!drag) {
  11460. return;
  11461. }
  11462. var getDeltas = function (start, end, data, direction) {
  11463. var rlength = data.length, // rows
  11464. clength = data ? data[0].length : 0; // cols
  11465. var deltas = [];
  11466. var diffRow = end.row - start.row,
  11467. diffCol = end.col - start.col;
  11468. var startValue, endValue, delta;
  11469. var arr = [];
  11470. if (['down', 'up'].indexOf(direction) !== -1) {
  11471. for (var col = 0; col <= diffCol; col++) {
  11472. startValue = parseInt(data[0][col], 10);
  11473. endValue = parseInt(data[rlength-1][col], 10);
  11474. delta = (direction === 'down' ? (endValue - startValue) : (startValue - endValue)) / (rlength - 1) || 0;
  11475. arr.push(delta);
  11476. }
  11477. deltas.push(arr);
  11478. }
  11479. if (['right', 'left'].indexOf(direction) !== -1) {
  11480. for (var row = 0; row <= diffRow; row++) {
  11481. startValue = parseInt(data[row][0], 10);
  11482. endValue = parseInt(data[row][clength-1], 10);
  11483. delta = (direction === 'right' ? (endValue - startValue) : (startValue - endValue)) / (clength - 1) || 0;
  11484. arr = [];
  11485. arr.push(delta);
  11486. deltas.push(arr);
  11487. }
  11488. }
  11489. return deltas;
  11490. };
  11491. this.instance.view.wt.selections.fill.clear();
  11492. if (this.instance.selection.isMultiple()) {
  11493. select = this.instance.view.wt.selections.area.getCorners();
  11494. }
  11495. else {
  11496. select = this.instance.view.wt.selections.current.getCorners();
  11497. }
  11498. var direction;
  11499. if (drag[0] === select[0] && drag[1] < select[1]) {
  11500. direction = 'left';
  11501. start = new WalkontableCellCoords(
  11502. drag[0],
  11503. drag[1]
  11504. );
  11505. end = new WalkontableCellCoords(
  11506. drag[2],
  11507. select[1] - 1
  11508. );
  11509. }
  11510. else if (drag[0] === select[0] && drag[3] > select[3]) {
  11511. direction = 'right';
  11512. start = new WalkontableCellCoords(
  11513. drag[0],
  11514. select[3] + 1
  11515. );
  11516. end = new WalkontableCellCoords(
  11517. drag[2],
  11518. drag[3]
  11519. );
  11520. }
  11521. else if (drag[0] < select[0] && drag[1] === select[1]) {
  11522. direction = 'up';
  11523. start = new WalkontableCellCoords(
  11524. drag[0],
  11525. drag[1]
  11526. );
  11527. end = new WalkontableCellCoords(
  11528. select[0] - 1,
  11529. drag[3]
  11530. );
  11531. }
  11532. else if (drag[2] > select[2] && drag[1] === select[1]) {
  11533. direction = 'down';
  11534. start = new WalkontableCellCoords(
  11535. select[2] + 1,
  11536. drag[1]
  11537. );
  11538. end = new WalkontableCellCoords(
  11539. drag[2],
  11540. drag[3]
  11541. );
  11542. }
  11543. if (start && start.row > -1 && start.col > -1) {
  11544. var selRange = {from: this.instance.getSelectedRange().from, to: this.instance.getSelectedRange().to};
  11545. _data = this.instance.getData(selRange.from.row, selRange.from.col, selRange.to.row, selRange.to.col);
  11546. var deltas = getDeltas(start, end, _data, direction);
  11547. Handsontable.hooks.run(this.instance, 'beforeAutofill', start, end, _data);
  11548. this.instance.populateFromArray(start.row, start.col, _data, end.row, end.col, 'autofill', null, direction, deltas);
  11549. this.instance.selection.setRangeStart(new WalkontableCellCoords(drag[0], drag[1]));
  11550. this.instance.selection.setRangeEnd(new WalkontableCellCoords(drag[2], drag[3]));
  11551. } else {
  11552. //reset to avoid some range bug
  11553. this.instance.selection.refreshBorders();
  11554. }
  11555. },
  11556. /**
  11557. * Show fill border
  11558. * @param {WalkontableCellCoords} coords
  11559. */
  11560. Autofill.prototype.showBorder = function (coords) {
  11561. var topLeft = this.instance.getSelectedRange().getTopLeftCorner();
  11562. var bottomRight = this.instance.getSelectedRange().getBottomRightCorner();
  11563. if (this.instance.getSettings().fillHandle !== 'horizontal' && (bottomRight.row < coords.row || topLeft.row > coords.row)) {
  11564. coords = new WalkontableCellCoords(coords.row, bottomRight.col);
  11565. }
  11566. else if (this.instance.getSettings().fillHandle !== 'vertical') {
  11567. coords = new WalkontableCellCoords(bottomRight.row, coords.col);
  11568. }
  11569. else {
  11570. return; //wrong direction
  11571. }
  11572. this.instance.view.wt.selections.fill.clear();
  11573. this.instance.view.wt.selections.fill.add(this.instance.getSelectedRange().from);
  11574. this.instance.view.wt.selections.fill.add(this.instance.getSelectedRange().to);
  11575. this.instance.view.wt.selections.fill.add(coords);
  11576. this.instance.view.render();
  11577. };
  11578. Autofill.prototype.checkIfNewRowNeeded = function () {
  11579. var fillCorners,
  11580. selection,
  11581. tableRows = this.instance.countRows(),
  11582. that = this;
  11583. if (this.instance.view.wt.selections.fill.cellRange && this.addingStarted === false) {
  11584. selection = this.instance.getSelected();
  11585. fillCorners = this.instance.view.wt.selections.fill.getCorners();
  11586. if (selection[2] < tableRows - 1 && fillCorners[2] === tableRows - 1) {
  11587. this.addingStarted = true;
  11588. this.instance._registerTimeout(setTimeout(function () {
  11589. that.instance.alter('insert_row');
  11590. that.addingStarted = false;
  11591. }, 200));
  11592. }
  11593. }
  11594. };
  11595. Handsontable.hooks.add('afterInit', function () {
  11596. var autofill = new Autofill(this);
  11597. if (typeof this.getSettings().fillHandle !== "undefined") {
  11598. if (autofill.handle && this.getSettings().fillHandle === false) {
  11599. autofill.disable();
  11600. }
  11601. else if (!autofill.handle && this.getSettings().fillHandle !== false) {
  11602. this.autofill = autofill;
  11603. this.autofill.init();
  11604. }
  11605. }
  11606. });
  11607. Handsontable.Autofill = Autofill;
  11608. })(Handsontable);
  11609. var Grouping = function (instance) {
  11610. /**
  11611. * array of items
  11612. * @type {Array}
  11613. */
  11614. var groups = [];
  11615. /**
  11616. * group definition
  11617. * @type {{id: String, level: Number, rows: Array, cols: Array, hidden: Number}}
  11618. */
  11619. var item = {
  11620. id: '',
  11621. level: 0,
  11622. hidden: 0,
  11623. rows: [],
  11624. cols: []
  11625. };
  11626. /**
  11627. * total rows and cols merged in groups
  11628. * @type {{rows: number, cols: number}}
  11629. */
  11630. var counters = {
  11631. rows: 0,
  11632. cols: 0
  11633. };
  11634. /**
  11635. * Number of group levels in each dimension
  11636. * @type {{rows: number, cols: number}}
  11637. */
  11638. var levels = {
  11639. rows: 0,
  11640. cols: 0
  11641. };
  11642. /**
  11643. * List of hidden rows
  11644. * @type {Array}
  11645. */
  11646. var hiddenRows = [];
  11647. /**
  11648. * List of hidden columns
  11649. * @type {Array}
  11650. */
  11651. var hiddenCols = [];
  11652. /**
  11653. * Classes used
  11654. */
  11655. var classes = {
  11656. 'groupIndicatorContainer': 'htGroupIndicatorContainer',
  11657. 'groupIndicator': function (direction) {
  11658. return 'ht' + direction + 'Group';
  11659. },
  11660. 'groupStart': 'htGroupStart',
  11661. 'collapseButton': 'htCollapseButton',
  11662. 'expandButton': 'htExpandButton',
  11663. 'collapseGroupId': function (id) {
  11664. return 'htCollapse-' + id;
  11665. },
  11666. 'collapseFromLevel': function (direction, level) {
  11667. return 'htCollapse' + direction + 'FromLevel-' + level;
  11668. },
  11669. 'clickable': 'clickable',
  11670. 'levelTrigger': 'htGroupLevelTrigger'
  11671. };
  11672. /**
  11673. * compare object properties
  11674. * @param {String} property
  11675. * @param {String} orderDirection
  11676. * @returns {Function}
  11677. */
  11678. var compare = function (property, orderDirection) {
  11679. return function (item1, item2) {
  11680. return typeof (orderDirection) === 'undefined' || orderDirection === 'asc' ? item1[property] - item2[property] : item2[property] - item1[property];
  11681. }
  11682. };
  11683. /**
  11684. * Create range array between from and to
  11685. * @param {Number} from
  11686. * @param {Number} to
  11687. * @returns {Array}
  11688. */
  11689. var range = function (from, to) {
  11690. var arr = [];
  11691. while (from <= to) {
  11692. arr.push(from++);
  11693. }
  11694. return arr;
  11695. };
  11696. /**
  11697. * * Get groups for range
  11698. * @param from
  11699. * @param to
  11700. * @returns {{total: {rows: number, cols: number}, groups: Array}}
  11701. */
  11702. var getRangeGroups = function (dataType, from, to) {
  11703. var cells = [],
  11704. cell = {
  11705. row: null,
  11706. col: null
  11707. };
  11708. if (dataType == "cols") {
  11709. // get all rows for selected columns
  11710. while (from <= to) {
  11711. cell = {
  11712. row: -1,
  11713. col: from++
  11714. };
  11715. cells.push(cell);
  11716. }
  11717. } else {
  11718. // get all columns for selected rows
  11719. while (from <= to) {
  11720. cell = {
  11721. row: from++,
  11722. col: -1
  11723. };
  11724. cells.push(cell);
  11725. }
  11726. }
  11727. var cellsGroups = getCellsGroups(cells),
  11728. totalRows = 0,
  11729. totalCols = 0;
  11730. // for selected cells, calculate total groups divided into rows and columns
  11731. for (var i = 0; i < cellsGroups.length; i++) {
  11732. totalRows += cellsGroups[i].filter(function (item) {
  11733. return item['rows']
  11734. }).length;
  11735. totalCols += cellsGroups[i].filter(function (item) {
  11736. return item['cols']
  11737. }).length;
  11738. }
  11739. return {
  11740. total: {
  11741. rows: totalRows,
  11742. cols: totalCols
  11743. },
  11744. groups: cellsGroups
  11745. };
  11746. };
  11747. /**
  11748. * Get all groups for cells
  11749. * @param {Array} cells [{row:0, col:0}, {row:0, col:1}, {row:1, col:2}]
  11750. * @returns {Array}
  11751. */
  11752. var getCellsGroups = function (cells) {
  11753. var _groups = [];
  11754. for (var i = 0; i < cells.length; i++) {
  11755. _groups.push(getCellGroups(cells[i]));
  11756. }
  11757. return _groups;
  11758. };
  11759. /**
  11760. * Get all groups for cell
  11761. * @param {Object} coords {row:1, col:2}
  11762. * @param {Number} groupLevel Optional
  11763. * @param {String} groupType Optional
  11764. * @returns {Array}
  11765. */
  11766. var getCellGroups = function (coords, groupLevel, groupType) {
  11767. var row = coords.row,
  11768. col = coords.col;
  11769. // for row = -1 and col = -1, get all columns and rows
  11770. var tmpRow = (row === -1 ? 0 : row),
  11771. tmpCol = (col === -1 ? 0 : col);
  11772. var _groups = [];
  11773. for (var i = 0; i < groups.length; i++) {
  11774. var group = groups[i],
  11775. id = group['id'],
  11776. level = group['level'],
  11777. rows = group['rows'] || [],
  11778. cols = group['cols'] || [];
  11779. if (_groups.indexOf(id) === -1) {
  11780. if (rows.indexOf(tmpRow) !== -1 || cols.indexOf(tmpCol) !== -1) {
  11781. _groups.push(group);
  11782. }
  11783. }
  11784. }
  11785. // add col groups
  11786. if (col === -1) {
  11787. _groups = _groups.concat(getColGroups());
  11788. } else if (row === -1) {
  11789. // add row groups
  11790. _groups = _groups.concat(getRowGroups());
  11791. }
  11792. if (groupLevel) {
  11793. _groups = _groups.filter(function (item) {
  11794. return item['level'] === groupLevel;
  11795. });
  11796. }
  11797. if (groupType) {
  11798. if (groupType === 'cols') {
  11799. _groups = _groups.filter(function (item) {
  11800. return item['cols'];
  11801. });
  11802. } else if (groupType === 'rows') {
  11803. _groups = _groups.filter(function (item) {
  11804. return item['rows'];
  11805. });
  11806. }
  11807. }
  11808. // remove duplicates
  11809. var tmp = [];
  11810. return _groups.filter(function (item) {
  11811. if (tmp.indexOf(item.id) === -1) {
  11812. tmp.push(item.id);
  11813. return item;
  11814. }
  11815. });
  11816. };
  11817. /**
  11818. * get group by id
  11819. * @param id
  11820. * @returns {Object} group
  11821. */
  11822. var getGroupById = function (id) {
  11823. for (var i = 0, groupsLength = groups.length; i < groupsLength; i++) {
  11824. if (groups[i].id == id) return groups[i];
  11825. }
  11826. return false;
  11827. };
  11828. /**
  11829. * get group by row and level
  11830. * @param row
  11831. * @param level
  11832. * @returns {Object} group
  11833. */
  11834. var getGroupByRowAndLevel = function (row, level) {
  11835. for (var i = 0, groupsLength = groups.length; i < groupsLength; i++) {
  11836. if (groups[i].level == level && groups[i].rows && groups[i].rows.indexOf(row) > -1) return groups[i];
  11837. }
  11838. return false;
  11839. };
  11840. /**
  11841. * get group by row and level
  11842. * @param row
  11843. * @param level
  11844. * @returns {Object} group
  11845. */
  11846. var getGroupByColAndLevel = function (col, level) {
  11847. for (var i = 0, groupsLength = groups.length; i < groupsLength; i++) {
  11848. if (groups[i].level == level && groups[i].cols && groups[i].cols.indexOf(col) > -1) return groups[i];
  11849. }
  11850. return false;
  11851. };
  11852. /**
  11853. * get total column groups
  11854. * @returns {*|Array}
  11855. */
  11856. var getColGroups = function () {
  11857. var result = [];
  11858. for (var i = 0, groupsLength = groups.length; i < groupsLength; i++) {
  11859. if (Array.isArray(groups[i]['cols'])) result.push(groups[i]);
  11860. }
  11861. return result;
  11862. };
  11863. /**
  11864. * get total col groups by level
  11865. * @param {Number} level
  11866. * @returns {*|Array}
  11867. */
  11868. var getColGroupsByLevel = function (level) {
  11869. var result = [];
  11870. for (var i = 0, groupsLength = groups.length; i < groupsLength; i++) {
  11871. if (groups[i]['cols'] && groups[i]['level'] === level) result.push(groups[i]);
  11872. }
  11873. return result;
  11874. };
  11875. /**
  11876. * get total row groups
  11877. * @returns {*|Array}
  11878. */
  11879. var getRowGroups = function () {
  11880. var result = [];
  11881. for (var i = 0, groupsLength = groups.length; i < groupsLength; i++) {
  11882. if (Array.isArray(groups[i]['rows'])) result.push(groups[i]);
  11883. }
  11884. return result;
  11885. };
  11886. /**
  11887. * get total row groups by level
  11888. * @param {Number} level
  11889. * @returns {*|Array}
  11890. */
  11891. var getRowGroupsByLevel = function (level) {
  11892. var result = [];
  11893. for (var i = 0, groupsLength = groups.length; i < groupsLength; i++) {
  11894. if (groups[i]['rows'] && groups[i]['level'] === level) result.push(groups[i]);
  11895. }
  11896. return result;
  11897. };
  11898. /**
  11899. * get last inserted range level in columns
  11900. * @param {Array} rangeGroups
  11901. * @returns {number}
  11902. */
  11903. var getLastLevelColsInRange = function (rangeGroups) {
  11904. var level = 0;
  11905. if (rangeGroups.length) {
  11906. rangeGroups.forEach(function (items) {
  11907. items = items.filter(function (item) {
  11908. return item['cols'];
  11909. });
  11910. if (items.length) {
  11911. var sortedGroup = items.sort(compare('level', 'desc')),
  11912. lastLevel = sortedGroup[0].level;
  11913. if (level < lastLevel) {
  11914. level = lastLevel;
  11915. }
  11916. }
  11917. });
  11918. }
  11919. return level;
  11920. };
  11921. /**
  11922. * get last inserted range level in rows
  11923. * @param {Array} rangeGroups
  11924. * @returns {number}
  11925. */
  11926. var getLastLevelRowsInRange = function (rangeGroups) {
  11927. var level = 0;
  11928. if (rangeGroups.length) {
  11929. rangeGroups.forEach(function (items) {
  11930. items = items.filter(function (item) {
  11931. return item['rows'];
  11932. });
  11933. if (items.length) {
  11934. var sortedGroup = items.sort(compare('level', 'desc')),
  11935. lastLevel = sortedGroup[0].level;
  11936. if (level < lastLevel) {
  11937. level = lastLevel;
  11938. }
  11939. }
  11940. });
  11941. }
  11942. return level;
  11943. };
  11944. /**
  11945. * create group for cols
  11946. * @param {Number} from
  11947. * @param {Number} to
  11948. */
  11949. var groupCols = function (from, to) {
  11950. var rangeGroups = getRangeGroups("cols", from, to),
  11951. lastLevel = getLastLevelColsInRange(rangeGroups.groups);
  11952. if (lastLevel === levels.cols) {
  11953. levels.cols++;
  11954. } else if (lastLevel > levels.cols) {
  11955. levels.cols = lastLevel + 1;
  11956. }
  11957. if (!counters.cols) {
  11958. counters.cols = getColGroups().length;
  11959. }
  11960. counters.cols++;
  11961. groups.push({
  11962. id: 'c' + counters.cols,
  11963. level: lastLevel + 1,
  11964. cols: range(from, to),
  11965. hidden: 0
  11966. });
  11967. };
  11968. /**
  11969. * create group for rows
  11970. * @param {Number} from
  11971. * @param {Number} to
  11972. */
  11973. var groupRows = function (from, to) {
  11974. var rangeGroups = getRangeGroups("rows", from, to),
  11975. lastLevel = getLastLevelRowsInRange(rangeGroups.groups);
  11976. levels.rows = Math.max(levels.rows, lastLevel + 1);
  11977. if (!counters.rows) {
  11978. counters.rows = getRowGroups().length;
  11979. }
  11980. counters.rows++;
  11981. groups.push({
  11982. id: 'r' + counters.rows,
  11983. level: lastLevel + 1,
  11984. rows: range(from, to),
  11985. hidden: 0
  11986. });
  11987. };
  11988. /**
  11989. * show or hide groups
  11990. * @param showHide
  11991. * @param groups
  11992. */
  11993. var showHideGroups = function (hidden, groups) {
  11994. var level;
  11995. for (var i = 0, groupsLength = groups.length; i < groupsLength; i++) {
  11996. groups[i].hidden = hidden;
  11997. level = groups[i].level;
  11998. if (!hiddenRows[level]) hiddenRows[level] = [];
  11999. if (!hiddenCols[level]) hiddenCols[level] = [];
  12000. if (groups[i].rows) {
  12001. for (var j = 0, rowsLength = groups[i].rows.length; j < rowsLength; j++) {
  12002. if (hidden > 0) {
  12003. hiddenRows[level][groups[i].rows[j]] = true;
  12004. } else {
  12005. hiddenRows[level][groups[i].rows[j]] = void 0;
  12006. }
  12007. }
  12008. } else if (groups[i].cols) {
  12009. for (var j = 0, colsLength = groups[i].cols.length; j < colsLength; j++) {
  12010. if (hidden > 0) {
  12011. hiddenCols[level][groups[i].cols[j]] = true;
  12012. } else {
  12013. hiddenCols[level][groups[i].cols[j]] = void 0;
  12014. }
  12015. }
  12016. }
  12017. }
  12018. };
  12019. /**
  12020. * Check if the next cell of the dimension (row / column) contains a group at the same level
  12021. * @param dimension
  12022. * @param currentPosition
  12023. * @param level
  12024. * @param currentGroupId
  12025. * @returns {boolean}
  12026. */
  12027. var nextIndexSharesLevel = function (dimension, currentPosition, level, currentGroupId) {
  12028. var nextCellGroupId
  12029. , levelsByOrder;
  12030. switch (dimension) {
  12031. case 'rows':
  12032. nextCellGroupId = getGroupByRowAndLevel(currentPosition + 1, level).id;
  12033. levelsByOrder = Handsontable.Grouping.getGroupLevelsByRows();
  12034. break;
  12035. case 'cols':
  12036. nextCellGroupId = getGroupByColAndLevel(currentPosition + 1, level).id;
  12037. levelsByOrder = Handsontable.Grouping.getGroupLevelsByCols();
  12038. break;
  12039. }
  12040. return !!(levelsByOrder[currentPosition + 1] && levelsByOrder[currentPosition + 1].indexOf(level) > -1 && currentGroupId == nextCellGroupId);
  12041. };
  12042. /**
  12043. * Check if the previous cell of the dimension (row / column) contains a group at the same level
  12044. * @param dimension
  12045. * @param currentPosition
  12046. * @param level
  12047. * @param currentGroupId
  12048. * @returns {boolean}
  12049. */
  12050. var previousIndexSharesLevel = function (dimension, currentPosition, level, currentGroupId) {
  12051. var previousCellGroupId
  12052. , levelsByOrder;
  12053. switch (dimension) {
  12054. case 'rows':
  12055. previousCellGroupId = getGroupByRowAndLevel(currentPosition - 1, level).id;
  12056. levelsByOrder = Handsontable.Grouping.getGroupLevelsByRows();
  12057. break;
  12058. case 'cols':
  12059. previousCellGroupId = getGroupByColAndLevel(currentPosition - 1, level).id;
  12060. levelsByOrder = Handsontable.Grouping.getGroupLevelsByCols();
  12061. break;
  12062. }
  12063. return !!(levelsByOrder[currentPosition - 1] && levelsByOrder[currentPosition - 1].indexOf(level) > -1 && currentGroupId == previousCellGroupId);
  12064. };
  12065. /**
  12066. * Check if the provided index is at the end of the group indicator line
  12067. * @param dimension
  12068. * @param index
  12069. * @param level
  12070. * @param currentGroupId
  12071. * @returns {boolean}
  12072. */
  12073. var isLastIndexOfTheLine = function (dimension, index, level, currentGroupId) {
  12074. if (index === 0) return false;
  12075. var levelsByOrder
  12076. , entriesLength
  12077. , previousSharesLevel = previousIndexSharesLevel(dimension, index, level, currentGroupId)
  12078. , nextSharesLevel = nextIndexSharesLevel(dimension, index, level, currentGroupId)
  12079. , nextIsHidden = false;
  12080. switch (dimension) {
  12081. case 'rows':
  12082. levelsByOrder = Handsontable.Grouping.getGroupLevelsByRows();
  12083. entriesLength = instance.countRows();
  12084. for (var i = 0; i <= levels.rows; i++) {
  12085. if (hiddenRows[i] && hiddenRows[i][index + 1]) {
  12086. nextIsHidden = true;
  12087. break;
  12088. }
  12089. }
  12090. break;
  12091. case 'cols':
  12092. levelsByOrder = Handsontable.Grouping.getGroupLevelsByCols();
  12093. entriesLength = instance.countCols();
  12094. for (var i = 0; i <= levels.cols; i++) {
  12095. if (hiddenCols[i] && hiddenCols[i][index + 1]) {
  12096. nextIsHidden = true;
  12097. break;
  12098. }
  12099. }
  12100. break;
  12101. }
  12102. if (previousSharesLevel) {
  12103. if (index == entriesLength - 1) {
  12104. return true;
  12105. } else if (!nextSharesLevel || (nextSharesLevel && nextIsHidden)) {
  12106. return true;
  12107. } else if (!levelsByOrder[index + 1]) {
  12108. return true;
  12109. }
  12110. }
  12111. return false;
  12112. };
  12113. /**
  12114. * Check if all rows/cols are hidden
  12115. * @param dataType
  12116. */
  12117. var isLastHidden = function (dataType) {
  12118. var levelAmount;
  12119. switch (dataType) {
  12120. case 'rows':
  12121. levelAmount = levels.rows;
  12122. for (var j = 0; j <= levelAmount; j++) {
  12123. if (hiddenRows[j] && hiddenRows[j][instance.countRows() - 1]) {
  12124. return true;
  12125. }
  12126. }
  12127. break;
  12128. case 'cols':
  12129. levelAmount = levels.cols;
  12130. for (var j = 0; j <= levelAmount; j++) {
  12131. if (hiddenCols[j] && hiddenCols[j][instance.countCols() - 1]) {
  12132. return true;
  12133. }
  12134. }
  12135. break;
  12136. }
  12137. return false;
  12138. };
  12139. /**
  12140. * Check if the provided index is at the beginning of the group indicator line
  12141. * @param dimension
  12142. * @param index
  12143. * @param level
  12144. * @param currentGroupId
  12145. * @returns {boolean}
  12146. */
  12147. var isFirstIndexOfTheLine = function (dimension, index, level, currentGroupId) {
  12148. var levelsByOrder
  12149. , entriesLength
  12150. , currentGroup = getGroupById(currentGroupId)
  12151. , previousAreHidden = false
  12152. , arePreviousHidden = function (dimension) {
  12153. var hidden = false
  12154. , hiddenArr = dimension == 'rows' ? hiddenRows : hiddenCols;
  12155. for (var i = 0; i <= levels[dimension]; i++) {
  12156. tempInd = index;
  12157. while (currentGroup[dimension].indexOf(tempInd) > -1) {
  12158. hidden = !!(hiddenArr[i] && hiddenArr[i][tempInd]);
  12159. tempInd--;
  12160. }
  12161. if (hidden) break;
  12162. }
  12163. return hidden;
  12164. }
  12165. , previousSharesLevel = previousIndexSharesLevel(dimension, index, level, currentGroupId)
  12166. , nextSharesLevel = nextIndexSharesLevel(dimension, index, level, currentGroupId)
  12167. , tempInd;
  12168. switch (dimension) {
  12169. case 'rows':
  12170. levelsByOrder = Handsontable.Grouping.getGroupLevelsByRows();
  12171. entriesLength = instance.countRows();
  12172. previousAreHidden = arePreviousHidden(dimension);
  12173. break;
  12174. case 'cols':
  12175. levelsByOrder = Handsontable.Grouping.getGroupLevelsByCols();
  12176. entriesLength = instance.countCols();
  12177. previousAreHidden = arePreviousHidden(dimension);
  12178. break;
  12179. }
  12180. if (index == entriesLength - 1) return false;
  12181. else if (index == 0) {
  12182. if (nextSharesLevel) {
  12183. return true;
  12184. }
  12185. } else if (!previousSharesLevel || (previousSharesLevel && previousAreHidden)) {
  12186. if (nextSharesLevel) {
  12187. return true;
  12188. }
  12189. } else if (!levelsByOrder[index - 1]) {
  12190. if (nextSharesLevel) {
  12191. return true;
  12192. }
  12193. }
  12194. return false;
  12195. };
  12196. /**
  12197. * Add group expander button
  12198. * @param dimension
  12199. * @param index
  12200. * @param level
  12201. * @param id
  12202. * @param elem
  12203. * @returns {*}
  12204. */
  12205. var addGroupExpander = function (dataType, index, level, id, elem) {
  12206. var previousIndexGroupId;
  12207. switch (dataType) {
  12208. case 'rows':
  12209. previousIndexGroupId = getGroupByRowAndLevel(index - 1, level).id;
  12210. break;
  12211. case 'cols':
  12212. previousIndexGroupId = getGroupByColAndLevel(index - 1, level).id;
  12213. break;
  12214. }
  12215. if (!previousIndexGroupId) return null;
  12216. if (index > 0) {
  12217. if (previousIndexSharesLevel(dataType, index - 1, level, previousIndexGroupId) && previousIndexGroupId != id) {
  12218. var expanderButton = document.createElement('DIV');
  12219. Handsontable.Dom.addClass(expanderButton, classes.expandButton);
  12220. expanderButton.id = 'htExpand-' + previousIndexGroupId;
  12221. expanderButton.appendChild(document.createTextNode('+'));
  12222. expanderButton.setAttribute('data-level', level);
  12223. expanderButton.setAttribute('data-type', dataType);
  12224. expanderButton.setAttribute('data-hidden', "1");
  12225. elem.appendChild(expanderButton);
  12226. return expanderButton;
  12227. }
  12228. }
  12229. return null;
  12230. };
  12231. /**
  12232. * Check if provided cell is collapsed (either by rows or cols)
  12233. * @param currentPosition
  12234. * @returns {boolean}
  12235. */
  12236. var isCollapsed = function (currentPosition) {
  12237. var rowGroups = getRowGroups()
  12238. , colGroups = getColGroups();
  12239. for (var i = 0, rowGroupsCount = rowGroups.length; i < rowGroupsCount; i++) {
  12240. if (rowGroups[i].rows.indexOf(currentPosition.row) > -1 && rowGroups[i].hidden) {
  12241. return true;
  12242. }
  12243. }
  12244. if (currentPosition.col === null) { // if col is set to null, check only rows
  12245. return false;
  12246. }
  12247. for (var i = 0, colGroupsCount = colGroups.length; i < colGroupsCount; i++) {
  12248. if (colGroups[i].cols.indexOf(currentPosition.col) > -1 && colGroups[i].hidden) {
  12249. return true;
  12250. }
  12251. }
  12252. return false;
  12253. };
  12254. return {
  12255. /**
  12256. * all groups for ht instance
  12257. */
  12258. getGroups: function () {
  12259. return groups;
  12260. },
  12261. /**
  12262. * All levels for rows and cols respectively
  12263. */
  12264. getLevels: function () {
  12265. return levels;
  12266. },
  12267. /**
  12268. * Current instance
  12269. */
  12270. instance: instance,
  12271. /**
  12272. * Initial setting for minSpareRows
  12273. */
  12274. baseSpareRows: instance.getSettings().minSpareRows,
  12275. /**
  12276. * Initial setting for minSpareCols
  12277. */
  12278. baseSpareCols: instance.getSettings().minSpareCols,
  12279. getRowGroups: getRowGroups,
  12280. getColGroups: getColGroups,
  12281. /**
  12282. * init group
  12283. * @param {Object} settings, could be an array of objects [{cols: [0,1,2]}, {cols: [3,4,5]}, {rows: [0,1]}]
  12284. */
  12285. init: function () {
  12286. var groupsSetting = instance.getSettings().groups;
  12287. if (groupsSetting) {
  12288. if (Array.isArray(groupsSetting)) {
  12289. Handsontable.Grouping.initGroups(groupsSetting);
  12290. }
  12291. }
  12292. },
  12293. /**
  12294. * init groups from configuration on startup
  12295. */
  12296. initGroups: function (initialGroups) {
  12297. var that = this;
  12298. groups = [];
  12299. initialGroups.forEach(function (item) {
  12300. var _group = [],
  12301. isRow = false,
  12302. isCol = false;
  12303. if (Array.isArray(item.rows)) {
  12304. _group = item.rows;
  12305. isRow = true;
  12306. } else if (Array.isArray(item.cols)) {
  12307. _group = item.cols;
  12308. isCol = true;
  12309. }
  12310. var from = _group[0],
  12311. to = _group[_group.length - 1];
  12312. if (isRow) {
  12313. groupRows(from, to);
  12314. } else if (isCol) {
  12315. groupCols(from, to);
  12316. }
  12317. });
  12318. // this.render();
  12319. },
  12320. /**
  12321. * Remove all existing groups
  12322. */
  12323. resetGroups: function () {
  12324. groups = [];
  12325. counters = {
  12326. rows: 0,
  12327. cols: 0
  12328. };
  12329. levels = {
  12330. rows: 0,
  12331. cols: 0
  12332. };
  12333. var allOccurrences;
  12334. for (var i in classes) {
  12335. if (typeof classes[i] != 'function') {
  12336. allOccurrences = document.querySelectorAll('.' + classes[i]);
  12337. for (var j = 0, occurrencesLength = allOccurrences.length; j < occurrencesLength; j++) {
  12338. Handsontable.Dom.removeClass(allOccurrences[j], classes[i]);
  12339. }
  12340. }
  12341. }
  12342. var otherClasses = ['htGroupColClosest', 'htGroupCol'];
  12343. for (var i = 0, otherClassesLength = otherClasses.length; i < otherClassesLength; i++) {
  12344. allOccurrences = document.querySelectorAll('.' + otherClasses[i]);
  12345. for (var j = 0, occurrencesLength = allOccurrences.length; j < occurrencesLength; j++) {
  12346. Handsontable.Dom.removeClass(allOccurrences[j], otherClasses[i]);
  12347. }
  12348. }
  12349. },
  12350. /**
  12351. * Update groups from the instance settings
  12352. */
  12353. updateGroups: function () {
  12354. var groupSettings = this.getSettings().groups;
  12355. Handsontable.Grouping.resetGroups();
  12356. Handsontable.Grouping.initGroups(groupSettings);
  12357. },
  12358. afterGetRowHeader: function (row, TH) {
  12359. var currentRowHidden = false;
  12360. for (var i = 0, levels = hiddenRows.length; i < levels; i++) {
  12361. if (hiddenRows[i] && hiddenRows[i][row] === true) {
  12362. currentRowHidden = true;
  12363. }
  12364. }
  12365. if (currentRowHidden) {
  12366. Handsontable.Dom.addClass(TH.parentNode, 'hidden');
  12367. } else if (!currentRowHidden && Handsontable.Dom.hasClass(TH.parentNode, 'hidden')) {
  12368. Handsontable.Dom.removeClass(TH.parentNode, 'hidden');
  12369. }
  12370. },
  12371. afterGetColHeader: function (col, TH) {
  12372. var rowHeaders = this.view.wt.wtSettings.getSetting('rowHeaders').length
  12373. , thisColgroup = instance.rootElement.querySelectorAll('colgroup col:nth-child(' + parseInt(col + rowHeaders + 1, 10) + ')');
  12374. if (thisColgroup.length === 0) {
  12375. return;
  12376. }
  12377. var currentColHidden = false;
  12378. for (var i = 0, levels = hiddenCols.length; i < levels; i++) {
  12379. if (hiddenCols[i] && hiddenCols[i][col] === true) {
  12380. currentColHidden = true;
  12381. }
  12382. }
  12383. if (currentColHidden) {
  12384. for (var i = 0, colsAmount = thisColgroup.length; i < colsAmount; i++) {
  12385. Handsontable.Dom.addClass(thisColgroup[i], 'hidden');
  12386. }
  12387. } else if (!currentColHidden && Handsontable.Dom.hasClass(thisColgroup[0], 'hidden')) {
  12388. for (var i = 0, colsAmount = thisColgroup.length; i < colsAmount; i++) {
  12389. Handsontable.Dom.removeClass(thisColgroup[i], 'hidden');
  12390. }
  12391. }
  12392. },
  12393. /**
  12394. * Create a renderer for additional row/col headers, acting as group indicators
  12395. * @param walkontableConfig
  12396. * @param direction
  12397. */
  12398. groupIndicatorsFactory: function (renderersArr, direction) {
  12399. var groupsLevelsList
  12400. , getCurrentLevel
  12401. , getCurrentGroupId
  12402. , dataType
  12403. , getGroupByIndexAndLevel
  12404. , headersType
  12405. , currentHeaderModifier
  12406. , createLevelTriggers;
  12407. switch (direction) {
  12408. case 'horizontal':
  12409. groupsLevelsList = Handsontable.Grouping.getGroupLevelsByCols();
  12410. getCurrentLevel = function (elem) {
  12411. return Array.prototype.indexOf.call(elem.parentNode.parentNode.childNodes, elem.parentNode) + 1;
  12412. };
  12413. getCurrentGroupId = function (col, level) {
  12414. return getGroupByColAndLevel(col, level).id;
  12415. };
  12416. dataType = 'cols';
  12417. getGroupByIndexAndLevel = function (col, level) {
  12418. return getGroupByColAndLevel(col - 1, level);
  12419. };
  12420. headersType = "columnHeaders";
  12421. currentHeaderModifier = function (headerRenderers) {
  12422. if (headerRenderers.length === 1) {
  12423. var oldFn = headerRenderers[0];
  12424. headerRenderers[0] = function (index, elem, level) {
  12425. if (index < -1)
  12426. makeGroupIndicatorsForLevel()(index, elem, level);
  12427. else {
  12428. Handsontable.Dom.removeClass(elem, classes.groupIndicatorContainer);
  12429. oldFn(index, elem, level);
  12430. }
  12431. }
  12432. }
  12433. return function () {
  12434. return headerRenderers;
  12435. };
  12436. };
  12437. createLevelTriggers = true;
  12438. break;
  12439. case 'vertical':
  12440. groupsLevelsList = Handsontable.Grouping.getGroupLevelsByRows();
  12441. getCurrentLevel = function (elem) {
  12442. return Handsontable.Dom.index(elem) + 1;
  12443. };
  12444. getCurrentGroupId = function (row, level) {
  12445. return getGroupByRowAndLevel(row, level).id;
  12446. };
  12447. dataType = 'rows';
  12448. getGroupByIndexAndLevel = function (row, level) {
  12449. return getGroupByRowAndLevel(row - 1, level);
  12450. };
  12451. headersType = "rowHeaders";
  12452. currentHeaderModifier = function (headerRenderers) {
  12453. return headerRenderers;
  12454. };
  12455. break;
  12456. }
  12457. var createButton = function (parent) {
  12458. var button = document.createElement('div');
  12459. parent.appendChild(button);
  12460. return {
  12461. button: button,
  12462. addClass: function (className) {
  12463. Handsontable.Dom.addClass(button, className);
  12464. }
  12465. };
  12466. };
  12467. var makeGroupIndicatorsForLevel = function () {
  12468. var directionClassname = direction.charAt(0).toUpperCase() + direction.slice(1); // capitalize the first letter
  12469. return function (index, elem, level) { // header rendering function
  12470. level++;
  12471. var child
  12472. , collapseButton;
  12473. while (child = elem.lastChild) {
  12474. elem.removeChild(child);
  12475. }
  12476. Handsontable.Dom.addClass(elem, classes.groupIndicatorContainer);
  12477. var currentGroupId = getCurrentGroupId(index, level);
  12478. if (index > -1 && (groupsLevelsList[index] && groupsLevelsList[index].indexOf(level) > -1)) {
  12479. collapseButton = createButton(elem);
  12480. collapseButton.addClass(classes.groupIndicator(directionClassname));
  12481. if (isFirstIndexOfTheLine(dataType, index, level, currentGroupId)) { // add a little thingy and the top of the group indicator
  12482. collapseButton.addClass(classes.groupStart);
  12483. }
  12484. if (isLastIndexOfTheLine(dataType, index, level, currentGroupId)) { // add [+]/[-] button at the end of the line
  12485. collapseButton.button.appendChild(document.createTextNode('-'));
  12486. collapseButton.addClass(classes.collapseButton);
  12487. collapseButton.button.id = classes.collapseGroupId(currentGroupId);
  12488. collapseButton.button.setAttribute('data-level', level);
  12489. collapseButton.button.setAttribute('data-type', dataType);
  12490. }
  12491. }
  12492. if (createLevelTriggers) {
  12493. var rowInd = Handsontable.Dom.index(elem.parentNode);
  12494. if (index === -1 || (index < -1 && rowInd === Handsontable.Grouping.getLevels().cols + 1) || (rowInd == 0 && Handsontable.Grouping.getLevels().cols == 0)) {
  12495. collapseButton = createButton(elem);
  12496. collapseButton.addClass(classes.levelTrigger);
  12497. if (index === -1) {
  12498. collapseButton.button.id = classes.collapseFromLevel("Cols", level);
  12499. collapseButton.button.appendChild(document.createTextNode(level));
  12500. } else if (index < -1 && rowInd === Handsontable.Grouping.getLevels().cols + 1 || (rowInd == 0 && Handsontable.Grouping.getLevels().cols == 0)) {
  12501. var colInd = Handsontable.Dom.index(elem) + 1;
  12502. collapseButton.button.id = classes.collapseFromLevel("Rows", colInd);
  12503. collapseButton.button.appendChild(document.createTextNode(colInd));
  12504. }
  12505. }
  12506. }
  12507. // add group expending button
  12508. var expanderButton = addGroupExpander(dataType, index, level, currentGroupId, elem);
  12509. if (index > 0) {
  12510. var previousGroupObj = getGroupByIndexAndLevel(index - 1, level);
  12511. if (expanderButton && previousGroupObj.hidden) {
  12512. Handsontable.Dom.addClass(expanderButton, classes.clickable);
  12513. }
  12514. }
  12515. updateHeaderWidths();
  12516. };
  12517. };
  12518. renderersArr = currentHeaderModifier(renderersArr);
  12519. if (counters[dataType] > 0) {
  12520. for (var i = 0; i < levels[dataType] + 1; i++) { // for each level of col groups add a header renderer
  12521. if (!Array.isArray(renderersArr)) {
  12522. renderersArr = typeof renderersArr === 'function' ? renderersArr() : new Array(renderersArr);
  12523. }
  12524. renderersArr.unshift(makeGroupIndicatorsForLevel());
  12525. }
  12526. }
  12527. },
  12528. /**
  12529. * Get group levels array arranged by rows
  12530. * @returns {Array}
  12531. */
  12532. getGroupLevelsByRows: function () {
  12533. var rowGroups = getRowGroups()
  12534. , result = [];
  12535. for (var i = 0, groupsLength = rowGroups.length; i < groupsLength; i++) {
  12536. if (rowGroups[i].rows) {
  12537. for (var j = 0, groupRowsLength = rowGroups[i].rows.length; j < groupRowsLength; j++) {
  12538. if (!result[rowGroups[i].rows[j]]) result[rowGroups[i].rows[j]] = [];
  12539. result[rowGroups[i].rows[j]].push(rowGroups[i].level);
  12540. }
  12541. }
  12542. }
  12543. return result;
  12544. },
  12545. /**
  12546. * Get group levels array arranged by cols
  12547. * @returns {Array}
  12548. */
  12549. getGroupLevelsByCols: function () {
  12550. var colGroups = getColGroups()
  12551. , result = [];
  12552. for (var i = 0, groupsLength = colGroups.length; i < groupsLength; i++) {
  12553. if (colGroups[i].cols) {
  12554. for (var j = 0, groupColsLength = colGroups[i].cols.length; j < groupColsLength; j++) {
  12555. if (!result[colGroups[i].cols[j]]) result[colGroups[i].cols[j]] = [];
  12556. result[colGroups[i].cols[j]].push(colGroups[i].level);
  12557. }
  12558. }
  12559. }
  12560. return result;
  12561. },
  12562. /**
  12563. * Toggle the group visibility ( + / - event handler)
  12564. * @param event
  12565. * @param coords
  12566. * @param TD
  12567. */
  12568. toggleGroupVisibility: function (event, coords, TD) {
  12569. if (Handsontable.Dom.hasClass(event.target, classes.expandButton)
  12570. || Handsontable.Dom.hasClass(event.target, classes.collapseButton)
  12571. || Handsontable.Dom.hasClass(event.target, classes.levelTrigger)) {
  12572. var element = event.target
  12573. , elemIdSplit = element.id.split('-');
  12574. var groups = []
  12575. , id
  12576. , level
  12577. , type
  12578. , hidden;
  12579. var prepareGroupData = function (componentElement) {
  12580. if (componentElement) element = componentElement;
  12581. elemIdSplit = element.id.split('-');
  12582. id = elemIdSplit[1];
  12583. level = parseInt(element.getAttribute('data-level'), 10);
  12584. type = element.getAttribute('data-type');
  12585. hidden = parseInt(element.getAttribute('data-hidden'));
  12586. if (isNaN(hidden)) {
  12587. hidden = 1;
  12588. } else {
  12589. hidden = (hidden ? 0 : 1);
  12590. }
  12591. element.setAttribute('data-hidden', hidden.toString());
  12592. groups.push(getGroupById(id));
  12593. };
  12594. if (element.className.indexOf(classes.levelTrigger) > -1) { // show levels below, hide all above
  12595. var groupsInLevel
  12596. , groupsToExpand = []
  12597. , groupsToCollapse = []
  12598. , levelType = element.id.indexOf("Rows") > -1 ? "rows" : "cols";
  12599. for (var i = 1, levelsCount = levels[levelType]; i <= levelsCount; i++) {
  12600. groupsInLevel = levelType == "rows" ? getRowGroupsByLevel(i) : getColGroupsByLevel(i);
  12601. if (i >= parseInt(elemIdSplit[1], 10)) {
  12602. for (var j = 0, groupCount = groupsInLevel.length; j < groupCount; j++) {
  12603. groupsToCollapse.push(groupsInLevel[j]);
  12604. }
  12605. } else {
  12606. for (var j = 0, groupCount = groupsInLevel.length; j < groupCount; j++) {
  12607. groupsToExpand.push(groupsInLevel[j]);
  12608. }
  12609. }
  12610. }
  12611. showHideGroups(true, groupsToCollapse);
  12612. showHideGroups(false, groupsToExpand);
  12613. } else {
  12614. prepareGroupData();
  12615. showHideGroups(hidden, groups);
  12616. }
  12617. // add the expander button to a dummy spare row/col, if no longer needed -> remove it
  12618. type = type || levelType;
  12619. var lastHidden = isLastHidden(type)
  12620. , typeUppercase = type.charAt(0).toUpperCase() + type.slice(1)
  12621. , spareElements = Handsontable.Grouping['baseSpare' + typeUppercase];
  12622. if (lastHidden) {
  12623. if (spareElements == 0) {
  12624. instance.alter('insert_' + type.slice(0, -1), instance['count' + typeUppercase]());
  12625. Handsontable.Grouping["dummy" + type.slice(0, -1)] = true;
  12626. }
  12627. } else {
  12628. if (spareElements == 0) {
  12629. if (Handsontable.Grouping["dummy" + type.slice(0, -1)]) {
  12630. instance.alter('remove_' + type.slice(0, -1), instance['count' + typeUppercase]() - 1);
  12631. Handsontable.Grouping["dummy" + type.slice(0, -1)] = false;
  12632. }
  12633. }
  12634. }
  12635. instance.render();
  12636. event.stopImmediatePropagation();
  12637. }
  12638. },
  12639. /**
  12640. * Modify the delta when changing cells using keyobard
  12641. * @param position
  12642. * @returns {Function}
  12643. */
  12644. modifySelectionFactory: function (position) {
  12645. var instance = this.instance;
  12646. var currentlySelected
  12647. , nextPosition = new WalkontableCellCoords(0, 0)
  12648. , nextVisible = function (direction, currentPosition) { // updates delta to skip to the next visible cell
  12649. var updateDelta = 0;
  12650. switch (direction) {
  12651. case 'down':
  12652. while (isCollapsed(currentPosition)) {
  12653. updateDelta++;
  12654. currentPosition.row += 1;
  12655. }
  12656. break;
  12657. case 'up':
  12658. while (isCollapsed(currentPosition)) {
  12659. updateDelta--;
  12660. currentPosition.row -= 1;
  12661. }
  12662. break;
  12663. case 'right':
  12664. while (isCollapsed(currentPosition)) {
  12665. updateDelta++;
  12666. currentPosition.col += 1;
  12667. }
  12668. break;
  12669. case 'left':
  12670. while (isCollapsed(currentPosition)) {
  12671. updateDelta--;
  12672. currentPosition.col -= 1;
  12673. }
  12674. break;
  12675. }
  12676. return updateDelta;
  12677. }
  12678. , updateDelta = function (delta, nextPosition) {
  12679. if (delta.row > 0) { // moving down
  12680. if (isCollapsed(nextPosition)) {
  12681. delta.row += nextVisible('down', nextPosition);
  12682. }
  12683. } else if (delta.row < 0) { // moving up
  12684. if (isCollapsed(nextPosition)) {
  12685. delta.row += nextVisible('up', nextPosition);
  12686. }
  12687. }
  12688. if (delta.col > 0) { // moving right
  12689. if (isCollapsed(nextPosition)) {
  12690. delta.col += nextVisible('right', nextPosition);
  12691. }
  12692. } else if (delta.col < 0) { // moving left
  12693. if (isCollapsed(nextPosition)) {
  12694. delta.col += nextVisible('left', nextPosition);
  12695. }
  12696. }
  12697. };
  12698. switch (position) {
  12699. case 'start':
  12700. return function (delta) {
  12701. currentlySelected = instance.getSelected();
  12702. nextPosition.row = currentlySelected[0] + delta.row;
  12703. nextPosition.col = currentlySelected[1] + delta.col;
  12704. updateDelta(delta, nextPosition);
  12705. };
  12706. break;
  12707. case 'end':
  12708. return function (delta) {
  12709. currentlySelected = instance.getSelected();
  12710. nextPosition.row = currentlySelected[2] + delta.row;
  12711. nextPosition.col = currentlySelected[3] + delta.col;
  12712. updateDelta(delta, nextPosition);
  12713. };
  12714. break;
  12715. }
  12716. },
  12717. modifyRowHeight: function (height, row) {
  12718. if (instance.view.wt.wtTable.rowFilter && isCollapsed({row: row, col: null})) {
  12719. return 0;
  12720. }
  12721. },
  12722. validateGroups: function () {
  12723. var areRangesOverlapping = function (a, b) {
  12724. if ((a[0] < b[0] && a[1] < b[1] && b[0] <= a[1])
  12725. || (a[0] > b[0] && b[1] < a[1] && a[0] <= b[1])) {
  12726. return true;
  12727. }
  12728. };
  12729. var configGroups = instance.getSettings().groups
  12730. , cols = []
  12731. , rows = [];
  12732. for (var i = 0, groupsLength = configGroups.length; i < groupsLength; i++) {
  12733. if (configGroups[i].rows) {
  12734. if(configGroups[i].rows.length === 1) { // single-entry group
  12735. throw new Error("Grouping error: Group {" + configGroups[i].rows[0] + "} is invalid. Cannot define single-entry groups.");
  12736. return false;
  12737. } else if(configGroups[i].rows.length === 0) {
  12738. throw new Error("Grouping error: Cannot define empty groups.");
  12739. return false;
  12740. }
  12741. rows.push(configGroups[i].rows);
  12742. for (var j = 0, rowsLength = rows.length; j < rowsLength; j++) {
  12743. if (areRangesOverlapping(configGroups[i].rows, rows[j])) {
  12744. throw new Error("Grouping error: ranges {" + configGroups[i].rows[0] + ", " + configGroups[i].rows[1] + "} and {" + rows[j][0] + ", " + rows[j][1] + "} are overlapping.");
  12745. return false;
  12746. }
  12747. }
  12748. } else if (configGroups[i].cols) {
  12749. if(configGroups[i].cols.length === 1) { // single-entry group
  12750. throw new Error("Grouping error: Group {" + configGroups[i].cols[0] + "} is invalid. Cannot define single-entry groups.");
  12751. return false;
  12752. } else if(configGroups[i].cols.length === 0) {
  12753. throw new Error("Grouping error: Cannot define empty groups.");
  12754. return false;
  12755. }
  12756. cols.push(configGroups[i].cols);
  12757. for (var j = 0, colsLength = cols.length; j < colsLength; j++) {
  12758. if (areRangesOverlapping(configGroups[i].cols, cols[j])) {
  12759. throw new Error("Grouping error: ranges {" + configGroups[i].cols[0] + ", " + configGroups[i].cols[1] + "} and {" + cols[j][0] + ", " + cols[j][1] + "} are overlapping.");
  12760. return false;
  12761. }
  12762. }
  12763. }
  12764. }
  12765. return true;
  12766. },
  12767. afterGetRowHeaderRenderers: function (arr) {
  12768. Handsontable.Grouping.groupIndicatorsFactory(arr, 'vertical');
  12769. },
  12770. afterGetColumnHeaderRenderers: function (arr) {
  12771. Handsontable.Grouping.groupIndicatorsFactory(arr, 'horizontal');
  12772. },
  12773. hookProxy: function (fn, arg) {
  12774. return function () {
  12775. if (instance.getSettings().groups) {
  12776. return arg ? Handsontable.Grouping[fn](arg).apply(this, arguments) : Handsontable.Grouping[fn].apply(this, arguments);
  12777. } else {
  12778. return void 0;
  12779. }
  12780. };
  12781. }
  12782. }
  12783. };
  12784. /**
  12785. * create new instance
  12786. */
  12787. var init = function () {
  12788. var instance = this,
  12789. groupingSetting = !!(instance.getSettings().groups);
  12790. if (groupingSetting) {
  12791. var headerUpdates = {};
  12792. Handsontable.Grouping = new Grouping(instance);
  12793. if (!instance.getSettings().rowHeaders) { // force using rowHeaders -- needs to be changed later
  12794. headerUpdates.rowHeaders = true;
  12795. }
  12796. if (!instance.getSettings().colHeaders) { // force using colHeaders -- needs to be changed later
  12797. headerUpdates.colHeaders = true;
  12798. }
  12799. if (headerUpdates.colHeaders || headerUpdates.rowHeaders) {
  12800. instance.updateSettings(headerUpdates);
  12801. }
  12802. var groupConfigValid = Handsontable.Grouping.validateGroups();
  12803. if (!groupConfigValid) {
  12804. return;
  12805. }
  12806. instance.addHook('beforeInit', Handsontable.Grouping.hookProxy('init'));
  12807. instance.addHook('afterUpdateSettings', Handsontable.Grouping.hookProxy('updateGroups'));
  12808. instance.addHook('afterGetColumnHeaderRenderers', Handsontable.Grouping.hookProxy('afterGetColumnHeaderRenderers'));
  12809. instance.addHook('afterGetRowHeaderRenderers', Handsontable.Grouping.hookProxy('afterGetRowHeaderRenderers'));
  12810. instance.addHook('afterGetRowHeader', Handsontable.Grouping.hookProxy('afterGetRowHeader'));
  12811. instance.addHook('afterGetColHeader', Handsontable.Grouping.hookProxy('afterGetColHeader'));
  12812. instance.addHook('beforeOnCellMouseDown', Handsontable.Grouping.hookProxy('toggleGroupVisibility'));
  12813. instance.addHook('modifyTransformStart', Handsontable.Grouping.hookProxy('modifySelectionFactory', 'start'));
  12814. instance.addHook('modifyTransformEnd', Handsontable.Grouping.hookProxy('modifySelectionFactory', 'end'));
  12815. instance.addHook('modifyRowHeight', Handsontable.Grouping.hookProxy('modifyRowHeight'));
  12816. }
  12817. };
  12818. /**
  12819. * Update headers widths for the group indicators
  12820. */
  12821. // TODO: this needs cleaning up
  12822. var updateHeaderWidths = function () {
  12823. var colgroups = document.querySelectorAll('colgroup');
  12824. for (var i = 0, colgroupsLength = colgroups.length; i < colgroupsLength; i++) {
  12825. var rowHeaders = colgroups[i].querySelectorAll('col.rowHeader');
  12826. if (rowHeaders.length == 0) {
  12827. return;
  12828. }
  12829. for (var j = 0, rowHeadersLength = rowHeaders.length + 1; j < rowHeadersLength; j++) {
  12830. if (rowHeadersLength == 2) {
  12831. return;
  12832. }
  12833. if (j < Handsontable.Grouping.getLevels().rows + 1) {
  12834. if (j == Handsontable.Grouping.getLevels().rows) {
  12835. Handsontable.Dom.addClass(rowHeaders[j], 'htGroupColClosest');
  12836. } else {
  12837. Handsontable.Dom.addClass(rowHeaders[j], 'htGroupCol');
  12838. }
  12839. }
  12840. }
  12841. }
  12842. };
  12843. Handsontable.hooks.add('beforeInit', init);
  12844. Handsontable.hooks.add('afterUpdateSettings', function () {
  12845. if (this.getSettings().groups && !Handsontable.Grouping) {
  12846. init.call(this, arguments);
  12847. } else if (!this.getSettings().groups && Handsontable.Grouping) {
  12848. Handsontable.Grouping.resetGroups();
  12849. Handsontable.Grouping = void 0;
  12850. }
  12851. });
  12852. Handsontable.plugins.Grouping = Grouping;
  12853. (function (Handsontable) {
  12854. /**
  12855. * Plugin used to allow user to copy and paste from the context menu
  12856. * Currently uses ZeroClipboard due to browser limitations
  12857. * @constructor
  12858. */
  12859. function ContextMenuCopyPaste() {
  12860. this.zeroClipboardInstance = null;
  12861. this.instance = null;
  12862. }
  12863. /**
  12864. * Configure ZeroClipboard
  12865. */
  12866. ContextMenuCopyPaste.prototype.prepareZeroClipboard = function () {
  12867. if(this.swfPath) {
  12868. ZeroClipboard.config({
  12869. swfPath: this.swfPath
  12870. });
  12871. }
  12872. };
  12873. /**
  12874. * Copy action
  12875. * @returns {CopyPasteClass.elTextarea.value|*}
  12876. */
  12877. ContextMenuCopyPaste.prototype.copy = function () {
  12878. this.instance.copyPaste.setCopyableText();
  12879. return this.instance.copyPaste.copyPasteInstance.elTextarea.value;
  12880. };
  12881. /**
  12882. * Adds copy/paste items to context menu
  12883. */
  12884. ContextMenuCopyPaste.prototype.addToContextMenu = function (defaultOptions) {
  12885. if (!this.getSettings().contextMenuCopyPaste) {
  12886. return;
  12887. }
  12888. defaultOptions.items.unshift(
  12889. {
  12890. key: 'copy',
  12891. name: 'Copy'
  12892. },
  12893. {
  12894. key: 'paste',
  12895. name: 'Paste',
  12896. callback: function () {
  12897. this.copyPaste.triggerPaste();
  12898. }
  12899. },
  12900. Handsontable.ContextMenu.SEPARATOR
  12901. );
  12902. };
  12903. /**
  12904. * Setup ZeroClipboard swf clip position and event handlers
  12905. * @param cmInstance Current context menu instance
  12906. */
  12907. ContextMenuCopyPaste.prototype.setupZeroClipboard = function (cmInstance) {
  12908. var plugin = this;
  12909. this.cmInstance = cmInstance;
  12910. if (!Handsontable.Dom.hasClass(this.cmInstance.rootElement, 'htContextMenu')) {
  12911. return;
  12912. }
  12913. var data = cmInstance.getData();
  12914. for (var i = 0, ilen = data.length; i < ilen; i++) { //find position of 'copy' option
  12915. if (data[i].key === 'copy') {
  12916. this.zeroClipboardInstance = new ZeroClipboard(cmInstance.getCell(i, 0));
  12917. this.zeroClipboardInstance.off();
  12918. this.zeroClipboardInstance.on("copy", function (event) {
  12919. var clipboard = event.clipboardData;
  12920. clipboard.setData("text/plain", plugin.copy());
  12921. plugin.instance.getSettings().outsideClickDeselects = plugin.outsideClickDeselectsCache;
  12922. });
  12923. cmCopyPaste.bindEvents();
  12924. break;
  12925. }
  12926. }
  12927. };
  12928. /**
  12929. * Bind all the standard events
  12930. */
  12931. ContextMenuCopyPaste.prototype.bindEvents = function () {
  12932. var plugin = this;
  12933. // Workaround for 'current' and 'zeroclipboard-is-hover' classes being stuck when moving the cursor over the context menu
  12934. if (plugin.cmInstance) {
  12935. var eventManager = new Handsontable.eventManager(this.instance);
  12936. var removeCurrenClass = function (event) {
  12937. var hadClass = plugin.cmInstance.rootElement.querySelector('td.current');
  12938. if (hadClass) {
  12939. Handsontable.Dom.removeClass(hadClass, 'current');
  12940. }
  12941. plugin.outsideClickDeselectsCache = plugin.instance.getSettings().outsideClickDeselects;
  12942. plugin.instance.getSettings().outsideClickDeselects = false;
  12943. };
  12944. var removeZeroClipboardClass = function (event) {
  12945. var hadClass = plugin.cmInstance.rootElement.querySelector('td.zeroclipboard-is-hover');
  12946. if (hadClass) {
  12947. Handsontable.Dom.removeClass(hadClass, 'zeroclipboard-is-hover');
  12948. }
  12949. plugin.instance.getSettings().outsideClickDeselects = plugin.outsideClickDeselectsCache;
  12950. };
  12951. eventManager.removeEventListener(document,'mouseenter', function () {
  12952. removeCurrenClass();
  12953. });
  12954. eventManager.addEventListener(document, 'mouseenter', function (e) {
  12955. removeCurrenClass();
  12956. });
  12957. eventManager.removeEventListener(document,'mouseleave', function () {
  12958. removeZeroClipboardClass();
  12959. });
  12960. eventManager.addEventListener(document, 'mouseleave', function (e) {
  12961. removeZeroClipboardClass();
  12962. });
  12963. }
  12964. };
  12965. /**
  12966. * Initialize plugin
  12967. * @returns {boolean} Returns false if ZeroClipboard is not properly included
  12968. */
  12969. ContextMenuCopyPaste.prototype.init = function () {
  12970. if (!this.getSettings().contextMenuCopyPaste) {
  12971. return;
  12972. } else if (typeof this.getSettings().contextMenuCopyPaste == "object") {
  12973. cmCopyPaste.swfPath = this.getSettings().contextMenuCopyPaste.swfPath;
  12974. }
  12975. if (typeof ZeroClipboard === 'undefined') {
  12976. throw new Error("To be able to use the Copy/Paste feature from the context menu, you need to manualy include ZeroClipboard.js file to your website.");
  12977. return false;
  12978. }
  12979. try {
  12980. var flashTest = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
  12981. } catch(exception) {
  12982. if(!('undefined' != typeof navigator.mimeTypes['application/x-shockwave-flash'])) {
  12983. throw new Error("To be able to use the Copy/Paste feature from the context menu, your browser needs to have Flash Plugin installed.");
  12984. return false;
  12985. }
  12986. }
  12987. cmCopyPaste.instance = this;
  12988. cmCopyPaste.prepareZeroClipboard();
  12989. };
  12990. var cmCopyPaste = new ContextMenuCopyPaste();
  12991. Handsontable.hooks.add('afterRender', function () {
  12992. cmCopyPaste.setupZeroClipboard(this);
  12993. });
  12994. Handsontable.hooks.add('afterInit', cmCopyPaste.init);
  12995. Handsontable.hooks.add('afterContextMenuDefaultOptions', cmCopyPaste.addToContextMenu);
  12996. Handsontable.ContextMenuCopyPaste = ContextMenuCopyPaste;
  12997. })(Handsontable);
  12998. (function (Handsontable) {
  12999. 'use strict';
  13000. function MultipleSelectionHandles(instance) {
  13001. this.instance = instance;
  13002. this.dragged = [];
  13003. this.eventManager = Handsontable.eventManager(instance);
  13004. this.bindTouchEvents();
  13005. }
  13006. MultipleSelectionHandles.prototype.getCurrentRangeCoords = function (selectedRange, currentTouch, touchStartDirection, currentDirection, draggedHandle) {
  13007. var topLeftCorner = selectedRange.getTopLeftCorner()
  13008. , bottomRightCorner = selectedRange.getBottomRightCorner()
  13009. , bottomLeftCorner = selectedRange.getBottomLeftCorner()
  13010. , topRightCorner = selectedRange.getTopRightCorner();
  13011. var newCoords = {
  13012. start: null,
  13013. end: null
  13014. };
  13015. switch (touchStartDirection) {
  13016. case "NE-SW":
  13017. switch (currentDirection) {
  13018. case "NE-SW":
  13019. case "NW-SE":
  13020. if (draggedHandle == "topLeft") {
  13021. newCoords = {
  13022. start: new WalkontableCellCoords(currentTouch.row, selectedRange.highlight.col),
  13023. end: new WalkontableCellCoords(bottomLeftCorner.row, currentTouch.col)
  13024. };
  13025. } else {
  13026. newCoords = {
  13027. start: new WalkontableCellCoords(selectedRange.highlight.row, currentTouch.col),
  13028. end: new WalkontableCellCoords(currentTouch.row, topLeftCorner.col)
  13029. };
  13030. }
  13031. break;
  13032. case "SE-NW":
  13033. if (draggedHandle == "bottomRight") {
  13034. newCoords = {
  13035. start: new WalkontableCellCoords(bottomRightCorner.row, currentTouch.col),
  13036. end: new WalkontableCellCoords(currentTouch.row, topLeftCorner.col)
  13037. };
  13038. }
  13039. break;
  13040. //case "SW-NE":
  13041. // break;
  13042. }
  13043. break;
  13044. case "NW-SE":
  13045. switch (currentDirection) {
  13046. case "NE-SW":
  13047. if (draggedHandle == "topLeft") {
  13048. newCoords = {
  13049. start: currentTouch,
  13050. end: bottomLeftCorner
  13051. };
  13052. } else {
  13053. newCoords.end = currentTouch;
  13054. }
  13055. break;
  13056. case "NW-SE":
  13057. if (draggedHandle == "topLeft") {
  13058. newCoords = {
  13059. start: currentTouch,
  13060. end: bottomRightCorner
  13061. };
  13062. } else {
  13063. newCoords.end = currentTouch;
  13064. }
  13065. break;
  13066. case "SE-NW":
  13067. if (draggedHandle == "topLeft") {
  13068. newCoords = {
  13069. start: currentTouch,
  13070. end: topLeftCorner
  13071. };
  13072. } else {
  13073. newCoords.end = currentTouch;
  13074. }
  13075. break;
  13076. case "SW-NE":
  13077. if (draggedHandle == "topLeft") {
  13078. newCoords = {
  13079. start: currentTouch,
  13080. end: topRightCorner
  13081. };
  13082. } else {
  13083. newCoords.end = currentTouch;
  13084. }
  13085. break;
  13086. }
  13087. break;
  13088. case "SW-NE":
  13089. switch (currentDirection) {
  13090. case "NW-SE":
  13091. if (draggedHandle == "bottomRight") {
  13092. newCoords = {
  13093. start: new WalkontableCellCoords(currentTouch.row, topLeftCorner.col),
  13094. end: new WalkontableCellCoords(bottomLeftCorner.row, currentTouch.col)
  13095. };
  13096. } else {
  13097. newCoords = {
  13098. start: new WalkontableCellCoords(topLeftCorner.row, currentTouch.col),
  13099. end: new WalkontableCellCoords(currentTouch.row, bottomRightCorner.col)
  13100. };
  13101. }
  13102. break;
  13103. //case "NE-SW":
  13104. //
  13105. // break;
  13106. case "SW-NE":
  13107. if (draggedHandle == "topLeft") {
  13108. newCoords = {
  13109. start: new WalkontableCellCoords(selectedRange.highlight.row, currentTouch.col),
  13110. end: new WalkontableCellCoords(currentTouch.row, bottomRightCorner.col)
  13111. };
  13112. } else {
  13113. newCoords = {
  13114. start: new WalkontableCellCoords(currentTouch.row, topLeftCorner.col),
  13115. end: new WalkontableCellCoords(topLeftCorner.row, currentTouch.col)
  13116. };
  13117. }
  13118. break;
  13119. case "SE-NW":
  13120. if (draggedHandle == "bottomRight") {
  13121. newCoords = {
  13122. start: new WalkontableCellCoords(currentTouch.row, topRightCorner.col),
  13123. end: new WalkontableCellCoords(topLeftCorner.row, currentTouch.col)
  13124. };
  13125. } else if (draggedHandle == "topLeft") {
  13126. newCoords = {
  13127. start: bottomLeftCorner,
  13128. end: currentTouch
  13129. };
  13130. }
  13131. break;
  13132. }
  13133. break;
  13134. case "SE-NW":
  13135. switch (currentDirection) {
  13136. case "NW-SE":
  13137. case "NE-SW":
  13138. case "SW-NE":
  13139. if (draggedHandle == "topLeft") {
  13140. newCoords.end = currentTouch;
  13141. }
  13142. break;
  13143. case "SE-NW":
  13144. if (draggedHandle == "topLeft") {
  13145. newCoords.end = currentTouch;
  13146. } else {
  13147. newCoords = {
  13148. start: currentTouch,
  13149. end: topLeftCorner
  13150. };
  13151. }
  13152. break;
  13153. }
  13154. break;
  13155. }
  13156. return newCoords;
  13157. };
  13158. MultipleSelectionHandles.prototype.bindTouchEvents = function () {
  13159. var that = this;
  13160. var removeFromDragged = function (query) {
  13161. if (this.dragged.length == 1) {
  13162. this.dragged = [];
  13163. return true;
  13164. }
  13165. var entryPosition = this.dragged.indexOf(query);
  13166. if (entryPosition == -1) {
  13167. return false;
  13168. } else if (entryPosition == 0) {
  13169. this.dragged = this.dragged.slice(0, 1);
  13170. } else if (entryPosition == 1) {
  13171. this.dragged = this.dragged.slice(-1);
  13172. }
  13173. };
  13174. this.eventManager.addEventListener(this.instance.rootElement,'touchstart', function (event) {
  13175. if(Handsontable.Dom.hasClass(event.target, "topLeftSelectionHandle-HitArea")) {
  13176. that.dragged.push("topLeft");
  13177. var selectedRange = that.instance.getSelectedRange();
  13178. that.touchStartRange = {
  13179. width: selectedRange.getWidth(),
  13180. height: selectedRange.getHeight(),
  13181. direction: selectedRange.getDirection()
  13182. };
  13183. event.preventDefault();
  13184. return false;
  13185. } else if (Handsontable.Dom.hasClass(event.target, "bottomRightSelectionHandle-HitArea")) {
  13186. that.dragged.push("bottomRight");
  13187. var selectedRange = that.instance.getSelectedRange();
  13188. that.touchStartRange = {
  13189. width: selectedRange.getWidth(),
  13190. height: selectedRange.getHeight(),
  13191. direction: selectedRange.getDirection()
  13192. };
  13193. event.preventDefault();
  13194. return false;
  13195. }
  13196. });
  13197. this.eventManager.addEventListener(this.instance.rootElement,'touchend', function (event) {
  13198. if(Handsontable.Dom.hasClass(event.target, "topLeftSelectionHandle-HitArea")) {
  13199. removeFromDragged.call(that, "topLeft");
  13200. that.touchStartRange = void 0;
  13201. event.preventDefault();
  13202. return false;
  13203. } else if (Handsontable.Dom.hasClass(event.target, "bottomRightSelectionHandle-HitArea")) {
  13204. removeFromDragged.call(that, "bottomRight");
  13205. that.touchStartRange = void 0;
  13206. event.preventDefault();
  13207. return false;
  13208. }
  13209. });
  13210. this.eventManager.addEventListener(this.instance.rootElement,'touchmove', function (event) {
  13211. var scrollTop = Handsontable.Dom.getWindowScrollTop()
  13212. , scrollLeft = Handsontable.Dom.getWindowScrollLeft();
  13213. if (that.dragged.length > 0) {
  13214. var endTarget = document.elementFromPoint(
  13215. event.touches[0].screenX - scrollLeft,
  13216. event.touches[0].screenY - scrollTop
  13217. );
  13218. if(!endTarget) {
  13219. return;
  13220. }
  13221. if (endTarget.nodeName == "TD" || endTarget.nodeName == "TH") {
  13222. var targetCoords = that.instance.getCoords(endTarget);
  13223. if(targetCoords.col == -1) {
  13224. targetCoords.col = 0;
  13225. }
  13226. var selectedRange = that.instance.getSelectedRange()
  13227. , rangeWidth = selectedRange.getWidth()
  13228. , rangeHeight = selectedRange.getHeight()
  13229. , rangeDirection = selectedRange.getDirection();
  13230. if (rangeWidth == 1 && rangeHeight == 1) {
  13231. that.instance.selection.setRangeEnd(targetCoords);
  13232. }
  13233. var newRangeCoords = that.getCurrentRangeCoords(selectedRange, targetCoords, that.touchStartRange.direction, rangeDirection, that.dragged[0]);
  13234. if(newRangeCoords.start != null) {
  13235. that.instance.selection.setRangeStart(newRangeCoords.start);
  13236. }
  13237. that.instance.selection.setRangeEnd(newRangeCoords.end);
  13238. }
  13239. event.preventDefault();
  13240. }
  13241. });
  13242. };
  13243. MultipleSelectionHandles.prototype.isDragged = function () {
  13244. if (this.dragged.length == 0) {
  13245. return false;
  13246. } else {
  13247. return true;
  13248. }
  13249. };
  13250. var init = function () {
  13251. var instance = this;
  13252. Handsontable.plugins.multipleSelectionHandles = new MultipleSelectionHandles(instance);
  13253. };
  13254. Handsontable.hooks.add('afterInit', init);
  13255. })(Handsontable);
  13256. var TouchScroll = (function(instance) {
  13257. function TouchScroll(instance) {}
  13258. TouchScroll.prototype.init = function(instance) {
  13259. this.instance = instance;
  13260. this.bindEvents();
  13261. this.scrollbars = [
  13262. this.instance.view.wt.wtScrollbars.vertical,
  13263. this.instance.view.wt.wtScrollbars.horizontal,
  13264. this.instance.view.wt.wtScrollbars.corner
  13265. ]
  13266. this.clones = [
  13267. this.instance.view.wt.wtScrollbars.vertical.clone.wtTable.holder.parentNode,
  13268. this.instance.view.wt.wtScrollbars.horizontal.clone.wtTable.holder.parentNode,
  13269. this.instance.view.wt.wtScrollbars.corner.clone.wtTable.holder.parentNode
  13270. ]
  13271. };
  13272. TouchScroll.prototype.bindEvents = function () {
  13273. var that = this;
  13274. this.instance.addHook('beforeTouchScroll', function () {
  13275. Handsontable.freezeOverlays = true;
  13276. for(var i = 0, cloneCount = that.clones.length; i < cloneCount ; i++) {
  13277. Handsontable.Dom.addClass(that.clones[i], 'hide-tween');
  13278. }
  13279. });
  13280. this.instance.addHook('afterMomentumScroll', function () {
  13281. Handsontable.freezeOverlays = false;
  13282. for(var i = 0, cloneCount = that.clones.length; i < cloneCount ; i++) {
  13283. Handsontable.Dom.removeClass(that.clones[i], 'hide-tween');
  13284. }
  13285. for(var i = 0, cloneCount = that.clones.length; i < cloneCount ; i++) {
  13286. Handsontable.Dom.addClass(that.clones[i], 'show-tween');
  13287. }
  13288. setTimeout(function () {
  13289. for(var i = 0, cloneCount = that.clones.length; i < cloneCount ; i++) {
  13290. Handsontable.Dom.removeClass(that.clones[i], 'show-tween');
  13291. }
  13292. },400);
  13293. for(var i = 0, cloneCount = that.scrollbars.length; i < cloneCount ; i++) {
  13294. that.scrollbars[i].refresh();
  13295. that.scrollbars[i].resetFixedPosition();
  13296. }
  13297. });
  13298. };
  13299. return TouchScroll;
  13300. }());
  13301. var touchScrollHandler = new TouchScroll();
  13302. Handsontable.hooks.add('afterInit', function() {
  13303. touchScrollHandler.init.call(touchScrollHandler, this);
  13304. });
  13305. /**
  13306. * Creates an overlay over the original Walkontable instance. The overlay renders the clone of the original Walkontable
  13307. * and (optionally) implements behavior needed for native horizontal and vertical scrolling
  13308. */
  13309. function WalkontableOverlay() {}
  13310. /*
  13311. Possible optimizations:
  13312. [x] don't rerender if scroll delta is smaller than the fragment outside of the viewport
  13313. [ ] move .style.top change before .draw()
  13314. [ ] put .draw() in requestAnimationFrame
  13315. [ ] don't rerender rows that remain visible after the scroll
  13316. */
  13317. WalkontableOverlay.prototype.init = function () {
  13318. this.TABLE = this.instance.wtTable.TABLE;
  13319. this.fixed = this.instance.wtTable.hider;
  13320. this.fixedContainer = this.instance.wtTable.holder;
  13321. this.scrollHandler = this.getScrollableElement(this.TABLE);
  13322. };
  13323. WalkontableOverlay.prototype.makeClone = function (direction) {
  13324. var clone = document.createElement('DIV');
  13325. clone.className = 'ht_clone_' + direction + ' handsontable';
  13326. clone.style.position = 'absolute';
  13327. clone.style.top = 0;
  13328. clone.style.left = 0;
  13329. clone.style.overflow = 'hidden';
  13330. var table2 = document.createElement('TABLE');
  13331. table2.className = this.instance.wtTable.TABLE.className;
  13332. clone.appendChild(table2);
  13333. this.instance.wtTable.holder.parentNode.appendChild(clone);
  13334. return new Walkontable({
  13335. cloneSource: this.instance,
  13336. cloneOverlay: this,
  13337. table: table2
  13338. });
  13339. };
  13340. WalkontableOverlay.prototype.getScrollableElement = function (TABLE) {
  13341. var el = TABLE.parentNode;
  13342. while (el && el.style) {
  13343. if (el.style.overflow !== 'visible' && el.style.overflow !== '') {
  13344. return el;
  13345. }
  13346. if (this instanceof WalkontableHorizontalScrollbarNative && el.style.overflowX !== 'visible' && el.style.overflowX !== '') {
  13347. return el;
  13348. }
  13349. el = el.parentNode;
  13350. }
  13351. return window;
  13352. };
  13353. WalkontableOverlay.prototype.refresh = function (fastDraw) {
  13354. this.clone && this.clone.draw(fastDraw);
  13355. };
  13356. WalkontableOverlay.prototype.destroy = function () {
  13357. var eventManager = Handsontable.eventManager(this.clone);
  13358. eventManager.clear();
  13359. };
  13360. function WalkontableBorder(instance, settings) {
  13361. var style;
  13362. var createMultipleSelectorHandles = function () {
  13363. this.selectionHandles = {
  13364. topLeft: document.createElement('DIV'),
  13365. topLeftHitArea: document.createElement('DIV'),
  13366. bottomRight: document.createElement('DIV'),
  13367. bottomRightHitArea: document.createElement('DIV')
  13368. };
  13369. var width = 10
  13370. , hitAreaWidth = 40;
  13371. this.selectionHandles.topLeft.className = 'topLeftSelectionHandle';
  13372. this.selectionHandles.topLeftHitArea.className = 'topLeftSelectionHandle-HitArea';
  13373. this.selectionHandles.bottomRight.className = 'bottomRightSelectionHandle';
  13374. this.selectionHandles.bottomRightHitArea.className = 'bottomRightSelectionHandle-HitArea';
  13375. this.selectionHandles.styles = {
  13376. topLeft: this.selectionHandles.topLeft.style,
  13377. topLeftHitArea: this.selectionHandles.topLeftHitArea.style,
  13378. bottomRight: this.selectionHandles.bottomRight.style,
  13379. bottomRightHitArea: this.selectionHandles.bottomRightHitArea.style
  13380. };
  13381. var hitAreaStyle = {
  13382. 'position': 'absolute',
  13383. 'height': hitAreaWidth + 'px',
  13384. 'width': hitAreaWidth + 'px',
  13385. 'border-radius': parseInt(hitAreaWidth/1.5,10) + 'px'
  13386. };
  13387. for (var prop in hitAreaStyle) {
  13388. this.selectionHandles.styles.bottomRightHitArea[prop] = hitAreaStyle[prop];
  13389. this.selectionHandles.styles.topLeftHitArea[prop] = hitAreaStyle[prop];
  13390. }
  13391. var handleStyle = {
  13392. 'position': 'absolute',
  13393. 'height': width + 'px',
  13394. 'width': width + 'px',
  13395. 'border-radius': parseInt(width/1.5,10) + 'px',
  13396. 'background': '#F5F5FF',
  13397. 'border': '1px solid #4285c8'
  13398. };
  13399. for (var prop in handleStyle) {
  13400. this.selectionHandles.styles.bottomRight[prop] = handleStyle[prop];
  13401. this.selectionHandles.styles.topLeft[prop] = handleStyle[prop];
  13402. }
  13403. this.main.appendChild(this.selectionHandles.topLeft);
  13404. this.main.appendChild(this.selectionHandles.bottomRight);
  13405. this.main.appendChild(this.selectionHandles.topLeftHitArea);
  13406. this.main.appendChild(this.selectionHandles.bottomRightHitArea);
  13407. };
  13408. if(!settings){
  13409. return;
  13410. }
  13411. var eventManager = Handsontable.eventManager(instance);
  13412. //reference to instance
  13413. this.instance = instance;
  13414. this.settings = settings;
  13415. this.main = document.createElement("div");
  13416. style = this.main.style;
  13417. style.position = 'absolute';
  13418. style.top = 0;
  13419. style.left = 0;
  13420. var borderDivs = ['top','left','bottom','right','corner'];
  13421. for (var i = 0; i < 5; i++) {
  13422. var position = borderDivs[i];
  13423. var DIV = document.createElement('DIV');
  13424. DIV.className = 'wtBorder ' + (this.settings.className || ''); // + borderDivs[i];
  13425. if(this.settings[position] && this.settings[position].hide){
  13426. DIV.className += " hidden";
  13427. }
  13428. style = DIV.style;
  13429. style.backgroundColor = (this.settings[position] && this.settings[position].color) ? this.settings[position].color : settings.border.color;
  13430. style.height = (this.settings[position] && this.settings[position].width) ? this.settings[position].width + 'px' : settings.border.width + 'px';
  13431. style.width = (this.settings[position] && this.settings[position].width) ? this.settings[position].width + 'px' : settings.border.width + 'px';
  13432. this.main.appendChild(DIV);
  13433. }
  13434. this.top = this.main.childNodes[0];
  13435. this.left = this.main.childNodes[1];
  13436. this.bottom = this.main.childNodes[2];
  13437. this.right = this.main.childNodes[3];
  13438. this.topStyle = this.top.style;
  13439. this.leftStyle = this.left.style;
  13440. this.bottomStyle = this.bottom.style;
  13441. this.rightStyle = this.right.style;
  13442. this.corner = this.main.childNodes[4];
  13443. this.corner.className += ' corner';
  13444. this.cornerStyle = this.corner.style;
  13445. this.cornerStyle.width = '5px';
  13446. this.cornerStyle.height = '5px';
  13447. this.cornerStyle.border = '2px solid #FFF';
  13448. if(Handsontable.mobileBrowser) {
  13449. createMultipleSelectorHandles.call(this);
  13450. }
  13451. this.disappear();
  13452. if (!instance.wtTable.bordersHolder) {
  13453. instance.wtTable.bordersHolder = document.createElement('div');
  13454. instance.wtTable.bordersHolder.className = 'htBorders';
  13455. instance.wtTable.hider.appendChild(instance.wtTable.bordersHolder);
  13456. }
  13457. instance.wtTable.bordersHolder.insertBefore(this.main, instance.wtTable.bordersHolder.firstChild);
  13458. var down = false;
  13459. eventManager.addEventListener(document.body, 'mousedown', function () {
  13460. down = true;
  13461. });
  13462. eventManager.addEventListener(document.body, 'mouseup', function () {
  13463. down = false
  13464. });
  13465. for (var c = 0, len = this.main.childNodes.length; c < len; c++) {
  13466. eventManager.addEventListener(this.main.childNodes[c], 'mouseenter', function (event) {
  13467. if (!down || !instance.getSetting('hideBorderOnMouseDownOver')) {
  13468. return;
  13469. }
  13470. event.preventDefault();
  13471. event.stopImmediatePropagation();
  13472. var bounds = this.getBoundingClientRect();
  13473. this.style.display = 'none';
  13474. var isOutside = function (event) {
  13475. if (event.clientY < Math.floor(bounds.top)) {
  13476. return true;
  13477. }
  13478. if (event.clientY > Math.ceil(bounds.top + bounds.height)) {
  13479. return true;
  13480. }
  13481. if (event.clientX < Math.floor(bounds.left)) {
  13482. return true;
  13483. }
  13484. if (event.clientX > Math.ceil(bounds.left + bounds.width)) {
  13485. return true;
  13486. }
  13487. };
  13488. var handler = function (event) {
  13489. if (isOutside(event)) {
  13490. eventManager.removeEventListener(document.body, 'mousemove', handler);
  13491. this.style.display = 'block';
  13492. }
  13493. };
  13494. eventManager.addEventListener(document.body, 'mousemove', handler);;
  13495. });
  13496. }
  13497. }
  13498. /**
  13499. * Show border around one or many cells
  13500. * @param {Array} corners
  13501. */
  13502. WalkontableBorder.prototype.appear = function (corners) {
  13503. var isMultiple, fromTD, toTD, fromOffset, toOffset, containerOffset, top, minTop, left, minLeft, height, width;
  13504. if (this.disabled) {
  13505. return;
  13506. }
  13507. var instance = this.instance;
  13508. var fromRow
  13509. , fromColumn
  13510. , toRow
  13511. , toColumn
  13512. , i
  13513. , ilen
  13514. , s;
  13515. var isPartRange = function () {
  13516. if(this.instance.selections.area.cellRange) {
  13517. if(toRow != this.instance.selections.area.cellRange.to.row
  13518. || toColumn != this.instance.selections.area.cellRange.to.col) {
  13519. return true;
  13520. }
  13521. }
  13522. return false;
  13523. };
  13524. var updateMultipleSelectionHandlesPosition = function (top, left, width, height) {
  13525. var handleWidth = parseInt(this.selectionHandles.styles.topLeft.width, 10)
  13526. , hitAreaWidth = parseInt(this.selectionHandles.styles.topLeftHitArea.width, 10);
  13527. this.selectionHandles.styles.topLeft.top = parseInt(top - handleWidth,10) + "px";
  13528. this.selectionHandles.styles.topLeft.left = parseInt(left - handleWidth,10) + "px";
  13529. this.selectionHandles.styles.topLeftHitArea.top = parseInt(top - (hitAreaWidth/4)*3,10) + "px";
  13530. this.selectionHandles.styles.topLeftHitArea.left = parseInt(left - (hitAreaWidth/4)*3,10) + "px";
  13531. this.selectionHandles.styles.bottomRight.top = parseInt(top + height,10) + "px";
  13532. this.selectionHandles.styles.bottomRight.left = parseInt(left + width,10) + "px";
  13533. this.selectionHandles.styles.bottomRightHitArea.top = parseInt(top + height - hitAreaWidth/4,10) + "px";
  13534. this.selectionHandles.styles.bottomRightHitArea.left = parseInt(left + width - hitAreaWidth/4,10) + "px";
  13535. if(this.settings.border.multipleSelectionHandlesVisible && this.settings.border.multipleSelectionHandlesVisible()) {
  13536. this.selectionHandles.styles.topLeft.display = "block";
  13537. this.selectionHandles.styles.topLeftHitArea.display = "block";
  13538. if(!isPartRange.call(this)) {
  13539. this.selectionHandles.styles.bottomRight.display = "block";
  13540. this.selectionHandles.styles.bottomRightHitArea.display = "block";
  13541. } else {
  13542. this.selectionHandles.styles.bottomRight.display = "none";
  13543. this.selectionHandles.styles.bottomRightHitArea.display = "none";
  13544. }
  13545. } else {
  13546. this.selectionHandles.styles.topLeft.display = "none";
  13547. this.selectionHandles.styles.bottomRight.display = "none";
  13548. this.selectionHandles.styles.topLeftHitArea.display = "none";
  13549. this.selectionHandles.styles.bottomRightHitArea.display = "none";
  13550. }
  13551. if(fromRow == this.instance.wtSettings.getSetting('fixedRowsTop') || fromColumn == this.instance.wtSettings.getSetting('fixedColumnsLeft')) {
  13552. this.selectionHandles.styles.topLeft.zIndex = "9999";
  13553. this.selectionHandles.styles.topLeftHitArea.zIndex = "9999";
  13554. } else {
  13555. this.selectionHandles.styles.topLeft.zIndex = "";
  13556. this.selectionHandles.styles.topLeftHitArea.zIndex = "";
  13557. }
  13558. };
  13559. if (instance.cloneOverlay instanceof WalkontableVerticalScrollbarNative || instance.cloneOverlay instanceof WalkontableCornerScrollbarNative) {
  13560. ilen = instance.getSetting('fixedRowsTop');
  13561. }
  13562. else {
  13563. ilen = instance.wtTable.getRenderedRowsCount();
  13564. }
  13565. for (i = 0; i < ilen; i++) {
  13566. s = instance.wtTable.rowFilter.renderedToSource(i);
  13567. if (s >= corners[0] && s <= corners[2]) {
  13568. fromRow = s;
  13569. break;
  13570. }
  13571. }
  13572. for (i = ilen - 1; i >= 0; i--) {
  13573. s = instance.wtTable.rowFilter.renderedToSource(i);
  13574. if (s >= corners[0] && s <= corners[2]) {
  13575. toRow = s;
  13576. break;
  13577. }
  13578. }
  13579. ilen = instance.wtTable.getRenderedColumnsCount();
  13580. for (i = 0; i < ilen; i++) {
  13581. s = instance.wtTable.columnFilter.renderedToSource(i);
  13582. if (s >= corners[1] && s <= corners[3]) {
  13583. fromColumn = s;
  13584. break;
  13585. }
  13586. }
  13587. for (i = ilen - 1; i >= 0; i--) {
  13588. s = instance.wtTable.columnFilter.renderedToSource(i);
  13589. if (s >= corners[1] && s <= corners[3]) {
  13590. toColumn = s;
  13591. break;
  13592. }
  13593. }
  13594. if (fromRow !== void 0 && fromColumn !== void 0) {
  13595. isMultiple = (fromRow !== toRow || fromColumn !== toColumn);
  13596. fromTD = instance.wtTable.getCell(new WalkontableCellCoords(fromRow, fromColumn));
  13597. toTD = isMultiple ? instance.wtTable.getCell(new WalkontableCellCoords(toRow, toColumn)) : fromTD;
  13598. fromOffset = Handsontable.Dom.offset(fromTD);
  13599. toOffset = isMultiple ? Handsontable.Dom.offset(toTD) : fromOffset;
  13600. containerOffset = Handsontable.Dom.offset(instance.wtTable.TABLE);
  13601. minTop = fromOffset.top;
  13602. height = toOffset.top + Handsontable.Dom.outerHeight(toTD) - minTop;
  13603. minLeft = fromOffset.left;
  13604. width = toOffset.left + Handsontable.Dom.outerWidth(toTD) - minLeft;
  13605. top = minTop - containerOffset.top - 1;
  13606. left = minLeft - containerOffset.left - 1;
  13607. var style = Handsontable.Dom.getComputedStyle(fromTD);
  13608. if (parseInt(style['borderTopWidth'], 10) > 0) {
  13609. top += 1;
  13610. height = height > 0 ? height - 1 : 0;
  13611. }
  13612. if (parseInt(style['borderLeftWidth'], 10) > 0) {
  13613. left += 1;
  13614. width = width > 0 ? width - 1 : 0;
  13615. }
  13616. }
  13617. else {
  13618. this.disappear();
  13619. return;
  13620. }
  13621. this.topStyle.top = top + 'px';
  13622. this.topStyle.left = left + 'px';
  13623. this.topStyle.width = width + 'px';
  13624. this.topStyle.display = 'block';
  13625. this.leftStyle.top = top + 'px';
  13626. this.leftStyle.left = left + 'px';
  13627. this.leftStyle.height = height + 'px';
  13628. this.leftStyle.display = 'block';
  13629. var delta = Math.floor(this.settings.border.width / 2);
  13630. this.bottomStyle.top = top + height - delta + 'px';
  13631. this.bottomStyle.left = left + 'px';
  13632. this.bottomStyle.width = width + 'px';
  13633. this.bottomStyle.display = 'block';
  13634. this.rightStyle.top = top + 'px';
  13635. this.rightStyle.left = left + width - delta + 'px';
  13636. this.rightStyle.height = height + 1 + 'px';
  13637. this.rightStyle.display = 'block';
  13638. if (Handsontable.mobileBrowser || (!this.hasSetting(this.settings.border.cornerVisible) || isPartRange.call(this))) {
  13639. this.cornerStyle.display = 'none';
  13640. }
  13641. else {
  13642. this.cornerStyle.top = top + height - 4 + 'px';
  13643. this.cornerStyle.left = left + width - 4 + 'px';
  13644. this.cornerStyle.display = 'block';
  13645. }
  13646. if(Handsontable.mobileBrowser) {
  13647. updateMultipleSelectionHandlesPosition.call(this,top, left, width, height);
  13648. }
  13649. };
  13650. /**
  13651. * Hide border
  13652. */
  13653. WalkontableBorder.prototype.disappear = function () {
  13654. this.topStyle.display = 'none';
  13655. this.leftStyle.display = 'none';
  13656. this.bottomStyle.display = 'none';
  13657. this.rightStyle.display = 'none';
  13658. this.cornerStyle.display = 'none';
  13659. if(Handsontable.mobileBrowser) {
  13660. this.selectionHandles.styles.topLeft.display = 'none';
  13661. this.selectionHandles.styles.bottomRight.display = 'none';
  13662. }
  13663. };
  13664. WalkontableBorder.prototype.hasSetting = function (setting) {
  13665. if (typeof setting === 'function') {
  13666. return setting();
  13667. }
  13668. return !!setting;
  13669. };
  13670. /**
  13671. * WalkontableCellCoords holds cell coordinates (row, column) and few metiod to validate them and retrieve as an array or an object
  13672. * TODO: change interface to WalkontableCellCoords(row, col) everywhere, remove those unnecessary setter and getter functions
  13673. */
  13674. function WalkontableCellCoords(row, col) {
  13675. if (typeof row !== 'undefined' && typeof col !== 'undefined') {
  13676. this.row = row;
  13677. this.col = col;
  13678. }
  13679. else {
  13680. this.row = null;
  13681. this.col = null;
  13682. }
  13683. }
  13684. /**
  13685. * Returns boolean information if given set of coordinates is valid in context of a given Walkontable instance
  13686. * @param instance
  13687. * @returns {boolean}
  13688. */
  13689. WalkontableCellCoords.prototype.isValid = function (instance) {
  13690. //is it a valid cell index (0 or higher)
  13691. if (this.row < 0 || this.col < 0) {
  13692. return false;
  13693. }
  13694. //is selection within total rows and columns
  13695. if (this.row >= instance.getSetting('totalRows') || this.col >= instance.getSetting('totalColumns')) {
  13696. return false;
  13697. }
  13698. return true;
  13699. };
  13700. /**
  13701. * Returns boolean information if this cell coords are the same as cell coords given as a parameter
  13702. * @param {WalkontableCellCoords} cellCoords
  13703. * @returns {boolean}
  13704. */
  13705. WalkontableCellCoords.prototype.isEqual = function (cellCoords) {
  13706. if (cellCoords === this) {
  13707. return true;
  13708. }
  13709. return (this.row === cellCoords.row && this.col === cellCoords.col);
  13710. };
  13711. WalkontableCellCoords.prototype.isSouthEastOf = function (testedCoords) {
  13712. return this.row >= testedCoords.row && this.col >= testedCoords.col;
  13713. };
  13714. WalkontableCellCoords.prototype.isNorthWestOf = function (testedCoords) {
  13715. return this.row <= testedCoords.row && this.col <= testedCoords.col;
  13716. };
  13717. WalkontableCellCoords.prototype.isSouthWestOf = function (testedCoords) {
  13718. return this.row >= testedCoords.row && this.col <= testedCoords.col;
  13719. };
  13720. WalkontableCellCoords.prototype.isNorthEastOf = function (testedCoords) {
  13721. return this.row <= testedCoords.row && this.col >= testedCoords.col;
  13722. };
  13723. window.WalkontableCellCoords = WalkontableCellCoords; //export
  13724. /**
  13725. * A cell range is a set of exactly two WalkontableCellCoords (that can be the same or different)
  13726. */
  13727. function WalkontableCellRange(highlight, from, to) {
  13728. this.highlight = highlight; //this property is used to draw bold border around a cell where selection was started and to edit the cell when you press Enter
  13729. this.from = from; //this property is usually the same as highlight, but in Excel there is distinction - one can change highlight within a selection
  13730. this.to = to;
  13731. }
  13732. WalkontableCellRange.prototype.isValid = function (instance) {
  13733. return (this.from.isValid(instance) && this.to.isValid(instance));
  13734. };
  13735. WalkontableCellRange.prototype.isSingle = function () {
  13736. return (this.from.row === this.to.row && this.from.col === this.to.col);
  13737. };
  13738. /**
  13739. * Returns selected range height (in number of rows)
  13740. * @returns {number}
  13741. */
  13742. WalkontableCellRange.prototype.getHeight = function () {
  13743. return Math.max(this.from.row, this.to.row) - Math.min(this.from.row, this.to.row) + 1;
  13744. };
  13745. /**
  13746. * Returns selected range width (in number of columns)
  13747. * @returns {number}
  13748. */
  13749. WalkontableCellRange.prototype.getWidth = function () {
  13750. return Math.max(this.from.col, this.to.col) - Math.min(this.from.col, this.to.col) + 1;
  13751. };
  13752. /**
  13753. * Returns boolean information if given cell coords is within `from` and `to` cell coords of this range
  13754. * @param {WalkontableCellCoords} cellCoords
  13755. * @returns {boolean}
  13756. */
  13757. WalkontableCellRange.prototype.includes = function (cellCoords) {
  13758. var topLeft = this.getTopLeftCorner();
  13759. var bottomRight = this.getBottomRightCorner();
  13760. if (cellCoords.row < 0) {
  13761. cellCoords.row = 0;
  13762. }
  13763. if (cellCoords.col < 0) {
  13764. cellCoords.col = 0;
  13765. }
  13766. return (topLeft.row <= cellCoords.row && bottomRight.row >= cellCoords.row && topLeft.col <= cellCoords.col && bottomRight.col >= cellCoords.col);
  13767. };
  13768. WalkontableCellRange.prototype.includesRange = function (testedRange) {
  13769. return this.includes(testedRange.getTopLeftCorner()) && this.includes(testedRange.getBottomRightCorner());
  13770. };
  13771. WalkontableCellRange.prototype.isEqual = function (testedRange) {
  13772. return (Math.min(this.from.row, this.to.row) == Math.min(testedRange.from.row, testedRange.to.row))
  13773. && (Math.max(this.from.row, this.to.row) == Math.max(testedRange.from.row, testedRange.to.row))
  13774. && (Math.min(this.from.col, this.to.col) == Math.min(testedRange.from.col, testedRange.to.col))
  13775. && (Math.max(this.from.col, this.to.col) == Math.max(testedRange.from.col, testedRange.to.col));
  13776. };
  13777. /**
  13778. * Returns true if tested range overlaps with the range.
  13779. * Range A is considered to to be overlapping with range B if intersection of A and B or B and A is not empty.
  13780. * @param testedRange
  13781. * @returns {boolean}
  13782. */
  13783. WalkontableCellRange.prototype.overlaps = function (testedRange) {
  13784. return testedRange.isSouthEastOf(this.getTopLeftCorner()) && testedRange.isNorthWestOf(this.getBottomRightCorner());
  13785. };
  13786. WalkontableCellRange.prototype.isSouthEastOf = function (testedCoords) {
  13787. return this.getTopLeftCorner().isSouthEastOf(testedCoords) || this.getBottomRightCorner().isSouthEastOf(testedCoords);
  13788. };
  13789. WalkontableCellRange.prototype.isNorthWestOf = function (testedCoords) {
  13790. return this.getTopLeftCorner().isNorthWestOf(testedCoords) || this.getBottomRightCorner().isNorthWestOf(testedCoords);
  13791. };
  13792. /**
  13793. * Adds a cell to a range (only if exceeds corners of the range). Returns information if range was expanded
  13794. * @param {WalkontableCellCoords} cellCoords
  13795. * @returns {boolean}
  13796. */
  13797. WalkontableCellRange.prototype.expand = function (cellCoords) {
  13798. var topLeft = this.getTopLeftCorner();
  13799. var bottomRight = this.getBottomRightCorner();
  13800. if (cellCoords.row < topLeft.row || cellCoords.col < topLeft.col || cellCoords.row > bottomRight.row || cellCoords.col > bottomRight.col) {
  13801. this.from = new WalkontableCellCoords(Math.min(topLeft.row, cellCoords.row), Math.min(topLeft.col, cellCoords.col));
  13802. this.to = new WalkontableCellCoords(Math.max(bottomRight.row, cellCoords.row), Math.max(bottomRight.col, cellCoords.col));
  13803. return true;
  13804. }
  13805. return false;
  13806. };
  13807. WalkontableCellRange.prototype.expandByRange = function (expandingRange) {
  13808. if (this.includesRange(expandingRange) || !this.overlaps(expandingRange)) {
  13809. return false;
  13810. }
  13811. var topLeft = this.getTopLeftCorner()
  13812. , bottomRight = this.getBottomRightCorner()
  13813. , topRight = this.getTopRightCorner()
  13814. , bottomLeft = this.getBottomLeftCorner();
  13815. var expandingTopLeft = expandingRange.getTopLeftCorner();
  13816. var expandingBottomRight = expandingRange.getBottomRightCorner();
  13817. var resultTopRow = Math.min(topLeft.row, expandingTopLeft.row);
  13818. var resultTopCol = Math.min(topLeft.col, expandingTopLeft.col);
  13819. var resultBottomRow = Math.max(bottomRight.row, expandingBottomRight.row);
  13820. var resultBottomCol = Math.max(bottomRight.col, expandingBottomRight.col);
  13821. var finalFrom = new WalkontableCellCoords(resultTopRow, resultTopCol)
  13822. , finalTo = new WalkontableCellCoords(resultBottomRow, resultBottomCol);
  13823. var isCorner = new WalkontableCellRange(finalFrom, finalFrom, finalTo).isCorner(this.from, expandingRange)
  13824. , onlyMerge = expandingRange.isEqual(new WalkontableCellRange(finalFrom, finalFrom, finalTo));
  13825. if (isCorner && !onlyMerge) {
  13826. if (this.from.col > finalFrom.col) {
  13827. finalFrom.col = resultBottomCol;
  13828. finalTo.col = resultTopCol;
  13829. }
  13830. if (this.from.row > finalFrom.row) {
  13831. finalFrom.row = resultBottomRow;
  13832. finalTo.row = resultTopRow;
  13833. }
  13834. }
  13835. this.from = finalFrom;
  13836. this.to = finalTo;
  13837. return true;
  13838. };
  13839. WalkontableCellRange.prototype.getDirection = function () {
  13840. if (this.from.isNorthWestOf(this.to)) { // NorthWest - SouthEast
  13841. return "NW-SE";
  13842. } else if (this.from.isNorthEastOf(this.to)) { // NorthEast - SouthWest
  13843. return "NE-SW";
  13844. } else if (this.from.isSouthEastOf(this.to)) { // SouthEast - NorthWest
  13845. return "SE-NW";
  13846. } else if (this.from.isSouthWestOf(this.to)) { // SouthWest - NorthEast
  13847. return "SW-NE";
  13848. }
  13849. };
  13850. WalkontableCellRange.prototype.setDirection = function (direction) {
  13851. switch (direction) {
  13852. case "NW-SE" :
  13853. this.from = this.getTopLeftCorner();
  13854. this.to = this.getBottomRightCorner();
  13855. break;
  13856. case "NE-SW" :
  13857. this.from = this.getTopRightCorner();
  13858. this.to = this.getBottomLeftCorner();
  13859. break;
  13860. case "SE-NW" :
  13861. this.from = this.getBottomRightCorner();
  13862. this.to = this.getTopLeftCorner();
  13863. break;
  13864. case "SW-NE" :
  13865. this.from = this.getBottomLeftCorner();
  13866. this.to = this.getTopRightCorner();
  13867. break;
  13868. }
  13869. };
  13870. WalkontableCellRange.prototype.getTopLeftCorner = function () {
  13871. return new WalkontableCellCoords(Math.min(this.from.row, this.to.row), Math.min(this.from.col, this.to.col));
  13872. };
  13873. WalkontableCellRange.prototype.getBottomRightCorner = function () {
  13874. return new WalkontableCellCoords(Math.max(this.from.row, this.to.row), Math.max(this.from.col, this.to.col));
  13875. };
  13876. WalkontableCellRange.prototype.getTopRightCorner = function () {
  13877. return new WalkontableCellCoords(Math.min(this.from.row, this.to.row), Math.max(this.from.col, this.to.col));
  13878. };
  13879. WalkontableCellRange.prototype.getBottomLeftCorner = function () {
  13880. return new WalkontableCellCoords(Math.max(this.from.row, this.to.row), Math.min(this.from.col, this.to.col));
  13881. };
  13882. WalkontableCellRange.prototype.isCorner = function (coords, expandedRange) {
  13883. if (expandedRange) {
  13884. if (expandedRange.includes(coords)) {
  13885. if (this.getTopLeftCorner().isEqual(new WalkontableCellCoords(expandedRange.from.row, expandedRange.from.col))
  13886. || this.getTopRightCorner().isEqual(new WalkontableCellCoords(expandedRange.from.row, expandedRange.to.col))
  13887. || this.getBottomLeftCorner().isEqual(new WalkontableCellCoords(expandedRange.to.row, expandedRange.from.col))
  13888. || this.getBottomRightCorner().isEqual(new WalkontableCellCoords(expandedRange.to.row, expandedRange.to.col))) {
  13889. return true;
  13890. }
  13891. }
  13892. }
  13893. return coords.isEqual(this.getTopLeftCorner()) || coords.isEqual(this.getTopRightCorner()) || coords.isEqual(this.getBottomLeftCorner()) || coords.isEqual(this.getBottomRightCorner());
  13894. };
  13895. WalkontableCellRange.prototype.getOppositeCorner = function (coords, expandedRange) {
  13896. if (!(coords instanceof WalkontableCellCoords)) return false;
  13897. if (expandedRange) {
  13898. if (expandedRange.includes(coords)) {
  13899. if (this.getTopLeftCorner().isEqual(new WalkontableCellCoords(expandedRange.from.row, expandedRange.from.col))) return this.getBottomRightCorner();
  13900. if (this.getTopRightCorner().isEqual(new WalkontableCellCoords(expandedRange.from.row, expandedRange.to.col))) return this.getBottomLeftCorner();
  13901. if (this.getBottomLeftCorner().isEqual(new WalkontableCellCoords(expandedRange.to.row, expandedRange.from.col))) return this.getTopRightCorner();
  13902. if (this.getBottomRightCorner().isEqual(new WalkontableCellCoords(expandedRange.to.row, expandedRange.to.col))) return this.getTopLeftCorner();
  13903. }
  13904. }
  13905. if (coords.isEqual(this.getBottomRightCorner())) {
  13906. return this.getTopLeftCorner();
  13907. } else if (coords.isEqual(this.getTopLeftCorner())) {
  13908. return this.getBottomRightCorner();
  13909. } else if (coords.isEqual(this.getTopRightCorner())) {
  13910. return this.getBottomLeftCorner();
  13911. } else if (coords.isEqual(this.getBottomLeftCorner())) {
  13912. return this.getTopRightCorner();
  13913. }
  13914. };
  13915. WalkontableCellRange.prototype.getBordersSharedWith = function (range) {
  13916. if (!this.includesRange(range)) {
  13917. return [];
  13918. }
  13919. var thisBorders = {
  13920. top: Math.min(this.from.row, this.to.row),
  13921. bottom: Math.max(this.from.row, this.to.row),
  13922. left: Math.min(this.from.col, this.to.col),
  13923. right: Math.max(this.from.col, this.to.col)
  13924. }
  13925. , rangeBorders = {
  13926. top: Math.min(range.from.row, range.to.row),
  13927. bottom: Math.max(range.from.row, range.to.row),
  13928. left: Math.min(range.from.col, range.to.col),
  13929. right: Math.max(range.from.col, range.to.col)
  13930. }
  13931. , result = [];
  13932. if (thisBorders.top == rangeBorders.top) {
  13933. result.push('top');
  13934. }
  13935. if (thisBorders.right == rangeBorders.right) {
  13936. result.push('right');
  13937. }
  13938. if (thisBorders.bottom == rangeBorders.bottom) {
  13939. result.push('bottom');
  13940. }
  13941. if (thisBorders.left == rangeBorders.left) {
  13942. result.push('left');
  13943. }
  13944. return result;
  13945. };
  13946. WalkontableCellRange.prototype.getInner = function () {
  13947. var topLeft = this.getTopLeftCorner();
  13948. var bottomRight = this.getBottomRightCorner();
  13949. var out = [];
  13950. for (var r = topLeft.row; r <= bottomRight.row; r++) {
  13951. for (var c = topLeft.col; c <= bottomRight.col; c++) {
  13952. if (!(this.from.row === r && this.from.col === c) && !(this.to.row === r && this.to.col === c)) {
  13953. out.push(new WalkontableCellCoords(r, c));
  13954. }
  13955. }
  13956. }
  13957. return out;
  13958. };
  13959. WalkontableCellRange.prototype.getAll = function () {
  13960. var topLeft = this.getTopLeftCorner();
  13961. var bottomRight = this.getBottomRightCorner();
  13962. var out = [];
  13963. for (var r = topLeft.row; r <= bottomRight.row; r++) {
  13964. for (var c = topLeft.col; c <= bottomRight.col; c++) {
  13965. if (topLeft.row === r && topLeft.col === c) {
  13966. out.push(topLeft);
  13967. }
  13968. else if (bottomRight.row === r && bottomRight.col === c) {
  13969. out.push(bottomRight);
  13970. }
  13971. else {
  13972. out.push(new WalkontableCellCoords(r, c));
  13973. }
  13974. }
  13975. }
  13976. return out;
  13977. };
  13978. /**
  13979. * Runs a callback function against all cells in the range. You can break the iteration by returning false in the callback function
  13980. * @param callback {Function}
  13981. */
  13982. WalkontableCellRange.prototype.forAll = function (callback) {
  13983. var topLeft = this.getTopLeftCorner();
  13984. var bottomRight = this.getBottomRightCorner();
  13985. for (var r = topLeft.row; r <= bottomRight.row; r++) {
  13986. for (var c = topLeft.col; c <= bottomRight.col; c++) {
  13987. var breakIteration = callback(r, c);
  13988. if (breakIteration === false) {
  13989. return;
  13990. }
  13991. }
  13992. }
  13993. };
  13994. window.WalkontableCellRange = WalkontableCellRange; //export
  13995. /**
  13996. * WalkontableColumnFilter
  13997. * @constructor
  13998. */
  13999. function WalkontableColumnFilter(offset,total, countTH) {
  14000. this.offset = offset;
  14001. this.total = total;
  14002. this.countTH = countTH;
  14003. }
  14004. WalkontableColumnFilter.prototype.offsetted = function (n) {
  14005. return n + this.offset;
  14006. };
  14007. WalkontableColumnFilter.prototype.unOffsetted = function (n) {
  14008. return n - this.offset;
  14009. };
  14010. WalkontableColumnFilter.prototype.renderedToSource = function (n) {
  14011. return this.offsetted(n);
  14012. };
  14013. WalkontableColumnFilter.prototype.sourceToRendered = function (n) {
  14014. return this.unOffsetted(n);
  14015. };
  14016. WalkontableColumnFilter.prototype.offsettedTH = function (n) {
  14017. return n - this.countTH;
  14018. };
  14019. WalkontableColumnFilter.prototype.unOffsettedTH = function (n) {
  14020. return n + this.countTH;
  14021. };
  14022. WalkontableColumnFilter.prototype.visibleRowHeadedColumnToSourceColumn = function (n) {
  14023. return this.renderedToSource(this.offsettedTH(n));
  14024. };
  14025. WalkontableColumnFilter.prototype.sourceColumnToVisibleRowHeadedColumn = function (n) {
  14026. return this.unOffsettedTH(this.sourceToRendered(n));
  14027. };
  14028. /**
  14029. * WalkontableColumnStrategy
  14030. * @param containerSizeFn
  14031. * @param sizeAtIndex
  14032. * @param strategy - all, last, none
  14033. * @constructor
  14034. */
  14035. function WalkontableColumnStrategy(instance, containerSizeFn, sizeAtIndex, strategy) {
  14036. var size
  14037. , i = 0;
  14038. this.instance = instance;
  14039. this.containerSizeFn = containerSizeFn;
  14040. this.cellSizesSum = 0;
  14041. this.cellSizes = [];
  14042. this.cellStretch = [];
  14043. this.cellCount = 0;
  14044. this.visibleCellCount = 0;
  14045. this.remainingSize = 0;
  14046. this.strategy = strategy;
  14047. //step 1 - determine cells that fit containerSize and cache their widths
  14048. while (true) {
  14049. size = sizeAtIndex(i);
  14050. if (size === void 0) {
  14051. break; //total columns exceeded
  14052. }
  14053. if (this.cellSizesSum < this.getContainerSize()) {
  14054. this.visibleCellCount++;
  14055. }
  14056. this.cellSizes.push(size);
  14057. this.cellSizesSum += size;
  14058. this.cellCount++;
  14059. i++;
  14060. }
  14061. var containerSize = this.getContainerSize();
  14062. this.remainingSize = this.cellSizesSum - containerSize;
  14063. //negative value means the last cell is fully visible and there is some space left for stretching
  14064. //positive value means the last cell is not fully visible
  14065. }
  14066. WalkontableColumnStrategy.prototype.getContainerSize = function () {
  14067. return typeof this.containerSizeFn === 'function' ? this.containerSizeFn() : this.containerSizeFn;
  14068. };
  14069. WalkontableColumnStrategy.prototype.getSize = function (index) {
  14070. return this.cellSizes[index] + (this.cellStretch[index] || 0);
  14071. };
  14072. WalkontableColumnStrategy.prototype.stretch = function () {
  14073. //step 2 - apply stretching strategy
  14074. var containerSize = this.getContainerSize()
  14075. , i = 0;
  14076. this.remainingSize = this.cellSizesSum - containerSize;
  14077. this.cellStretch.length = 0; //clear previous stretch
  14078. if (this.strategy === 'all') {
  14079. if (this.remainingSize < 0) {
  14080. var ratio = containerSize / this.cellSizesSum;
  14081. var newSize;
  14082. while (i < this.cellCount - 1) { //"i < this.cellCount - 1" is needed because last cellSize is adjusted after the loop
  14083. newSize = Math.floor(ratio * this.cellSizes[i]);
  14084. this.remainingSize += newSize - this.cellSizes[i];
  14085. this.cellStretch[i] = newSize - this.cellSizes[i];
  14086. i++;
  14087. }
  14088. this.cellStretch[this.cellCount - 1] = -this.remainingSize;
  14089. this.remainingSize = 0;
  14090. }
  14091. }
  14092. else if (this.strategy === 'last') {
  14093. if (this.remainingSize < 0 && containerSize !== Infinity) { //Infinity is with native scroll when the table is wider than the viewport (TODO: test)
  14094. this.cellStretch[this.cellCount - 1] = -this.remainingSize;
  14095. this.remainingSize = 0;
  14096. }
  14097. }
  14098. };
  14099. WalkontableColumnStrategy.prototype.countVisible = function () {
  14100. return this.visibleCellCount;
  14101. };
  14102. WalkontableColumnStrategy.prototype.isLastIncomplete = function () {
  14103. var firstRow = this.instance.wtTable.getFirstVisibleRow();
  14104. var lastCol = this.instance.wtTable.getLastVisibleColumn();
  14105. var cell = this.instance.wtTable.getCell(new WalkontableCellCoords(firstRow, lastCol));
  14106. var cellOffset = Handsontable.Dom.offset(cell);
  14107. var cellWidth = Handsontable.Dom.outerWidth(cell);
  14108. var cellEnd = cellOffset.left + cellWidth;
  14109. var viewportOffsetLeft = this.instance.wtScrollbars.vertical.getScrollPosition();
  14110. var viewportWitdh = this.instance.wtViewport.getViewportWidth();
  14111. var viewportEnd = viewportOffsetLeft + viewportWitdh;
  14112. return viewportEnd >= cellEnd;
  14113. };
  14114. function Walkontable(settings) {
  14115. var originalHeaders = [];
  14116. this.guid = 'wt_' + walkontableRandomString(); //this is the namespace for global events
  14117. //bootstrap from settings
  14118. if (settings.cloneSource) {
  14119. this.cloneSource = settings.cloneSource;
  14120. this.cloneOverlay = settings.cloneOverlay;
  14121. this.wtSettings = settings.cloneSource.wtSettings;
  14122. this.wtTable = new WalkontableTable(this, settings.table);
  14123. this.wtScroll = new WalkontableScroll(this);
  14124. this.wtViewport = settings.cloneSource.wtViewport;
  14125. this.wtEvent = new WalkontableEvent(this);
  14126. this.selections = this.cloneSource.selections;
  14127. }
  14128. else {
  14129. this.wtSettings = new WalkontableSettings(this, settings);
  14130. this.wtTable = new WalkontableTable(this, settings.table);
  14131. this.wtScroll = new WalkontableScroll(this);
  14132. this.wtViewport = new WalkontableViewport(this);
  14133. this.wtEvent = new WalkontableEvent(this);
  14134. this.selections = this.getSetting('selections');
  14135. this.wtScrollbars = new WalkontableScrollbars(this);
  14136. }
  14137. //find original headers
  14138. if (this.wtTable.THEAD.childNodes.length && this.wtTable.THEAD.childNodes[0].childNodes.length) {
  14139. for (var c = 0, clen = this.wtTable.THEAD.childNodes[0].childNodes.length; c < clen; c++) {
  14140. originalHeaders.push(this.wtTable.THEAD.childNodes[0].childNodes[c].innerHTML);
  14141. }
  14142. if (!this.getSetting('columnHeaders').length) {
  14143. this.update('columnHeaders', [function (column, TH) {
  14144. Handsontable.Dom.fastInnerText(TH, originalHeaders[column]);
  14145. }]);
  14146. }
  14147. }
  14148. this.drawn = false;
  14149. this.drawInterrupted = false;
  14150. }
  14151. /**
  14152. * Force rerender of Walkontable
  14153. * @param fastDraw {Boolean} When TRUE, try to refresh only the positions of borders without rerendering the data. It will only work if WalkontableTable.draw() does not force rendering anyway
  14154. * @returns {Walkontable}
  14155. */
  14156. Walkontable.prototype.draw = function (fastDraw) {
  14157. this.drawInterrupted = false;
  14158. if (!fastDraw && !Handsontable.Dom.isVisible(this.wtTable.TABLE)) {
  14159. this.drawInterrupted = true; //draw interrupted because TABLE is not visible
  14160. return;
  14161. }
  14162. this.wtTable.draw(fastDraw);
  14163. return this;
  14164. };
  14165. /**
  14166. * Returns the TD at coords. If topmost is set to true, returns TD from the topmost overlay layer,
  14167. * if not set or set to false, returns TD from the master table.
  14168. * @param {WalkontableCellCoords} coords
  14169. * @param {Boolean} topmost
  14170. * @returns {Object}
  14171. */
  14172. Walkontable.prototype.getCell = function (coords, topmost) {
  14173. if(!topmost) {
  14174. return this.wtTable.getCell(coords);
  14175. } else {
  14176. var fixedRows = this.wtSettings.getSetting('fixedRowsTop')
  14177. , fixedColumns = this.wtSettings.getSetting('fixedColumnsLeft');
  14178. if(coords.row < fixedRows && coords.col < fixedColumns) {
  14179. return this.wtScrollbars.corner.clone.wtTable.getCell(coords);
  14180. } else if(coords.row < fixedRows) {
  14181. return this.wtScrollbars.vertical.clone.wtTable.getCell(coords);
  14182. } else if (coords.col < fixedColumns) {
  14183. return this.wtScrollbars.horizontal.clone.wtTable.getCell(coords);
  14184. } else {
  14185. return this.wtTable.getCell(coords);
  14186. }
  14187. }
  14188. };
  14189. Walkontable.prototype.update = function (settings, value) {
  14190. return this.wtSettings.update(settings, value);
  14191. };
  14192. /**
  14193. * Scroll the viewport to a row at the given index in the data source
  14194. * @param row
  14195. * @returns {Walkontable}
  14196. */
  14197. Walkontable.prototype.scrollVertical = function (row) {
  14198. this.wtScrollbars.vertical.scrollTo(row);
  14199. this.getSetting('onScrollVertically');
  14200. return this;
  14201. };
  14202. /**
  14203. * Scroll the viewport to a column at the given index in the data source
  14204. * @param row
  14205. * @returns {Walkontable}
  14206. */
  14207. Walkontable.prototype.scrollHorizontal = function (column) {
  14208. this.wtScrollbars.horizontal.scrollTo(column);
  14209. this.getSetting('onScrollHorizontally');
  14210. return this;
  14211. };
  14212. /**
  14213. * Scrolls the viewport to a cell (rerenders if needed)
  14214. * @param {WalkontableCellCoords} coords
  14215. * @returns {Walkontable}
  14216. */
  14217. Walkontable.prototype.scrollViewport = function (coords) {
  14218. this.wtScroll.scrollViewport(coords);
  14219. return this;
  14220. };
  14221. Walkontable.prototype.getViewport = function () {
  14222. return [
  14223. this.wtTable.getFirstVisibleRow(),
  14224. this.wtTable.getFirstVisibleColumn(),
  14225. this.wtTable.getLastVisibleRow(),
  14226. this.wtTable.getLastVisibleColumn()
  14227. ];
  14228. };
  14229. Walkontable.prototype.getSetting = function (key, param1, param2, param3, param4) {
  14230. return this.wtSettings.getSetting(key, param1, param2, param3, param4); //this is faster than .apply - https://github.com/handsontable/handsontable/wiki/JavaScript-&-DOM-performance-tips
  14231. };
  14232. Walkontable.prototype.hasSetting = function (key) {
  14233. return this.wtSettings.has(key);
  14234. };
  14235. Walkontable.prototype.destroy = function () {
  14236. this.wtScrollbars.destroy();
  14237. this.wtEvent && this.wtEvent.destroy();
  14238. };
  14239. /**
  14240. * A overlay that renders ALL available rows & columns positioned on top of the original Walkontable instance and all other overlays.
  14241. * Used for debugging purposes to see if the other overlays (that render only part of the rows & columns) are positioned correctly
  14242. * @param instance
  14243. * @constructor
  14244. */
  14245. function WalkontableDebugOverlay(instance) {
  14246. this.instance = instance;
  14247. this.init();
  14248. this.clone = this.makeClone('debug');
  14249. this.clone.wtTable.holder.style.opacity = 0.4;
  14250. this.clone.wtTable.holder.style.textShadow = '0 0 2px #ff0000';
  14251. this.lastTimeout = null;
  14252. Handsontable.Dom.addClass(this.clone.wtTable.holder.parentNode, 'wtDebugVisible');
  14253. /*var that = this;
  14254. var lastX = 0;
  14255. var lastY = 0;
  14256. var overlayContainer = that.clone.wtTable.holder.parentNode;
  14257. var eventManager = Handsontable.eventManager(instance);
  14258. eventManager.addEventListener(document.body, 'mousemove', function (event) {
  14259. if (!that.instance.wtTable.holder.parentNode) {
  14260. return; //removed from DOM
  14261. }
  14262. if ((event.clientX - lastX > -5 && event.clientX - lastX < 5) && (event.clientY - lastY > -5 && event.clientY - lastY < 5)) {
  14263. return; //ignore minor mouse movement
  14264. }
  14265. lastX = event.clientX;
  14266. lastY = event.clientY;
  14267. Handsontable.Dom.addClass(overlayContainer, 'wtDebugHidden');
  14268. Handsontable.Dom.removeClass(overlayContainer, 'wtDebugVisible');
  14269. clearTimeout(this.lastTimeout);
  14270. this.lastTimeout = setTimeout(function () {
  14271. Handsontable.Dom.removeClass(overlayContainer, 'wtDebugHidden');
  14272. Handsontable.Dom.addClass(overlayContainer, 'wtDebugVisible');
  14273. }, 1000);
  14274. });*/
  14275. }
  14276. WalkontableDebugOverlay.prototype = new WalkontableOverlay();
  14277. WalkontableDebugOverlay.prototype.destroy = function () {
  14278. WalkontableOverlay.prototype.destroy.call(this);
  14279. clearTimeout(this.lastTimeout);
  14280. };
  14281. function WalkontableEvent(instance) {
  14282. var that = this;
  14283. var eventManager = Handsontable.eventManager(instance);
  14284. //reference to instance
  14285. this.instance = instance;
  14286. var dblClickOrigin = [null, null];
  14287. this.dblClickTimeout = [null, null];
  14288. var onMouseDown = function (event) {
  14289. var cell = that.parentCell(event.target);
  14290. if (Handsontable.Dom.hasClass(event.target, 'corner')) {
  14291. that.instance.getSetting('onCellCornerMouseDown', event, event.target);
  14292. }
  14293. else if (cell.TD) {
  14294. if (that.instance.hasSetting('onCellMouseDown')) {
  14295. that.instance.getSetting('onCellMouseDown', event, cell.coords, cell.TD, that.instance);
  14296. }
  14297. }
  14298. if (event.button !== 2) { //if not right mouse button
  14299. if (cell.TD) {
  14300. dblClickOrigin[0] = cell.TD;
  14301. clearTimeout(that.dblClickTimeout[0]);
  14302. that.dblClickTimeout[0] = setTimeout(function () {
  14303. dblClickOrigin[0] = null;
  14304. }, 1000);
  14305. }
  14306. }
  14307. };
  14308. var onTouchMove = function (event) {
  14309. that.instance.touchMoving = true;
  14310. };
  14311. var longTouchTimeout;
  14312. ///**
  14313. // * Update touch event target - if user taps on resize handle 'hit area', update the target to the cell itself
  14314. // * @param event
  14315. // */
  14316. /*
  14317. var adjustTapTarget = function (event) {
  14318. var currentSelection
  14319. , properTarget;
  14320. if(Handsontable.Dom.hasClass(event.target,'SelectionHandle')) {
  14321. if(that.instance.selections[0].cellRange) {
  14322. currentSelection = that.instance.selections[0].cellRange.highlight;
  14323. properTarget = that.instance.getCell(currentSelection, true);
  14324. }
  14325. }
  14326. if(properTarget) {
  14327. Object.defineProperty(event,'target',{
  14328. value: properTarget
  14329. });
  14330. }
  14331. return event;
  14332. };*/
  14333. var onTouchStart = function (event) {
  14334. var container = this;
  14335. eventManager.addEventListener(this, 'touchmove', onTouchMove);
  14336. //this.addEventListener("touchmove", onTouchMove, false);
  14337. // touch-and-hold event
  14338. //longTouchTimeout = setTimeout(function () {
  14339. // if(!that.instance.touchMoving) {
  14340. // that.instance.longTouch = true;
  14341. //
  14342. // var targetCoords = Handsontable.Dom.offset(event.target);
  14343. // var contextMenuEvent = new MouseEvent('contextmenu', {
  14344. // clientX: targetCoords.left + event.target.offsetWidth,
  14345. // clientY: targetCoords.top + event.target.offsetHeight,
  14346. // button: 2
  14347. // });
  14348. //
  14349. // that.instance.wtTable.holder.parentNode.parentNode.dispatchEvent(contextMenuEvent);
  14350. // }
  14351. //},200);
  14352. // Prevent cell selection when scrolling with touch event - not the best solution performance-wise
  14353. that.checkIfTouchMove = setTimeout(function () {
  14354. if (that.instance.touchMoving == true) {
  14355. that.instance.touchMoving = void 0;
  14356. eventManager.removeEventListener("touchmove", onTouchMove, false);
  14357. return;
  14358. } else {
  14359. //event = adjustTapTarget(event);
  14360. onMouseDown(event);
  14361. }
  14362. }, 30);
  14363. //eventManager.removeEventListener(that.instance.wtTable.holder, "mousedown", onMouseDown);
  14364. };
  14365. var lastMouseOver;
  14366. var onMouseOver = function (event) {
  14367. if (that.instance.hasSetting('onCellMouseOver')) {
  14368. var TABLE = that.instance.wtTable.TABLE;
  14369. var TD = Handsontable.Dom.closest(event.target, ['TD', 'TH'], TABLE);
  14370. if (TD && TD !== lastMouseOver && Handsontable.Dom.isChildOf(TD, TABLE)) {
  14371. lastMouseOver = TD;
  14372. that.instance.getSetting('onCellMouseOver', event, that.instance.wtTable.getCoords(TD), TD, that.instance);
  14373. }
  14374. }
  14375. };
  14376. /* var lastMouseOut;
  14377. var onMouseOut = function (event) {
  14378. if (that.instance.hasSetting('onCellMouseOut')) {
  14379. var TABLE = that.instance.wtTable.TABLE;
  14380. var TD = Handsontable.Dom.closest(event.target, ['TD', 'TH'], TABLE);
  14381. if (TD && TD !== lastMouseOut && Handsontable.Dom.isChildOf(TD, TABLE)) {
  14382. lastMouseOut = TD;
  14383. if (TD.nodeName === 'TD') {
  14384. that.instance.getSetting('onCellMouseOut', event, that.instance.wtTable.getCoords(TD), TD);
  14385. }
  14386. }
  14387. }
  14388. };*/
  14389. var onMouseUp = function (event) {
  14390. if (event.button !== 2) { //if not right mouse button
  14391. var cell = that.parentCell(event.target);
  14392. if (cell.TD === dblClickOrigin[0] && cell.TD === dblClickOrigin[1]) {
  14393. if (Handsontable.Dom.hasClass(event.target, 'corner')) {
  14394. that.instance.getSetting('onCellCornerDblClick', event, cell.coords, cell.TD, that.instance);
  14395. }
  14396. else {
  14397. that.instance.getSetting('onCellDblClick', event, cell.coords, cell.TD, that.instance);
  14398. }
  14399. dblClickOrigin[0] = null;
  14400. dblClickOrigin[1] = null;
  14401. }
  14402. else if (cell.TD === dblClickOrigin[0]) {
  14403. dblClickOrigin[1] = cell.TD;
  14404. clearTimeout(that.dblClickTimeout[1]);
  14405. that.dblClickTimeout[1] = setTimeout(function () {
  14406. dblClickOrigin[1] = null;
  14407. }, 500);
  14408. }
  14409. }
  14410. };
  14411. var onTouchEnd = function (event) {
  14412. clearTimeout(longTouchTimeout);
  14413. //that.instance.longTouch == void 0;
  14414. event.preventDefault();
  14415. onMouseUp(event);
  14416. //eventManager.removeEventListener(that.instance.wtTable.holder, "mouseup", onMouseUp);
  14417. };
  14418. eventManager.addEventListener(this.instance.wtTable.holder, 'mousedown', onMouseDown);
  14419. eventManager.addEventListener(this.instance.wtTable.TABLE, 'mouseover', onMouseOver);
  14420. eventManager.addEventListener(this.instance.wtTable.holder, 'mouseup', onMouseUp);
  14421. if(this.instance.wtTable.holder.parentNode.parentNode && Handsontable.mobileBrowser) { // check if full HOT instance, or detached WOT AND run on mobile device
  14422. var classSelector = "." + this.instance.wtTable.holder.parentNode.className.split(" ").join(".");
  14423. eventManager.addEventListener(this.instance.wtTable.holder.parentNode.parentNode, 'touchstart', function (event) {
  14424. that.instance.touchApplied = true;
  14425. if (Handsontable.Dom.isChildOf(event.target, classSelector)) {
  14426. onTouchStart.call(event.target, event);
  14427. }
  14428. });
  14429. eventManager.addEventListener(this.instance.wtTable.holder.parentNode.parentNode, 'touchend', function (event) {
  14430. that.instance.touchApplied = false;
  14431. if (Handsontable.Dom.isChildOf(event.target, classSelector)) {
  14432. onTouchEnd.call(event.target, event);
  14433. }
  14434. });
  14435. if(!that.instance.momentumScrolling) {
  14436. that.instance.momentumScrolling = {};
  14437. }
  14438. eventManager.addEventListener(this.instance.wtTable.holder.parentNode.parentNode, 'scroll', function (event) {
  14439. clearTimeout(that.instance.momentumScrolling._timeout);
  14440. if(!that.instance.momentumScrolling.ongoing) {
  14441. that.instance.getSetting('onBeforeTouchScroll');
  14442. }
  14443. that.instance.momentumScrolling.ongoing = true;
  14444. that.instance.momentumScrolling._timeout = setTimeout(function () {
  14445. if(!that.instance.touchApplied) {
  14446. that.instance.momentumScrolling.ongoing = false;
  14447. that.instance.getSetting('onAfterMomentumScroll');
  14448. }
  14449. },200);
  14450. });
  14451. }
  14452. eventManager.addEventListener(window, 'resize', function () {
  14453. that.instance.draw();
  14454. });
  14455. this.destroy = function () {
  14456. clearTimeout(this.dblClickTimeout[0]);
  14457. clearTimeout(this.dblClickTimeout[1]);
  14458. eventManager.clear();
  14459. };
  14460. }
  14461. WalkontableEvent.prototype.parentCell = function (elem) {
  14462. var cell = {};
  14463. var TABLE = this.instance.wtTable.TABLE;
  14464. var TD = Handsontable.Dom.closest(elem, ['TD', 'TH'], TABLE);
  14465. if (TD && Handsontable.Dom.isChildOf(TD, TABLE)) {
  14466. cell.coords = this.instance.wtTable.getCoords(TD);
  14467. cell.TD = TD;
  14468. } else if (Handsontable.Dom.hasClass(elem, 'wtBorder') && Handsontable.Dom.hasClass(elem, 'current')) {
  14469. cell.coords = this.instance.selections.current.cellRange.highlight; //selections.current is current selected cell
  14470. cell.TD = this.instance.wtTable.getCell(cell.coords);
  14471. } else if (Handsontable.Dom.hasClass(elem, 'wtBorder') && Handsontable.Dom.hasClass(elem, 'area')) {
  14472. if (this.instance.selections.area.cellRange) {
  14473. cell.coords = this.instance.selections.area.cellRange.to; //selections.area is area selected cells
  14474. cell.TD = this.instance.wtTable.getCell(cell.coords);
  14475. }
  14476. }
  14477. return cell;
  14478. };
  14479. function walkontableRangesIntersect() {
  14480. var from = arguments[0];
  14481. var to = arguments[1];
  14482. for (var i = 1, ilen = arguments.length / 2; i < ilen; i++) {
  14483. if (from <= arguments[2 * i + 1] && to >= arguments[2 * i]) {
  14484. return true;
  14485. }
  14486. }
  14487. return false;
  14488. }
  14489. /**
  14490. * Generates a random hex string. Used as namespace for Walkontable instance events.
  14491. * @return {String} - 16 character random string: "92b1bfc74ec4"
  14492. */
  14493. function walkontableRandomString() {
  14494. function s4() {
  14495. return Math.floor((1 + Math.random()) * 0x10000)
  14496. .toString(16)
  14497. .substring(1);
  14498. }
  14499. return s4() + s4() + s4() + s4();
  14500. }
  14501. /**
  14502. * http://notes.jetienne.com/2011/05/18/cancelRequestAnimFrame-for-paul-irish-requestAnimFrame.html
  14503. */
  14504. window.requestAnimFrame = (function () {
  14505. return window.requestAnimationFrame ||
  14506. window.webkitRequestAnimationFrame ||
  14507. window.mozRequestAnimationFrame ||
  14508. window.oRequestAnimationFrame ||
  14509. window.msRequestAnimationFrame ||
  14510. function (/* function */ callback, /* DOMElement */ element) {
  14511. return window.setTimeout(callback, 1000 / 60);
  14512. };
  14513. })();
  14514. window.cancelRequestAnimFrame = (function () {
  14515. return window.cancelAnimationFrame ||
  14516. window.webkitCancelRequestAnimationFrame ||
  14517. window.mozCancelRequestAnimationFrame ||
  14518. window.oCancelRequestAnimationFrame ||
  14519. window.msCancelRequestAnimationFrame ||
  14520. clearTimeout
  14521. })();
  14522. //http://snipplr.com/view/13523/
  14523. //modified for speed
  14524. //http://jsperf.com/getcomputedstyle-vs-style-vs-css/8
  14525. if (!window.getComputedStyle) {
  14526. (function () {
  14527. var elem;
  14528. var styleObj = {
  14529. getPropertyValue: function getPropertyValue(prop) {
  14530. if (prop == 'float') prop = 'styleFloat';
  14531. return elem.currentStyle[prop.toUpperCase()] || null;
  14532. }
  14533. };
  14534. window.getComputedStyle = function (el) {
  14535. elem = el;
  14536. return styleObj;
  14537. }
  14538. })();
  14539. }
  14540. /**
  14541. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim
  14542. */
  14543. if (!String.prototype.trim) {
  14544. var trimRegex = /^\s+|\s+$/g;
  14545. String.prototype.trim = function () {
  14546. return this.replace(trimRegex, '');
  14547. };
  14548. }
  14549. /**
  14550. * WalkontableRowFilter
  14551. * @constructor
  14552. */
  14553. function WalkontableRowFilter(offset, total, countTH) {
  14554. this.offset = offset;
  14555. this.total = total;
  14556. this.countTH = countTH;
  14557. }
  14558. WalkontableRowFilter.prototype.offsetted = function (n) {
  14559. return n + this.offset;
  14560. };
  14561. WalkontableRowFilter.prototype.unOffsetted = function (n) {
  14562. return n - this.offset;
  14563. };
  14564. WalkontableRowFilter.prototype.renderedToSource = function (n) {
  14565. return this.offsetted(n);
  14566. };
  14567. WalkontableRowFilter.prototype.sourceToRendered = function (n) {
  14568. return this.unOffsetted(n);
  14569. };
  14570. WalkontableRowFilter.prototype.offsettedTH = function (n) {
  14571. return n - this.countTH;
  14572. };
  14573. WalkontableRowFilter.prototype.visibleColHeadedRowToSourceRow = function (n) {
  14574. return this.renderedToSource(this.offsettedTH(n));
  14575. };
  14576. WalkontableRowFilter.prototype.sourceRowToVisibleColHeadedRow = function (n) {
  14577. return this.unOffsettedTH(this.sourceToRendered(n));
  14578. };
  14579. function WalkontableScroll(instance) {
  14580. this.instance = instance;
  14581. }
  14582. /**
  14583. * Scrolls viewport to a cell by minimum number of cells
  14584. * @param {WalkontableCellCoords} coords
  14585. */
  14586. WalkontableScroll.prototype.scrollViewport = function (coords) {
  14587. if (!this.instance.drawn) {
  14588. return;
  14589. }
  14590. var totalRows = this.instance.getSetting('totalRows')
  14591. , totalColumns = this.instance.getSetting('totalColumns');
  14592. if (coords.row < 0 || coords.row > totalRows - 1) {
  14593. throw new Error('row ' + coords.row + ' does not exist');
  14594. }
  14595. if (coords.col < 0 || coords.col > totalColumns - 1) {
  14596. throw new Error('column ' + coords.col + ' does not exist');
  14597. }
  14598. if (coords.row > this.instance.wtTable.getLastVisibleRow()) {
  14599. this.instance.wtScrollbars.vertical.scrollTo(coords.row, true);
  14600. } else if (coords.row >= this.instance.getSetting('fixedRowsTop') && coords.row < this.instance.wtTable.getFirstVisibleRow()){
  14601. this.instance.wtScrollbars.vertical.scrollTo(coords.row);
  14602. }
  14603. if (coords.col >= this.instance.wtTable.getLastVisibleColumn()) {
  14604. this.instance.wtScrollbars.horizontal.scrollTo(coords.col, true);
  14605. } else if (coords.col >= this.instance.getSetting('fixedColumnsLeft') && coords.col < this.instance.wtTable.getFirstVisibleColumn()){
  14606. this.instance.wtScrollbars.horizontal.scrollTo(coords.col);
  14607. }
  14608. //}
  14609. };
  14610. function WalkontableCornerScrollbarNative(instance) {
  14611. this.instance = instance;
  14612. this.type = 'corner';
  14613. this.init();
  14614. this.clone = this.makeClone('corner');
  14615. }
  14616. WalkontableCornerScrollbarNative.prototype = new WalkontableOverlay();
  14617. WalkontableCornerScrollbarNative.prototype.resetFixedPosition = function () {
  14618. if (!this.instance.wtTable.holder.parentNode) {
  14619. return; //removed from DOM
  14620. }
  14621. var elem = this.clone.wtTable.holder.parentNode;
  14622. if (this.scrollHandler === window) {
  14623. var box = this.instance.wtTable.holder.getBoundingClientRect();
  14624. var top = Math.ceil(box.top);
  14625. var left = Math.ceil(box.left);
  14626. var finalLeft
  14627. , finalTop;
  14628. var bottom = Math.ceil(box.bottom);
  14629. var right = Math.ceil(box.right);
  14630. if (left < 0 && (right - elem.offsetWidth) > 0) {
  14631. finalLeft = -left + 'px';
  14632. } else {
  14633. finalLeft = '0';
  14634. }
  14635. if (top < 0 && (bottom - elem.offsetHeight) > 0) {
  14636. finalTop = -top + "px";
  14637. } else {
  14638. finalTop = "0";
  14639. }
  14640. }
  14641. else if(!Handsontable.freezeOverlays) {
  14642. finalLeft = this.instance.wtScrollbars.horizontal.getScrollPosition() + "px";
  14643. finalTop = this.instance.wtScrollbars.vertical.getScrollPosition() + "px";
  14644. }
  14645. Handsontable.Dom.setOverlayPosition(elem, finalLeft, finalTop);
  14646. elem.style.width = Handsontable.Dom.outerWidth(this.clone.wtTable.TABLE) + 4 + 'px';
  14647. elem.style.height = Handsontable.Dom.outerHeight(this.clone.wtTable.TABLE) + 4 + 'px';
  14648. };
  14649. function WalkontableHorizontalScrollbarNative(instance) {
  14650. this.instance = instance;
  14651. this.type = 'horizontal';
  14652. this.offset = 0;
  14653. this.init();
  14654. this.clone = this.makeClone('left');
  14655. }
  14656. WalkontableHorizontalScrollbarNative.prototype = new WalkontableOverlay();
  14657. //resetFixedPosition (in future merge it with this.refresh?)
  14658. WalkontableHorizontalScrollbarNative.prototype.resetFixedPosition = function () {
  14659. if (!this.instance.wtTable.holder.parentNode) {
  14660. return; //removed from DOM
  14661. }
  14662. var elem = this.clone.wtTable.holder.parentNode;
  14663. if (this.scrollHandler === window) {
  14664. var box = this.instance.wtTable.holder.getBoundingClientRect();
  14665. var left = Math.ceil(box.left);
  14666. var finalLeft
  14667. , finalTop;
  14668. var right = Math.ceil(box.right);
  14669. if (left < 0 && (right - elem.offsetWidth) > 0) {
  14670. finalLeft = -left + 'px';
  14671. } else {
  14672. finalLeft = '0';
  14673. }
  14674. finalTop = this.instance.wtTable.hider.style.top;
  14675. }
  14676. else if(!Handsontable.freezeOverlays) {
  14677. finalLeft = this.getScrollPosition() + "px";
  14678. finalTop = this.instance.wtTable.hider.style.top;
  14679. }
  14680. Handsontable.Dom.setOverlayPosition(elem, finalLeft, finalTop);
  14681. elem.style.height = Handsontable.Dom.outerHeight(this.clone.wtTable.TABLE) + 'px';
  14682. elem.style.width = Handsontable.Dom.outerWidth(this.clone.wtTable.TABLE) + 4 + 'px';// + 4 + 'px';
  14683. };
  14684. WalkontableHorizontalScrollbarNative.prototype.refresh = function (fastDraw) {
  14685. this.applyToDOM();
  14686. WalkontableOverlay.prototype.refresh.call(this, fastDraw);
  14687. };
  14688. WalkontableHorizontalScrollbarNative.prototype.getScrollPosition = function () {
  14689. return Handsontable.Dom.getScrollLeft(this.scrollHandler);
  14690. };
  14691. WalkontableHorizontalScrollbarNative.prototype.setScrollPosition = function (pos) {
  14692. if (this.scrollHandler === window) {
  14693. window.scrollTo(pos, Handsontable.Dom.getWindowScrollTop());
  14694. } else {
  14695. this.scrollHandler.scrollLeft = pos;
  14696. }
  14697. };
  14698. WalkontableHorizontalScrollbarNative.prototype.onScroll = function () {
  14699. this.instance.getSetting('onScrollHorizontally');
  14700. };
  14701. WalkontableHorizontalScrollbarNative.prototype.sumCellSizes = function (from, length) {
  14702. var sum = 0;
  14703. while(from < length) {
  14704. sum += this.instance.wtTable.getStretchedColumnWidth(from) || this.instance.wtSettings.defaultColumnWidth;
  14705. from++;
  14706. }
  14707. return sum;
  14708. };
  14709. //applyToDOM (in future merge it with this.refresh?)
  14710. WalkontableHorizontalScrollbarNative.prototype.applyToDOM = function () {
  14711. var total = this.instance.getSetting('totalColumns');
  14712. var headerSize = this.instance.wtViewport.getRowHeaderWidth();
  14713. this.fixedContainer.style.width = headerSize + this.sumCellSizes(0, total) + 'px';// + 4 + 'px';
  14714. if (typeof this.instance.wtViewport.columnsRenderCalculator.startPosition === 'number'){
  14715. this.fixed.style.left = this.instance.wtViewport.columnsRenderCalculator.startPosition + 'px';
  14716. }
  14717. else if (total === 0) {
  14718. this.fixed.style.left = '0';
  14719. } else {
  14720. throw new Error('Incorrect value of the columnsRenderCalculator');
  14721. }
  14722. this.fixed.style.right = '';
  14723. };
  14724. /**
  14725. * Scrolls horizontally to a column at the left edge of the viewport
  14726. * @param sourceCol {Number}
  14727. * @param beyondRendered {Boolean} if TRUE, scrolls according to the bottom edge (top edge is by default)
  14728. */
  14729. WalkontableHorizontalScrollbarNative.prototype.scrollTo = function (sourceCol, beyondRendered) {
  14730. var newX = this.getTableParentOffset();
  14731. if (beyondRendered) {
  14732. newX += this.sumCellSizes(0, sourceCol + 1);
  14733. newX -= this.instance.wtViewport.getViewportWidth()
  14734. }
  14735. else {
  14736. var fixedColumnsLeft = this.instance.getSetting('fixedColumnsLeft');
  14737. newX += this.sumCellSizes(fixedColumnsLeft, sourceCol);
  14738. }
  14739. this.setScrollPosition(newX);
  14740. };
  14741. WalkontableHorizontalScrollbarNative.prototype.getTableParentOffset = function () {
  14742. if (this.scrollHandler === window) {
  14743. return this.instance.wtTable.holderOffset.left;
  14744. }
  14745. else {
  14746. return 0;
  14747. }
  14748. };
  14749. function WalkontableVerticalScrollbarNative(instance) {
  14750. this.instance = instance;
  14751. this.type = 'vertical';
  14752. this.init();
  14753. this.clone = this.makeClone('top');
  14754. }
  14755. WalkontableVerticalScrollbarNative.prototype = new WalkontableOverlay();
  14756. //resetFixedPosition (in future merge it with this.refresh?)
  14757. WalkontableVerticalScrollbarNative.prototype.resetFixedPosition = function () {
  14758. if (!this.instance.wtTable.holder.parentNode) {
  14759. return; //removed from DOM
  14760. }
  14761. var elem = this.clone.wtTable.holder.parentNode;
  14762. if (this.scrollHandler === window) {
  14763. var box = this.instance.wtTable.holder.getBoundingClientRect();
  14764. var top = Math.ceil(box.top);
  14765. var finalLeft
  14766. , finalTop;
  14767. var bottom = Math.ceil(box.bottom);
  14768. finalLeft = this.instance.wtTable.hider.style.left;
  14769. if (top < 0 && (bottom - elem.offsetHeight) > 0) {
  14770. finalTop = -top + "px";
  14771. } else {
  14772. finalTop = "0";
  14773. }
  14774. }
  14775. else if(!Handsontable.freezeOverlays) {
  14776. finalTop = this.getScrollPosition() + "px";
  14777. finalLeft = this.instance.wtTable.hider.style.left;
  14778. }
  14779. Handsontable.Dom.setOverlayPosition(elem, finalLeft, finalTop);
  14780. if (this.instance.wtScrollbars.horizontal.scrollHandler === window) {
  14781. elem.style.width = this.instance.wtViewport.getWorkspaceActualWidth() + 'px';
  14782. }
  14783. else {
  14784. elem.style.width = Handsontable.Dom.outerWidth(this.clone.wtTable.TABLE) + 'px';
  14785. }
  14786. elem.style.height = Handsontable.Dom.outerHeight(this.clone.wtTable.TABLE) + 4 + 'px';// + 4 + 'px';
  14787. };
  14788. WalkontableVerticalScrollbarNative.prototype.getScrollPosition = function () {
  14789. return Handsontable.Dom.getScrollTop(this.scrollHandler);
  14790. };
  14791. WalkontableVerticalScrollbarNative.prototype.setScrollPosition = function (pos) {
  14792. if (this.scrollHandler === window){
  14793. window.scrollTo(Handsontable.Dom.getWindowScrollLeft(), pos);
  14794. } else {
  14795. this.scrollHandler.scrollTop = pos;
  14796. }
  14797. };
  14798. WalkontableVerticalScrollbarNative.prototype.onScroll = function () {
  14799. this.instance.getSetting('onScrollVertically');
  14800. };
  14801. WalkontableVerticalScrollbarNative.prototype.sumCellSizes = function (from, length) {
  14802. var sum = 0;
  14803. while (from < length) {
  14804. sum += this.instance.wtTable.getRowHeight(from) || this.instance.wtSettings.settings.defaultRowHeight; //TODO optimize getSetting, because this is MUCH faster then getSetting
  14805. from++;
  14806. }
  14807. return sum;
  14808. };
  14809. WalkontableVerticalScrollbarNative.prototype.refresh = function (fastDraw) {
  14810. this.applyToDOM();
  14811. WalkontableOverlay.prototype.refresh.call(this, fastDraw);
  14812. };
  14813. //applyToDOM (in future merge it with this.refresh?)
  14814. WalkontableVerticalScrollbarNative.prototype.applyToDOM = function () {
  14815. var total = this.instance.getSetting('totalRows');
  14816. var headerSize = this.instance.wtViewport.getColumnHeaderHeight();
  14817. this.fixedContainer.style.height = headerSize + this.sumCellSizes(0, total) + 'px';// + 4 + 'px'; //+4 is needed, otherwise vertical scroll appears in Chrome (window scroll mode) - maybe because of fill handle in last row or because of box shadow
  14818. if (typeof this.instance.wtViewport.rowsRenderCalculator.startPosition === 'number') {
  14819. this.fixed.style.top = this.instance.wtViewport.rowsRenderCalculator.startPosition + 'px';
  14820. }
  14821. else if (total === 0) {
  14822. this.fixed.style.top = '0'; //can happen if there are 0 rows
  14823. }
  14824. else {
  14825. throw new Error("Incorrect value of the rowsRenderCalculator");
  14826. }
  14827. this.fixed.style.bottom = '';
  14828. };
  14829. /**
  14830. * Scrolls vertically to a row
  14831. * @param sourceRow {Number}
  14832. * @param bottomEdge {Boolean} if TRUE, scrolls according to the bottom edge (top edge is by default)
  14833. */
  14834. WalkontableVerticalScrollbarNative.prototype.scrollTo = function (sourceRow, bottomEdge) {
  14835. var newY = this.getTableParentOffset();
  14836. if (bottomEdge) {
  14837. newY += this.sumCellSizes(0, sourceRow + 1);
  14838. newY -= this.instance.wtViewport.getViewportHeight();
  14839. }
  14840. else {
  14841. var fixedRowsTop = this.instance.getSetting('fixedRowsTop');
  14842. newY += this.sumCellSizes(fixedRowsTop, sourceRow);
  14843. }
  14844. this.setScrollPosition(newY);
  14845. };
  14846. WalkontableVerticalScrollbarNative.prototype.getTableParentOffset = function () {
  14847. if (this.scrollHandler === window) {
  14848. return this.instance.wtTable.holderOffset.top;
  14849. }
  14850. else {
  14851. return 0;
  14852. }
  14853. };
  14854. function WalkontableScrollbars(instance) {
  14855. this.instance = instance;
  14856. instance.update('scrollbarWidth', Handsontable.Dom.getScrollbarWidth());
  14857. instance.update('scrollbarHeight', Handsontable.Dom.getScrollbarWidth());
  14858. this.vertical = new WalkontableVerticalScrollbarNative(instance);
  14859. this.horizontal = new WalkontableHorizontalScrollbarNative(instance);
  14860. this.corner = new WalkontableCornerScrollbarNative(instance);
  14861. if (instance.getSetting('debug')) {
  14862. this.debug = new WalkontableDebugOverlay(instance);
  14863. }
  14864. this.registerListeners();
  14865. }
  14866. WalkontableScrollbars.prototype.registerListeners = function () {
  14867. var that = this;
  14868. this.refreshAll = function refreshAll() {
  14869. if(!that.instance.drawn) {
  14870. return;
  14871. }
  14872. if (!that.instance.wtTable.holder.parentNode) {
  14873. //Walkontable was detached from DOM, but this handler was not removed
  14874. that.destroy();
  14875. return;
  14876. }
  14877. that.instance.draw(true);
  14878. that.vertical.onScroll();
  14879. that.horizontal.onScroll();
  14880. };
  14881. var eventManager = Handsontable.eventManager(that.instance);
  14882. eventManager.addEventListener(this.vertical.scrollHandler, 'scroll', this.refreshAll);
  14883. if (this.vertical.scrollHandler !== this.horizontal.scrollHandler) {
  14884. eventManager.addEventListener(this.horizontal.scrollHandler, 'scroll', this.refreshAll);
  14885. }
  14886. if (this.vertical.scrollHandler !== window && this.horizontal.scrollHandler !== window) {
  14887. eventManager.addEventListener(window,'scroll', this.refreshAll);
  14888. }
  14889. };
  14890. WalkontableScrollbars.prototype.destroy = function () {
  14891. var eventManager = Handsontable.eventManager(this.instance);
  14892. if (this.vertical) {
  14893. this.vertical.destroy();
  14894. eventManager.removeEventListener(this.vertical.scrollHandler,'scroll', this.refreshAll);
  14895. }
  14896. if (this.horizontal) {
  14897. this.horizontal.destroy();
  14898. eventManager.removeEventListener(this.horizontal.scrollHandler,'scroll', this.refreshAll);
  14899. }
  14900. eventManager.removeEventListener(window,'scroll', this.refreshAll);
  14901. this.corner && this.corner.destroy();
  14902. this.debug && this.debug.destroy();
  14903. };
  14904. WalkontableScrollbars.prototype.refresh = function (fastDraw) {
  14905. this.horizontal && this.horizontal.refresh(fastDraw);
  14906. this.vertical && this.vertical.refresh(fastDraw);
  14907. this.corner && this.corner.refresh(fastDraw);
  14908. this.debug && this.debug.refresh(fastDraw);
  14909. };
  14910. WalkontableScrollbars.prototype.applyToDOM = function () {
  14911. this.horizontal && this.horizontal.applyToDOM();
  14912. this.vertical && this.vertical.applyToDOM();
  14913. };
  14914. function WalkontableSelection(settings, cellRange) {
  14915. this.settings = settings;
  14916. this.cellRange = cellRange || null;
  14917. this.instanceBorders = {};
  14918. }
  14919. /**
  14920. * Each Walkontable clone requires it's own border for every selection. This method creates and returns selection borders per instance
  14921. * @param {Walkontable} instance
  14922. * @returns {WalkontableBorder}
  14923. */
  14924. WalkontableSelection.prototype.getBorder = function (instance) {
  14925. if (this.instanceBorders[instance.guid]) {
  14926. return this.instanceBorders[instance.guid];
  14927. }
  14928. //where is this returned?
  14929. this.instanceBorders[instance.guid] = new WalkontableBorder(instance, this.settings);
  14930. };
  14931. /**
  14932. * Returns boolean information if selection is empty
  14933. * @returns {boolean}
  14934. */
  14935. WalkontableSelection.prototype.isEmpty = function () {
  14936. return this.cellRange === null;
  14937. };
  14938. /**
  14939. * Adds a cell coords to the selection
  14940. * @param {WalkontableCellCoords} coords
  14941. */
  14942. WalkontableSelection.prototype.add = function (coords) {
  14943. if (this.isEmpty()) {
  14944. this.cellRange = new WalkontableCellRange(coords, coords, coords);
  14945. }
  14946. else {
  14947. this.cellRange.expand(coords);
  14948. }
  14949. };
  14950. /**
  14951. * If selection range from or to property equals oldCoords, replace it with newCoords. Return boolean information about success
  14952. * @param {WalkontableCellCoords} oldCoords
  14953. * @param {WalkontableCellCoords} newCoords
  14954. * @return {boolean}
  14955. */
  14956. WalkontableSelection.prototype.replace = function (oldCoords, newCoords) {
  14957. if (!this.isEmpty()) {
  14958. if (this.cellRange.from.isEqual(oldCoords)) {
  14959. this.cellRange.from = newCoords;
  14960. return true;
  14961. }
  14962. if (this.cellRange.to.isEqual(oldCoords)) {
  14963. this.cellRange.to = newCoords;
  14964. return true;
  14965. }
  14966. }
  14967. return false;
  14968. };
  14969. WalkontableSelection.prototype.clear = function () {
  14970. this.cellRange = null;
  14971. };
  14972. /**
  14973. * Returns the top left (TL) and bottom right (BR) selection coordinates
  14974. * @returns {Object}
  14975. */
  14976. WalkontableSelection.prototype.getCorners = function () {
  14977. var
  14978. topLeft = this.cellRange.getTopLeftCorner(),
  14979. bottomRight = this.cellRange.getBottomRightCorner();
  14980. return [topLeft.row, topLeft.col, bottomRight.row, bottomRight.col];
  14981. };
  14982. WalkontableSelection.prototype.addClassAtCoords = function (instance, sourceRow, sourceColumn, cls) {
  14983. var TD = instance.wtTable.getCell(new WalkontableCellCoords(sourceRow, sourceColumn));
  14984. if (typeof TD === 'object') {
  14985. Handsontable.Dom.addClass(TD, cls);
  14986. }
  14987. };
  14988. WalkontableSelection.prototype.draw = function (instance) {
  14989. var
  14990. _this = this,
  14991. renderedRows = instance.wtTable.getRenderedRowsCount(),
  14992. renderedColumns = instance.wtTable.getRenderedColumnsCount(),
  14993. corners, sourceRow, sourceCol, border, TH;
  14994. if (this.isEmpty()) {
  14995. if (this.settings.border) {
  14996. border = this.getBorder(instance);
  14997. if (border) {
  14998. border.disappear();
  14999. }
  15000. }
  15001. return;
  15002. }
  15003. corners = this.getCorners();
  15004. for (var column = 0; column < renderedColumns; column++) {
  15005. sourceCol = instance.wtTable.columnFilter.renderedToSource(column);
  15006. if (sourceCol >= corners[1] && sourceCol <= corners[3]) {
  15007. TH = instance.wtTable.getColumnHeader(sourceCol);
  15008. if (TH && _this.settings.highlightColumnClassName) {
  15009. Handsontable.Dom.addClass(TH, _this.settings.highlightColumnClassName);
  15010. }
  15011. }
  15012. }
  15013. for (var row = 0; row < renderedRows; row++) {
  15014. sourceRow = instance.wtTable.rowFilter.renderedToSource(row);
  15015. if (sourceRow >= corners[0] && sourceRow <= corners[2]) {
  15016. TH = instance.wtTable.getRowHeader(sourceRow);
  15017. if (TH && _this.settings.highlightRowClassName) {
  15018. Handsontable.Dom.addClass(TH, _this.settings.highlightRowClassName);
  15019. }
  15020. }
  15021. for (var column = 0; column < renderedColumns; column++) {
  15022. sourceCol = instance.wtTable.columnFilter.renderedToSource(column);
  15023. if (sourceRow >= corners[0] && sourceRow <= corners[2] && sourceCol >= corners[1] && sourceCol <= corners[3]) {
  15024. // selected cell
  15025. if (_this.settings.className) {
  15026. _this.addClassAtCoords(instance, sourceRow, sourceCol, _this.settings.className);
  15027. }
  15028. }
  15029. else if (sourceRow >= corners[0] && sourceRow <= corners[2]) {
  15030. // selection is in this row
  15031. if (_this.settings.highlightRowClassName) {
  15032. _this.addClassAtCoords(instance, sourceRow, sourceCol, _this.settings.highlightRowClassName);
  15033. }
  15034. }
  15035. else if (sourceCol >= corners[1] && sourceCol <= corners[3]) {
  15036. // selection is in this column
  15037. if (_this.settings.highlightColumnClassName) {
  15038. _this.addClassAtCoords(instance, sourceRow, sourceCol, _this.settings.highlightColumnClassName);
  15039. }
  15040. }
  15041. }
  15042. }
  15043. instance.getSetting('onBeforeDrawBorders', corners, this.settings.className);
  15044. if (this.settings.border) {
  15045. border = this.getBorder(instance);
  15046. if (border) {
  15047. // warning! border.appear modifies corners!
  15048. border.appear(corners);
  15049. }
  15050. }
  15051. };
  15052. function WalkontableSettings(instance, settings) {
  15053. var that = this;
  15054. this.instance = instance;
  15055. //default settings. void 0 means it is required, null means it can be empty
  15056. this.defaults = {
  15057. table: void 0,
  15058. debug: false, //shows WalkontableDebugOverlay
  15059. //presentation mode
  15060. stretchH: 'none', //values: all, last, none
  15061. currentRowClassName: null,
  15062. currentColumnClassName: null,
  15063. //data source
  15064. data: void 0,
  15065. fixedColumnsLeft: 0,
  15066. fixedRowsTop: 0,
  15067. rowHeaders: function () {
  15068. return []
  15069. }, //this must be array of functions: [function (row, TH) {}]
  15070. columnHeaders: function () {
  15071. return []
  15072. }, //this must be array of functions: [function (column, TH) {}]
  15073. totalRows: void 0,
  15074. totalColumns: void 0,
  15075. cellRenderer: function (row, column, TD) {
  15076. var cellData = that.getSetting('data', row, column);
  15077. Handsontable.Dom.fastInnerText(TD, cellData === void 0 || cellData === null ? '' : cellData);
  15078. },
  15079. //columnWidth: 50,
  15080. columnWidth: function (col) {
  15081. return; //return undefined means use default size for the rendered cell content
  15082. },
  15083. rowHeight: function (row) {
  15084. return; //return undefined means use default size for the rendered cell content
  15085. },
  15086. defaultRowHeight: 23,
  15087. defaultColumnWidth: 50,
  15088. selections: null,
  15089. hideBorderOnMouseDownOver: false,
  15090. viewportRowCalculatorOverride: null,
  15091. viewportColumnCalculatorOverride: null,
  15092. //callbacks
  15093. onCellMouseDown: null,
  15094. onCellMouseOver: null,
  15095. // onCellMouseOut: null,
  15096. onCellDblClick: null,
  15097. onCellCornerMouseDown: null,
  15098. onCellCornerDblClick: null,
  15099. beforeDraw: null,
  15100. onDraw: null,
  15101. onBeforeDrawBorders: null,
  15102. onScrollVertically: null,
  15103. onScrollHorizontally: null,
  15104. onBeforeTouchScroll: null,
  15105. onAfterMomentumScroll: null,
  15106. //constants
  15107. scrollbarWidth: 10,
  15108. scrollbarHeight: 10,
  15109. renderAllRows: false,
  15110. groups: false
  15111. };
  15112. //reference to settings
  15113. this.settings = {};
  15114. for (var i in this.defaults) {
  15115. if (this.defaults.hasOwnProperty(i)) {
  15116. if (settings[i] !== void 0) {
  15117. this.settings[i] = settings[i];
  15118. }
  15119. else if (this.defaults[i] === void 0) {
  15120. throw new Error('A required setting "' + i + '" was not provided');
  15121. }
  15122. else {
  15123. this.settings[i] = this.defaults[i];
  15124. }
  15125. }
  15126. }
  15127. }
  15128. /**
  15129. * generic methods
  15130. */
  15131. WalkontableSettings.prototype.update = function (settings, value) {
  15132. if (value === void 0) { //settings is object
  15133. for (var i in settings) {
  15134. if (settings.hasOwnProperty(i)) {
  15135. this.settings[i] = settings[i];
  15136. }
  15137. }
  15138. }
  15139. else { //if value is defined then settings is the key
  15140. this.settings[settings] = value;
  15141. }
  15142. return this.instance;
  15143. };
  15144. WalkontableSettings.prototype.getSetting = function (key, param1, param2, param3, param4) {
  15145. if (typeof this.settings[key] === 'function') {
  15146. return this.settings[key](param1, param2, param3, param4); //this is faster than .apply - https://github.com/handsontable/handsontable/wiki/JavaScript-&-DOM-performance-tips
  15147. }
  15148. else if (param1 !== void 0 && Array.isArray(this.settings[key])) { //perhaps this can be removed, it is only used in tests
  15149. return this.settings[key][param1];
  15150. }
  15151. else {
  15152. return this.settings[key];
  15153. }
  15154. };
  15155. WalkontableSettings.prototype.has = function (key) {
  15156. return !!this.settings[key]
  15157. };
  15158. function WalkontableTable(instance, table) {
  15159. //reference to instance
  15160. this.instance = instance;
  15161. this.TABLE = table;
  15162. Handsontable.Dom.removeTextNodes(this.TABLE);
  15163. //wtSpreader
  15164. var parent = this.TABLE.parentNode;
  15165. if (!parent || parent.nodeType !== 1 || !Handsontable.Dom.hasClass(parent, 'wtHolder')) {
  15166. var spreader = document.createElement('DIV');
  15167. spreader.className = 'wtSpreader';
  15168. if (parent) {
  15169. 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
  15170. }
  15171. spreader.appendChild(this.TABLE);
  15172. }
  15173. this.spreader = this.TABLE.parentNode;
  15174. //wtHider
  15175. parent = this.spreader.parentNode;
  15176. if (!parent || parent.nodeType !== 1 || !Handsontable.Dom.hasClass(parent, 'wtHolder')) {
  15177. var hider = document.createElement('DIV');
  15178. hider.className = 'wtHider';
  15179. if (parent) {
  15180. 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
  15181. }
  15182. hider.appendChild(this.spreader);
  15183. }
  15184. this.hider = this.spreader.parentNode;
  15185. this.hiderStyle = this.hider.style;
  15186. this.hiderStyle.position = 'relative';
  15187. //wtHolder
  15188. parent = this.hider.parentNode;
  15189. if (!parent || parent.nodeType !== 1 || !Handsontable.Dom.hasClass(parent, 'wtHolder')) {
  15190. var holder = document.createElement('DIV');
  15191. holder.style.position = 'relative';
  15192. holder.className = 'wtHolder';
  15193. if(!instance.cloneSource) {
  15194. holder.className += ' ht_master';
  15195. }
  15196. if (parent) {
  15197. 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
  15198. }
  15199. holder.appendChild(this.hider);
  15200. }
  15201. this.holder = this.hider.parentNode;
  15202. if (!this.isWorkingOnClone()) {
  15203. this.holder.parentNode.style.position = "relative";
  15204. }
  15205. //bootstrap from settings
  15206. this.TBODY = this.TABLE.getElementsByTagName('TBODY')[0];
  15207. if (!this.TBODY) {
  15208. this.TBODY = document.createElement('TBODY');
  15209. this.TABLE.appendChild(this.TBODY);
  15210. }
  15211. this.THEAD = this.TABLE.getElementsByTagName('THEAD')[0];
  15212. if (!this.THEAD) {
  15213. this.THEAD = document.createElement('THEAD');
  15214. this.TABLE.insertBefore(this.THEAD, this.TBODY);
  15215. }
  15216. this.COLGROUP = this.TABLE.getElementsByTagName('COLGROUP')[0];
  15217. if (!this.COLGROUP) {
  15218. this.COLGROUP = document.createElement('COLGROUP');
  15219. this.TABLE.insertBefore(this.COLGROUP, this.THEAD);
  15220. }
  15221. if (this.instance.getSetting('columnHeaders').length) {
  15222. if (!this.THEAD.childNodes.length) {
  15223. var TR = document.createElement('TR');
  15224. this.THEAD.appendChild(TR);
  15225. }
  15226. }
  15227. this.colgroupChildrenLength = this.COLGROUP.childNodes.length;
  15228. this.theadChildrenLength = this.THEAD.firstChild ? this.THEAD.firstChild.childNodes.length : 0;
  15229. this.tbodyChildrenLength = this.TBODY.childNodes.length;
  15230. this.rowFilter = null;
  15231. this.columnFilter = null;
  15232. }
  15233. WalkontableTable.prototype.isWorkingOnClone = function () {
  15234. return !!this.instance.cloneSource;
  15235. };
  15236. /**
  15237. * Redraws the table
  15238. * @param fastDraw {Boolean} If TRUE, will try to avoid full redraw and only update the border positions. If FALSE or UNDEFINED, will perform a full redraw
  15239. * @returns {WalkontableTable}
  15240. */
  15241. WalkontableTable.prototype.draw = function (fastDraw) {
  15242. if (!this.isWorkingOnClone()) {
  15243. this.holderOffset = Handsontable.Dom.offset(this.holder);
  15244. fastDraw = this.instance.wtViewport.createRenderCalculators(fastDraw);
  15245. }
  15246. if (!fastDraw) {
  15247. if (this.isWorkingOnClone()) {
  15248. this.tableOffset = this.instance.cloneSource.wtTable.tableOffset;
  15249. }
  15250. else {
  15251. this.tableOffset = Handsontable.Dom.offset(this.TABLE);
  15252. }
  15253. var startRow;
  15254. if (this.instance.cloneOverlay instanceof WalkontableDebugOverlay
  15255. || this.instance.cloneOverlay instanceof WalkontableVerticalScrollbarNative
  15256. || this.instance.cloneOverlay instanceof WalkontableCornerScrollbarNative) {
  15257. startRow = 0;
  15258. }
  15259. else {
  15260. startRow = this.instance.wtViewport.rowsRenderCalculator.startRow;
  15261. }
  15262. var startColumn;
  15263. if (this.instance.cloneOverlay instanceof WalkontableDebugOverlay
  15264. || this.instance.cloneOverlay instanceof WalkontableHorizontalScrollbarNative
  15265. || this.instance.cloneOverlay instanceof WalkontableCornerScrollbarNative) {
  15266. startColumn = 0;
  15267. } else {
  15268. startColumn = this.instance.wtViewport.columnsRenderCalculator.startColumn;
  15269. }
  15270. this.rowFilter = new WalkontableRowFilter(
  15271. startRow,
  15272. this.instance.getSetting('totalRows'),
  15273. this.instance.getSetting('columnHeaders').length
  15274. );
  15275. this.columnFilter = new WalkontableColumnFilter(
  15276. startColumn,
  15277. this.instance.getSetting('totalColumns'),
  15278. this.instance.getSetting('rowHeaders').length
  15279. );
  15280. this._doDraw(); //creates calculator after draw
  15281. }
  15282. else {
  15283. if (!this.isWorkingOnClone()) {
  15284. this.instance.wtViewport.createVisibleCalculators(); //in case we only scrolled without redraw, update visible rows information in oldRowsCalculator
  15285. }
  15286. this.instance.wtScrollbars && this.instance.wtScrollbars.refresh(true);
  15287. }
  15288. this.refreshSelections(fastDraw);
  15289. if (!this.isWorkingOnClone()) {
  15290. this.instance.wtScrollbars.vertical.resetFixedPosition();
  15291. this.instance.wtScrollbars.horizontal.resetFixedPosition();
  15292. this.instance.wtScrollbars.corner.resetFixedPosition();
  15293. }
  15294. this.instance.drawn = true;
  15295. return this;
  15296. };
  15297. WalkontableTable.prototype._doDraw = function () {
  15298. var wtRenderer = new WalkontableTableRenderer(this);
  15299. wtRenderer.render();
  15300. };
  15301. WalkontableTable.prototype.removeClassFromCells = function (className) {
  15302. var nodes = this.TABLE.querySelectorAll('.' + className);
  15303. for (var i = 0, ilen = nodes.length; i < ilen; i++) {
  15304. Handsontable.Dom.removeClass(nodes[i], className);
  15305. }
  15306. };
  15307. WalkontableTable.prototype.refreshSelections = function (fastDraw) {
  15308. var i, len;
  15309. if (!this.instance.selections) {
  15310. return;
  15311. }
  15312. len = this.instance.selections.length;
  15313. if (fastDraw) {
  15314. for (i = 0; i < len; i++) {
  15315. // there was no rerender, so we need to remove classNames by ourselves
  15316. if (this.instance.selections[i].settings.className) {
  15317. this.removeClassFromCells(this.instance.selections[i].settings.className);
  15318. }
  15319. if (this.instance.selections[i].settings.highlightRowClassName) {
  15320. this.removeClassFromCells(this.instance.selections[i].settings.highlightRowClassName);
  15321. }
  15322. if (this.instance.selections[i].settings.highlightColumnClassName) {
  15323. this.removeClassFromCells(this.instance.selections[i].settings.highlightColumnClassName);
  15324. }
  15325. }
  15326. }
  15327. for (i = 0; i < len; i++) {
  15328. this.instance.selections[i].draw(this.instance, fastDraw);
  15329. }
  15330. };
  15331. /**
  15332. * getCell
  15333. * @param {WalkontableCellCoords} coords
  15334. * @return {Object} HTMLElement on success or {Number} one of the exit codes on error:
  15335. * -1 row before viewport
  15336. * -2 row after viewport
  15337. *
  15338. */
  15339. WalkontableTable.prototype.getCell = function (coords) {
  15340. if (this.isRowBeforeRenderedRows(coords.row)) {
  15341. return -1; //row before rendered rows
  15342. }
  15343. else if (this.isRowAfterRenderedRows(coords.row)) {
  15344. return -2; //row after rendered rows
  15345. }
  15346. var TR = this.TBODY.childNodes[this.rowFilter.sourceToRendered(coords.row)];
  15347. if (TR) {
  15348. return TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(coords.col)];
  15349. }
  15350. };
  15351. /**
  15352. * getColumnHeader
  15353. * @param col
  15354. * @return {Object} HTMLElement on success or undefined on error
  15355. *
  15356. */
  15357. WalkontableTable.prototype.getColumnHeader = function(col) {
  15358. var THEAD = this.THEAD.childNodes[0];
  15359. if (THEAD) {
  15360. return THEAD.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(col)];
  15361. }
  15362. };
  15363. /**
  15364. * getRowHeader
  15365. * @param row
  15366. * @return {Object} HTMLElement on success or {Number} one of the exit codes on error:
  15367. * null table doesn't have row headers
  15368. *
  15369. */
  15370. WalkontableTable.prototype.getRowHeader = function(row) {
  15371. if(this.columnFilter.sourceColumnToVisibleRowHeadedColumn(0) == 0) {
  15372. return null;
  15373. }
  15374. var TR = this.TBODY.childNodes[this.rowFilter.sourceToRendered(row)];
  15375. if (TR) {
  15376. return TR.childNodes[0];
  15377. }
  15378. };
  15379. /**
  15380. * Returns cell coords object for a given TD
  15381. * @param TD
  15382. * @returns {WalkontableCellCoords}
  15383. */
  15384. WalkontableTable.prototype.getCoords = function (TD) {
  15385. var TR = TD.parentNode;
  15386. var row = Handsontable.Dom.index(TR);
  15387. if (TR.parentNode === this.THEAD) {
  15388. row = this.rowFilter.visibleColHeadedRowToSourceRow(row);
  15389. }
  15390. else {
  15391. row = this.rowFilter.renderedToSource(row);
  15392. }
  15393. return new WalkontableCellCoords(
  15394. row,
  15395. this.columnFilter.visibleRowHeadedColumnToSourceColumn(TD.cellIndex)
  15396. );
  15397. };
  15398. WalkontableTable.prototype.getTrForRow = function (row) {
  15399. return this.TBODY.childNodes[this.rowFilter.sourceToRendered(row)];
  15400. };
  15401. WalkontableTable.prototype.getFirstRenderedRow = function () {
  15402. return this.instance.wtViewport.rowsRenderCalculator.startRow;
  15403. };
  15404. WalkontableTable.prototype.getFirstVisibleRow = function () {
  15405. return this.instance.wtViewport.rowsVisibleCalculator.startRow;
  15406. };
  15407. WalkontableTable.prototype.getFirstRenderedColumn = function () {
  15408. return this.instance.wtViewport.columnsRenderCalculator.startColumn;
  15409. };
  15410. //returns -1 if no column is visible
  15411. WalkontableTable.prototype.getFirstVisibleColumn = function () {
  15412. return this.instance.wtViewport.columnsVisibleCalculator.startColumn;
  15413. };
  15414. //returns -1 if no row is visible
  15415. WalkontableTable.prototype.getLastRenderedRow = function () {
  15416. return this.instance.wtViewport.rowsRenderCalculator.endRow;
  15417. };
  15418. WalkontableTable.prototype.getLastVisibleRow = function () {
  15419. return this.instance.wtViewport.rowsVisibleCalculator.endRow;
  15420. };
  15421. WalkontableTable.prototype.getLastRenderedColumn = function () {
  15422. return this.instance.wtViewport.columnsRenderCalculator.endColumn;
  15423. };
  15424. //returns -1 if no column is visible
  15425. WalkontableTable.prototype.getLastVisibleColumn = function () {
  15426. return this.instance.wtViewport.columnsVisibleCalculator.endColumn;
  15427. };
  15428. WalkontableTable.prototype.isRowBeforeRenderedRows = function (r) {
  15429. return (this.rowFilter.sourceToRendered(r) < 0 && r >= 0);
  15430. };
  15431. WalkontableTable.prototype.isRowAfterViewport = function (r) {
  15432. return (r > this.getLastVisibleRow());
  15433. };
  15434. WalkontableTable.prototype.isRowAfterRenderedRows = function (r) {
  15435. return (r > this.getLastRenderedRow());
  15436. };
  15437. WalkontableTable.prototype.isColumnBeforeViewport = function (c) {
  15438. return (this.columnFilter.sourceToRendered(c) < 0 && c >= 0);
  15439. };
  15440. WalkontableTable.prototype.isColumnAfterViewport = function (c) {
  15441. return (c > this.getLastVisibleColumn());
  15442. };
  15443. WalkontableTable.prototype.isLastRowFullyVisible = function () {
  15444. return (this.getLastVisibleRow() === this.getLastRenderedRow());
  15445. };
  15446. WalkontableTable.prototype.isLastColumnFullyVisible = function () {
  15447. return (this.getLastVisibleColumn() === this.getLastRenderedColumn);
  15448. };
  15449. WalkontableTable.prototype.getRenderedColumnsCount = function () {
  15450. if (this.instance.cloneOverlay instanceof WalkontableDebugOverlay) {
  15451. return this.instance.getSetting('totalColumns');
  15452. }
  15453. else if (this.instance.cloneOverlay instanceof WalkontableHorizontalScrollbarNative || this.instance.cloneOverlay instanceof WalkontableCornerScrollbarNative) {
  15454. return this.instance.getSetting('fixedColumnsLeft');
  15455. }
  15456. else {
  15457. return this.instance.wtViewport.columnsRenderCalculator.count;
  15458. }
  15459. };
  15460. WalkontableTable.prototype.getRenderedRowsCount = function () {
  15461. if (this.instance.cloneOverlay instanceof WalkontableDebugOverlay) {
  15462. return this.instance.getSetting('totalRows');
  15463. }
  15464. else if (this.instance.cloneOverlay instanceof WalkontableVerticalScrollbarNative || this.instance.cloneOverlay instanceof WalkontableCornerScrollbarNative) {
  15465. return this.instance.getSetting('fixedRowsTop');
  15466. }
  15467. return this.instance.wtViewport.rowsRenderCalculator.count;
  15468. };
  15469. WalkontableTable.prototype.getVisibleRowsCount = function () {
  15470. return this.instance.wtViewport.rowsVisibleCalculator.count;
  15471. };
  15472. WalkontableTable.prototype.allRowsInViewport = function () {
  15473. return this.instance.getSetting('totalRows') == this.getVisibleRowsCount();
  15474. };
  15475. /**
  15476. * Checks if any of the row's cells content exceeds its initial height, and if so, returns the oversized height
  15477. * @param {Number} sourceRow
  15478. * @return {Number}
  15479. */
  15480. WalkontableTable.prototype.getRowHeight = function (sourceRow) {
  15481. var height = this.instance.wtSettings.settings.rowHeight(sourceRow);
  15482. var oversizedHeight = this.instance.wtViewport.oversizedRows[sourceRow];
  15483. if (oversizedHeight !== void 0) {
  15484. height = height ? Math.max(height, oversizedHeight) : oversizedHeight;
  15485. }
  15486. return height;
  15487. };
  15488. WalkontableTable.prototype.getVisibleColumnsCount = function () {
  15489. return this.instance.wtViewport.columnsVisibleCalculator.count;
  15490. };
  15491. WalkontableTable.prototype.allColumnsInViewport = function () {
  15492. return this.instance.getSetting('totalColumns') == this.getVisibleColumnsCount();
  15493. };
  15494. WalkontableTable.prototype.getColumnWidth = function (sourceColumn) {
  15495. var width = this.instance.wtSettings.settings.columnWidth;
  15496. if(typeof width === 'function') {
  15497. width = width(sourceColumn);
  15498. } else if(typeof width === 'object') {
  15499. width = width[sourceColumn];
  15500. }
  15501. var oversizedWidth = this.instance.wtViewport.oversizedCols[sourceColumn];
  15502. if (oversizedWidth !== void 0) {
  15503. width = width ? Math.max(width, oversizedWidth) : oversizedWidth;
  15504. }
  15505. return width;
  15506. };
  15507. WalkontableTable.prototype.getStretchedColumnWidth = function (sourceColumn) {
  15508. var allColumns = this.instance.getSetting('totalColumns');
  15509. var width = this.getColumnWidth(sourceColumn) || this.instance.wtSettings.settings.defaultColumnWidth;
  15510. if(this.instance.wtViewport.columnsRenderCalculator) {
  15511. if (this.instance.wtViewport.columnsRenderCalculator.stretchAllRatio != 0) {
  15512. width = width * this.instance.wtViewport.columnsRenderCalculator.stretchAllRatio;
  15513. } else if (this.instance.wtViewport.columnsRenderCalculator.stretchLastWidth != 0) {
  15514. if (sourceColumn == allColumns - 1) {
  15515. width = this.instance.wtViewport.columnsRenderCalculator.stretchLastWidth;
  15516. }
  15517. }
  15518. }
  15519. return width;
  15520. };
  15521. function WalkontableTableRenderer(wtTable) {
  15522. this.wtTable = wtTable;
  15523. this.instance = wtTable.instance;
  15524. this.rowFilter = wtTable.rowFilter;
  15525. this.columnFilter = wtTable.columnFilter;
  15526. this.TABLE = wtTable.TABLE;
  15527. this.THEAD = wtTable.THEAD;
  15528. this.TBODY = wtTable.TBODY;
  15529. this.COLGROUP = wtTable.COLGROUP;
  15530. this.utils = WalkontableTableRenderer.utils;
  15531. }
  15532. WalkontableTableRenderer.prototype.render = function () {
  15533. if (!this.wtTable.isWorkingOnClone()) {
  15534. this.instance.getSetting('beforeDraw', true);
  15535. }
  15536. this.rowHeaders = this.instance.getSetting('rowHeaders');
  15537. this.rowHeaderCount = this.rowHeaders.length;
  15538. this.fixedRowsTop = this.instance.getSetting('fixedRowsTop');
  15539. this.columnHeaders = this.instance.getSetting('columnHeaders');
  15540. this.columnHeaderCount = this.columnHeaders.length;
  15541. var visibleColIndex
  15542. , totalRows = this.instance.getSetting('totalRows')
  15543. , totalColumns = this.instance.getSetting('totalColumns')
  15544. , displayTds
  15545. , adjusted = false
  15546. , workspaceWidth
  15547. , cloneLimit = this.wtTable.getRenderedRowsCount();
  15548. if (totalColumns > 0) {
  15549. this.adjustAvailableNodes();
  15550. adjusted = true;
  15551. this.renderColGroups();
  15552. this.renderColumnHeaders();
  15553. displayTds = this.wtTable.getRenderedColumnsCount();
  15554. //Render table rows
  15555. this.renderRows(totalRows, cloneLimit, displayTds);
  15556. if (!this.wtTable.isWorkingOnClone()) {
  15557. workspaceWidth = this.instance.wtViewport.getWorkspaceWidth();
  15558. this.instance.wtViewport.containerWidth = null;
  15559. }
  15560. this.adjustColumnWidths(displayTds);
  15561. }
  15562. if (!adjusted) {
  15563. this.adjustAvailableNodes();
  15564. }
  15565. this.removeRedundantRows(cloneLimit);
  15566. if (!this.wtTable.isWorkingOnClone()) {
  15567. this.markOversizedRows();
  15568. this.instance.wtViewport.createVisibleCalculators();
  15569. this.instance.wtScrollbars.applyToDOM();
  15570. if (workspaceWidth !== this.instance.wtViewport.getWorkspaceWidth()) {
  15571. //workspace width changed though to shown/hidden vertical scrollbar. Let's reapply stretching
  15572. this.instance.wtViewport.containerWidth = null;
  15573. var firstRendered = this.wtTable.getFirstRenderedColumn();
  15574. var lastRendered = this.wtTable.getLastRenderedColumn();
  15575. for (var i = firstRendered ; i < lastRendered; i++) {
  15576. var width = this.wtTable.getStretchedColumnWidth(i);
  15577. var renderedIndex = this.columnFilter.sourceToRendered(i);
  15578. this.COLGROUP.childNodes[renderedIndex + this.rowHeaderCount].style.width = width + 'px';
  15579. }
  15580. }
  15581. this.instance.wtScrollbars.refresh(false);
  15582. this.instance.getSetting('onDraw', true);
  15583. }
  15584. };
  15585. WalkontableTableRenderer.prototype.removeRedundantRows = function (renderedRowsCount) {
  15586. while (this.wtTable.tbodyChildrenLength > renderedRowsCount) {
  15587. this.TBODY.removeChild(this.TBODY.lastChild);
  15588. this.wtTable.tbodyChildrenLength--;
  15589. }
  15590. };
  15591. WalkontableTableRenderer.prototype.renderRows = function (totalRows, cloneLimit, displayTds) {
  15592. var lastTD, TR;
  15593. var visibleRowIndex = 0;
  15594. var sourceRowIndex = this.rowFilter.renderedToSource(visibleRowIndex);
  15595. var isWorkingOnClone = this.wtTable.isWorkingOnClone();
  15596. while (sourceRowIndex < totalRows && sourceRowIndex >= 0) {
  15597. if (visibleRowIndex > 1000) {
  15598. throw new Error('Security brake: Too much TRs. Please define height for your table, which will enforce scrollbars.');
  15599. }
  15600. if (cloneLimit !== void 0 && visibleRowIndex === cloneLimit) {
  15601. break; //we have as much rows as needed for this clone
  15602. }
  15603. TR = this.getOrCreateTrForRow(visibleRowIndex, TR);
  15604. //Render row headers
  15605. this.renderRowHeaders(sourceRowIndex, TR);
  15606. this.adjustColumns(TR, displayTds + this.rowHeaderCount);
  15607. lastTD = this.renderCells(sourceRowIndex, TR, displayTds);
  15608. //after last column is rendered, check if last cell is fully displayed
  15609. if (!isWorkingOnClone) {
  15610. this.resetOversizedRow(sourceRowIndex);
  15611. }
  15612. if (TR.firstChild) {
  15613. var height = this.instance.wtTable.getRowHeight(sourceRowIndex); //if I have 2 fixed columns with one-line content and the 3rd column has a multiline content, this is the way to make sure that the overlay will has same row height
  15614. if (height) {
  15615. TR.firstChild.style.height = height + 'px';
  15616. }
  15617. else {
  15618. TR.firstChild.style.height = '';
  15619. }
  15620. }
  15621. visibleRowIndex++;
  15622. sourceRowIndex = this.rowFilter.renderedToSource(visibleRowIndex);
  15623. }
  15624. };
  15625. WalkontableTableRenderer.prototype.resetOversizedRow = function (sourceRow) {
  15626. if (this.instance.wtViewport.oversizedRows && this.instance.wtViewport.oversizedRows[sourceRow]) {
  15627. this.instance.wtViewport.oversizedRows[sourceRow] = void 0; //void 0 is faster than delete, see http://jsperf.com/delete-vs-undefined-vs-null/16
  15628. }
  15629. };
  15630. WalkontableTableRenderer.prototype.markOversizedRows = function () {
  15631. var previousRowHeight
  15632. , trInnerHeight
  15633. , sourceRowIndex
  15634. , currentTr;
  15635. var rowCount = this.instance.wtTable.TBODY.childNodes.length;
  15636. while (rowCount) {
  15637. rowCount--;
  15638. sourceRowIndex = this.instance.wtTable.rowFilter.renderedToSource(rowCount);
  15639. previousRowHeight = this.instance.wtTable.getRowHeight(sourceRowIndex);
  15640. currentTr = this.instance.wtTable.getTrForRow(sourceRowIndex);
  15641. trInnerHeight = Handsontable.Dom.innerHeight(currentTr) - 1;
  15642. if ((!previousRowHeight && this.instance.wtSettings.settings.defaultRowHeight < trInnerHeight || previousRowHeight < trInnerHeight)) {
  15643. this.instance.wtViewport.oversizedRows[sourceRowIndex] = trInnerHeight;
  15644. }
  15645. }
  15646. };
  15647. WalkontableTableRenderer.prototype.renderCells = function (sourceRowIndex, TR, displayTds) {
  15648. var TD, sourceColIndex;
  15649. for (var visibleColIndex = 0; visibleColIndex < displayTds; visibleColIndex++) {
  15650. sourceColIndex = this.columnFilter.renderedToSource(visibleColIndex);
  15651. if (visibleColIndex === 0) {
  15652. TD = TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(sourceColIndex)];
  15653. }
  15654. else {
  15655. TD = TD.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes
  15656. }
  15657. //If the number of headers has been reduced, we need to replace excess TH with TD
  15658. if (TD.nodeName == 'TH') {
  15659. TD = this.utils.replaceThWithTd(TD, TR);
  15660. }
  15661. if (!Handsontable.Dom.hasClass(TD, 'hide')) {
  15662. TD.className = '';
  15663. }
  15664. TD.removeAttribute('style');
  15665. this.instance.wtSettings.settings.cellRenderer(sourceRowIndex, sourceColIndex, TD);
  15666. }
  15667. return TD;
  15668. };
  15669. WalkontableTableRenderer.prototype.adjustColumnWidths = function (displayTds) {
  15670. var width;
  15671. this.instance.wtViewport.columnsRenderCalculator.refreshStretching(this.instance.wtViewport.getViewportWidth());
  15672. for (var renderedColIndex = 0; renderedColIndex < displayTds; renderedColIndex++) {
  15673. width = this.wtTable.getStretchedColumnWidth(this.columnFilter.renderedToSource(renderedColIndex));
  15674. this.COLGROUP.childNodes[renderedColIndex + this.rowHeaderCount].style.width = width + 'px';
  15675. }
  15676. };
  15677. WalkontableTableRenderer.prototype.appendToTbody = function (TR) {
  15678. this.TBODY.appendChild(TR);
  15679. this.wtTable.tbodyChildrenLength++;
  15680. };
  15681. WalkontableTableRenderer.prototype.getOrCreateTrForRow = function (rowIndex, currentTr) {
  15682. var TR;
  15683. if (rowIndex >= this.wtTable.tbodyChildrenLength) {
  15684. TR = this.createRow();
  15685. this.appendToTbody(TR);
  15686. } else if (rowIndex === 0) {
  15687. TR = this.TBODY.firstChild;
  15688. } else {
  15689. TR = currentTr.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes
  15690. }
  15691. return TR;
  15692. };
  15693. WalkontableTableRenderer.prototype.createRow = function () {
  15694. var TR = document.createElement('TR');
  15695. for (var visibleColIndex = 0; visibleColIndex < this.rowHeaderCount; visibleColIndex++) {
  15696. TR.appendChild(document.createElement('TH'));
  15697. }
  15698. return TR;
  15699. };
  15700. WalkontableTableRenderer.prototype.renderRowHeader = function(row, col, TH){
  15701. TH.className = '';
  15702. TH.removeAttribute('style');
  15703. this.rowHeaders[col](row, TH, col);
  15704. };
  15705. WalkontableTableRenderer.prototype.renderRowHeaders = function (row, TR) {
  15706. for (var TH = TR.firstChild, visibleColIndex = 0; visibleColIndex < this.rowHeaderCount; visibleColIndex++) {
  15707. //If the number of row headers increased we need to create TH or replace an existing TD node with TH
  15708. if (!TH) {
  15709. TH = document.createElement('TH');
  15710. TR.appendChild(TH);
  15711. } else if (TH.nodeName == 'TD') {
  15712. TH = this.utils.replaceTdWithTh(TH, TR);
  15713. }
  15714. this.renderRowHeader(row, visibleColIndex, TH);
  15715. TH = TH.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes
  15716. }
  15717. };
  15718. WalkontableTableRenderer.prototype.adjustAvailableNodes = function () {
  15719. //adjust COLGROUP
  15720. this.adjustColGroups();
  15721. //adjust THEAD
  15722. this.adjustThead();
  15723. };
  15724. WalkontableTableRenderer.prototype.renderColumnHeaders = function () {
  15725. if (!this.columnHeaderCount) {
  15726. return;
  15727. }
  15728. var columnCount = this.wtTable.getRenderedColumnsCount(),
  15729. TR,
  15730. renderedColumnIndex;
  15731. for (var i = 0; i < this.columnHeaderCount; i++) {
  15732. TR = this.getTrForColumnHeaders(i);
  15733. for (renderedColumnIndex = (-1) * this.rowHeaderCount; renderedColumnIndex < columnCount; renderedColumnIndex++) {
  15734. var sourceCol = this.columnFilter.renderedToSource(renderedColumnIndex);
  15735. this.renderColumnHeader(i, sourceCol, TR.childNodes[renderedColumnIndex + this.rowHeaderCount]);
  15736. }
  15737. }
  15738. };
  15739. WalkontableTableRenderer.prototype.adjustColGroups = function () {
  15740. var columnCount = this.wtTable.getRenderedColumnsCount();
  15741. //adjust COLGROUP
  15742. while (this.wtTable.colgroupChildrenLength < columnCount + this.rowHeaderCount) {
  15743. this.COLGROUP.appendChild(document.createElement('COL'));
  15744. this.wtTable.colgroupChildrenLength++;
  15745. }
  15746. while (this.wtTable.colgroupChildrenLength > columnCount + this.rowHeaderCount) {
  15747. this.COLGROUP.removeChild(this.COLGROUP.lastChild);
  15748. this.wtTable.colgroupChildrenLength--;
  15749. }
  15750. };
  15751. WalkontableTableRenderer.prototype.adjustThead = function () {
  15752. var columnCount = this.wtTable.getRenderedColumnsCount();
  15753. var TR = this.THEAD.firstChild;
  15754. if (this.columnHeaders.length) {
  15755. for (var i = 0, columnHeadersLength = this.columnHeaders.length; i < columnHeadersLength; i++) {
  15756. TR = this.THEAD.childNodes[i];
  15757. if (!TR) {
  15758. TR = document.createElement('TR');
  15759. this.THEAD.appendChild(TR);
  15760. }
  15761. this.theadChildrenLength = TR.childNodes.length;
  15762. while (this.theadChildrenLength < columnCount + this.rowHeaderCount) {
  15763. TR.appendChild(document.createElement('TH'));
  15764. this.theadChildrenLength++;
  15765. }
  15766. while (this.theadChildrenLength > columnCount + this.rowHeaderCount) {
  15767. TR.removeChild(TR.lastChild);
  15768. this.theadChildrenLength--;
  15769. }
  15770. }
  15771. var theadChildrenLength = this.THEAD.childNodes.length;
  15772. if(theadChildrenLength > this.columnHeaders.length) {
  15773. for(var i = this.columnHeaders.length; i < theadChildrenLength; i++ ) {
  15774. this.THEAD.removeChild(this.THEAD.lastChild);
  15775. }
  15776. }
  15777. }
  15778. else if (TR) {
  15779. Handsontable.Dom.empty(TR);
  15780. }
  15781. };
  15782. WalkontableTableRenderer.prototype.getTrForColumnHeaders = function (index) {
  15783. var TR = this.THEAD.childNodes[index];
  15784. // if (this.rowHeaderCount) {
  15785. // for(var i = 0; i < this.rowHeaderCount; i++) {
  15786. // this.renderRowHeaders(i - this.rowHeaderCount, TR);
  15787. // }
  15788. // }
  15789. return TR;
  15790. };
  15791. WalkontableTableRenderer.prototype.renderColumnHeader = function (row, col, TH) {
  15792. TH.className = '';
  15793. TH.removeAttribute('style');
  15794. return this.columnHeaders[row](col, TH, row);
  15795. };
  15796. WalkontableTableRenderer.prototype.renderColGroups = function () {
  15797. for (var colIndex = 0; colIndex < this.wtTable.colgroupChildrenLength; colIndex++) {
  15798. if (colIndex < this.rowHeaderCount) {
  15799. Handsontable.Dom.addClass(this.COLGROUP.childNodes[colIndex], 'rowHeader');
  15800. }
  15801. else {
  15802. Handsontable.Dom.removeClass(this.COLGROUP.childNodes[colIndex], 'rowHeader');
  15803. }
  15804. }
  15805. };
  15806. WalkontableTableRenderer.prototype.adjustColumns = function (TR, desiredCount) {
  15807. var count = TR.childNodes.length;
  15808. while (count < desiredCount) {
  15809. var TD = document.createElement('TD');
  15810. TR.appendChild(TD);
  15811. count++;
  15812. }
  15813. while (count > desiredCount) {
  15814. TR.removeChild(TR.lastChild);
  15815. count--;
  15816. }
  15817. };
  15818. WalkontableTableRenderer.prototype.removeRedundantColumns = function (renderedColumnsCount) {
  15819. while (this.wtTable.tbodyChildrenLength > renderedColumnsCount) {
  15820. this.TBODY.removeChild(this.TBODY.lastChild);
  15821. this.wtTable.tbodyChildrenLength--;
  15822. }
  15823. };
  15824. /*
  15825. Helper functions, which does not have any side effects
  15826. */
  15827. WalkontableTableRenderer.utils = {};
  15828. WalkontableTableRenderer.utils.replaceTdWithTh = function (TD, TR) {
  15829. var TH;
  15830. TH = document.createElement('TH');
  15831. TR.insertBefore(TH, TD);
  15832. TR.removeChild(TD);
  15833. return TH;
  15834. };
  15835. WalkontableTableRenderer.utils.replaceThWithTd = function (TH, TR) {
  15836. var TD = document.createElement('TD');
  15837. TR.insertBefore(TD, TH);
  15838. TR.removeChild(TH);
  15839. return TD;
  15840. };
  15841. function WalkontableViewport(instance) {
  15842. this.instance = instance;
  15843. this.oversizedRows = [];
  15844. this.oversizedCols = [];
  15845. var that = this;
  15846. var eventManager = Handsontable.eventManager(instance);
  15847. eventManager.addEventListener(window,'resize',function () {
  15848. that.clientHeight = that.getWorkspaceHeight();
  15849. });
  15850. }
  15851. WalkontableViewport.prototype.getWorkspaceHeight = function () {
  15852. var scrollHandler = this.instance.wtScrollbars.vertical.scrollHandler;
  15853. if (scrollHandler === window) {
  15854. return document.documentElement.clientHeight;
  15855. }
  15856. else {
  15857. var elemHeight = Handsontable.Dom.outerHeight(scrollHandler);
  15858. var height = (elemHeight > 0 && scrollHandler.clientHeight > 0) ? scrollHandler.clientHeight : Infinity; //returns height without DIV scrollbar
  15859. return height;
  15860. }
  15861. };
  15862. WalkontableViewport.prototype.getWorkspaceWidth = function () {
  15863. var width;
  15864. var totalColumns = this.instance.getSetting("totalColumns");
  15865. var scrollHandler = this.instance.wtScrollbars.horizontal.scrollHandler;
  15866. if(Handsontable.freezeOverlays) {
  15867. width = Math.min(document.documentElement.offsetWidth - this.getWorkspaceOffset().left, document.documentElement.offsetWidth);
  15868. } else {
  15869. width = Math.min(this.getContainerFillWidth(), document.documentElement.offsetWidth - this.getWorkspaceOffset().left, document.documentElement.offsetWidth);
  15870. }
  15871. if (scrollHandler === window && totalColumns > 0 && this.sumColumnWidths(0, totalColumns - 1) > width) {
  15872. //in case sum of column widths is higher than available stylesheet width, let's assume using the whole window
  15873. //otherwise continue below, which will allow stretching
  15874. //this is used in `scroll_window.html`
  15875. //TODO test me
  15876. return document.documentElement.clientWidth;
  15877. }
  15878. if (scrollHandler !== window){
  15879. var overflow = this.instance.wtScrollbars.horizontal.scrollHandler.style.overflow;
  15880. if (overflow == "scroll" || overflow == "hidden" || overflow == "auto") {
  15881. //this is used in `scroll.html`
  15882. //TODO test me
  15883. return Math.max(width, scrollHandler.clientWidth);
  15884. }
  15885. }
  15886. //this is used in `stretch.html`, `stretch_window.html`
  15887. //TODO test me
  15888. return Math.max(width, Handsontable.Dom.outerWidth(this.instance.wtTable.TABLE));
  15889. };
  15890. WalkontableViewport.prototype.sumColumnWidths = function (from, length) {
  15891. var sum = 0;
  15892. while(from < length) {
  15893. sum += this.instance.wtTable.getColumnWidth(from) || this.instance.wtSettings.defaultColumnWidth;
  15894. from++;
  15895. }
  15896. return sum;
  15897. };
  15898. WalkontableViewport.prototype.getContainerFillWidth = function() {
  15899. if(this.containerWidth) {
  15900. return this.containerWidth;
  15901. }
  15902. var mainContainer = this.instance.wtTable.holder,
  15903. fillWidth,
  15904. dummyElement;
  15905. while(mainContainer.parentNode != document.body && mainContainer.parentNode != null && mainContainer.className.indexOf('handsontable') === -1) {
  15906. mainContainer = mainContainer.parentNode;
  15907. }
  15908. dummyElement = document.createElement("DIV");
  15909. dummyElement.style.width = "100%";
  15910. dummyElement.style.height = "1px";
  15911. mainContainer.appendChild(dummyElement);
  15912. fillWidth = dummyElement.offsetWidth;
  15913. this.containerWidth = fillWidth;
  15914. mainContainer.removeChild(dummyElement);
  15915. return fillWidth;
  15916. };
  15917. WalkontableViewport.prototype.getWorkspaceOffset = function () {
  15918. return Handsontable.Dom.offset(this.instance.wtTable.TABLE);
  15919. };
  15920. WalkontableViewport.prototype.getWorkspaceActualHeight = function () {
  15921. return Handsontable.Dom.outerHeight(this.instance.wtTable.TABLE);
  15922. };
  15923. WalkontableViewport.prototype.getWorkspaceActualWidth = function () {
  15924. return Handsontable.Dom.outerWidth(this.instance.wtTable.TABLE) || Handsontable.Dom.outerWidth(this.instance.wtTable.TBODY) || Handsontable.Dom.outerWidth(this.instance.wtTable.THEAD); //IE8 reports 0 as <table> offsetWidth;
  15925. };
  15926. WalkontableViewport.prototype.getColumnHeaderHeight = function () {
  15927. if (isNaN(this.columnHeaderHeight)) {
  15928. this.columnHeaderHeight = Handsontable.Dom.outerHeight(this.instance.wtTable.THEAD);
  15929. }
  15930. return this.columnHeaderHeight;
  15931. };
  15932. WalkontableViewport.prototype.getViewportHeight = function () {
  15933. var containerHeight = this.getWorkspaceHeight();
  15934. if (containerHeight === Infinity) {
  15935. return containerHeight;
  15936. }
  15937. var columnHeaderHeight = this.getColumnHeaderHeight();
  15938. if (columnHeaderHeight > 0) {
  15939. containerHeight -= columnHeaderHeight;
  15940. }
  15941. return containerHeight;
  15942. };
  15943. WalkontableViewport.prototype.getRowHeaderWidth = function () {
  15944. if (this.instance.cloneSource) {
  15945. return this.instance.cloneSource.wtViewport.getRowHeaderWidth();
  15946. }
  15947. if (isNaN(this.rowHeaderWidth)) {
  15948. var rowHeaders = this.instance.getSetting('rowHeaders');
  15949. if (rowHeaders.length) {
  15950. var TH = this.instance.wtTable.TABLE.querySelector('TH');
  15951. this.rowHeaderWidth = 0;
  15952. for (var i = 0, ilen = rowHeaders.length; i < ilen; i++) {
  15953. if (TH) {
  15954. this.rowHeaderWidth += Handsontable.Dom.outerWidth(TH);
  15955. TH = TH.nextSibling;
  15956. }
  15957. else {
  15958. this.rowHeaderWidth += 50; //yes this is a cheat but it worked like that before, just taking assumption from CSS instead of measuring. TODO: proper fix
  15959. }
  15960. }
  15961. }
  15962. else {
  15963. this.rowHeaderWidth = 0;
  15964. }
  15965. }
  15966. return this.rowHeaderWidth;
  15967. };
  15968. // Viewport width = Workspace width - Row Headers width
  15969. WalkontableViewport.prototype.getViewportWidth = function () {
  15970. var containerWidth = this.getWorkspaceWidth();
  15971. if (containerWidth === Infinity) {
  15972. return containerWidth;
  15973. }
  15974. var rowHeaderWidth = this.getRowHeaderWidth();
  15975. if (rowHeaderWidth > 0) {
  15976. return containerWidth - rowHeaderWidth;
  15977. }
  15978. else {
  15979. return containerWidth;
  15980. }
  15981. };
  15982. /**
  15983. * Creates:
  15984. * - rowsRenderCalculator (before draw, to qualify rows for rendering)
  15985. * - rowsVisibleCalculator (after draw, to measure which rows are actually visible)
  15986. * @returns {WalkontableViewportRowsCalculator}
  15987. */
  15988. WalkontableViewport.prototype.createRowsCalculator = function (visible) {
  15989. this.rowHeaderWidth = NaN;
  15990. var height;
  15991. if (this.instance.wtSettings.settings.renderAllRows) {
  15992. height = Infinity;
  15993. }
  15994. else {
  15995. height = this.getViewportHeight();
  15996. }
  15997. var pos = this.instance.wtScrollbars.vertical.getScrollPosition() - this.instance.wtScrollbars.vertical.getTableParentOffset();
  15998. if (pos < 0) {
  15999. pos = 0;
  16000. }
  16001. var fixedRowsTop = this.instance.getSetting('fixedRowsTop');
  16002. if(fixedRowsTop) {
  16003. var fixedRowsHeight = this.instance.wtScrollbars.vertical.sumCellSizes(0, fixedRowsTop);
  16004. pos += fixedRowsHeight;
  16005. height -= fixedRowsHeight;
  16006. }
  16007. var that = this;
  16008. return new WalkontableViewportRowsCalculator(
  16009. height,
  16010. pos,
  16011. this.instance.getSetting('totalRows'),
  16012. function(sourceRow) {
  16013. return that.instance.wtTable.getRowHeight(sourceRow)
  16014. },
  16015. visible ? null : this.instance.wtSettings.settings.viewportRowCalculatorOverride,
  16016. visible ? true : false
  16017. );
  16018. };
  16019. /**
  16020. * Creates:
  16021. * - columnsRenderCalculator (before draw, to qualify columns for rendering)
  16022. * - columnsVisibleCalculator (after draw, to measure which columns are actually visible)
  16023. * @returns {WalkontableViewportRowsCalculator}
  16024. */
  16025. WalkontableViewport.prototype.createColumnsCalculator = function (visible) {
  16026. this.columnHeaderHeight = NaN;
  16027. var width = this.getViewportWidth();
  16028. var pos = this.instance.wtScrollbars.horizontal.getScrollPosition() - this.instance.wtScrollbars.vertical.getTableParentOffset();
  16029. if (pos < 0) {
  16030. pos = 0;
  16031. }
  16032. var fixedColumnsLeft = this.instance.getSetting('fixedColumnsLeft');
  16033. if(fixedColumnsLeft) {
  16034. var fixedColumnsWidth = this.instance.wtScrollbars.horizontal.sumCellSizes(0, fixedColumnsLeft);
  16035. pos += fixedColumnsWidth;
  16036. width -= fixedColumnsWidth;
  16037. }
  16038. var that = this;
  16039. return new WalkontableViewportColumnsCalculator(
  16040. width,
  16041. pos,
  16042. this.instance.getSetting('totalColumns'),
  16043. function (sourceCol) {
  16044. return that.instance.wtTable.getColumnWidth(sourceCol);
  16045. },
  16046. visible ? null : this.instance.wtSettings.settings.viewportColumnCalculatorOverride,
  16047. visible ? true : false,
  16048. this.instance.getSetting('stretchH')
  16049. )
  16050. };
  16051. /**
  16052. * Creates rowsRenderCalculator and columnsRenderCalculator (before draw, to determine what rows and cols should be rendered)
  16053. * @param fastDraw {Boolean} If TRUE, will try to avoid full redraw and only update the border positions. If FALSE or UNDEFINED, will perform a full redraw
  16054. */
  16055. WalkontableViewport.prototype.createRenderCalculators = function (fastDraw) {
  16056. if (fastDraw) {
  16057. var proposedRowsVisibleCalculator = this.createRowsCalculator(true);
  16058. var proposedColumnsVisibleCalculator = this.createColumnsCalculator(true);
  16059. if (!(this.areAllProposedVisibleRowsAlreadyRendered(proposedRowsVisibleCalculator) && this.areAllProposedVisibleColumnsAlreadyRendered(proposedColumnsVisibleCalculator) ) ) {
  16060. fastDraw = false;
  16061. }
  16062. }
  16063. if(!fastDraw) {
  16064. this.rowsRenderCalculator = this.createRowsCalculator();
  16065. this.columnsRenderCalculator = this.createColumnsCalculator();
  16066. }
  16067. this.rowsVisibleCalculator = null; //delete temporarily to make sure that renderers always use rowsRenderCalculator, not rowsVisibleCalculator
  16068. this.columnsVisibleCalculator = null;
  16069. return fastDraw;
  16070. };
  16071. /**
  16072. * Creates rowsVisibleCalculator and columnsVisibleCalculator (after draw, to determine what are the actually visible rows and columns)
  16073. */
  16074. WalkontableViewport.prototype.createVisibleCalculators = function () {
  16075. this.rowsVisibleCalculator = this.createRowsCalculator(true);
  16076. this.columnsVisibleCalculator = this.createColumnsCalculator(true);
  16077. };
  16078. /**
  16079. * Returns information whether proposedRowsVisibleCalculator viewport
  16080. * is contained inside rows rendered in previous draw (cached in rowsRenderCalculator)
  16081. *
  16082. * Returns TRUE if all proposed visible rows are already rendered (meaning: redraw is not needed)
  16083. * Returns FALSE if at least one proposed visible row is not already rendered (meaning: redraw is needed)
  16084. *
  16085. * @returns {boolean}
  16086. */
  16087. WalkontableViewport.prototype.areAllProposedVisibleRowsAlreadyRendered = function (proposedRowsVisibleCalculator) {
  16088. if (this.rowsVisibleCalculator) {
  16089. if (proposedRowsVisibleCalculator.startRow < this.rowsRenderCalculator.startRow || (proposedRowsVisibleCalculator.startRow === this.rowsRenderCalculator.startRow && proposedRowsVisibleCalculator.startRow > 0)) {
  16090. return false;
  16091. }
  16092. else if (proposedRowsVisibleCalculator.endRow > this.rowsRenderCalculator.endRow || (proposedRowsVisibleCalculator.endRow === this.rowsRenderCalculator.endRow && proposedRowsVisibleCalculator.endRow < this.instance.getSetting('totalRows') - 1)) {
  16093. return false;
  16094. }
  16095. else {
  16096. return true;
  16097. }
  16098. }
  16099. return false;
  16100. };
  16101. /**
  16102. * Returns information whether proposedColumnsVisibleCalculator viewport
  16103. * is contained inside column rendered in previous draw (cached in columnsRenderCalculator)
  16104. *
  16105. * Returns TRUE if all proposed visible columns are already rendered (meaning: redraw is not needed)
  16106. * Returns FALSE if at least one proposed visible column is not already rendered (meaning: redraw is needed)
  16107. *
  16108. * @returns {boolean}
  16109. */
  16110. WalkontableViewport.prototype.areAllProposedVisibleColumnsAlreadyRendered = function (proposedColumnsVisibleCalculator) {
  16111. if (this.columnsVisibleCalculator) {
  16112. if (proposedColumnsVisibleCalculator.startColumn < this.columnsRenderCalculator.startColumn || (proposedColumnsVisibleCalculator.startColumn === this.columnsRenderCalculator.startColumn && proposedColumnsVisibleCalculator.startColumn > 0)) {
  16113. return false;
  16114. }
  16115. else if (proposedColumnsVisibleCalculator.endColumn > this.columnsRenderCalculator.endColumn || (proposedColumnsVisibleCalculator.endColumn === this.columnsRenderCalculator.endColumn && proposedColumnsVisibleCalculator.endColumn < this.instance.getSetting('totalColumns') - 1)) {
  16116. return false;
  16117. }
  16118. else {
  16119. return true;
  16120. }
  16121. }
  16122. return false;
  16123. };
  16124. function WalkontableViewportColumnsCalculator (width, scrollOffset, totalColumns, columnWidthFn, overrideFn, onlyFullyVisible, stretchH) {
  16125. this.scrollOffset = scrollOffset;
  16126. this.startColumn = null;
  16127. this.endColumn = null;
  16128. this.startPosition = null;
  16129. this.count = 0;
  16130. this.stretchAllRatio = 0;
  16131. this.stretchLastWidth = 0;
  16132. this.stretch = stretchH;
  16133. var i;
  16134. var sum = 0;
  16135. var columnWidth;
  16136. var needReverse = true;
  16137. var defaultColumnWidth = 50;
  16138. var startPositions = [];
  16139. var ratio = 1;
  16140. var getColumnWidth = function (i) {
  16141. ratio = ratio || 1;
  16142. var width = columnWidthFn(i);
  16143. if (width === undefined) {
  16144. width = defaultColumnWidth ;
  16145. }
  16146. return width;
  16147. };
  16148. this.refreshStretching = function (width) {
  16149. var columnWidth;
  16150. var sumAll = 0;
  16151. for(var i = 0; i < totalColumns; i++) {
  16152. columnWidth = getColumnWidth(i);
  16153. sumAll +=columnWidth;
  16154. }
  16155. var remainingSize = sumAll - width;
  16156. if (this.stretch === 'all' && remainingSize < 0){
  16157. this.stretchAllRatio = width / sumAll;
  16158. } else if (this.stretch === 'last' && width !== Infinity) {
  16159. this.stretchLastWidth = -remainingSize + getColumnWidth(totalColumns-1);
  16160. }
  16161. };
  16162. for (i = 0; i< totalColumns; i++) {
  16163. columnWidth = getColumnWidth(i);
  16164. if (sum <= scrollOffset && !onlyFullyVisible){
  16165. this.startColumn = i;
  16166. }
  16167. if (sum >= scrollOffset && sum + columnWidth <= scrollOffset + width) {
  16168. if (this.startColumn == null) {
  16169. this.startColumn = i;
  16170. }
  16171. this.endColumn = i;
  16172. }
  16173. startPositions.push(sum);
  16174. sum += columnWidth;
  16175. if(!onlyFullyVisible) {
  16176. this.endColumn = i;
  16177. }
  16178. if(sum >= scrollOffset + width) {
  16179. needReverse = false;
  16180. break;
  16181. }
  16182. }
  16183. if (this.endColumn == totalColumns - 1 && needReverse) {
  16184. this.startColumn = this.endColumn;
  16185. while(this.startColumn > 0) {
  16186. var viewportSum = startPositions[this.endColumn] + columnWidth - startPositions[this.startColumn - 1];
  16187. if (viewportSum <= width || !onlyFullyVisible) {
  16188. this.startColumn--;
  16189. }
  16190. if (viewportSum > width) {
  16191. break;
  16192. }
  16193. }
  16194. }
  16195. if (this.startColumn !== null && overrideFn){
  16196. overrideFn(this);
  16197. }
  16198. this.startPosition = startPositions[this.startColumn];
  16199. if (this.startPosition == void 0) {
  16200. this.startPosition = null;
  16201. }
  16202. if (this.startColumn != null) {
  16203. this.count = this.endColumn - this.startColumn + 1;
  16204. }
  16205. }
  16206. /**
  16207. * Viewport calculator constructor. Calculates indexes of rows to render OR rows that are visible.
  16208. * To redo the calculation, you need to create a new calculator.
  16209. *
  16210. * Object properties:
  16211. * this.scrollOffset - position of vertical scroll (in px)
  16212. * this.startRow - index of the first rendered/visible row (can be overwritten using overrideFn)
  16213. * this.startPosition - position of the first rendered/visible row (in px)
  16214. * this.endRow - index of the last rendered/visible row (can be overwritten using overrideFn)
  16215. * this.count - number of rendered/visible rows
  16216. *
  16217. * @param height - height of the viewport
  16218. * @param scrollOffset - current vertical scroll position of the viewport
  16219. * @param totalRows - total number of rows
  16220. * @param rowHeightFn - function that returns the height of the row at a given index (in px)
  16221. * @param overrideFn - function that changes calculated this.startRow, this.endRow (used by mergeCells.js plugin)
  16222. * @param onlyFullyVisible {bool} - if TRUE, only startRow and endRow will be indexes of rows that are FULLY in viewport
  16223. * @constructor
  16224. */
  16225. function WalkontableViewportRowsCalculator(height, scrollOffset, totalRows, rowHeightFn, overrideFn, onlyFullyVisible) {
  16226. this.scrollOffset = scrollOffset;
  16227. this.startRow = null;
  16228. this.startPosition = null;
  16229. this.endRow = null;
  16230. this.count = 0;
  16231. var sum = 0;
  16232. var rowHeight;
  16233. var needReverse = true;
  16234. var defaultRowHeight = 23;
  16235. var startPositions = [];
  16236. for (var i = 0; i < totalRows; i++) {
  16237. rowHeight = rowHeightFn(i);
  16238. if (rowHeight === undefined) {
  16239. rowHeight = defaultRowHeight;
  16240. }
  16241. if (sum <= scrollOffset && !onlyFullyVisible) {
  16242. this.startRow = i;
  16243. }
  16244. if (sum >= scrollOffset && sum + rowHeight <= scrollOffset + height) {
  16245. if (this.startRow == null) {
  16246. this.startRow = i;
  16247. }
  16248. this.endRow = i;
  16249. }
  16250. startPositions.push(sum);
  16251. sum += rowHeight;
  16252. if(!onlyFullyVisible) {
  16253. this.endRow = i;
  16254. }
  16255. if (sum >= scrollOffset + height) {
  16256. needReverse = false;
  16257. break;
  16258. }
  16259. }
  16260. //If the rendering has reached the last row and there is still some space available in the viewport, we need to render in reverse in order to fill the whole viewport with rows
  16261. if (this.endRow == totalRows - 1 && needReverse) {
  16262. this.startRow = this.endRow;
  16263. while(this.startRow > 0) {
  16264. var viewportSum = startPositions[this.endRow] + rowHeight - startPositions[this.startRow - 1]; //rowHeight is the height of the last row
  16265. if (viewportSum <= height || !onlyFullyVisible)
  16266. {
  16267. this.startRow--;
  16268. }
  16269. if (viewportSum >= height)
  16270. {
  16271. break;
  16272. }
  16273. }
  16274. }
  16275. if (this.startRow !== null && overrideFn) {
  16276. overrideFn(this);
  16277. }
  16278. this.startPosition = startPositions[this.startRow];
  16279. if (this.startPosition == void 0) {
  16280. this.startPosition = null;
  16281. }
  16282. if (this.startRow != null) {
  16283. this.count = this.endRow - this.startRow + 1;
  16284. }
  16285. }
  16286. if (window.jQuery) {
  16287. (function (window, $, Handsontable) {
  16288. $.fn.handsontable = function (action) {
  16289. var i
  16290. , ilen
  16291. , args
  16292. , output
  16293. , userSettings
  16294. , $this = this.first() // Use only first element from list
  16295. , instance = $this.data('handsontable');
  16296. // Init case
  16297. if (typeof action !== 'string') {
  16298. userSettings = action || {};
  16299. if (instance) {
  16300. instance.updateSettings(userSettings);
  16301. }
  16302. else {
  16303. instance = new Handsontable.Core($this[0], userSettings);
  16304. $this.data('handsontable', instance);
  16305. instance.init();
  16306. }
  16307. return $this;
  16308. }
  16309. // Action case
  16310. else {
  16311. args = [];
  16312. if (arguments.length > 1) {
  16313. for (i = 1, ilen = arguments.length; i < ilen; i++) {
  16314. args.push(arguments[i]);
  16315. }
  16316. }
  16317. if (instance) {
  16318. if (typeof instance[action] !== 'undefined') {
  16319. output = instance[action].apply(instance, args);
  16320. if (action === 'destroy'){
  16321. $this.removeData();
  16322. }
  16323. }
  16324. else {
  16325. throw new Error('Handsontable do not provide action: ' + action);
  16326. }
  16327. }
  16328. return output;
  16329. }
  16330. };
  16331. })(window, jQuery, Handsontable);
  16332. }
  16333. })(window, Handsontable);
  16334. /*!
  16335. * numeral.js
  16336. * version : 1.5.3
  16337. * author : Adam Draper
  16338. * license : MIT
  16339. * http://adamwdraper.github.com/Numeral-js/
  16340. */
  16341. (function () {
  16342. /************************************
  16343. Constants
  16344. ************************************/
  16345. var numeral,
  16346. VERSION = '1.5.3',
  16347. // internal storage for language config files
  16348. languages = {},
  16349. currentLanguage = 'en',
  16350. zeroFormat = null,
  16351. defaultFormat = '0,0',
  16352. // check for nodeJS
  16353. hasModule = (typeof module !== 'undefined' && module.exports);
  16354. /************************************
  16355. Constructors
  16356. ************************************/
  16357. // Numeral prototype object
  16358. function Numeral (number) {
  16359. this._value = number;
  16360. }
  16361. /**
  16362. * Implementation of toFixed() that treats floats more like decimals
  16363. *
  16364. * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present
  16365. * problems for accounting- and finance-related software.
  16366. */
  16367. function toFixed (value, precision, roundingFunction, optionals) {
  16368. var power = Math.pow(10, precision),
  16369. optionalsRegExp,
  16370. output;
  16371. //roundingFunction = (roundingFunction !== undefined ? roundingFunction : Math.round);
  16372. // Multiply up by precision, round accurately, then divide and use native toFixed():
  16373. output = (roundingFunction(value * power) / power).toFixed(precision);
  16374. if (optionals) {
  16375. optionalsRegExp = new RegExp('0{1,' + optionals + '}$');
  16376. output = output.replace(optionalsRegExp, '');
  16377. }
  16378. return output;
  16379. }
  16380. /************************************
  16381. Formatting
  16382. ************************************/
  16383. // determine what type of formatting we need to do
  16384. function formatNumeral (n, format, roundingFunction) {
  16385. var output;
  16386. // figure out what kind of format we are dealing with
  16387. if (format.indexOf('$') > -1) { // currency!!!!!
  16388. output = formatCurrency(n, format, roundingFunction);
  16389. } else if (format.indexOf('%') > -1) { // percentage
  16390. output = formatPercentage(n, format, roundingFunction);
  16391. } else if (format.indexOf(':') > -1) { // time
  16392. output = formatTime(n, format);
  16393. } else { // plain ol' numbers or bytes
  16394. output = formatNumber(n._value, format, roundingFunction);
  16395. }
  16396. // return string
  16397. return output;
  16398. }
  16399. // revert to number
  16400. function unformatNumeral (n, string) {
  16401. var stringOriginal = string,
  16402. thousandRegExp,
  16403. millionRegExp,
  16404. billionRegExp,
  16405. trillionRegExp,
  16406. suffixes = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  16407. bytesMultiplier = false,
  16408. power;
  16409. if (string.indexOf(':') > -1) {
  16410. n._value = unformatTime(string);
  16411. } else {
  16412. if (string === zeroFormat) {
  16413. n._value = 0;
  16414. } else {
  16415. if (languages[currentLanguage].delimiters.decimal !== '.') {
  16416. string = string.replace(/\./g,'').replace(languages[currentLanguage].delimiters.decimal, '.');
  16417. }
  16418. // see if abbreviations are there so that we can multiply to the correct number
  16419. thousandRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.thousand + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
  16420. millionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.million + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
  16421. billionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.billion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
  16422. trillionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.trillion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
  16423. // see if bytes are there so that we can multiply to the correct number
  16424. for (power = 0; power <= suffixes.length; power++) {
  16425. bytesMultiplier = (string.indexOf(suffixes[power]) > -1) ? Math.pow(1024, power + 1) : false;
  16426. if (bytesMultiplier) {
  16427. break;
  16428. }
  16429. }
  16430. // do some math to create our number
  16431. n._value = ((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) * (((string.split('-').length + Math.min(string.split('(').length-1, string.split(')').length-1)) % 2)? 1: -1) * Number(string.replace(/[^0-9\.]+/g, ''));
  16432. // round if we are talking about bytes
  16433. n._value = (bytesMultiplier) ? Math.ceil(n._value) : n._value;
  16434. }
  16435. }
  16436. return n._value;
  16437. }
  16438. function formatCurrency (n, format, roundingFunction) {
  16439. var symbolIndex = format.indexOf('$'),
  16440. openParenIndex = format.indexOf('('),
  16441. minusSignIndex = format.indexOf('-'),
  16442. space = '',
  16443. spliceIndex,
  16444. output;
  16445. // check for space before or after currency
  16446. if (format.indexOf(' $') > -1) {
  16447. space = ' ';
  16448. format = format.replace(' $', '');
  16449. } else if (format.indexOf('$ ') > -1) {
  16450. space = ' ';
  16451. format = format.replace('$ ', '');
  16452. } else {
  16453. format = format.replace('$', '');
  16454. }
  16455. // format the number
  16456. output = formatNumber(n._value, format, roundingFunction);
  16457. // position the symbol
  16458. if (symbolIndex <= 1) {
  16459. if (output.indexOf('(') > -1 || output.indexOf('-') > -1) {
  16460. output = output.split('');
  16461. spliceIndex = 1;
  16462. if (symbolIndex < openParenIndex || symbolIndex < minusSignIndex){
  16463. // the symbol appears before the "(" or "-"
  16464. spliceIndex = 0;
  16465. }
  16466. output.splice(spliceIndex, 0, languages[currentLanguage].currency.symbol + space);
  16467. output = output.join('');
  16468. } else {
  16469. output = languages[currentLanguage].currency.symbol + space + output;
  16470. }
  16471. } else {
  16472. if (output.indexOf(')') > -1) {
  16473. output = output.split('');
  16474. output.splice(-1, 0, space + languages[currentLanguage].currency.symbol);
  16475. output = output.join('');
  16476. } else {
  16477. output = output + space + languages[currentLanguage].currency.symbol;
  16478. }
  16479. }
  16480. return output;
  16481. }
  16482. function formatPercentage (n, format, roundingFunction) {
  16483. var space = '',
  16484. output,
  16485. value = n._value * 100;
  16486. // check for space before %
  16487. if (format.indexOf(' %') > -1) {
  16488. space = ' ';
  16489. format = format.replace(' %', '');
  16490. } else {
  16491. format = format.replace('%', '');
  16492. }
  16493. output = formatNumber(value, format, roundingFunction);
  16494. if (output.indexOf(')') > -1 ) {
  16495. output = output.split('');
  16496. output.splice(-1, 0, space + '%');
  16497. output = output.join('');
  16498. } else {
  16499. output = output + space + '%';
  16500. }
  16501. return output;
  16502. }
  16503. function formatTime (n) {
  16504. var hours = Math.floor(n._value/60/60),
  16505. minutes = Math.floor((n._value - (hours * 60 * 60))/60),
  16506. seconds = Math.round(n._value - (hours * 60 * 60) - (minutes * 60));
  16507. return hours + ':' + ((minutes < 10) ? '0' + minutes : minutes) + ':' + ((seconds < 10) ? '0' + seconds : seconds);
  16508. }
  16509. function unformatTime (string) {
  16510. var timeArray = string.split(':'),
  16511. seconds = 0;
  16512. // turn hours and minutes into seconds and add them all up
  16513. if (timeArray.length === 3) {
  16514. // hours
  16515. seconds = seconds + (Number(timeArray[0]) * 60 * 60);
  16516. // minutes
  16517. seconds = seconds + (Number(timeArray[1]) * 60);
  16518. // seconds
  16519. seconds = seconds + Number(timeArray[2]);
  16520. } else if (timeArray.length === 2) {
  16521. // minutes
  16522. seconds = seconds + (Number(timeArray[0]) * 60);
  16523. // seconds
  16524. seconds = seconds + Number(timeArray[1]);
  16525. }
  16526. return Number(seconds);
  16527. }
  16528. function formatNumber (value, format, roundingFunction) {
  16529. var negP = false,
  16530. signed = false,
  16531. optDec = false,
  16532. abbr = '',
  16533. abbrK = false, // force abbreviation to thousands
  16534. abbrM = false, // force abbreviation to millions
  16535. abbrB = false, // force abbreviation to billions
  16536. abbrT = false, // force abbreviation to trillions
  16537. abbrForce = false, // force abbreviation
  16538. bytes = '',
  16539. ord = '',
  16540. abs = Math.abs(value),
  16541. suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  16542. min,
  16543. max,
  16544. power,
  16545. w,
  16546. precision,
  16547. thousands,
  16548. d = '',
  16549. neg = false;
  16550. // check if number is zero and a custom zero format has been set
  16551. if (value === 0 && zeroFormat !== null) {
  16552. return zeroFormat;
  16553. } else {
  16554. // see if we should use parentheses for negative number or if we should prefix with a sign
  16555. // if both are present we default to parentheses
  16556. if (format.indexOf('(') > -1) {
  16557. negP = true;
  16558. format = format.slice(1, -1);
  16559. } else if (format.indexOf('+') > -1) {
  16560. signed = true;
  16561. format = format.replace(/\+/g, '');
  16562. }
  16563. // see if abbreviation is wanted
  16564. if (format.indexOf('a') > -1) {
  16565. // check if abbreviation is specified
  16566. abbrK = format.indexOf('aK') >= 0;
  16567. abbrM = format.indexOf('aM') >= 0;
  16568. abbrB = format.indexOf('aB') >= 0;
  16569. abbrT = format.indexOf('aT') >= 0;
  16570. abbrForce = abbrK || abbrM || abbrB || abbrT;
  16571. // check for space before abbreviation
  16572. if (format.indexOf(' a') > -1) {
  16573. abbr = ' ';
  16574. format = format.replace(' a', '');
  16575. } else {
  16576. format = format.replace('a', '');
  16577. }
  16578. if (abs >= Math.pow(10, 12) && !abbrForce || abbrT) {
  16579. // trillion
  16580. abbr = abbr + languages[currentLanguage].abbreviations.trillion;
  16581. value = value / Math.pow(10, 12);
  16582. } else if (abs < Math.pow(10, 12) && abs >= Math.pow(10, 9) && !abbrForce || abbrB) {
  16583. // billion
  16584. abbr = abbr + languages[currentLanguage].abbreviations.billion;
  16585. value = value / Math.pow(10, 9);
  16586. } else if (abs < Math.pow(10, 9) && abs >= Math.pow(10, 6) && !abbrForce || abbrM) {
  16587. // million
  16588. abbr = abbr + languages[currentLanguage].abbreviations.million;
  16589. value = value / Math.pow(10, 6);
  16590. } else if (abs < Math.pow(10, 6) && abs >= Math.pow(10, 3) && !abbrForce || abbrK) {
  16591. // thousand
  16592. abbr = abbr + languages[currentLanguage].abbreviations.thousand;
  16593. value = value / Math.pow(10, 3);
  16594. }
  16595. }
  16596. // see if we are formatting bytes
  16597. if (format.indexOf('b') > -1) {
  16598. // check for space before
  16599. if (format.indexOf(' b') > -1) {
  16600. bytes = ' ';
  16601. format = format.replace(' b', '');
  16602. } else {
  16603. format = format.replace('b', '');
  16604. }
  16605. for (power = 0; power <= suffixes.length; power++) {
  16606. min = Math.pow(1024, power);
  16607. max = Math.pow(1024, power+1);
  16608. if (value >= min && value < max) {
  16609. bytes = bytes + suffixes[power];
  16610. if (min > 0) {
  16611. value = value / min;
  16612. }
  16613. break;
  16614. }
  16615. }
  16616. }
  16617. // see if ordinal is wanted
  16618. if (format.indexOf('o') > -1) {
  16619. // check for space before
  16620. if (format.indexOf(' o') > -1) {
  16621. ord = ' ';
  16622. format = format.replace(' o', '');
  16623. } else {
  16624. format = format.replace('o', '');
  16625. }
  16626. ord = ord + languages[currentLanguage].ordinal(value);
  16627. }
  16628. if (format.indexOf('[.]') > -1) {
  16629. optDec = true;
  16630. format = format.replace('[.]', '.');
  16631. }
  16632. w = value.toString().split('.')[0];
  16633. precision = format.split('.')[1];
  16634. thousands = format.indexOf(',');
  16635. if (precision) {
  16636. if (precision.indexOf('[') > -1) {
  16637. precision = precision.replace(']', '');
  16638. precision = precision.split('[');
  16639. d = toFixed(value, (precision[0].length + precision[1].length), roundingFunction, precision[1].length);
  16640. } else {
  16641. d = toFixed(value, precision.length, roundingFunction);
  16642. }
  16643. w = d.split('.')[0];
  16644. if (d.split('.')[1].length) {
  16645. d = languages[currentLanguage].delimiters.decimal + d.split('.')[1];
  16646. } else {
  16647. d = '';
  16648. }
  16649. if (optDec && Number(d.slice(1)) === 0) {
  16650. d = '';
  16651. }
  16652. } else {
  16653. w = toFixed(value, null, roundingFunction);
  16654. }
  16655. // format number
  16656. if (w.indexOf('-') > -1) {
  16657. w = w.slice(1);
  16658. neg = true;
  16659. }
  16660. if (thousands > -1) {
  16661. w = w.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + languages[currentLanguage].delimiters.thousands);
  16662. }
  16663. if (format.indexOf('.') === 0) {
  16664. w = '';
  16665. }
  16666. return ((negP && neg) ? '(' : '') + ((!negP && neg) ? '-' : '') + ((!neg && signed) ? '+' : '') + w + d + ((ord) ? ord : '') + ((abbr) ? abbr : '') + ((bytes) ? bytes : '') + ((negP && neg) ? ')' : '');
  16667. }
  16668. }
  16669. /************************************
  16670. Top Level Functions
  16671. ************************************/
  16672. numeral = function (input) {
  16673. if (numeral.isNumeral(input)) {
  16674. input = input.value();
  16675. } else if (input === 0 || typeof input === 'undefined') {
  16676. input = 0;
  16677. } else if (!Number(input)) {
  16678. input = numeral.fn.unformat(input);
  16679. }
  16680. return new Numeral(Number(input));
  16681. };
  16682. // version number
  16683. numeral.version = VERSION;
  16684. // compare numeral object
  16685. numeral.isNumeral = function (obj) {
  16686. return obj instanceof Numeral;
  16687. };
  16688. // This function will load languages and then set the global language. If
  16689. // no arguments are passed in, it will simply return the current global
  16690. // language key.
  16691. numeral.language = function (key, values) {
  16692. if (!key) {
  16693. return currentLanguage;
  16694. }
  16695. if (key && !values) {
  16696. if(!languages[key]) {
  16697. throw new Error('Unknown language : ' + key);
  16698. }
  16699. currentLanguage = key;
  16700. }
  16701. if (values || !languages[key]) {
  16702. loadLanguage(key, values);
  16703. }
  16704. return numeral;
  16705. };
  16706. // This function provides access to the loaded language data. If
  16707. // no arguments are passed in, it will simply return the current
  16708. // global language object.
  16709. numeral.languageData = function (key) {
  16710. if (!key) {
  16711. return languages[currentLanguage];
  16712. }
  16713. if (!languages[key]) {
  16714. throw new Error('Unknown language : ' + key);
  16715. }
  16716. return languages[key];
  16717. };
  16718. numeral.language('en', {
  16719. delimiters: {
  16720. thousands: ',',
  16721. decimal: '.'
  16722. },
  16723. abbreviations: {
  16724. thousand: 'k',
  16725. million: 'm',
  16726. billion: 'b',
  16727. trillion: 't'
  16728. },
  16729. ordinal: function (number) {
  16730. var b = number % 10;
  16731. return (~~ (number % 100 / 10) === 1) ? 'th' :
  16732. (b === 1) ? 'st' :
  16733. (b === 2) ? 'nd' :
  16734. (b === 3) ? 'rd' : 'th';
  16735. },
  16736. currency: {
  16737. symbol: '$'
  16738. }
  16739. });
  16740. numeral.zeroFormat = function (format) {
  16741. zeroFormat = typeof(format) === 'string' ? format : null;
  16742. };
  16743. numeral.defaultFormat = function (format) {
  16744. defaultFormat = typeof(format) === 'string' ? format : '0.0';
  16745. };
  16746. /************************************
  16747. Helpers
  16748. ************************************/
  16749. function loadLanguage(key, values) {
  16750. languages[key] = values;
  16751. }
  16752. /************************************
  16753. Floating-point helpers
  16754. ************************************/
  16755. // The floating-point helper functions and implementation
  16756. // borrows heavily from sinful.js: http://guipn.github.io/sinful.js/
  16757. /**
  16758. * Array.prototype.reduce for browsers that don't support it
  16759. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Compatibility
  16760. */
  16761. if ('function' !== typeof Array.prototype.reduce) {
  16762. Array.prototype.reduce = function (callback, opt_initialValue) {
  16763. 'use strict';
  16764. if (null === this || 'undefined' === typeof this) {
  16765. // At the moment all modern browsers, that support strict mode, have
  16766. // native implementation of Array.prototype.reduce. For instance, IE8
  16767. // does not support strict mode, so this check is actually useless.
  16768. throw new TypeError('Array.prototype.reduce called on null or undefined');
  16769. }
  16770. if ('function' !== typeof callback) {
  16771. throw new TypeError(callback + ' is not a function');
  16772. }
  16773. var index,
  16774. value,
  16775. length = this.length >>> 0,
  16776. isValueSet = false;
  16777. if (1 < arguments.length) {
  16778. value = opt_initialValue;
  16779. isValueSet = true;
  16780. }
  16781. for (index = 0; length > index; ++index) {
  16782. if (this.hasOwnProperty(index)) {
  16783. if (isValueSet) {
  16784. value = callback(value, this[index], index, this);
  16785. } else {
  16786. value = this[index];
  16787. isValueSet = true;
  16788. }
  16789. }
  16790. }
  16791. if (!isValueSet) {
  16792. throw new TypeError('Reduce of empty array with no initial value');
  16793. }
  16794. return value;
  16795. };
  16796. }
  16797. /**
  16798. * Computes the multiplier necessary to make x >= 1,
  16799. * effectively eliminating miscalculations caused by
  16800. * finite precision.
  16801. */
  16802. function multiplier(x) {
  16803. var parts = x.toString().split('.');
  16804. if (parts.length < 2) {
  16805. return 1;
  16806. }
  16807. return Math.pow(10, parts[1].length);
  16808. }
  16809. /**
  16810. * Given a variable number of arguments, returns the maximum
  16811. * multiplier that must be used to normalize an operation involving
  16812. * all of them.
  16813. */
  16814. function correctionFactor() {
  16815. var args = Array.prototype.slice.call(arguments);
  16816. return args.reduce(function (prev, next) {
  16817. var mp = multiplier(prev),
  16818. mn = multiplier(next);
  16819. return mp > mn ? mp : mn;
  16820. }, -Infinity);
  16821. }
  16822. /************************************
  16823. Numeral Prototype
  16824. ************************************/
  16825. numeral.fn = Numeral.prototype = {
  16826. clone : function () {
  16827. return numeral(this);
  16828. },
  16829. format : function (inputString, roundingFunction) {
  16830. return formatNumeral(this,
  16831. inputString ? inputString : defaultFormat,
  16832. (roundingFunction !== undefined) ? roundingFunction : Math.round
  16833. );
  16834. },
  16835. unformat : function (inputString) {
  16836. if (Object.prototype.toString.call(inputString) === '[object Number]') {
  16837. return inputString;
  16838. }
  16839. return unformatNumeral(this, inputString ? inputString : defaultFormat);
  16840. },
  16841. value : function () {
  16842. return this._value;
  16843. },
  16844. valueOf : function () {
  16845. return this._value;
  16846. },
  16847. set : function (value) {
  16848. this._value = Number(value);
  16849. return this;
  16850. },
  16851. add : function (value) {
  16852. var corrFactor = correctionFactor.call(null, this._value, value);
  16853. function cback(accum, curr, currI, O) {
  16854. return accum + corrFactor * curr;
  16855. }
  16856. this._value = [this._value, value].reduce(cback, 0) / corrFactor;
  16857. return this;
  16858. },
  16859. subtract : function (value) {
  16860. var corrFactor = correctionFactor.call(null, this._value, value);
  16861. function cback(accum, curr, currI, O) {
  16862. return accum - corrFactor * curr;
  16863. }
  16864. this._value = [value].reduce(cback, this._value * corrFactor) / corrFactor;
  16865. return this;
  16866. },
  16867. multiply : function (value) {
  16868. function cback(accum, curr, currI, O) {
  16869. var corrFactor = correctionFactor(accum, curr);
  16870. return (accum * corrFactor) * (curr * corrFactor) /
  16871. (corrFactor * corrFactor);
  16872. }
  16873. this._value = [this._value, value].reduce(cback, 1);
  16874. return this;
  16875. },
  16876. divide : function (value) {
  16877. function cback(accum, curr, currI, O) {
  16878. var corrFactor = correctionFactor(accum, curr);
  16879. return (accum * corrFactor) / (curr * corrFactor);
  16880. }
  16881. this._value = [this._value, value].reduce(cback);
  16882. return this;
  16883. },
  16884. difference : function (value) {
  16885. return Math.abs(numeral(this._value).subtract(value).value());
  16886. }
  16887. };
  16888. /************************************
  16889. Exposing Numeral
  16890. ************************************/
  16891. // CommonJS module is defined
  16892. if (hasModule) {
  16893. module.exports = numeral;
  16894. }
  16895. /*global ender:false */
  16896. if (typeof ender === 'undefined') {
  16897. // here, `this` means `window` in the browser, or `global` on the server
  16898. // add `numeral` as a global object via a string identifier,
  16899. // for Closure Compiler 'advanced' mode
  16900. this['numeral'] = numeral;
  16901. }
  16902. /*global define:false */
  16903. if (typeof define === 'function' && define.amd) {
  16904. define([], function () {
  16905. return numeral;
  16906. });
  16907. }
  16908. }).call(this);