暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dataTables.editor.js 261KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303
  1. /*! DataTables Editor v1.9.2
  2. *
  3. * ©2012-2019 SpryMedia Ltd, all rights reserved.
  4. * License: editor.datatables.net/license
  5. */
  6. /**
  7. * @summary DataTables Editor
  8. * @description Table editing library for DataTables
  9. * @version 1.9.2
  10. * @file dataTables.editor.js
  11. * @author SpryMedia Ltd
  12. * @contact www.datatables.net/contact
  13. */
  14. /*jslint evil: true, undef: true, browser: true */
  15. /*globals jQuery,alert,console */
  16. (function( factory ){
  17. if ( typeof define === 'function' && define.amd ) {
  18. // AMD
  19. // @jscrambler disable *
  20. define( ['jquery', 'datatables.net'], function ( $ ) {
  21. return factory( $, window, document );
  22. } );
  23. }
  24. else if ( typeof exports === 'object' ) {
  25. // CommonJS
  26. // @jscrambler disable *
  27. module.exports = function (root, $) {
  28. if ( ! root ) {
  29. root = window;
  30. }
  31. if ( ! $ || ! $.fn.dataTable ) {
  32. $ = require('datatables.net')(root, $).$;
  33. }
  34. return factory( $, root, root.document );
  35. };
  36. }
  37. else {
  38. // Browser
  39. factory( jQuery, window, document );
  40. }
  41. }(function( $, window, document, undefined ) {
  42. 'use strict';
  43. var DataTable = $.fn.dataTable;
  44. if ( ! DataTable || ! DataTable.versionCheck || ! DataTable.versionCheck('1.10.7') ) {
  45. throw new Error('Editor requires DataTables 1.10.7 or newer');
  46. }
  47. /**
  48. * Editor is a plug-in for <a href="http://datatables.net">DataTables</a> which
  49. * provides an interface for creating, reading, editing and deleting and entries
  50. * (a CRUD interface) in a DataTable. The documentation presented here is
  51. * primarily focused on presenting the API for Editor. For a full list of
  52. * features, examples and the server interface protocol, please refer to the <a
  53. * href="http://editor.datatables.net">Editor web-site</a>.
  54. *
  55. * Note that in this documentation, for brevity, the `DataTable` refers to the
  56. * jQuery parameter `jQuery.fn.dataTable` through which it may be accessed.
  57. * Therefore, when creating a new Editor instance, use `jQuery.fn.Editor` as
  58. * shown in the examples below.
  59. *
  60. * @class
  61. * @param {object} [oInit={}] Configuration object for Editor. Options
  62. * are defined by {@link Editor.defaults}.
  63. * @requires jQuery 1.7+
  64. * @requires DataTables 1.10+
  65. */
  66. var Editor = function ( opts )
  67. {
  68. if ( ! (this instanceof Editor) ) {
  69. alert( "DataTables Editor must be initialised as a 'new' instance'" );
  70. }
  71. this._constructor( opts );
  72. };
  73. // Export Editor as a DataTables property
  74. DataTable.Editor = Editor;
  75. $.fn.DataTable.Editor = Editor;
  76. // Internal methods
  77. /**
  78. * Get an Editor node based on the data-dte-e (element) attribute and return it
  79. * as a jQuery object.
  80. * @param {string} dis The data-dte-e attribute name to match for the element
  81. * @param {node} [ctx=document] The context for the search - recommended this
  82. * parameter is included for performance.
  83. * @returns {jQuery} jQuery object of found node(s).
  84. * @private
  85. */
  86. var _editor_el = function ( dis, ctx )
  87. {
  88. if ( ctx === undefined ) {
  89. ctx = document;
  90. }
  91. return $('*[data-dte-e="'+dis+'"]', ctx);
  92. };
  93. /** @internal Counter for unique event namespaces in the inline control */
  94. var __inlineCounter = 0;
  95. var _pluck = function ( a, prop )
  96. {
  97. var out = [];
  98. $.each( a, function ( idx, el ) {
  99. out.push( el[ prop ] );
  100. } );
  101. return out;
  102. };
  103. // The file and file methods are common on both the DataTables and Editor APIs
  104. // so rather than writing the same methods twice, they are defined once here and
  105. // assigned as required.
  106. var _api_file = function ( name, id )
  107. {
  108. var table = this.files( name ); // can throw. `this` will be Editor or
  109. var file = table[ id ]; // DataTables.Api context. Both work.
  110. if ( ! file ) {
  111. throw 'Unknown file id '+ id +' in table '+ name;
  112. }
  113. return table[ id ];
  114. };
  115. var _api_files = function ( name )
  116. {
  117. if ( ! name ) {
  118. return Editor.files;
  119. }
  120. var table = Editor.files[ name ];
  121. if ( ! table ) {
  122. throw 'Unknown file table name: '+ name;
  123. }
  124. return table;
  125. };
  126. /**
  127. * Get the keys of an object / array
  128. *
  129. * @param {object} o Object to get the keys of
  130. * @return {array} Keys
  131. */
  132. var _objectKeys = function ( o ) {
  133. var out = [];
  134. for ( var key in o ) {
  135. if ( o.hasOwnProperty( key ) ) {
  136. out.push( key );
  137. }
  138. }
  139. return out;
  140. };
  141. /**
  142. * Compare parameters for difference - diving into arrays and objects if
  143. * needed, allowing the object reference to be different, but the contents to
  144. * match.
  145. *
  146. * Please note that LOOSE type checking is used
  147. *
  148. * @param {*} o1 Object to compare
  149. * @param {*} o2 Object to compare
  150. * @return {boolean} `true` if matching, `false` otherwise
  151. */
  152. var _deepCompare = function (o1, o2) {
  153. if ( typeof o1 !== 'object' || typeof o2 !== 'object' ) {
  154. return o1 == o2;
  155. }
  156. var o1Props = _objectKeys( o1 );
  157. var o2Props = _objectKeys( o2 );
  158. if (o1Props.length !== o2Props.length) {
  159. return false;
  160. }
  161. for ( var i=0, ien=o1Props.length ; i<ien ; i++ ) {
  162. var propName = o1Props[i];
  163. if ( typeof o1[propName] === 'object' ) {
  164. if ( ! _deepCompare( o1[propName], o2[propName] ) ) {
  165. return false;
  166. }
  167. }
  168. else if (o1[propName] != o2[propName]) {
  169. return false;
  170. }
  171. }
  172. return true;
  173. };
  174. // Field class
  175. Editor.Field = function ( opts, classes, host ) {
  176. var that = this;
  177. var multiI18n = host.i18n.multi;
  178. opts = $.extend( true, {}, Editor.Field.defaults, opts );
  179. if ( ! Editor.fieldTypes[ opts.type ] ) {
  180. throw "Error adding field - unknown field type "+opts.type;
  181. }
  182. this.s = $.extend( {}, Editor.Field.settings, { // has to be a shallow copy!
  183. type: Editor.fieldTypes[ opts.type ],
  184. name: opts.name,
  185. classes: classes,
  186. host: host,
  187. opts: opts,
  188. multiValue: false
  189. } );
  190. // No id, so assign one to have the label reference work
  191. if ( ! opts.id ) {
  192. opts.id = 'DTE_Field_'+opts.name;
  193. }
  194. // Backwards compatibility
  195. if ( opts.dataProp ) {
  196. opts.data = opts.dataProp;
  197. }
  198. // If no `data` option is given, then we use the name from the field as the
  199. // data prop to read data for the field from DataTables
  200. if ( opts.data === '' ) {
  201. opts.data = opts.name;
  202. }
  203. // Get and set functions in the data object for the record
  204. var dtPrivateApi = DataTable.ext.oApi;
  205. this.valFromData = function ( d ) { // get val from data
  206. // wrapper to automatically pass `editor` as the type
  207. return dtPrivateApi._fnGetObjectDataFn( opts.data )( d, 'editor' );
  208. };
  209. this.valToData = dtPrivateApi._fnSetObjectDataFn( opts.data ); // set val to data
  210. // Field HTML structure
  211. var template = $(
  212. '<div class="'+classes.wrapper+' '+classes.typePrefix+opts.type+' '+classes.namePrefix+opts.name+' '+opts.className+'">'+
  213. '<label data-dte-e="label" class="'+classes.label+'" for="'+Editor.safeId(opts.id)+'">'+
  214. opts.label+
  215. '<div data-dte-e="msg-label" class="'+classes['msg-label']+'">'+opts.labelInfo+'</div>'+
  216. '</label>'+
  217. '<div data-dte-e="input" class="'+classes.input+'">'+
  218. // Field specific HTML is added here if there is any
  219. '<div data-dte-e="input-control" class="'+classes.inputControl+'"/>'+
  220. '<div data-dte-e="multi-value" class="'+classes.multiValue+'">'+
  221. multiI18n.title+
  222. '<span data-dte-e="multi-info" class="'+classes.multiInfo+'">'+
  223. multiI18n.info+
  224. '</span>'+
  225. '</div>'+
  226. '<div data-dte-e="msg-multi" class="'+classes.multiRestore+'">'+
  227. multiI18n.restore+
  228. '</div>'+
  229. '<div data-dte-e="msg-error" class="'+classes['msg-error']+'"></div>'+
  230. '<div data-dte-e="msg-message" class="'+classes['msg-message']+'">'+opts.message+'</div>'+
  231. '<div data-dte-e="msg-info" class="'+classes['msg-info']+'">'+opts.fieldInfo+'</div>'+
  232. '</div>'+
  233. '<div data-dte-e="field-processing" class="'+classes.processing+'"><span/></div>'+
  234. '</div>');
  235. var input = this._typeFn( 'create', opts );
  236. if ( input !== null ) {
  237. _editor_el('input-control', template).prepend( input );
  238. }
  239. else {
  240. template.css('display', "none");
  241. }
  242. this.dom = $.extend( true, {}, Editor.Field.models.dom, {
  243. container: template,
  244. inputControl: _editor_el('input-control', template),
  245. label: _editor_el('label', template),
  246. fieldInfo: _editor_el('msg-info', template),
  247. labelInfo: _editor_el('msg-label', template),
  248. fieldError: _editor_el('msg-error', template),
  249. fieldMessage: _editor_el('msg-message', template),
  250. multi: _editor_el('multi-value', template),
  251. multiReturn: _editor_el('msg-multi', template),
  252. multiInfo: _editor_el('multi-info', template),
  253. processing: _editor_el('field-processing', template)
  254. } );
  255. // On click - set a common value for the field
  256. this.dom.multi.on( 'click', function () {
  257. if ( that.s.opts.multiEditable && ! template.hasClass( classes.disabled ) && opts.type !== 'readonly' ) {
  258. that.val('');
  259. that.focus();
  260. }
  261. } );
  262. this.dom.multiReturn.on( 'click', function () {
  263. that.multiRestore();
  264. } );
  265. // Field type extension methods - add a method to the field for the public
  266. // methods that each field type defines beyond the default ones that already
  267. // exist as part of this instance
  268. $.each( this.s.type, function ( name, fn ) {
  269. if ( typeof fn === 'function' && that[name] === undefined ) {
  270. that[ name ] = function () {
  271. var args = Array.prototype.slice.call( arguments );
  272. args.unshift( name );
  273. var ret = that._typeFn.apply( that, args );
  274. // Return the given value if there is one, or the field instance
  275. // for chaining if there is no value
  276. return ret === undefined ?
  277. that :
  278. ret;
  279. };
  280. }
  281. } );
  282. };
  283. Editor.Field.prototype = {
  284. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  285. * Public
  286. */
  287. def: function ( set ) {
  288. var opts = this.s.opts;
  289. if ( set === undefined ) {
  290. // Backwards compat
  291. var def = opts['default'] !== undefined ?
  292. opts['default'] :
  293. opts.def;
  294. return typeof def === 'function' ?
  295. def() :
  296. def;
  297. }
  298. opts.def = set;
  299. return this;
  300. },
  301. disable: function () {
  302. this.dom.container.addClass( this.s.classes.disabled );
  303. this._typeFn( 'disable' );
  304. return this;
  305. },
  306. displayed: function () {
  307. var container = this.dom.container;
  308. return container.parents('body').length && container.css('display') != 'none' ?
  309. true :
  310. false;
  311. },
  312. enable: function () {
  313. this.dom.container.removeClass( this.s.classes.disabled );
  314. this._typeFn( 'enable' );
  315. return this;
  316. },
  317. enabled: function () {
  318. return this.dom.container.hasClass( this.s.classes.disabled ) === false;
  319. },
  320. error: function ( msg, fn ) {
  321. var classes = this.s.classes;
  322. // Add or remove the error class
  323. if ( msg ) {
  324. this.dom.container.addClass( classes.error );
  325. }
  326. else {
  327. this.dom.container.removeClass( classes.error );
  328. }
  329. this._typeFn( 'errorMessage', msg );
  330. return this._msg( this.dom.fieldError, msg, fn );
  331. },
  332. fieldInfo: function ( msg ) {
  333. return this._msg( this.dom.fieldInfo, msg );
  334. },
  335. isMultiValue: function () {
  336. return this.s.multiValue && this.s.multiIds.length !== 1;
  337. },
  338. inError: function () {
  339. return this.dom.container.hasClass( this.s.classes.error );
  340. },
  341. input: function () {
  342. return this.s.type.input ?
  343. this._typeFn( 'input' ) :
  344. $('input, select, textarea', this.dom.container);
  345. },
  346. focus: function () {
  347. if ( this.s.type.focus ) {
  348. this._typeFn( 'focus' );
  349. }
  350. else {
  351. $('input, select, textarea', this.dom.container).focus();
  352. }
  353. return this;
  354. },
  355. get: function () {
  356. // When multi-value a single get is undefined
  357. if ( this.isMultiValue() ) {
  358. return undefined;
  359. }
  360. var val = this._typeFn( 'get' );
  361. return val !== undefined ?
  362. val :
  363. this.def();
  364. },
  365. hide: function ( animate ) {
  366. var el = this.dom.container;
  367. if ( animate === undefined ) {
  368. animate = true;
  369. }
  370. if ( this.s.host.display() && animate && $.fn.slideUp ) {
  371. el.slideUp();
  372. }
  373. else {
  374. el.css( 'display', 'none' );
  375. }
  376. return this;
  377. },
  378. label: function ( str ) {
  379. var label = this.dom.label;
  380. var labelInfo = this.dom.labelInfo.detach();
  381. if ( str === undefined ) {
  382. return label.html();
  383. }
  384. label.html( str );
  385. label.append( labelInfo );
  386. return this;
  387. },
  388. labelInfo: function ( msg ) {
  389. return this._msg( this.dom.labelInfo, msg );
  390. },
  391. message: function ( msg, fn ) {
  392. return this._msg( this.dom.fieldMessage, msg, fn );
  393. },
  394. // There is no `multiVal()` as its arguments could be ambiguous
  395. // id is an idSrc value _only_
  396. multiGet: function ( id ) {
  397. var value;
  398. var multiValues = this.s.multiValues;
  399. var multiIds = this.s.multiIds;
  400. var isMultiValue = this.isMultiValue();
  401. if ( id === undefined ) {
  402. var fieldVal = this.val();
  403. // Get an object with the values for each item being edited
  404. value = {};
  405. for ( var i=0 ; i<multiIds.length ; i++ ) {
  406. value[ multiIds[i] ] = isMultiValue ?
  407. multiValues[ multiIds[i] ] :
  408. fieldVal;
  409. }
  410. }
  411. else if ( isMultiValue ) {
  412. // Individual value
  413. value = multiValues[ id ];
  414. }
  415. else {
  416. // Common value
  417. value = this.val();
  418. }
  419. return value;
  420. },
  421. multiRestore: function () {
  422. this.s.multiValue = true;
  423. this._multiValueCheck();
  424. },
  425. multiSet: function ( id, val )
  426. {
  427. var multiValues = this.s.multiValues;
  428. var multiIds = this.s.multiIds;
  429. if ( val === undefined ) {
  430. val = id;
  431. id = undefined;
  432. }
  433. // Set
  434. var set = function ( idSrc, val ) {
  435. // Get an individual item's value - add the id to the edit ids if
  436. // it isn't already in the set.
  437. if ( $.inArray( multiIds ) === -1 ) {
  438. multiIds.push( idSrc );
  439. }
  440. multiValues[ idSrc ] = val;
  441. };
  442. if ( $.isPlainObject( val ) && id === undefined ) {
  443. // idSrc / value pairs passed in
  444. $.each( val, function ( idSrc, innerVal ) {
  445. set( idSrc, innerVal );
  446. } );
  447. }
  448. else if ( id === undefined ) {
  449. // Set same value for all existing ids
  450. $.each( multiIds, function ( i, idSrc ) {
  451. set( idSrc, val );
  452. } );
  453. }
  454. else {
  455. // Setting an individual property
  456. set( id, val );
  457. }
  458. this.s.multiValue = true;
  459. this._multiValueCheck();
  460. return this;
  461. },
  462. name: function () {
  463. return this.s.opts.name;
  464. },
  465. node: function () {
  466. return this.dom.container[0];
  467. },
  468. processing: function (set) {
  469. this.dom.processing.css('display', set ? 'block' : 'none');
  470. return this;
  471. },
  472. // multiCheck is not publically documented
  473. set: function ( val, multiCheck ) {
  474. var decodeFn = function ( d ) {
  475. return typeof d !== 'string' ?
  476. d :
  477. d
  478. .replace(/&gt;/g, '>')
  479. .replace(/&lt;/g, '<')
  480. .replace(/&amp;/g, '&')
  481. .replace(/&quot;/g, '"')
  482. .replace(/&#163;/g, '£')
  483. .replace(/&#39;/g, '\'')
  484. .replace(/&#10;/g, '\n');
  485. };
  486. this.s.multiValue = false;
  487. var decode = this.s.opts.entityDecode;
  488. if ( decode === undefined || decode === true ) {
  489. if ( $.isArray( val ) ) {
  490. for ( var i=0, ien=val.length ; i<ien ; i++ ) {
  491. val[i] = decodeFn( val[i] );
  492. }
  493. }
  494. else {
  495. val = decodeFn( val );
  496. }
  497. }
  498. this._typeFn( 'set', val );
  499. if ( multiCheck === undefined || multiCheck === true ) {
  500. this._multiValueCheck();
  501. }
  502. return this;
  503. },
  504. show: function ( animate ) {
  505. var el = this.dom.container;
  506. if ( animate === undefined ) {
  507. animate = true;
  508. }
  509. if ( this.s.host.display() && animate && $.fn.slideDown ) {
  510. el.slideDown();
  511. }
  512. else {
  513. el.css( 'display', '' ); // empty to restore css default (flex or block)
  514. }
  515. return this;
  516. },
  517. val: function ( val ) {
  518. return val === undefined ?
  519. this.get() :
  520. this.set( val );
  521. },
  522. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  523. * Internal - Called from Editor only and are not publicly documented -
  524. * these APIs can change!
  525. */
  526. compare: function ( value, original ) {
  527. var compare = this.s.opts.compare || _deepCompare;
  528. return compare( value, original );
  529. },
  530. dataSrc: function () {
  531. return this.s.opts.data;
  532. },
  533. destroy: function () {
  534. // remove element
  535. this.dom.container.remove();
  536. // field's own destroy method if there is one
  537. this._typeFn( 'destroy' );
  538. return this;
  539. },
  540. multiEditable: function () {
  541. return this.s.opts.multiEditable;
  542. },
  543. multiIds: function () {
  544. return this.s.multiIds;
  545. },
  546. multiInfoShown: function ( show ) {
  547. this.dom.multiInfo.css( { display: show ? 'block' : 'none' } );
  548. },
  549. multiReset: function () {
  550. this.s.multiIds = [];
  551. this.s.multiValues = {};
  552. },
  553. submittable: function () {
  554. return this.s.opts.submit;
  555. },
  556. valFromData: null,
  557. valToData: null,
  558. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  559. * Private
  560. */
  561. _errorNode: function () {
  562. return this.dom.fieldError;
  563. },
  564. _msg: function ( el, msg, fn ) {
  565. if ( msg === undefined ) {
  566. return el.html();
  567. }
  568. if ( typeof msg === 'function' ) {
  569. var editor = this.s.host;
  570. msg = msg( editor, new DataTable.Api( editor.s.table ) );
  571. }
  572. if ( el.parent().is(":visible") && $.fn.animate ) {
  573. el.html( msg );
  574. if ( msg ) {
  575. el.slideDown( fn ); // fn can be undefined - so jQuery won't execute it
  576. }
  577. else {
  578. el.slideUp( fn );
  579. }
  580. }
  581. else {
  582. // Not visible, so immediately set, or blank out the element
  583. el
  584. .html( msg || '' )
  585. .css( 'display', msg ? 'block' : 'none' );
  586. if ( fn ) {
  587. fn();
  588. }
  589. }
  590. return this;
  591. },
  592. _multiValueCheck: function () {
  593. var last;
  594. var ids = this.s.multiIds;
  595. var values = this.s.multiValues;
  596. var isMultiValue = this.s.multiValue;
  597. var isMultiEditable = this.s.opts.multiEditable;
  598. var val;
  599. var different = false;
  600. if ( ids ) {
  601. for ( var i=0 ; i<ids.length ; i++ ) {
  602. val = values[ ids[i] ];
  603. if ( i > 0 && ! _deepCompare( val, last ) ) {
  604. different = true;
  605. break;
  606. }
  607. last = val;
  608. }
  609. }
  610. if ( (different && isMultiValue) || (!isMultiEditable && this.isMultiValue()) ) {
  611. // Different values or same values, but not multiple editable
  612. this.dom.inputControl.css( { display: 'none' } );
  613. this.dom.multi.css( { display: 'block' } );
  614. }
  615. else {
  616. // All the same value
  617. this.dom.inputControl.css( { display: 'block' } );
  618. this.dom.multi.css( { display: 'none' } );
  619. if ( isMultiValue && ! different ) {
  620. this.set( last, false );
  621. }
  622. }
  623. this.dom.multiReturn.css( {
  624. display: ids && ids.length > 1 && different && ! isMultiValue ?
  625. 'block' :
  626. 'none'
  627. } );
  628. // Update information label
  629. var i18n = this.s.host.i18n.multi;
  630. this.dom.multiInfo.html( isMultiEditable ? i18n.info : i18n.noMulti);
  631. this.dom.multi.toggleClass( this.s.classes.multiNoEdit, ! isMultiEditable );
  632. this.s.host._multiInfo();
  633. return true;
  634. },
  635. _typeFn: function ( name /*, ... */ ) {
  636. // Remove the name from the arguments list, so the rest can be passed
  637. // straight into the field type
  638. var args = Array.prototype.slice.call( arguments );
  639. args.shift();
  640. // Insert the options as the first parameter - all field type methods
  641. // take the field's configuration object as the first parameter
  642. args.unshift( this.s.opts );
  643. var fn = this.s.type[ name ];
  644. if ( fn ) {
  645. return fn.apply( this.s.host, args );
  646. }
  647. }
  648. };
  649. Editor.Field.models = {};
  650. /**
  651. * Initialisation options that can be given to Editor.Field at initialisation
  652. * time.
  653. * @namespace
  654. */
  655. Editor.Field.defaults = {
  656. /**
  657. * Class name to assign to the field's container element (in addition to the other
  658. * classes that Editor assigns by default).
  659. * @type string
  660. * @default <i>Empty string</i>
  661. */
  662. "className": "",
  663. /**
  664. * The data property (`mData` in DataTables terminology) that is used to
  665. * read from and write to the table. If not given then it will take the same
  666. * value as the `name` that is given in the field object. Note that `data`
  667. * can be given as null, which will result in Editor not using a DataTables
  668. * row property for the value of the field for either getting or setting
  669. * data.
  670. *
  671. * In previous versions of Editor (1.2-) this was called `dataProp`. The old
  672. * name can still be used for backwards compatibility, but the new form is
  673. * preferred.
  674. * @type string
  675. * @default <i>Empty string</i>
  676. */
  677. "data": "",
  678. /**
  679. * The default value for the field. Used when creating new rows (editing will
  680. * use the currently set value). If given as a function the function will be
  681. * executed and the returned value used as the default
  682. *
  683. * In Editor 1.2 and earlier this field was called `default` - however
  684. * `default` is a reserved word in Javascript, so it couldn't be used
  685. * unquoted. `default` will still work with Editor 1.3, but the new property
  686. * name of `def` is preferred.
  687. * @type string|function
  688. * @default <i>Empty string</i>
  689. */
  690. "def": "",
  691. /**
  692. * Helpful information text about the field that is shown below the input control.
  693. * @type string
  694. * @default <i>Empty string</i>
  695. */
  696. "fieldInfo": "",
  697. /**
  698. * The ID of the field. This is used by the `label` HTML tag as the "for" attribute
  699. * improved accessibility. Although this using this parameter is not mandatory,
  700. * it is a good idea to assign the ID to the DOM element that is the input for the
  701. * field (if this is applicable).
  702. * @type string
  703. * @default <i>Calculated</i>
  704. */
  705. "id": "",
  706. /**
  707. * The label to display for the field input (i.e. the name that is visually
  708. * assigned to the field).
  709. * @type string
  710. * @default <i>Empty string</i>
  711. */
  712. "label": "",
  713. /**
  714. * Helpful information text about the field that is shown below the field label.
  715. * @type string
  716. * @default <i>Empty string</i>
  717. */
  718. "labelInfo": "",
  719. /**
  720. * The name for the field that is submitted to the server. This is the only
  721. * mandatory parameter in the field description object.
  722. * @type string
  723. * @default <i>null</i>
  724. */
  725. "name": null,
  726. /**
  727. * The input control that is presented to the end user. The options available
  728. * are defined by {@link Editor.fieldTypes} and any extensions made
  729. * to that object.
  730. * @type string
  731. * @default text
  732. */
  733. "type": "text",
  734. /**
  735. * Information message for the field - expected to be dynamic
  736. * @type string
  737. * @default <i>Empty string</i>
  738. */
  739. "message": "",
  740. /**
  741. * Allow a field to be editable when multiple rows are selected
  742. * @type boolean
  743. * @default true
  744. */
  745. "multiEditable": true,
  746. /**
  747. * Indicate if the field's value can be submitted
  748. * @type boolean
  749. * @default true
  750. */
  751. "submit": true
  752. };
  753. /**
  754. *
  755. * @namespace
  756. */
  757. Editor.Field.models.settings = {
  758. type: null,
  759. name: null,
  760. classes: null,
  761. opts: null,
  762. host: null
  763. };
  764. /**
  765. *
  766. * @namespace
  767. */
  768. Editor.Field.models.dom = {
  769. container: null,
  770. label: null,
  771. labelInfo: null,
  772. fieldInfo: null,
  773. fieldError: null,
  774. fieldMessage: null
  775. };
  776. /*
  777. * Models
  778. */
  779. /**
  780. * Object models container, for the various models that DataTables has available
  781. * to it. These models define the objects that are used to hold the active state
  782. * and configuration of the table.
  783. * @namespace
  784. */
  785. Editor.models = {};
  786. /**
  787. * Editor makes very few assumptions about how its form will actually be
  788. * displayed to the end user (where in the DOM, interaction etc), instead
  789. * focusing on providing form interaction controls only. To actually display
  790. * a form in the browser we need to use a display controller, and then select
  791. * which one we want to use at initialisation time using the `display`
  792. * option. For example a display controller could display the form in a
  793. * lightbox (as the default display controller does), it could completely
  794. * empty the document and put only the form in place, ir could work with
  795. * DataTables to use `fnOpen` / `fnClose` to show the form in a "details" row
  796. * and so on.
  797. *
  798. * Editor has two built-in display controllers ('lightbox' and 'envelope'),
  799. * but others can readily be created and installed for use as plug-ins. When
  800. * creating a display controller plug-in you **must** implement the methods
  801. * in this control. Additionally when closing the display internally you
  802. * **must** trigger a `requestClose` event which Editor will listen
  803. * for and act upon (this allows Editor to ask the user if they are sure
  804. * they want to close the form, for example).
  805. * @namespace
  806. */
  807. Editor.models.displayController = {
  808. /**
  809. * Initialisation method, called by Editor when itself, initialises.
  810. * @param {object} dte The DataTables Editor instance that has requested
  811. * the action - this allows access to the Editor API if required.
  812. * @returns {object} The object that Editor will use to run the 'open'
  813. * and 'close' methods against. If static methods are used then
  814. * just return the object that holds the init, open and close methods,
  815. * however, this allows the display to be created with a 'new'
  816. * instance of an object is the display controller calls for that.
  817. * @type function
  818. */
  819. "init": function ( dte ) {},
  820. /**
  821. * Display the form (add it to the visual display in the document)
  822. * @param {object} dte The DataTables Editor instance that has requested
  823. * the action - this allows access to the Editor API if required.
  824. * @param {element} append The DOM node that contains the form to be
  825. * displayed
  826. * @param {function} [fn] Callback function that is to be executed when
  827. * the form has been displayed. Note that this parameter is optional.
  828. */
  829. "open": function ( dte, append, fn ) {},
  830. /**
  831. * Hide the form (remove it form the visual display in the document)
  832. * @param {object} dte The DataTables Editor instance that has requested
  833. * the action - this allows access to the Editor API if required.
  834. * @param {function} [fn] Callback function that is to be executed when
  835. * the form has been hidden. Note that this parameter is optional.
  836. */
  837. "close": function ( dte, fn ) {}
  838. };
  839. /**
  840. * Model object for input types which are available to fields (assigned to
  841. * {@link Editor.fieldTypes}). Any plug-ins which add additional
  842. * input types to Editor **must** implement the methods in this object
  843. * (dummy functions are given in the model so they can be used as defaults
  844. * if extending this object).
  845. *
  846. * All functions in the model are executed in the Editor's instance scope,
  847. * so you have full access to the settings object and the API methods if
  848. * required.
  849. * @namespace
  850. * @example
  851. * // Add a simple text input (the 'text' type that is built into Editor
  852. * // does this, so you wouldn't implement this exactly as show, but it
  853. * // it is a good example.
  854. *
  855. * var Editor = $.fn.Editor;
  856. *
  857. * Editor.fieldTypes.myInput = $.extend( true, {}, Editor.models.type, {
  858. * "create": function ( conf ) {
  859. * // We store the 'input' element in the configuration object so
  860. * // we can easily access it again in future.
  861. * conf._input = document.createElement('input');
  862. * conf._input.id = conf.id;
  863. * return conf._input;
  864. * },
  865. *
  866. * "get": function ( conf ) {
  867. * return conf._input.value;
  868. * },
  869. *
  870. * "set": function ( conf, val ) {
  871. * conf._input.value = val;
  872. * },
  873. *
  874. * "enable": function ( conf ) {
  875. * conf._input.disabled = false;
  876. * },
  877. *
  878. * "disable": function ( conf ) {
  879. * conf._input.disabled = true;
  880. * }
  881. * } );
  882. */
  883. Editor.models.fieldType = {
  884. /**
  885. * Create the field - this is called when the field is added to the form.
  886. * Note that this is called at initialisation time, or when the
  887. * {@link Editor#add} API method is called, not when the form is displayed.
  888. * If you need to know when the form is shown, you can use the API to listen
  889. * for the `open` event.
  890. * @param {object} conf The configuration object for the field in question:
  891. * {@link Editor.models.field}.
  892. * @returns {element|null} The input element (or a wrapping element if a more
  893. * complex input is required) or null if nothing is to be added to the
  894. * DOM for this input type.
  895. * @type function
  896. */
  897. "create": function ( conf ) {},
  898. /**
  899. * Get the value from the field
  900. * @param {object} conf The configuration object for the field in question:
  901. * {@link Editor.models.field}.
  902. * @returns {*} The value from the field - the exact value will depend on the
  903. * formatting required by the input type control.
  904. * @type function
  905. */
  906. "get": function ( conf ) {},
  907. /**
  908. * Set the value for a field
  909. * @param {object} conf The configuration object for the field in question:
  910. * {@link Editor.models.field}.
  911. * @param {*} val The value to set the field to - the exact value will
  912. * depend on the formatting required by the input type control.
  913. * @type function
  914. */
  915. "set": function ( conf, val ) {},
  916. /**
  917. * Enable the field - i.e. allow user interface
  918. * @param {object} conf The configuration object for the field in question:
  919. * {@link Editor.models.field}.
  920. * @type function
  921. */
  922. "enable": function ( conf ) {},
  923. /**
  924. * Disable the field - i.e. disallow user interface
  925. * @param {object} conf The configuration object for the field in question:
  926. * {@link Editor.models.field}.
  927. * @type function
  928. */
  929. "disable": function ( conf ) {}
  930. };
  931. /**
  932. * Settings object for Editor - this provides the state for each instance of
  933. * Editor and can be accessed through the instance's `s` property. Note that the
  934. * settings object is considered to be "private" and thus is liable to change
  935. * between versions. As such if you do read any of the setting parameters,
  936. * please keep this in mind when upgrading!
  937. * @namespace
  938. */
  939. Editor.models.settings = {
  940. /**
  941. * URL to submit Ajax data to.
  942. * This is directly set by the initialisation parameter / default of the same name.
  943. * @type string
  944. * @default null
  945. */
  946. "ajaxUrl": null,
  947. /**
  948. * Ajax submit function.
  949. * This is directly set by the initialisation parameter / default of the same name.
  950. * @type function
  951. * @default null
  952. */
  953. "ajax": null,
  954. /**
  955. * Data source for get and set data actions. This allows Editor to perform
  956. * as an Editor for virtually any data source simply by defining additional
  957. * data sources.
  958. * @type object
  959. * @default null
  960. */
  961. "dataSource": null,
  962. /**
  963. * DataTable selector, can be anything that the Api supports
  964. * This is directly set by the initialisation parameter / default of the same name.
  965. * @type string
  966. * @default null
  967. */
  968. "domTable": null,
  969. /**
  970. * The initialisation object that was given by the user - stored for future reference.
  971. * This is directly set by the initialisation parameter / default of the same name.
  972. * @type string
  973. * @default null
  974. */
  975. "opts": null,
  976. /**
  977. * The display controller object for the Form.
  978. * This is directly set by the initialisation parameter / default of the same name.
  979. * @type string
  980. * @default null
  981. */
  982. "displayController": null,
  983. /**
  984. * The form fields - see {@link Editor.models.field} for details of the
  985. * objects held in this array.
  986. * @type object
  987. * @default null
  988. */
  989. "fields": {},
  990. /**
  991. * Field order - order that the fields will appear in on the form. Array of strings,
  992. * the names of the fields.
  993. * @type array
  994. * @default null
  995. */
  996. "order": [],
  997. /**
  998. * The ID of the row being edited (set to -1 on create and remove actions)
  999. * @type string
  1000. * @default null
  1001. */
  1002. "id": -1,
  1003. /**
  1004. * Flag to indicate if the form is currently displayed or not and what type of display
  1005. * @type string
  1006. * @default null
  1007. */
  1008. "displayed": false,
  1009. /**
  1010. * Flag to indicate if the form is current in a processing state (true) or not (false)
  1011. * @type string
  1012. * @default null
  1013. */
  1014. "processing": false,
  1015. /**
  1016. * Developer provided identifier for the elements to be edited (i.e. at
  1017. * `dt-type row-selector` to select rows to edit or delete.
  1018. * @type array
  1019. * @default null
  1020. */
  1021. "modifier": null,
  1022. /**
  1023. * The current form action - 'create', 'edit' or 'remove'. If no current action then
  1024. * it is set to null.
  1025. * @type string
  1026. * @default null
  1027. */
  1028. "action": null,
  1029. /**
  1030. * JSON property from which to read / write the row's ID property.
  1031. * @type string
  1032. * @default null
  1033. */
  1034. "idSrc": null,
  1035. /**
  1036. * Unique instance counter to be able to remove events
  1037. */
  1038. "unique": 0
  1039. };
  1040. /**
  1041. * Model of the buttons that can be used with the {@link Editor#buttons}
  1042. * method for creating and displaying buttons (also the {@link Editor#button}
  1043. * argument option for the {@link Editor#create}, {@link Editor#edit} and
  1044. * {@link Editor#remove} methods). Although you don't need to extend this object,
  1045. * it is available for reference to show the options available.
  1046. * @namespace
  1047. */
  1048. Editor.models.button = {
  1049. /**
  1050. * The text to put into the button. This can be any HTML string you wish as
  1051. * it will be rendered as HTML (allowing images etc to be shown inside the
  1052. * button).
  1053. * @type string
  1054. * @default null
  1055. */
  1056. "label": null,
  1057. /**
  1058. * Callback function which the button is activated. For example for a 'submit'
  1059. * button you would call the {@link Editor#submit} API method, while for a cancel button
  1060. * you would call the {@link Editor#close} API method. Note that the function is executed
  1061. * in the scope of the Editor instance, so you can call the Editor's API methods
  1062. * using the `this` keyword.
  1063. * @type function
  1064. * @default null
  1065. */
  1066. "fn": null,
  1067. /**
  1068. * The CSS class(es) to apply to the button which can be useful for styling buttons
  1069. * which preform different functions each with a distinctive visual appearance.
  1070. * @type string
  1071. * @default null
  1072. */
  1073. "className": null
  1074. };
  1075. /**
  1076. * This is really an internal namespace
  1077. *
  1078. * @namespace
  1079. */
  1080. Editor.models.formOptions = {
  1081. /**
  1082. * Action to take when the return key is pressed when focused in a form
  1083. * element. Cam be `submit` or `none`. Could also be `blur` or `close`, but
  1084. * why would you ever want that. Replaces `submitOnReturn` from 1.4.
  1085. *
  1086. * @type string
  1087. */
  1088. onReturn: 'submit',
  1089. /**
  1090. * Action to take on blur. Can be `close`, `submit` or `none`. Replaces
  1091. * `submitOnBlur` from 1.4
  1092. *
  1093. * @type string
  1094. */
  1095. onBlur: 'close',
  1096. /**
  1097. * Action to take when the lightbox background is clicked - can be `close`,
  1098. * `submit`, `blur` or `none`. Replaces `blurOnBackground` from 1.4
  1099. *
  1100. * @type string
  1101. */
  1102. onBackground: 'blur',
  1103. /**
  1104. * Close for at the end of the Ajax request. Can be `close` or `none`.
  1105. * Replaces `closeOnComplete` from 1.4.
  1106. *
  1107. * @type string
  1108. */
  1109. onComplete: 'close',
  1110. /**
  1111. * Action to take when the `esc` key is pressed when focused in the form -
  1112. * can be `close`, `submit`, `blur` or `none`
  1113. *
  1114. * @type string
  1115. */
  1116. onEsc: 'close',
  1117. /**
  1118. * Action to take when a field error is detected in the returned JSON - can
  1119. * be `focus` or `none`
  1120. *
  1121. * @type string
  1122. */
  1123. onFieldError: 'focus',
  1124. /**
  1125. * Data to submit to the server when submitting a form. If an option is
  1126. * selected that results in no data being submitted, the Ajax request will
  1127. * not be made Can be `all`, `changed` or `allIfChanged`. This effects the
  1128. * edit action only.
  1129. *
  1130. * @type string
  1131. */
  1132. submit: 'all',
  1133. /**
  1134. * Field identifier to focus on
  1135. *
  1136. * @type null|integer|string
  1137. */
  1138. focus: 0,
  1139. /**
  1140. * Buttons to show in the form
  1141. *
  1142. * @type string|boolean|array|object
  1143. */
  1144. buttons: true,
  1145. /**
  1146. * Form title
  1147. *
  1148. * @type string|boolean
  1149. */
  1150. title: true,
  1151. /**
  1152. * Form message
  1153. *
  1154. * @type string|boolean
  1155. */
  1156. message: true,
  1157. /**
  1158. * DataTables redraw option
  1159. *
  1160. * @type string|boolean
  1161. */
  1162. drawType: false,
  1163. /**
  1164. * Editing scope. Can be `row` or `cell`.
  1165. *
  1166. * @type string
  1167. */
  1168. scope: 'row'
  1169. };
  1170. /*
  1171. * Display controllers
  1172. */
  1173. /**
  1174. * Display controllers. See {@link Editor.models.displayController} for
  1175. * full information about the display controller options for Editor. The display
  1176. * controllers given in this object can be utilised by specifying the
  1177. * {@link Editor.defaults.display} option.
  1178. * @namespace
  1179. */
  1180. Editor.display = {};
  1181. (function() {
  1182. var self;
  1183. Editor.display.lightbox = $.extend( true, {}, Editor.models.displayController, {
  1184. /*
  1185. * API methods
  1186. */
  1187. "init": function ( dte ) {
  1188. self._init();
  1189. return self;
  1190. },
  1191. "open": function ( dte, append, callback ) {
  1192. if ( self._shown ) {
  1193. if ( callback ) {
  1194. callback();
  1195. }
  1196. return;
  1197. }
  1198. self._dte = dte;
  1199. var content = self._dom.content;
  1200. content.children().detach();
  1201. content
  1202. .append( append )
  1203. .append( self._dom.close );
  1204. self._shown = true;
  1205. self._show( callback );
  1206. },
  1207. "close": function ( dte, callback ) {
  1208. if ( !self._shown ) {
  1209. if ( callback ) {
  1210. callback();
  1211. }
  1212. return;
  1213. }
  1214. self._dte = dte;
  1215. self._hide( callback );
  1216. self._shown = false;
  1217. },
  1218. node: function ( dte ) {
  1219. return self._dom.wrapper[0];
  1220. },
  1221. /*
  1222. * Private methods
  1223. */
  1224. "_init": function () {
  1225. if ( self._ready ) {
  1226. return;
  1227. }
  1228. var dom = self._dom;
  1229. dom.content = $('div.DTED_Lightbox_Content', self._dom.wrapper);
  1230. dom.wrapper.css( 'opacity', 0 );
  1231. dom.background.css( 'opacity', 0 );
  1232. },
  1233. "_show": function ( callback ) {
  1234. var that = this;
  1235. var dom = self._dom;
  1236. // Mobiles have very poor position fixed abilities, so we need to know
  1237. // when using mobile A media query isn't good enough
  1238. if ( window.orientation !== undefined ) {
  1239. $('body').addClass( 'DTED_Lightbox_Mobile' );
  1240. }
  1241. // Adjust size for the content
  1242. dom.content.css( 'height', 'auto' );
  1243. dom.wrapper.css( {
  1244. top: -self.conf.offsetAni
  1245. } );
  1246. $('body')
  1247. .append( self._dom.background )
  1248. .append( self._dom.wrapper );
  1249. self._heightCalc();
  1250. self._dte._animate(
  1251. dom.wrapper,
  1252. {
  1253. opacity: 1,
  1254. top: 0
  1255. },
  1256. callback
  1257. );
  1258. self._dte._animate( dom.background, {
  1259. opacity: 1
  1260. } );
  1261. // Terrible Chrome workaround. Since m53 the footer would be incorrectly
  1262. // offset. This triggers a rerender. See thread 38145
  1263. setTimeout( function () {
  1264. $('div.DTE_Footer').css( 'text-indent', -1 );
  1265. }, 10 );
  1266. // Event handlers - assign on show (and unbind on hide) rather than init
  1267. // since we might need to refer to different editor instances - 12563
  1268. dom.close.bind( 'click.DTED_Lightbox', function (e) {
  1269. self._dte.close();
  1270. } );
  1271. dom.background.bind( 'click.DTED_Lightbox', function (e) {
  1272. self._dte.background();
  1273. } );
  1274. $('div.DTED_Lightbox_Content_Wrapper', dom.wrapper).bind( 'click.DTED_Lightbox', function (e) {
  1275. if ( $(e.target).hasClass('DTED_Lightbox_Content_Wrapper') ) {
  1276. self._dte.background();
  1277. }
  1278. } );
  1279. $(window).bind( 'resize.DTED_Lightbox', function () {
  1280. self._heightCalc();
  1281. } );
  1282. self._scrollTop = $('body').scrollTop();
  1283. // For smaller screens we need to hide the other elements in the
  1284. // document since iOS and Android both mess up display:fixed when
  1285. // the virtual keyboard is shown
  1286. if ( window.orientation !== undefined ) {
  1287. var kids = $('body').children().not( dom.background ).not( dom.wrapper );
  1288. $('body').append( '<div class="DTED_Lightbox_Shown"/>' );
  1289. $('div.DTED_Lightbox_Shown').append( kids );
  1290. }
  1291. },
  1292. "_heightCalc": function () {
  1293. // Set the max-height for the form content
  1294. var dom = self._dom;
  1295. var maxHeight = $(window).height() - (self.conf.windowPadding*2) -
  1296. $('div.DTE_Header', dom.wrapper).outerHeight() -
  1297. $('div.DTE_Footer', dom.wrapper).outerHeight();
  1298. $('div.DTE_Body_Content', dom.wrapper).css(
  1299. 'maxHeight',
  1300. maxHeight
  1301. );
  1302. },
  1303. "_hide": function ( callback ) {
  1304. var dom = self._dom;
  1305. if ( !callback ) {
  1306. callback = function () {};
  1307. }
  1308. if ( window.orientation !== undefined ) {
  1309. var show = $('div.DTED_Lightbox_Shown');
  1310. show.children().appendTo('body');
  1311. show.remove();
  1312. }
  1313. // Restore scroll state
  1314. $('body')
  1315. .removeClass( 'DTED_Lightbox_Mobile' )
  1316. .scrollTop( self._scrollTop );
  1317. self._dte._animate(
  1318. dom.wrapper,
  1319. {
  1320. opacity: 0,
  1321. top: self.conf.offsetAni
  1322. },
  1323. function () {
  1324. $(this).detach();
  1325. callback();
  1326. }
  1327. );
  1328. self._dte._animate(
  1329. dom.background,
  1330. {
  1331. opacity: 0
  1332. },
  1333. function () {
  1334. $(this).detach();
  1335. }
  1336. );
  1337. // Event handlers
  1338. dom.close.unbind( 'click.DTED_Lightbox' );
  1339. dom.background.unbind( 'click.DTED_Lightbox' );
  1340. $('div.DTED_Lightbox_Content_Wrapper', dom.wrapper).unbind( 'click.DTED_Lightbox' );
  1341. $(window).unbind( 'resize.DTED_Lightbox' );
  1342. },
  1343. /*
  1344. * Private properties
  1345. */
  1346. "_dte": null,
  1347. "_ready": false,
  1348. "_shown": false,
  1349. "_dom": {
  1350. "wrapper": $(
  1351. '<div class="DTED DTED_Lightbox_Wrapper">'+
  1352. '<div class="DTED_Lightbox_Container">'+
  1353. '<div class="DTED_Lightbox_Content_Wrapper">'+
  1354. '<div class="DTED_Lightbox_Content">'+
  1355. '</div>'+
  1356. '</div>'+
  1357. '</div>'+
  1358. '</div>'
  1359. ),
  1360. "background": $(
  1361. '<div class="DTED_Lightbox_Background"><div/></div>'
  1362. ),
  1363. "close": $(
  1364. '<div class="DTED_Lightbox_Close"></div>'
  1365. ),
  1366. "content": null
  1367. }
  1368. } );
  1369. self = Editor.display.lightbox;
  1370. self.conf = {
  1371. "offsetAni": 25,
  1372. "windowPadding": 25
  1373. };
  1374. }());
  1375. (function() {
  1376. var self;
  1377. Editor.display.envelope = $.extend( true, {}, Editor.models.displayController, {
  1378. /*
  1379. * API methods
  1380. */
  1381. "init": function ( dte ) {
  1382. self._dte = dte;
  1383. self._init();
  1384. return self;
  1385. },
  1386. "open": function ( dte, append, callback ) {
  1387. self._dte = dte;
  1388. $(self._dom.content).children().detach();
  1389. self._dom.content.appendChild( append );
  1390. self._dom.content.appendChild( self._dom.close );
  1391. self._show( callback );
  1392. },
  1393. "close": function ( dte, callback ) {
  1394. self._dte = dte;
  1395. self._hide( callback );
  1396. },
  1397. node: function ( dte ) {
  1398. return self._dom.wrapper[0];
  1399. },
  1400. /*
  1401. * Private methods
  1402. */
  1403. "_init": function () {
  1404. if ( self._ready ) {
  1405. return;
  1406. }
  1407. self._dom.content = $('div.DTED_Envelope_Container', self._dom.wrapper)[0];
  1408. // For IE6-8 we need to make it a block element to read the opacity...
  1409. self._dom.background.style.visbility = 'hidden';
  1410. self._dom.background.style.display = 'block';
  1411. self._cssBackgroundOpacity = $(self._dom.background).css('opacity');
  1412. self._dom.background.style.display = 'none';
  1413. self._dom.background.style.visbility = 'visible';
  1414. },
  1415. "_show": function ( callback ) {
  1416. var that = this;
  1417. var formHeight;
  1418. if ( !callback ) {
  1419. callback = function () {};
  1420. }
  1421. document.body.appendChild( self._dom.background );
  1422. document.body.appendChild( self._dom.wrapper );
  1423. // Adjust size for the content
  1424. self._dom.content.style.height = 'auto';
  1425. var style = self._dom.wrapper.style;
  1426. style.opacity = 0;
  1427. style.display = 'block';
  1428. var targetRow = self._findAttachRow();
  1429. var height = self._heightCalc();
  1430. var width = targetRow.offsetWidth;
  1431. style.display = 'none';
  1432. style.opacity = 1;
  1433. // Prep the display
  1434. self._dom.wrapper.style.width = width+"px";
  1435. self._dom.wrapper.style.marginLeft = -(width/2)+"px";
  1436. self._dom.wrapper.style.top = ($(targetRow).offset().top + targetRow.offsetHeight)+"px";
  1437. self._dom.content.style.top = ((-1 * height) - 20)+"px";
  1438. // Start animating in the background
  1439. self._dom.background.style.opacity = 0;
  1440. self._dom.background.style.display = 'block';
  1441. $(self._dom.background).animate( {
  1442. 'opacity': self._cssBackgroundOpacity
  1443. }, 'normal' );
  1444. // Animate in the display
  1445. $(self._dom.wrapper).fadeIn();
  1446. // Slide the slider down to 'open' the view
  1447. if ( self.conf.windowScroll ) {
  1448. // Scroll the window so we can see the editor first
  1449. $('html,body').animate( {
  1450. "scrollTop": $(targetRow).offset().top + targetRow.offsetHeight - self.conf.windowPadding
  1451. }, function () {
  1452. // Now open the editor
  1453. $(self._dom.content).animate( {
  1454. "top": 0
  1455. }, 600, callback );
  1456. } );
  1457. }
  1458. else {
  1459. // Just open the editor without moving the document position
  1460. $(self._dom.content).animate( {
  1461. "top": 0
  1462. }, 600, callback );
  1463. }
  1464. // Event handlers
  1465. $(self._dom.close).bind( 'click.DTED_Envelope', function (e) {
  1466. self._dte.close();
  1467. } );
  1468. $(self._dom.background).bind( 'click.DTED_Envelope', function (e) {
  1469. self._dte.background();
  1470. } );
  1471. $('div.DTED_Lightbox_Content_Wrapper', self._dom.wrapper).bind( 'click.DTED_Envelope', function (e) {
  1472. if ( $(e.target).hasClass('DTED_Envelope_Content_Wrapper') ) {
  1473. self._dte.background();
  1474. }
  1475. } );
  1476. $(window).bind( 'resize.DTED_Envelope', function () {
  1477. self._heightCalc();
  1478. } );
  1479. },
  1480. "_heightCalc": function () {
  1481. var formHeight;
  1482. formHeight = self.conf.heightCalc ?
  1483. self.conf.heightCalc( self._dom.wrapper ) :
  1484. $(self._dom.content).children().height();
  1485. // Set the max-height for the form content
  1486. var maxHeight = $(window).height() - (self.conf.windowPadding*2) -
  1487. $('div.DTE_Header', self._dom.wrapper).outerHeight() -
  1488. $('div.DTE_Footer', self._dom.wrapper).outerHeight();
  1489. $('div.DTE_Body_Content', self._dom.wrapper).css('maxHeight', maxHeight);
  1490. return $(self._dte.dom.wrapper).outerHeight();
  1491. },
  1492. "_hide": function ( callback ) {
  1493. if ( !callback ) {
  1494. callback = function () {};
  1495. }
  1496. $(self._dom.content).animate( {
  1497. "top": -(self._dom.content.offsetHeight+50)
  1498. }, 600, function () {
  1499. $([self._dom.wrapper, self._dom.background]).fadeOut( 'normal', function () {
  1500. $(this).remove();
  1501. callback();
  1502. } );
  1503. } );
  1504. // Event handlers
  1505. $(self._dom.close).unbind( 'click.DTED_Lightbox' );
  1506. $(self._dom.background).unbind( 'click.DTED_Lightbox' );
  1507. $('div.DTED_Lightbox_Content_Wrapper', self._dom.wrapper).unbind( 'click.DTED_Lightbox' );
  1508. $(window).unbind( 'resize.DTED_Lightbox' );
  1509. },
  1510. "_findAttachRow": function () {
  1511. var dt = new $.fn.dataTable.Api(self._dte.s.table);
  1512. // Figure out where we want to put the form display
  1513. if ( self.conf.attach === 'head' ) {
  1514. return dt.table().header();
  1515. }
  1516. else if ( self._dte.s.action === 'create' ) {
  1517. return dt.table().header();
  1518. }
  1519. else {
  1520. return dt.row( self._dte.s.modifier ).node();
  1521. }
  1522. },
  1523. /*
  1524. * Private properties
  1525. */
  1526. "_dte": null,
  1527. "_ready": false,
  1528. "_cssBackgroundOpacity": 1, // read from the CSS dynamically, but stored for future reference
  1529. "_dom": {
  1530. "wrapper": $(
  1531. '<div class="DTED DTED_Envelope_Wrapper">'+
  1532. '<div class="DTED_Envelope_Shadow"></div>'+
  1533. '<div class="DTED_Envelope_Container"></div>'+
  1534. '</div>'
  1535. )[0],
  1536. "background": $(
  1537. '<div class="DTED_Envelope_Background"><div/></div>'
  1538. )[0],
  1539. "close": $(
  1540. '<div class="DTED_Envelope_Close">&times;</div>'
  1541. )[0],
  1542. "content": null
  1543. }
  1544. } );
  1545. // Assign to 'self' for easy referencing of our own object!
  1546. self = Editor.display.envelope;
  1547. // Configuration object - can be accessed globally using
  1548. // $.fn.Editor.display.envelope.conf (!)
  1549. self.conf = {
  1550. "windowPadding": 50,
  1551. "heightCalc": null,
  1552. "attach": "row",
  1553. "windowScroll": true
  1554. };
  1555. }());
  1556. /*
  1557. * Prototype includes
  1558. */
  1559. /**
  1560. * Add a new field to the from. This is the method that is called automatically when
  1561. * fields are given in the initialisation objects as {@link Editor.defaults.fields}.
  1562. * @memberOf Editor
  1563. * @param {object|array} field The object that describes the field (the full
  1564. * object is described by {@link Editor.model.field}. Note that multiple
  1565. * fields can be given by passing in an array of field definitions.
  1566. * @param {string} [after] Existing field to insert the new field after. This
  1567. * can be `undefined` (insert at end), `null` (insert at start) or `string`
  1568. * the field name to insert after.
  1569. */
  1570. Editor.prototype.add = function ( cfg, after )
  1571. {
  1572. // Allow multiple fields to be added at the same time
  1573. if ( $.isArray( cfg ) ) {
  1574. // Do it in reverse to allow fields to appear in the same order given, otherwise,
  1575. // the would appear in reverse if given an `after`
  1576. if ( after !== undefined ) {
  1577. cfg.reverse();
  1578. }
  1579. for ( var i=0 ; i<cfg.length ; i++ ) {
  1580. this.add( cfg[i], after );
  1581. }
  1582. }
  1583. else {
  1584. var name = cfg.name;
  1585. if ( name === undefined ) {
  1586. throw "Error adding field. The field requires a `name` option";
  1587. }
  1588. if ( this.s.fields[ name ] ) {
  1589. throw "Error adding field '"+name+"'. A field already exists with this name";
  1590. }
  1591. // Allow the data source to add / modify the field properties
  1592. // Dev: would this be better as an event `preAddField`? And have the
  1593. // data sources init only once, but can listen for such events? More
  1594. // complexity, but probably more flexible...
  1595. this._dataSource( 'initField', cfg );
  1596. var field = new Editor.Field( cfg, this.classes.field, this );
  1597. // If in an editing mode, we need to set the field up for the data
  1598. if ( this.s.mode ) {
  1599. var editFields = this.s.editFields;
  1600. field.multiReset();
  1601. $.each( editFields, function ( idSrc, edit ) {
  1602. var val;
  1603. if ( edit.data ) {
  1604. val = field.valFromData( edit.data );
  1605. }
  1606. field.multiSet( idSrc, val !== undefined ?
  1607. val :
  1608. field.def()
  1609. );
  1610. } );
  1611. }
  1612. this.s.fields[ name ] = field;
  1613. if ( after === undefined ) {
  1614. this.s.order.push( name );
  1615. }
  1616. else if ( after === null ) {
  1617. this.s.order.unshift( name );
  1618. }
  1619. else {
  1620. var idx = $.inArray( after, this.s.order );
  1621. this.s.order.splice( idx+1, 0, name );
  1622. }
  1623. }
  1624. this._displayReorder( this.order() );
  1625. return this;
  1626. };
  1627. /**
  1628. * Get / set the Ajax configuration for the Editor instance
  1629. *
  1630. * @return {Editor} Editor instance, for chaining
  1631. */
  1632. Editor.prototype.ajax = function ( newAjax )
  1633. {
  1634. if ( newAjax ) {
  1635. this.s.ajax = newAjax;
  1636. return this;
  1637. }
  1638. return this.s.ajax;
  1639. };
  1640. /**
  1641. * Perform background activation tasks.
  1642. *
  1643. * This is NOT publicly documented on the Editor web-site, but rather can be
  1644. * used by display controller plug-ins to perform the required task on
  1645. * background activation.
  1646. *
  1647. * @return {Editor} Editor instance, for chaining
  1648. */
  1649. Editor.prototype.background = function ()
  1650. {
  1651. var onBackground = this.s.editOpts.onBackground;
  1652. if ( typeof onBackground === 'function' ) {
  1653. onBackground( this );
  1654. }
  1655. else if ( onBackground === 'blur' ) {
  1656. this.blur();
  1657. }
  1658. else if ( onBackground === 'close' ) {
  1659. this.close();
  1660. }
  1661. else if ( onBackground === 'submit' ) {
  1662. this.submit();
  1663. }
  1664. return this;
  1665. };
  1666. /**
  1667. * Blur the currently displayed editor.
  1668. *
  1669. * A blur is different from a `close()` in that it might cause either a close or
  1670. * the form to be submitted. A typical example of a blur would be clicking on
  1671. * the background of the bubble or main editing forms - i.e. it might be a
  1672. * close, or it might submit depending upon the configuration, while a click on
  1673. * the close box is a very definite close.
  1674. *
  1675. * @return {Editor} Editor instance, for chaining
  1676. */
  1677. Editor.prototype.blur = function ()
  1678. {
  1679. this._blur();
  1680. return this;
  1681. };
  1682. Editor.prototype.bubble = function ( cells, fieldNames, show, opts )
  1683. {
  1684. var that = this;
  1685. // Some other field in inline edit mode?
  1686. if ( this._tidy( function () { that.bubble( cells, fieldNames, opts ); } ) ) {
  1687. return this;
  1688. }
  1689. // Argument shifting
  1690. if ( $.isPlainObject( fieldNames ) ) {
  1691. opts = fieldNames;
  1692. fieldNames = undefined;
  1693. show = true;
  1694. }
  1695. else if ( typeof fieldNames === 'boolean' ) {
  1696. show = fieldNames;
  1697. fieldNames = undefined;
  1698. opts = undefined;
  1699. }
  1700. if ( $.isPlainObject( show ) ) {
  1701. opts = show;
  1702. show = true;
  1703. }
  1704. if ( show === undefined ) {
  1705. show = true;
  1706. }
  1707. opts = $.extend( {}, this.s.formOptions.bubble, opts );
  1708. var editFields = this._dataSource( 'individual', cells, fieldNames );
  1709. this._edit( cells, editFields, 'bubble', opts, function () {
  1710. var namespace = that._formOptions( opts );
  1711. var ret = that._preopen( 'bubble' );
  1712. if ( ! ret ) {
  1713. return that;
  1714. }
  1715. // Keep the bubble in position on resize
  1716. $(window).on( 'resize.'+namespace, function () {
  1717. that.bubblePosition();
  1718. } );
  1719. // Store the nodes that are being used so the bubble can be positioned
  1720. var nodes = [];
  1721. that.s.bubbleNodes = nodes.concat.apply( nodes, _pluck( editFields, 'attach' ) );
  1722. // Create container display
  1723. var classes = that.classes.bubble;
  1724. var background = $( '<div class="'+classes.bg+'"><div/></div>' );
  1725. var container = $(
  1726. '<div class="'+classes.wrapper+'">'+
  1727. '<div class="'+classes.liner+'">'+
  1728. '<div class="'+classes.table+'">'+
  1729. '<div class="'+classes.close+'" />'+
  1730. '<div class="DTE_Processing_Indicator"><span></div>'+
  1731. '</div>'+
  1732. '</div>'+
  1733. '<div class="'+classes.pointer+'" />'+
  1734. '</div>'
  1735. );
  1736. if ( show ) {
  1737. container.appendTo( 'body' );
  1738. background.appendTo( 'body' );
  1739. }
  1740. var liner = container.children().eq(0);
  1741. var table = liner.children();
  1742. var close = table.children();
  1743. liner.append( that.dom.formError );
  1744. table.prepend( that.dom.form );
  1745. if ( opts.message ) {
  1746. liner.prepend( that.dom.formInfo );
  1747. }
  1748. if ( opts.title ) {
  1749. liner.prepend( that.dom.header );
  1750. }
  1751. if ( opts.buttons ) {
  1752. table.append( that.dom.buttons );
  1753. }
  1754. var pair = $().add( container ).add( background );
  1755. that._closeReg( function ( submitComplete ) {
  1756. that._animate(
  1757. pair,
  1758. { opacity: 0 },
  1759. function () {
  1760. if (this === container[0]) {
  1761. pair.detach();
  1762. $(window).off( 'resize.'+namespace );
  1763. // Clear error messages "offline"
  1764. that._clearDynamicInfo();
  1765. that._event( 'closed', ['bubble'] );
  1766. }
  1767. }
  1768. );
  1769. } );
  1770. // Close event handlers
  1771. background.click( function () {
  1772. that.blur();
  1773. } );
  1774. close.click( function () {
  1775. that._close();
  1776. } );
  1777. that.bubblePosition();
  1778. that._animate( pair, { opacity: 1 } );
  1779. that._focus( that.s.includeFields, opts.focus );
  1780. that._postopen( 'bubble', true );
  1781. } );
  1782. return this;
  1783. };
  1784. /**
  1785. * Reposition the editing bubble (`bubble()`) when it is visible. This can be
  1786. * used to update the bubble position if other elements on the page change
  1787. * position. Editor will automatically call this method on window resize.
  1788. *
  1789. * @return {Editor} Editor instance, for chaining
  1790. */
  1791. Editor.prototype.bubblePosition = function ()
  1792. {
  1793. var
  1794. wrapper = $('div.DTE_Bubble'),
  1795. liner = $('div.DTE_Bubble_Liner'),
  1796. nodes = this.s.bubbleNodes;
  1797. // Average the node positions to insert the container
  1798. var position = { top: 0, left: 0, right: 0, bottom: 0 };
  1799. $.each( nodes, function (i, node) {
  1800. var pos = $(node).offset();
  1801. node = $(node).get(0);
  1802. position.top += pos.top;
  1803. position.left += pos.left;
  1804. position.right += pos.left + node.offsetWidth;
  1805. position.bottom += pos.top + node.offsetHeight;
  1806. } );
  1807. position.top /= nodes.length;
  1808. position.left /= nodes.length;
  1809. position.right /= nodes.length;
  1810. position.bottom /= nodes.length;
  1811. var
  1812. top = position.top,
  1813. left = (position.left + position.right) / 2,
  1814. width = liner.outerWidth(),
  1815. visLeft = left - (width / 2),
  1816. visRight = visLeft + width,
  1817. docWidth = $(window).width(),
  1818. padding = 15,
  1819. classes = this.classes.bubble;
  1820. wrapper.css( {
  1821. top: top,
  1822. left: left
  1823. } );
  1824. // Correct for overflow from the top of the document by positioning below
  1825. // the field if needed
  1826. if ( liner.length && liner.offset().top < 0 ) {
  1827. wrapper
  1828. .css( 'top', position.bottom )
  1829. .addClass( 'below' );
  1830. }
  1831. else {
  1832. wrapper.removeClass( 'below' );
  1833. }
  1834. // Attempt to correct for overflow to the right of the document
  1835. if ( visRight+padding > docWidth ) {
  1836. var diff = visRight - docWidth;
  1837. // If left overflowing, that takes priority
  1838. liner.css( 'left', visLeft < padding ?
  1839. -(visLeft-padding) :
  1840. -(diff+padding)
  1841. );
  1842. }
  1843. else {
  1844. // Correct overflow to the left
  1845. liner.css( 'left', visLeft < padding ? -(visLeft-padding) : 0 );
  1846. }
  1847. return this;
  1848. };
  1849. /**
  1850. * Setup the buttons that will be shown in the footer of the form - calling this
  1851. * method will replace any buttons which are currently shown in the form.
  1852. * @param {array|object} buttons A single button definition to add to the form or
  1853. * an array of objects with the button definitions to add more than one button.
  1854. * The options for the button definitions are fully defined by the
  1855. * {@link Editor.models.button} object.
  1856. * @param {string} buttons.text The text to put into the button. This can be any
  1857. * HTML string you wish as it will be rendered as HTML (allowing images etc to
  1858. * be shown inside the button).
  1859. * @param {function} [buttons.action] Callback function which the button is activated.
  1860. * For example for a 'submit' button you would call the {@link Editor#submit} method,
  1861. * while for a cancel button you would call the {@link Editor#close} method. Note that
  1862. * the function is executed in the scope of the Editor instance, so you can call
  1863. * the Editor's API methods using the `this` keyword.
  1864. * @param {string} [buttons.className] The CSS class(es) to apply to the button
  1865. * which can be useful for styling buttons which preform different functions
  1866. * each with a distinctive visual appearance.
  1867. * @return {Editor} Editor instance, for chaining
  1868. */
  1869. Editor.prototype.buttons = function ( buttons )
  1870. {
  1871. var that = this;
  1872. if ( buttons === '_basic' ) {
  1873. // Special string to create a basic button - undocumented
  1874. buttons = [ {
  1875. text: this.i18n[ this.s.action ].submit,
  1876. action: function () { this.submit(); }
  1877. } ];
  1878. }
  1879. else if ( ! $.isArray( buttons ) ) {
  1880. // Allow a single button to be passed in as an object with an array
  1881. buttons = [ buttons ];
  1882. }
  1883. $(this.dom.buttons).empty();
  1884. $.each( buttons, function ( i, btn ) {
  1885. if ( typeof btn === 'string' ) {
  1886. btn = {
  1887. text: btn,
  1888. action: function () { this.submit(); }
  1889. };
  1890. }
  1891. var text = btn.text || btn.label;
  1892. var action = btn.action || btn.fn;
  1893. $( '<button/>', {
  1894. 'class': that.classes.form.button+(btn.className ? ' '+btn.className : '')
  1895. } )
  1896. .html( typeof text === 'function' ?
  1897. text( that ) :
  1898. text || ''
  1899. )
  1900. .attr( 'tabindex', btn.tabIndex !== undefined ? btn.tabIndex : 0 )
  1901. .on( 'keyup', function (e) {
  1902. if ( e.keyCode === 13 && action ) {
  1903. action.call( that );
  1904. }
  1905. } )
  1906. .on( 'keypress', function (e) {
  1907. // Stop the browser activating the click event - if we don't
  1908. // have this and the Ajax return is fast, the keyup in
  1909. // `_formOptions()` might trigger another submit
  1910. if ( e.keyCode === 13 ) {
  1911. e.preventDefault();
  1912. }
  1913. } )
  1914. .on( 'click', function (e) {
  1915. e.preventDefault();
  1916. if ( action ) {
  1917. action.call( that, e );
  1918. }
  1919. } )
  1920. .appendTo( that.dom.buttons );
  1921. } );
  1922. return this;
  1923. };
  1924. /**
  1925. * Remove fields from the form (fields are those that have been added using the
  1926. * {@link Editor#add} method or the `fields` initialisation option). A single,
  1927. * multiple or all fields can be removed at a time based on the passed parameter.
  1928. * Fields are identified by the `name` property that was given to each field
  1929. * when added to the form.
  1930. * @param {string|array} [fieldName] Field or fields to remove from the form. If
  1931. * not given then all fields are removed from the form. If given as a string
  1932. * then the single matching field will be removed. If given as an array of
  1933. * strings, then all matching fields will be removed.
  1934. * @return {Editor} Editor instance, for chaining
  1935. *
  1936. * @example
  1937. * // Clear the form of current fields and then add a new field
  1938. * // before displaying a 'create' display
  1939. * editor.clear();
  1940. * editor.add( {
  1941. * "label": "User name",
  1942. * "name": "username"
  1943. * } );
  1944. * editor.create( "Create user" );
  1945. *
  1946. * @example
  1947. * // Remove an individual field
  1948. * editor.clear( "username" );
  1949. *
  1950. * @example
  1951. * // Remove multiple fields
  1952. * editor.clear( [ "first_name", "last_name" ] );
  1953. */
  1954. Editor.prototype.clear = function ( fieldName )
  1955. {
  1956. var that = this;
  1957. var fields = this.s.fields;
  1958. if ( typeof fieldName === 'string' ) {
  1959. // Remove an individual form element
  1960. that.field(fieldName).destroy();
  1961. delete fields[ fieldName ];
  1962. var orderIdx = $.inArray( fieldName, this.s.order );
  1963. this.s.order.splice( orderIdx, 1 );
  1964. var includeIdx = $.inArray( fieldName, this.s.includeFields );
  1965. if ( includeIdx !== -1 ) {
  1966. this.s.includeFields.splice( includeIdx, 1 );
  1967. }
  1968. }
  1969. else {
  1970. $.each( this._fieldNames( fieldName ), function (i, name) {
  1971. that.clear( name );
  1972. } );
  1973. }
  1974. return this;
  1975. };
  1976. /**
  1977. * Close the form display.
  1978. *
  1979. * Note that `close()` will close any of the three Editor form types (main,
  1980. * bubble and inline).
  1981. *
  1982. * @return {Editor} Editor instance, for chaining
  1983. */
  1984. Editor.prototype.close = function ()
  1985. {
  1986. this._close( false );
  1987. return this;
  1988. };
  1989. /**
  1990. * Create a new record - show the form that allows the user to enter information
  1991. * for a new row and then subsequently submit that data.
  1992. * @param {boolean} [show=true] Show the form or not.
  1993. *
  1994. * @example
  1995. * // Show the create form with a submit button
  1996. * editor
  1997. * .title( 'Add new record' )
  1998. * .buttons( {
  1999. * "label": "Save",
  2000. * "fn": function () {
  2001. * this.submit();
  2002. * }
  2003. * } )
  2004. * .create();
  2005. *
  2006. * @example
  2007. * // Don't show the form and automatically submit it after programatically
  2008. * // setting the values of fields (and using the field defaults)
  2009. * editor
  2010. * create()
  2011. * set( 'name', 'Test user' )
  2012. * set( 'access', 'Read only' )
  2013. * submit();
  2014. */
  2015. Editor.prototype.create = function ( arg1, arg2, arg3, arg4 )
  2016. {
  2017. var that = this;
  2018. var fields = this.s.fields;
  2019. var count = 1;
  2020. // Some other field in inline edit mode?
  2021. if ( this._tidy( function () { that.create( arg1, arg2, arg3, arg4 ); } ) ) {
  2022. return this;
  2023. }
  2024. // Multi-row creation support (only supported by the 1.3+ style of calling
  2025. // this method, so a max of three arguments
  2026. if ( typeof arg1 === 'number' ) {
  2027. count = arg1;
  2028. arg1 = arg2;
  2029. arg2 = arg3;
  2030. }
  2031. // Set up the edit fields for submission
  2032. this.s.editFields = {};
  2033. for ( var i=0 ; i<count ; i++ ) {
  2034. this.s.editFields[ i ] = {
  2035. fields: this.s.fields
  2036. };
  2037. }
  2038. var argOpts = this._crudArgs( arg1, arg2, arg3, arg4 );
  2039. this.s.mode = 'main';
  2040. this.s.action = "create";
  2041. this.s.modifier = null;
  2042. this.dom.form.style.display = 'block';
  2043. this._actionClass();
  2044. // Allow all fields to be displayed for the create form
  2045. this._displayReorder( this.fields() );
  2046. // Set the default for the fields
  2047. $.each( fields, function ( name, field ) {
  2048. field.multiReset();
  2049. // Set a value marker for each multi, so the field
  2050. // knows what the id's are (ints in this case)
  2051. for ( var i=0 ; i<count ; i++ ) {
  2052. field.multiSet( i, field.def() );
  2053. }
  2054. field.set( field.def() );
  2055. } );
  2056. this._event( 'initCreate', null, function () {
  2057. that._assembleMain();
  2058. that._formOptions( argOpts.opts );
  2059. argOpts.maybeOpen();
  2060. } );
  2061. return this;
  2062. };
  2063. /**
  2064. * Remove dependent links from a field
  2065. *
  2066. * @param {string} parent The name of the field to remove the existing dependencies
  2067. * @return {Editor} Editor instance, for chaining
  2068. */
  2069. Editor.prototype.undependent = function ( parent ) {
  2070. if ( $.isArray( parent ) ) {
  2071. for ( var i=0, ien=parent.length ; i<ien ; i++ ) {
  2072. this.undependent( parent[i] );
  2073. }
  2074. return this;
  2075. }
  2076. var field = this.field( parent );
  2077. $(field.node()).off( '.edep' );
  2078. return this;
  2079. }
  2080. /**
  2081. * Create a dependent link between two or more fields. This method is used to
  2082. * listen for a change in a field's value which will trigger updating of the
  2083. * form. This update can consist of updating an options list, changing values
  2084. * or making fields hidden / visible.
  2085. *
  2086. * @param {string} parent The name of the field to listen to changes from
  2087. * @param {string|object|function} url Callback definition. This can be:
  2088. * * A string, which will be used as a URL to submit the request for update to
  2089. * * An object, which is used to extend an Ajax object for the request. The
  2090. * `url` parameter must be specified.
  2091. * * A function, which is used as a callback, allowing non-ajax updates.
  2092. * @return {Editor} Editor instance, for chaining
  2093. */
  2094. Editor.prototype.dependent = function ( parent, url, opts ) {
  2095. if ( $.isArray( parent ) ) {
  2096. for ( var i=0, ien=parent.length ; i<ien ; i++ ) {
  2097. this.dependent( parent[i], url, opts );
  2098. }
  2099. return this;
  2100. }
  2101. var that = this;
  2102. var field = this.field( parent );
  2103. var ajaxOpts = {
  2104. type: 'POST',
  2105. dataType: 'json'
  2106. };
  2107. opts = $.extend( {
  2108. event: 'change',
  2109. data: null,
  2110. preUpdate: null,
  2111. postUpdate: null
  2112. }, opts );
  2113. var update = function ( json ) {
  2114. if ( opts.preUpdate ) {
  2115. opts.preUpdate( json );
  2116. }
  2117. // Field specific
  2118. $.each( {
  2119. labels: 'label',
  2120. options: 'update',
  2121. values: 'val',
  2122. messages: 'message',
  2123. errors: 'error'
  2124. }, function ( jsonProp, fieldFn ) {
  2125. if ( json[ jsonProp ] ) {
  2126. $.each( json[ jsonProp ], function ( field, val ) {
  2127. that.field( field )[ fieldFn ]( val );
  2128. } );
  2129. }
  2130. } );
  2131. // Form level
  2132. $.each( [ 'hide', 'show', 'enable', 'disable' ], function ( i, key ) {
  2133. if ( json[ key ] ) {
  2134. that[ key ]( json[ key ], json.animate );
  2135. }
  2136. } );
  2137. if ( opts.postUpdate ) {
  2138. opts.postUpdate( json );
  2139. }
  2140. field.processing( false );
  2141. };
  2142. // Use a delegate handler to account for field elements which are added and
  2143. // removed after `depenedent` has been called
  2144. $(field.node()).on( opts.event+'.edep', function (e) {
  2145. // Make sure that it was one of the field's elements that triggered the ev
  2146. if ( $( field.node() ).find( e.target ).length === 0 ) {
  2147. return;
  2148. }
  2149. field.processing(true);
  2150. var data = {};
  2151. data.rows = that.s.editFields ?
  2152. _pluck( that.s.editFields, 'data' ) :
  2153. null;
  2154. data.row = data.rows ?
  2155. data.rows[0] :
  2156. null;
  2157. data.values = that.val();
  2158. if ( opts.data ) {
  2159. var ret = opts.data( data );
  2160. if ( ret ) {
  2161. opts.data = ret;
  2162. }
  2163. }
  2164. if ( typeof url === 'function' ) {
  2165. var o = url.call( that, field.val(), data, update );
  2166. if ( o ) {
  2167. if ( typeof o === 'object' && typeof o.then === 'function' ) {
  2168. o.then( function ( resolved ) {
  2169. if ( resolved ) {
  2170. update( resolved );
  2171. }
  2172. } );
  2173. }
  2174. else {
  2175. update( o );
  2176. }
  2177. }
  2178. }
  2179. else {
  2180. if ( $.isPlainObject( url ) ) {
  2181. $.extend( ajaxOpts, url );
  2182. }
  2183. else {
  2184. ajaxOpts.url = url;
  2185. }
  2186. $.ajax( $.extend( ajaxOpts, {
  2187. url: url,
  2188. data: data,
  2189. success: update
  2190. } ) );
  2191. }
  2192. } );
  2193. return this;
  2194. };
  2195. /**
  2196. * Destroy the Editor instance, cleaning up fields, display and event handlers
  2197. */
  2198. Editor.prototype.destroy = function ()
  2199. {
  2200. if ( this.s.displayed ) {
  2201. this.close();
  2202. }
  2203. this.clear();
  2204. // Stick the template back into the document so it can be reused
  2205. if ( this.s.template ) {
  2206. $('body').append(this.s.template);
  2207. }
  2208. var controller = this.s.displayController;
  2209. if ( controller.destroy ) {
  2210. controller.destroy( this );
  2211. }
  2212. $(document).off( '.dte'+this.s.unique );
  2213. this.dom = null;
  2214. this.s = null;
  2215. };
  2216. /**
  2217. * Disable one or more field inputs, disallowing subsequent user interaction with the
  2218. * fields until they are re-enabled.
  2219. * @param {string|array} name The field name (from the `name` parameter given when
  2220. * originally setting up the field) to disable, or an array of field names to disable
  2221. * multiple fields with a single call.
  2222. * @return {Editor} Editor instance, for chaining
  2223. *
  2224. * @example
  2225. * // Show a 'create' record form, but with a field disabled
  2226. * editor.disable( 'account_type' );
  2227. * editor.create( 'Add new user', {
  2228. * "label": "Save",
  2229. * "fn": function () { this.submit(); }
  2230. * } );
  2231. *
  2232. * @example
  2233. * // Disable multiple fields by using an array of field names
  2234. * editor.disable( ['account_type', 'access_level'] );
  2235. */
  2236. Editor.prototype.disable = function ( name )
  2237. {
  2238. var that = this;
  2239. $.each( this._fieldNames( name ), function ( i, n ) {
  2240. that.field(n).disable();
  2241. } );
  2242. return this;
  2243. };
  2244. /**
  2245. * Display, or remove the editing form from the display
  2246. * @param {boolean} show Show (`true`) or hide (`false`)
  2247. * @return {Editor} Editor instance, for chaining
  2248. */
  2249. Editor.prototype.display = function ( show )
  2250. {
  2251. if ( show === undefined ) {
  2252. return this.s.displayed;
  2253. }
  2254. return this[ show ? 'open' : 'close' ]();
  2255. };
  2256. /**
  2257. * Fields which are currently displayed
  2258. * @return {string[]} Field names that are shown
  2259. */
  2260. Editor.prototype.displayed = function ()
  2261. {
  2262. return $.map( this.s.fields, function ( field, name ) {
  2263. return field.displayed() ? name : null;
  2264. } );
  2265. };
  2266. /**
  2267. * Get display controller node
  2268. *
  2269. * @return {node} Display controller host element
  2270. */
  2271. Editor.prototype.displayNode = function ()
  2272. {
  2273. return this.s.displayController.node( this );
  2274. };
  2275. /**
  2276. * Edit a record - show the form, pre-populated with the data that is in the given
  2277. * DataTables row, that allows the user to enter information for the row to be modified
  2278. * and then subsequently submit that data.
  2279. * @param {node} items The TR element from the DataTable that is to be edited
  2280. * @param {boolean} [show=true] Show the form or not.
  2281. * @return {Editor} Editor instance, for chaining
  2282. *
  2283. * @example
  2284. * // Show the edit form for the first row in the DataTable with a submit button
  2285. * editor.edit( $('#example tbody tr:eq(0)')[0], 'Edit record', {
  2286. * "label": "Update",
  2287. * "fn": function () { this.submit(); }
  2288. * } );
  2289. *
  2290. * @example
  2291. * // Use the title and buttons API methods to show an edit form (this provides
  2292. * // the same result as example above, but is a different way of achieving it
  2293. * editor.title( 'Edit record' );
  2294. * editor.buttons( {
  2295. * "label": "Update",
  2296. * "fn": function () { this.submit(); }
  2297. * } );
  2298. * editor.edit( $('#example tbody tr:eq(0)')[0] );
  2299. *
  2300. * @example
  2301. * // Automatically submit an edit without showing the user the form
  2302. * editor.edit( TRnode, null, null, false );
  2303. * editor.set( 'name', 'Updated name' );
  2304. * editor.set( 'access', 'Read only' );
  2305. * editor.submit();
  2306. */
  2307. Editor.prototype.edit = function ( items, arg1, arg2, arg3, arg4 )
  2308. {
  2309. var that = this;
  2310. // Some other field in inline edit mode?
  2311. if ( this._tidy( function () { that.edit( items, arg1, arg2, arg3, arg4 ); } ) ) {
  2312. return this;
  2313. }
  2314. var argOpts = this._crudArgs( arg1, arg2, arg3, arg4 );
  2315. this._edit(
  2316. items,
  2317. this._dataSource( 'fields', items ),
  2318. 'main',
  2319. argOpts.opts,
  2320. function () {
  2321. that._assembleMain();
  2322. that._formOptions( argOpts.opts );
  2323. argOpts.maybeOpen();
  2324. }
  2325. );
  2326. return this;
  2327. };
  2328. /**
  2329. * Enable one or more field inputs, restoring user interaction with the fields.
  2330. * @param {string|array} name The field name (from the `name` parameter given when
  2331. * originally setting up the field) to enable, or an array of field names to enable
  2332. * multiple fields with a single call.
  2333. * @return {Editor} Editor instance, for chaining
  2334. *
  2335. * @example
  2336. * // Show a 'create' form with buttons which will enable and disable certain fields
  2337. * editor.create( 'Add new user', [
  2338. * {
  2339. * "label": "User name only",
  2340. * "fn": function () {
  2341. * this.enable('username');
  2342. * this.disable( ['first_name', 'last_name'] );
  2343. * }
  2344. * }, {
  2345. * "label": "Name based",
  2346. * "fn": function () {
  2347. * this.disable('username');
  2348. * this.enable( ['first_name', 'last_name'] );
  2349. * }
  2350. * }, {
  2351. * "label": "Submit",
  2352. * "fn": function () { this.submit(); }
  2353. * }
  2354. * );
  2355. */
  2356. Editor.prototype.enable = function ( name )
  2357. {
  2358. var that = this;
  2359. $.each( this._fieldNames( name ), function ( i, n ) {
  2360. that.field(n).enable();
  2361. } );
  2362. return this;
  2363. };
  2364. /**
  2365. * Show that a field, or the form globally, is in an error state. Note that
  2366. * errors are cleared on each submission of the form.
  2367. * @param {string} [name] The name of the field that is in error. If not
  2368. * given then the global form error display is used.
  2369. * @param {string} msg The error message to show
  2370. * @return {Editor} Editor instance, for chaining
  2371. *
  2372. * @example
  2373. * // Show an error if the field is required
  2374. * editor.create( 'Add new user', {
  2375. * "label": "Submit",
  2376. * "fn": function () {
  2377. * if ( this.get('username') === '' ) {
  2378. * this.error( 'username', 'A user name is required' );
  2379. * return;
  2380. * }
  2381. * this.submit();
  2382. * }
  2383. * } );
  2384. *
  2385. * @example
  2386. * // Show a field and a global error for a required field
  2387. * editor.create( 'Add new user', {
  2388. * "label": "Submit",
  2389. * "fn": function () {
  2390. * if ( this.get('username') === '' ) {
  2391. * this.error( 'username', 'A user name is required' );
  2392. * this.error( 'The data could not be saved because it is incomplete' );
  2393. * return;
  2394. * }
  2395. * this.submit();
  2396. * }
  2397. * } );
  2398. */
  2399. Editor.prototype.error = function ( name, msg )
  2400. {
  2401. if ( msg === undefined ) {
  2402. // Global error
  2403. this._message( this.dom.formError, name );
  2404. // Store the error message so `inError` can check if there is an
  2405. // error or not without considering animation
  2406. this.s.globalError = name;
  2407. }
  2408. else {
  2409. // Field error
  2410. this.field( name ).error( msg );
  2411. }
  2412. return this;
  2413. };
  2414. /**
  2415. * Get a field object, configured for a named field, which can then be
  2416. * manipulated through its API. This function effectively acts as a
  2417. * proxy to the field extensions, allowing easy access to the methods
  2418. * for a named field. The methods that are available depend upon the field
  2419. * type plug-in for Editor.
  2420. *
  2421. * @param {string} name Field name to be obtained
  2422. * @return {Editor.Field} Field instance
  2423. *
  2424. * @example
  2425. * // Update the values available in a select list
  2426. * editor.field('island').update( [
  2427. * 'Lewis and Harris',
  2428. * 'South Uist',
  2429. * 'North Uist',
  2430. * 'Benbecula',
  2431. * 'Barra'
  2432. * ] );
  2433. *
  2434. * @example
  2435. * // Equivalent calls
  2436. * editor.field('name').set('John Smith');
  2437. *
  2438. * // results in the same action as:
  2439. * editor.set('John Smith');
  2440. */
  2441. Editor.prototype.field = function ( name )
  2442. {
  2443. var fields = this.s.fields;
  2444. if ( ! fields[name] ) {
  2445. throw 'Unknown field name - '+name;
  2446. }
  2447. return fields[ name ];
  2448. };
  2449. /**
  2450. * Get a list of the fields that are used by the Editor instance.
  2451. * @returns {string[]} Array of field names
  2452. *
  2453. * @example
  2454. * // Get current fields and move first item to the end
  2455. * var fields = editor.fields();
  2456. * var first = fields.shift();
  2457. * fields.push( first );
  2458. * editor.order( fields );
  2459. */
  2460. Editor.prototype.fields = function ()
  2461. {
  2462. return $.map( this.s.fields, function ( field, name ) {
  2463. return name;
  2464. } );
  2465. };
  2466. /**
  2467. * Get data object for a file from a table and id
  2468. *
  2469. * @param {string} name Table name
  2470. * @param {string|number} id Primary key identifier
  2471. * @return {object} Table information
  2472. */
  2473. Editor.prototype.file = _api_file;
  2474. /**
  2475. * Get data objects for available files
  2476. *
  2477. * @param {string} [name] Table name
  2478. * @return {object} Table array
  2479. */
  2480. Editor.prototype.files = _api_files;
  2481. /**
  2482. * Get the value of a field
  2483. * @param {string|array} [name] The field name (from the `name` parameter given
  2484. * when originally setting up the field) to disable. If not given, then an
  2485. * object of fields is returned, with the value of each field from the
  2486. * instance represented in the array (the object properties are the field
  2487. * names). Also an array of field names can be given to get a collection of
  2488. * data from the form.
  2489. * @returns {*|object} Value from the named field
  2490. *
  2491. * @example
  2492. * // Client-side validation - check that a field has been given a value
  2493. * // before submitting the form
  2494. * editor.create( 'Add new user', {
  2495. * "label": "Submit",
  2496. * "fn": function () {
  2497. * if ( this.get('username') === '' ) {
  2498. * this.error( 'username', 'A user name is required' );
  2499. * return;
  2500. * }
  2501. * this.submit();
  2502. * }
  2503. * } );
  2504. */
  2505. Editor.prototype.get = function ( name )
  2506. {
  2507. var that = this;
  2508. if ( ! name ) {
  2509. name = this.fields();
  2510. }
  2511. if ( $.isArray( name ) ) {
  2512. var out = {};
  2513. $.each( name, function (i, n) {
  2514. out[n] = that.field(n).get();
  2515. } );
  2516. return out;
  2517. }
  2518. return this.field(name).get();
  2519. };
  2520. /**
  2521. * Remove a field from the form display. Note that the field will still be submitted
  2522. * with the other fields in the form, but it simply won't be visible to the user.
  2523. * @param {string|array} [name] The field name (from the `name` parameter given when
  2524. * originally setting up the field) to hide or an array of names. If not given then all
  2525. * fields are hidden.
  2526. * @param {boolean} [animate=true] Animate if visible
  2527. * @return {Editor} Editor instance, for chaining
  2528. *
  2529. * @example
  2530. * // Show a 'create' record form, but with some fields hidden
  2531. * editor.hide( 'account_type' );
  2532. * editor.hide( 'access_level' );
  2533. * editor.create( 'Add new user', {
  2534. * "label": "Save",
  2535. * "fn": function () { this.submit(); }
  2536. * } );
  2537. *
  2538. * @example
  2539. * // Show a single field by hiding all and then showing one
  2540. * editor.hide();
  2541. * editor.show('access_type');
  2542. */
  2543. Editor.prototype.hide = function ( names, animate )
  2544. {
  2545. var that = this;
  2546. $.each( this._fieldNames( names ), function (i, n) {
  2547. that.field( n ).hide( animate );
  2548. } );
  2549. return this;
  2550. };
  2551. /**
  2552. * Get the ids of the rows being edited
  2553. */
  2554. Editor.prototype.ids = function ( includeHash )
  2555. {
  2556. return $.map( this.s.editFields, function ( edit, idSrc ) {
  2557. return includeHash === true ?
  2558. '#' + idSrc :
  2559. idSrc;
  2560. } );
  2561. };
  2562. /**
  2563. * Determine if there is an error state in the form, either the form's global
  2564. * error message, or one or more fields.
  2565. *
  2566. * @param {string|array|undefined} [inNames] The field names to check. All
  2567. * fields checked if undefined.
  2568. * @return {boolean} `true` if there is an error in the form
  2569. */
  2570. Editor.prototype.inError = function ( inNames )
  2571. {
  2572. var formError = $(this.dom.formError);
  2573. // Is there a global error?
  2574. if ( this.s.globalError ) {
  2575. return true;
  2576. }
  2577. // Field specific
  2578. var names = this._fieldNames( inNames );
  2579. for ( var i=0, ien=names.length ; i<ien ; i++ ) {
  2580. if ( this.field( names[i] ).inError() ) {
  2581. return true;
  2582. }
  2583. }
  2584. return false;
  2585. };
  2586. /**
  2587. * Inline editing for a single field. This method provides a method to allow
  2588. * end users to very quickly edit fields in place. For example, a user could
  2589. * simply click on a cell in a table, the contents of which would be replaced
  2590. * with the editing input field for that cell.
  2591. *
  2592. * @param {string|node|DataTables.Api|cell-selector} cell The cell or field to
  2593. * be edited (note that for table editing this must be a cell - for standalone
  2594. * editing it can also be the field name to edit).
  2595. * @param {string} [fieldName] The field name to be edited. This parameter is
  2596. * optional. If not provided, Editor will attempt to resolve the correct field
  2597. * from the cell / element given as the first parameter. If it is unable to do
  2598. * so, it will throw an error.
  2599. * @param {object} [opts] Inline editing options - see the `form-options` type
  2600. * @return {Editor} Editor instance, for chaining
  2601. */
  2602. Editor.prototype.inline = function ( cell, fieldName, opts )
  2603. {
  2604. var that = this;
  2605. // Argument shifting
  2606. if ( $.isPlainObject( fieldName ) ) {
  2607. opts = fieldName;
  2608. fieldName = undefined;
  2609. }
  2610. opts = $.extend( {}, this.s.formOptions.inline, opts );
  2611. var editFields = this._dataSource( 'individual', cell, fieldName );
  2612. var node, field;
  2613. var countOuter=0, countInner;
  2614. var closed=false;
  2615. var classes = this.classes.inline;
  2616. // Read the individual cell information from the editFields object
  2617. $.each( editFields, function ( i, editField ) {
  2618. // Only a single row
  2619. if ( countOuter > 0 ) {
  2620. throw 'Cannot edit more than one row inline at a time';
  2621. }
  2622. node = $(editField.attach[0]);
  2623. // Only a single item in that row
  2624. countInner = 0;
  2625. $.each( editField.displayFields, function ( j, f ) {
  2626. if ( countInner > 0 ) {
  2627. throw 'Cannot edit more than one field inline at a time';
  2628. }
  2629. field = f;
  2630. countInner++;
  2631. } );
  2632. countOuter++;
  2633. // If only changed values are to be submitted, then only allow the
  2634. // individual field that we are editing to be edited.
  2635. // This is currently disabled, as I'm not convinced that it is actually
  2636. // useful!
  2637. // if ( opts.submit === 'changed' ) {
  2638. // editField.fields = editField.displayFields;
  2639. // }
  2640. } );
  2641. // Already in edit mode for this cell?
  2642. if ( $('div.DTE_Field', node).length ) {
  2643. return this;
  2644. }
  2645. // Some other field in inline edit mode?
  2646. if ( this._tidy( function () { that.inline( cell, fieldName, opts ); } ) ) {
  2647. return this;
  2648. }
  2649. // Start a full row edit, but don't display - we will be showing the field
  2650. this._edit( cell, editFields, 'inline', opts, function () {
  2651. var namespace = that._formOptions( opts );
  2652. var ret = that._preopen( 'inline' );
  2653. if ( ! ret ) {
  2654. return that;
  2655. }
  2656. // Remove from DOM, keeping event handlers, and include text nodes in remove
  2657. var children = node.contents().detach();
  2658. // Note the wdith setting shouldn't be required, but Edge increases the column's
  2659. // width if a % width is used (even 1%). This is the workaround
  2660. node.append( $(
  2661. '<div class="'+classes.wrapper+'">'+
  2662. '<div class="'+classes.liner+'" style="width:'+node.width()+'px">'+
  2663. '<div class="DTE_Processing_Indicator"><span/></div>'+
  2664. '</div>'+
  2665. '<div class="'+classes.buttons+'"/>'+
  2666. '</div>'
  2667. ) );
  2668. node.find('div.'+classes.liner.replace(/ /g, '.'))
  2669. .append( field.node() )
  2670. .append( that.dom.formError );
  2671. if ( opts.buttons ) {
  2672. // Use prepend for the CSS, so we can float the buttons right
  2673. node.find('div.'+classes.buttons.replace(/ /g, '.')).append( that.dom.buttons );
  2674. }
  2675. that._closeReg( function ( submitComplete, action ) {
  2676. // Mark that this specific inline edit has closed
  2677. closed = true;
  2678. $(document).off( 'click'+namespace );
  2679. // If there was no submit, we need to put the DOM back as it was. If
  2680. // there was a submit, the write of the new value will set the DOM to
  2681. // how it should be. Note also, check if it was an edit action, if not
  2682. // a create will create new row so we tidy this one up
  2683. if ( ! submitComplete || action !== 'edit' ) {
  2684. node.contents().detach();
  2685. node.append( children );
  2686. }
  2687. // Clear error messages "offline"
  2688. that._clearDynamicInfo();
  2689. return 'inline'; // trigger `closed`
  2690. } );
  2691. // Submit and blur actions
  2692. setTimeout( function () {
  2693. // If already closed, possibly due to some other aspect of the event
  2694. // that triggered the inline call, don't add the event listener - it
  2695. // isn't needed (and is dangerous)
  2696. if ( closed ) {
  2697. return;
  2698. }
  2699. $(document).on( 'click'+namespace, function ( e ) {
  2700. // Was the click inside or owned by the editing node? If not, then
  2701. // come out of editing mode.
  2702. // andSelf is deprecated in jQ1.8, but we want 1.7 compat
  2703. var back = $.fn.addBack ? 'addBack' : 'andSelf';
  2704. if ( ! field._typeFn( 'owns', e.target ) &&
  2705. $.inArray( node[0], $(e.target).parents()[ back ]() ) === -1 )
  2706. {
  2707. that.blur();
  2708. }
  2709. } );
  2710. }, 0 );
  2711. that._focus( [ field ], opts.focus );
  2712. that._postopen( 'inline', true );
  2713. } );
  2714. return this;
  2715. };
  2716. /**
  2717. * Show an information message for the form as a whole, or for an individual
  2718. * field. This can be used to provide helpful information to a user about an
  2719. * individual field, or more typically the form (for example when deleting
  2720. * a record and asking for confirmation).
  2721. * @param {string} [name] The name of the field to show the message for. If not
  2722. * given then a global message is shown for the form
  2723. * @param {string|function} msg The message to show
  2724. * @return {Editor} Editor instance, for chaining
  2725. *
  2726. * @example
  2727. * // Show a global message for a 'create' form
  2728. * editor.message( 'Add a new user to the database by completing the fields below' );
  2729. * editor.create( 'Add new user', {
  2730. * "label": "Submit",
  2731. * "fn": function () { this.submit(); }
  2732. * } );
  2733. *
  2734. * @example
  2735. * // Show a message for an individual field when a 'help' icon is clicked on
  2736. * $('#user_help').click( function () {
  2737. * editor.message( 'user', 'The user name is what the system user will login with' );
  2738. * } );
  2739. */
  2740. Editor.prototype.message = function ( name, msg )
  2741. {
  2742. if ( msg === undefined ) {
  2743. // Global message
  2744. this._message( this.dom.formInfo, name );
  2745. }
  2746. else {
  2747. // Field message
  2748. this.field( name ).message( msg );
  2749. }
  2750. return this;
  2751. };
  2752. /**
  2753. * Get which mode of operation the Editor form is in
  2754. * @return {string} `create`, `edit`, `remove` or `null` if no active state.
  2755. */
  2756. Editor.prototype.mode = function ( mode )
  2757. {
  2758. if ( ! mode ) {
  2759. return this.s.action;
  2760. }
  2761. if ( ! this.s.action ) {
  2762. throw new Error('Not currently in an editing mode');
  2763. }
  2764. else if ( this.s.action === 'create' && mode !== 'create' ) {
  2765. throw new Error('Changing from create mode is not supported');
  2766. }
  2767. this.s.action = mode;
  2768. return this;
  2769. };
  2770. /**
  2771. * Get the modifier that was used to trigger the edit or delete action.
  2772. * @return {*} The identifier that was used for the editing / remove method
  2773. * called.
  2774. */
  2775. Editor.prototype.modifier = function ()
  2776. {
  2777. return this.s.modifier;
  2778. };
  2779. /**
  2780. * Get the values from one or more fields, taking into account multiple data
  2781. * points being edited at the same time.
  2782. *
  2783. * @param {string|array} fieldNames A single field name or an array of field
  2784. * names.
  2785. * @return {object} If a string is given as the first parameter an object that
  2786. * contains the value for each row being edited is returned. If an array is
  2787. * given, then the object has the field names as the parameter name and the
  2788. * value is the value object with values for each row being edited.
  2789. */
  2790. Editor.prototype.multiGet = function ( fieldNames )
  2791. {
  2792. var that = this;
  2793. if ( fieldNames === undefined ) {
  2794. fieldNames = this.fields();
  2795. }
  2796. if ( $.isArray( fieldNames ) ) {
  2797. var out = {};
  2798. $.each( fieldNames, function ( i, name ) {
  2799. out[ name ] = that.field( name ).multiGet();
  2800. } );
  2801. return out;
  2802. }
  2803. // String
  2804. return this.field( fieldNames ).multiGet();
  2805. };
  2806. /**
  2807. * Set the values for one or more fields, taking into account multiple data
  2808. * points being edited at the same time.
  2809. *
  2810. * @param {object|string} fieldNames The name of the field to set, or an object
  2811. * with the field names as the parameters that contains the value object to
  2812. * set for each field.
  2813. * @param {*} [val] Value to set if first parameter is given as a string.
  2814. * Otherwise it is ignored.
  2815. * @return {Editor} Editor instance, for chaining
  2816. */
  2817. Editor.prototype.multiSet = function ( fieldNames, val )
  2818. {
  2819. var that = this;
  2820. if ( $.isPlainObject( fieldNames ) && val === undefined ) {
  2821. $.each( fieldNames, function ( name, value ) {
  2822. that.field( name ).multiSet( value );
  2823. } );
  2824. }
  2825. else {
  2826. this.field( fieldNames ).multiSet( val );
  2827. }
  2828. return this;
  2829. };
  2830. /**
  2831. * Get the container node for an individual field.
  2832. * @param {string|array} name The field name (from the `name` parameter given
  2833. * when originally setting up the field) to get the DOM node for.
  2834. * @return {node|array} Field container node
  2835. *
  2836. * @example
  2837. * // Dynamically add a class to a field's container
  2838. * $(editor.node( 'account_type' )).addClass( 'account' );
  2839. */
  2840. Editor.prototype.node = function ( name )
  2841. {
  2842. var that = this;
  2843. if ( ! name ) {
  2844. name = this.order();
  2845. }
  2846. return $.isArray( name ) ?
  2847. $.map( name, function (n) {
  2848. return that.field( n ).node();
  2849. } ) :
  2850. this.field( name ).node();
  2851. };
  2852. /**
  2853. * Remove a bound event listener to the editor instance. This method provides a
  2854. * shorthand way of binding jQuery events that would be the same as writing
  2855. * `$(editor).off(...)` for convenience.
  2856. * @param {string} name Event name to remove the listeners for - event names are
  2857. * defined by {@link Editor}.
  2858. * @param {function} [fn] The function to remove. If not given, all functions which
  2859. * are assigned to the given event name will be removed.
  2860. * @return {Editor} Editor instance, for chaining
  2861. *
  2862. * @example
  2863. * // Add an event to alert when the form is shown and then remove the listener
  2864. * // so it will only fire once
  2865. * editor.on( 'open', function () {
  2866. * alert('Form displayed!');
  2867. * editor.off( 'open' );
  2868. * } );
  2869. */
  2870. Editor.prototype.off = function ( name, fn )
  2871. {
  2872. $(this).off( this._eventName( name ), fn );
  2873. return this;
  2874. };
  2875. /**
  2876. * Listen for an event which is fired off by Editor when it performs certain
  2877. * actions. This method provides a shorthand way of binding jQuery events that
  2878. * would be the same as writing `$(editor).on(...)` for convenience.
  2879. * @param {string} name Event name to add the listener for - event names are
  2880. * defined by {@link Editor}.
  2881. * @param {function} fn The function to run when the event is triggered.
  2882. * @return {Editor} Editor instance, for chaining
  2883. *
  2884. * @example
  2885. * // Log events on the console when they occur
  2886. * editor.on( 'open', function () { console.log( 'Form opened' ); } );
  2887. * editor.on( 'close', function () { console.log( 'Form closed' ); } );
  2888. * editor.on( 'submit', function () { console.log( 'Form submitted' ); } );
  2889. */
  2890. Editor.prototype.on = function ( name, fn )
  2891. {
  2892. $(this).on( this._eventName( name ), fn );
  2893. return this;
  2894. };
  2895. /**
  2896. * Listen for a single event event which is fired off by Editor when it performs
  2897. * certain actions. This method provides a shorthand way of binding jQuery
  2898. * events that would be the same as writing `$(editor).one(...)` for
  2899. * convenience.
  2900. * @param {string} name Event name to add the listener for - event names are
  2901. * defined by {@link Editor}.
  2902. * @param {function} fn The function to run when the event is triggered.
  2903. * @return {Editor} Editor instance, for chaining
  2904. */
  2905. Editor.prototype.one = function ( name, fn )
  2906. {
  2907. $(this).one( this._eventName( name ), fn );
  2908. return this;
  2909. };
  2910. /**
  2911. * Display the main form editor to the end user in the web-browser.
  2912. *
  2913. * Note that the `close()` method will close any of the three Editor form types
  2914. * (main, bubble and inline), but this method will open only the main type.
  2915. * @return {Editor} Editor instance, for chaining
  2916. *
  2917. * @example
  2918. * // Build a 'create' form, but don't display it until some values have
  2919. * // been set. When done, then display the form.
  2920. * editor.create( 'Create user', {
  2921. * "label": "Submit",
  2922. * "fn": function () { this.submit(); }
  2923. * }, false );
  2924. * editor.set( 'name', 'Test user' );
  2925. * editor.set( 'access', 'Read only' );
  2926. * editor.open();
  2927. */
  2928. Editor.prototype.open = function ()
  2929. {
  2930. var that = this;
  2931. // Insert the display elements in order
  2932. this._displayReorder();
  2933. // Define how to do a close
  2934. this._closeReg( function () {
  2935. that.s.displayController.close( that, function () {
  2936. that._clearDynamicInfo();
  2937. that._event('closed', ['main']);
  2938. } );
  2939. } );
  2940. // Run the standard open with common events
  2941. var ret = this._preopen( 'main' );
  2942. if ( ! ret ) {
  2943. return this;
  2944. }
  2945. this.s.displayController.open( this, this.dom.wrapper, function () {
  2946. that._focus(
  2947. $.map( that.s.order, function (name) {
  2948. return that.s.fields[ name ];
  2949. } ),
  2950. that.s.editOpts.focus
  2951. );
  2952. that._event( 'opened', ['main', that.s.action] );
  2953. } );
  2954. this._postopen( 'main', false );
  2955. return this;
  2956. };
  2957. /**
  2958. * Get or set the ordering of fields, as they are displayed in the form. When used as
  2959. * a getter, the field names are returned in an array, in their current order, and when
  2960. * used as a setting you can alter the field ordering by passing in an array with all
  2961. * field names in their new order.
  2962. *
  2963. * Note that all fields *must* be included when reordering, and no additional fields can
  2964. * be added here (use {@link Editor#add} to add more fields). Finally, for setting the
  2965. * order, you can pass an array of the field names, or give the field names as individual
  2966. * parameters (see examples below).
  2967. * @param {array|string} [set] Field order to set.
  2968. * @return {Editor} Editor instance, for chaining
  2969. *
  2970. * @example
  2971. * // Get field ordering
  2972. * var order = editor.order();
  2973. *
  2974. * @example
  2975. * // Set the field order
  2976. * var order = editor.order();
  2977. * order.unshift( order.pop() ); // move the last field into the first position
  2978. * editor.order( order );
  2979. *
  2980. * @example
  2981. * // Set the field order as arguments
  2982. * editor.order( "pupil", "grade", "dept", "exam-board" );
  2983. *
  2984. */
  2985. Editor.prototype.order = function ( set /*, ... */ )
  2986. {
  2987. if ( !set ) {
  2988. return this.s.order;
  2989. }
  2990. // Allow new layout to be passed in as arguments
  2991. if ( arguments.length && ! $.isArray( set ) ) {
  2992. set = Array.prototype.slice.call(arguments);
  2993. }
  2994. // Sanity check - array must exactly match the fields we have available
  2995. if ( this.s.order.slice().sort().join('-') !== set.slice().sort().join('-') ) {
  2996. throw "All fields, and no additional fields, must be provided for ordering.";
  2997. }
  2998. // Copy the new array into the order (so the reference is maintained)
  2999. $.extend( this.s.order, set );
  3000. this._displayReorder();
  3001. return this;
  3002. };
  3003. /**
  3004. * Remove (delete) entries from the table. The rows to remove are given as
  3005. * either a single DOM node or an array of DOM nodes (including a jQuery
  3006. * object).
  3007. * @param {node|array} items The row, or array of nodes, to delete
  3008. * @param {boolean} [show=true] Show the form or not.
  3009. * @return {Editor} Editor instance, for chaining
  3010. *
  3011. * @example
  3012. * // Delete a given row with a message to let the user know exactly what is
  3013. * // happening
  3014. * editor.message( "Are you sure you want to remove this row?" );
  3015. * editor.remove( row_to_delete, 'Delete row', {
  3016. * "label": "Confirm",
  3017. * "fn": function () { this.submit(); }
  3018. * } );
  3019. *
  3020. * @example
  3021. * // Delete the first row in a table without asking the user for confirmation
  3022. * editor.remove( '', $('#example tbody tr:eq(0)')[0], null, false );
  3023. * editor.submit();
  3024. *
  3025. * @example
  3026. * // Delete all rows in a table with a submit button
  3027. * editor.remove( $('#example tbody tr'), 'Delete all rows', {
  3028. * "label": "Delete all",
  3029. * "fn": function () { this.submit(); }
  3030. * } );
  3031. */
  3032. Editor.prototype.remove = function ( items, arg1, arg2, arg3, arg4 )
  3033. {
  3034. var that = this;
  3035. // Some other field in inline edit mode?
  3036. if ( this._tidy( function () { that.remove( items, arg1, arg2, arg3, arg4 ); } ) ) {
  3037. return this;
  3038. }
  3039. // Allow a single row node to be passed in to remove, Can't use $.isArray
  3040. // as we also allow array like objects to be passed in (API, jQuery)
  3041. if ( items.length === undefined ) {
  3042. items = [ items ];
  3043. }
  3044. var argOpts = this._crudArgs( arg1, arg2, arg3, arg4 );
  3045. var editFields = this._dataSource( 'fields', items );
  3046. this.s.action = "remove";
  3047. this.s.modifier = items;
  3048. this.s.editFields = editFields;
  3049. this.dom.form.style.display = 'none';
  3050. this._actionClass();
  3051. this._event(
  3052. 'initRemove',
  3053. [
  3054. _pluck( editFields, 'node' ),
  3055. _pluck( editFields, 'data' ),
  3056. items
  3057. ],
  3058. function () {
  3059. that._event(
  3060. 'initMultiRemove', // undocumented and to be removed in v2
  3061. [ editFields, items ],
  3062. function () {
  3063. that._assembleMain();
  3064. that._formOptions( argOpts.opts );
  3065. argOpts.maybeOpen();
  3066. var opts = that.s.editOpts;
  3067. if ( opts.focus !== null ) {
  3068. $('button', that.dom.buttons).eq( opts.focus ).focus();
  3069. }
  3070. }
  3071. );
  3072. }
  3073. );
  3074. return this;
  3075. };
  3076. /**
  3077. * Set the value of a field
  3078. * @param {string|object} name The field name (from the `name` parameter given
  3079. * when originally setting up the field) to set the value of. If given as an
  3080. * object the object parameter name will be the value of the field to set and
  3081. * the value the value to set for the field.
  3082. * @param {*} [val] The value to set the field to. The format of the value will
  3083. * depend upon the field type. Not required if the first parameter is given
  3084. * as an object.
  3085. * @return {Editor} Editor instance, for chaining
  3086. *
  3087. * @example
  3088. * // Set the values of a few fields before then automatically submitting the form
  3089. * editor.create( null, null, false );
  3090. * editor.set( 'name', 'Test user' );
  3091. * editor.set( 'access', 'Read only' );
  3092. * editor.submit();
  3093. */
  3094. Editor.prototype.set = function ( set, val )
  3095. {
  3096. var that = this;
  3097. if ( ! $.isPlainObject( set ) ) {
  3098. var o = {};
  3099. o[ set ] = val;
  3100. set = o;
  3101. }
  3102. $.each( set, function (n, v) {
  3103. that.field( n ).set( v );
  3104. } );
  3105. return this;
  3106. };
  3107. /**
  3108. * Show a field in the display that was previously hidden.
  3109. * @param {string|array} [names] The field name (from the `name` parameter
  3110. * given when originally setting up the field) to make visible, or an array of
  3111. * field names to make visible. If not given all fields are shown.
  3112. * @param {boolean} [animate=true] Animate if visible
  3113. * @return {Editor} Editor instance, for chaining
  3114. *
  3115. * @example
  3116. * // Shuffle the fields that are visible, hiding one field and making two
  3117. * // others visible before then showing the {@link Editor#create} record form.
  3118. * editor.hide( 'username' );
  3119. * editor.show( 'account_type' );
  3120. * editor.show( 'access_level' );
  3121. * editor.create( 'Add new user', {
  3122. * "label": "Save",
  3123. * "fn": function () { this.submit(); }
  3124. * } );
  3125. *
  3126. * @example
  3127. * // Show all fields
  3128. * editor.show();
  3129. */
  3130. Editor.prototype.show = function ( names, animate )
  3131. {
  3132. var that = this;
  3133. $.each( this._fieldNames( names ), function (i, n) {
  3134. that.field( n ).show( animate );
  3135. } );
  3136. return this;
  3137. };
  3138. /**
  3139. * Submit a form to the server for processing. The exact action performed will depend
  3140. * on which of the methods {@link Editor#create}, {@link Editor#edit} or
  3141. * {@link Editor#remove} were called to prepare the form - regardless of which one is
  3142. * used, you call this method to submit data.
  3143. * @param {function} [successCallback] Callback function that is executed once the
  3144. * form has been successfully submitted to the server and no errors occurred.
  3145. * @param {function} [errorCallback] Callback function that is executed if the
  3146. * server reports an error due to the submission (this includes a JSON formatting
  3147. * error should the error return invalid JSON).
  3148. * @param {function} [formatdata] Callback function that is passed in the data
  3149. * that will be submitted to the server, allowing pre-formatting of the data,
  3150. * removal of data or adding of extra fields.
  3151. * @param {boolean} [hide=true] When the form is successfully submitted, by default
  3152. * the form display will be hidden - this option allows that to be overridden.
  3153. * @return {Editor} Editor instance, for chaining
  3154. *
  3155. * @example
  3156. * // Submit data from a form button
  3157. * editor.create( 'Add new record', {
  3158. * "label": "Save",
  3159. * "fn": function () {
  3160. * this.submit();
  3161. * }
  3162. * } );
  3163. *
  3164. * @example
  3165. * // Submit without showing the user the form
  3166. * editor.create( null, null, false );
  3167. * editor.submit();
  3168. *
  3169. * @example
  3170. * // Provide success and error callback methods
  3171. * editor.create( 'Add new record', {
  3172. * "label": "Save",
  3173. * "fn": function () {
  3174. * this.submit( function () {
  3175. * alert( 'Form successfully submitted!' );
  3176. * }, function () {
  3177. * alert( 'Form encountered an error :-(' );
  3178. * }
  3179. * );
  3180. * }
  3181. * } );
  3182. *
  3183. * @example
  3184. * // Add an extra field to the data
  3185. * editor.create( 'Add new record', {
  3186. * "label": "Save",
  3187. * "fn": function () {
  3188. * this.submit( null, null, function (data) {
  3189. * data.extra = "Extra information";
  3190. * } );
  3191. * }
  3192. * } );
  3193. *
  3194. * @example
  3195. * // Don't hide the form immediately - change the title and then close the form
  3196. * // after a small amount of time
  3197. * editor.create( 'Add new record', {
  3198. * "label": "Save",
  3199. * "fn": function () {
  3200. * this.submit(
  3201. * function () {
  3202. * var that = this;
  3203. * this.title( 'Data successfully added!' );
  3204. * setTimeout( function () {
  3205. * that.close();
  3206. * }, 1000 );
  3207. * },
  3208. * null,
  3209. * null,
  3210. * false
  3211. * );
  3212. * }
  3213. * } );
  3214. *
  3215. */
  3216. Editor.prototype.submit = function ( successCallback, errorCallback, formatdata, hide )
  3217. {
  3218. var
  3219. that = this,
  3220. fields = this.s.fields,
  3221. errorFields = [],
  3222. errorReady = 0,
  3223. sent = false;
  3224. if ( this.s.processing || ! this.s.action ) {
  3225. return this;
  3226. }
  3227. this._processing( true );
  3228. // If there are fields in error, we want to wait for the error notification
  3229. // to be cleared before the form is submitted - errorFields tracks the
  3230. // fields which are in the error state, while errorReady tracks those which
  3231. // are ready to submit
  3232. var send = function () {
  3233. if ( errorFields.length !== errorReady || sent ) {
  3234. return;
  3235. }
  3236. that._event( 'initSubmit', [that.s.action], function (result) {
  3237. if ( result === false ) {
  3238. that._processing( false );
  3239. return;
  3240. }
  3241. sent = true;
  3242. that._submit( successCallback, errorCallback, formatdata, hide );
  3243. } );
  3244. };
  3245. // Remove the global error (don't know if the form is still in an error
  3246. // state!)
  3247. this.error();
  3248. // Count how many fields are in error
  3249. $.each( fields, function ( name, field ) {
  3250. if ( field.inError() ) {
  3251. errorFields.push( name );
  3252. }
  3253. } );
  3254. // Remove the error display
  3255. $.each( errorFields, function ( i, name ) {
  3256. fields[ name ].error('', function () {
  3257. errorReady++;
  3258. send();
  3259. } );
  3260. } );
  3261. send();
  3262. return this;
  3263. };
  3264. /**
  3265. * Get / set the form template
  3266. * @param {string|node|jQuery|undefined} set If undefined, treat as a getter,
  3267. * otherwise set as the template - usually a selector.
  3268. * @return {Editor|string|node|jQuery} Self is a setter, otherwise the template
  3269. */
  3270. Editor.prototype.template = function ( set )
  3271. {
  3272. if ( set === undefined ) {
  3273. return this.s.template;
  3274. }
  3275. this.s.template = set === null ?
  3276. null :
  3277. $(set);
  3278. return this;
  3279. };
  3280. /**
  3281. * Set the title of the form
  3282. * @param {string|function} title The title to give to the form
  3283. * @return {Editor} Editor instance, for chaining
  3284. *
  3285. * @example
  3286. * // Create an edit display used the title, buttons and edit methods (note that
  3287. * // this is just an example, typically you would use the parameters of the edit
  3288. * // method to achieve this.
  3289. * editor.title( 'Edit record' );
  3290. * editor.buttons( {
  3291. * "label": "Update",
  3292. * "fn": function () { this.submit(); }
  3293. * } );
  3294. * editor.edit( TR_to_edit );
  3295. *
  3296. * @example
  3297. * // Show a create form, with a timer for the duration that the form is open
  3298. * editor.create( 'Add new record - time on form: 0s', {
  3299. * "label": "Save",
  3300. * "fn": function () { this.submit(); }
  3301. * } );
  3302. *
  3303. * // Add an event to the editor to stop the timer when the display is removed
  3304. * var runTimer = true;
  3305. * var timer = 0;
  3306. * editor.on( 'close', function () {
  3307. * runTimer = false;
  3308. * editor.off( 'close' );
  3309. * } );
  3310. * // Start the timer running
  3311. * updateTitle();
  3312. *
  3313. * // Local function to update the title once per second
  3314. * function updateTitle() {
  3315. * editor.title( 'Add new record - time on form: '+timer+'s' );
  3316. * timer++;
  3317. * if ( runTimer ) {
  3318. * setTimeout( function() {
  3319. * updateTitle();
  3320. * }, 1000 );
  3321. * }
  3322. * }
  3323. */
  3324. Editor.prototype.title = function ( title )
  3325. {
  3326. var header = $(this.dom.header).children( 'div.'+this.classes.header.content );
  3327. if ( title === undefined ) {
  3328. return header.html();
  3329. }
  3330. if ( typeof title === 'function' ) {
  3331. title = title( this, new DataTable.Api(this.s.table) );
  3332. }
  3333. header.html( title );
  3334. return this;
  3335. };
  3336. /**
  3337. * Get or set the value of a specific field, or get the value of all fields in
  3338. * the form.
  3339. *
  3340. * @param {string|array} [names] The field name(s) to get or set the value of.
  3341. * If not given, then the value of all fields will be obtained.
  3342. * @param {*} [value] Value to set
  3343. * @return {Editor|object|*} Editor instance, for chaining if used as a setter,
  3344. * an object containing the values of the requested fields if used as a
  3345. * getter with multiple fields requested, or the value of the requested field
  3346. * if a single field is requested.
  3347. */
  3348. Editor.prototype.val = function ( field, value )
  3349. {
  3350. if ( value !== undefined || $.isPlainObject( field ) ) {
  3351. return this.set( field, value );
  3352. }
  3353. return this.get( field ); // field can be undefined to get all
  3354. };
  3355. /*
  3356. * DataTables 1.10 API integration. Provides the ability to control basic Editor
  3357. * aspects from the DataTables API. Full control does of course require use of
  3358. * the Editor API though.
  3359. */
  3360. var apiRegister = DataTable.Api.register;
  3361. function __getInst( api ) {
  3362. var ctx = api.context[0];
  3363. return ctx.oInit.editor || ctx._editor;
  3364. }
  3365. // Set sensible defaults for the editing options
  3366. function __setBasic( inst, opts, type, plural ) {
  3367. if ( ! opts ) {
  3368. opts = {};
  3369. }
  3370. if ( opts.buttons === undefined ) {
  3371. opts.buttons = '_basic';
  3372. }
  3373. if ( opts.title === undefined ) {
  3374. opts.title = inst.i18n[ type ].title;
  3375. }
  3376. if ( opts.message === undefined ) {
  3377. if ( type === 'remove' ) {
  3378. var confirm = inst.i18n[ type ].confirm;
  3379. opts.message = plural!==1 ? confirm._.replace(/%d/, plural) : confirm['1'];
  3380. }
  3381. else {
  3382. opts.message = '';
  3383. }
  3384. }
  3385. return opts;
  3386. }
  3387. apiRegister( 'editor()', function () {
  3388. return __getInst( this );
  3389. } );
  3390. // Row editing
  3391. apiRegister( 'row.create()', function ( opts ) {
  3392. // main
  3393. var inst = __getInst( this );
  3394. inst.create( __setBasic( inst, opts, 'create' ) );
  3395. return this;
  3396. } );
  3397. apiRegister( 'row().edit()', function ( opts ) {
  3398. // main
  3399. var inst = __getInst( this );
  3400. inst.edit( this[0][0], __setBasic( inst, opts, 'edit' ) );
  3401. return this;
  3402. } );
  3403. apiRegister( 'rows().edit()', function ( opts ) {
  3404. // main
  3405. var inst = __getInst( this );
  3406. inst.edit( this[0], __setBasic( inst, opts, 'edit' ) );
  3407. return this;
  3408. } );
  3409. apiRegister( 'row().delete()', function ( opts ) {
  3410. // main
  3411. var inst = __getInst( this );
  3412. inst.remove( this[0][0], __setBasic( inst, opts, 'remove', 1 ) );
  3413. return this;
  3414. } );
  3415. apiRegister( 'rows().delete()', function ( opts ) {
  3416. // main
  3417. var inst = __getInst( this );
  3418. inst.remove( this[0], __setBasic( inst, opts, 'remove', this[0].length ) );
  3419. return this;
  3420. } );
  3421. apiRegister( 'cell().edit()', function ( type, opts ) {
  3422. // inline or bubble
  3423. if ( ! type ) {
  3424. type = 'inline';
  3425. }
  3426. else if ( $.isPlainObject( type ) ) {
  3427. opts = type;
  3428. type = 'inline';
  3429. }
  3430. __getInst( this )[ type ]( this[0][0], opts );
  3431. return this;
  3432. } );
  3433. apiRegister( 'cells().edit()', function ( opts ) {
  3434. // bubble only at the moment
  3435. __getInst( this ).bubble( this[0], opts );
  3436. return this;
  3437. } );
  3438. apiRegister( 'file()', _api_file );
  3439. apiRegister( 'files()', _api_files );
  3440. // Global listener for file information updates via DataTables' Ajax JSON
  3441. $(document).on( 'xhr.dt', function (e, ctx, json) {
  3442. if ( e.namespace !== 'dt' ) {
  3443. return;
  3444. }
  3445. if ( json && json.files ) {
  3446. $.each( json.files, function ( name, files ) {
  3447. if ( !Editor.files[ name ] ) {
  3448. Editor.files[ name ] = {};
  3449. }
  3450. $.extend( Editor.files[ name ], files );
  3451. } );
  3452. }
  3453. } );
  3454. /**
  3455. * Common error message emitter. This method is not (yet) publicly documented on
  3456. * the Editor site. It might be in future.
  3457. *
  3458. * @param {string} msg Error message
  3459. * @param {int} tn Tech note link
  3460. */
  3461. Editor.error = function ( msg, tn )
  3462. {
  3463. throw tn ?
  3464. msg +' For more information, please refer to https://datatables.net/tn/'+tn :
  3465. msg;
  3466. };
  3467. /**
  3468. * Obtain label / value pairs of data from a data source, be it an array or
  3469. * object, for use in an input that requires label / value pairs such as
  3470. * `select`, `radio` and `checkbox` inputs.
  3471. *
  3472. * A callback function is triggered for each label / value pair found, so the
  3473. * caller can add it to the input as required.
  3474. *
  3475. * @static
  3476. * @param {object|array} An object or array of data to iterate over getting the
  3477. * label / value pairs.
  3478. * @param {object} props When an array of objects is passed in as the data
  3479. * source by default the label will be read from the `label` property and
  3480. * the value from the `value` property of the object. This option can alter
  3481. * that behaviour.
  3482. * @param {function} fn Callback function. Takes three parameters: the label,
  3483. * the value and the iterator index.
  3484. */
  3485. Editor.pairs = function ( data, props, fn )
  3486. {
  3487. var i, ien, dataPoint;
  3488. // Define default properties to read the data from if using an object.
  3489. // The passed in `props` object and override.
  3490. props = $.extend( {
  3491. label: 'label',
  3492. value: 'value'
  3493. }, props );
  3494. if ( $.isArray( data ) ) {
  3495. // As an array, we iterate each item which can be an object or value
  3496. for ( i=0, ien=data.length ; i<ien ; i++ ) {
  3497. dataPoint = data[i];
  3498. if ( $.isPlainObject( dataPoint ) ) {
  3499. fn(
  3500. dataPoint[ props.value ] === undefined ?
  3501. dataPoint[ props.label ] :
  3502. dataPoint[ props.value ],
  3503. dataPoint[ props.label ],
  3504. i,
  3505. dataPoint.attr // optional - can be undefined
  3506. );
  3507. }
  3508. else {
  3509. fn( dataPoint, dataPoint, i );
  3510. }
  3511. }
  3512. }
  3513. else {
  3514. // As an object the key is the label and the value is the value
  3515. i = 0;
  3516. $.each( data, function ( key, val ) {
  3517. fn( val, key, i );
  3518. i++;
  3519. } );
  3520. }
  3521. };
  3522. /**
  3523. * Make a string safe to use as a DOM ID. This is primarily for use by field
  3524. * plug-in authors.
  3525. *
  3526. * @static
  3527. * @param {string} String to make safe
  3528. * @param {string} Safe string
  3529. */
  3530. Editor.safeId = function ( id )
  3531. {
  3532. return id.replace(/\./g, '-');
  3533. };
  3534. /**
  3535. * Field specific upload method. This can be used to upload a file to the Editor
  3536. * libraries. This method is not (yet) publicly documented on the Editor site.
  3537. * It might be in future.
  3538. *
  3539. * @static
  3540. * @param {Editor} editor The Editor instance operating on
  3541. * @param {object} conf Field configuration object
  3542. * @param {Files} files The file(s) to upload
  3543. * @param {function} progressCallback Upload progress callback
  3544. * @param {function} completeCallback Callback function for once the file has
  3545. * been uploaded
  3546. */
  3547. Editor.upload = function ( editor, conf, files, progressCallback, completeCallback )
  3548. {
  3549. var reader = new FileReader();
  3550. var counter = 0;
  3551. var ids = [];
  3552. var generalError = 'A server error occurred while uploading the file';
  3553. // Clear any existing errors, as the new upload might not be in error
  3554. editor.error( conf.name, '' );
  3555. if ( typeof conf.ajax === 'function' ) {
  3556. conf.ajax( files, function (ids) {
  3557. completeCallback.call( editor, ids );
  3558. } );
  3559. return;
  3560. }
  3561. progressCallback( conf, conf.fileReadText || "<i>Uploading file</i>" );
  3562. reader.onload = function ( e ) {
  3563. var data = new FormData();
  3564. var ajax;
  3565. data.append( 'action', 'upload' );
  3566. data.append( 'uploadField', conf.name );
  3567. data.append( 'upload', files[ counter ] );
  3568. if ( conf.ajaxData ) {
  3569. conf.ajaxData( data, files[ counter ], counter );
  3570. }
  3571. if ( conf.ajax ) {
  3572. ajax = conf.ajax;
  3573. }
  3574. else if ( $.isPlainObject( editor.s.ajax ) ) {
  3575. ajax = editor.s.ajax.upload ?
  3576. editor.s.ajax.upload :
  3577. editor.s.ajax;
  3578. }
  3579. else if ( typeof editor.s.ajax === 'string' ) {
  3580. ajax = editor.s.ajax;
  3581. }
  3582. if ( ! ajax ) {
  3583. throw new Exception('No Ajax option specified for upload plug-in');
  3584. }
  3585. if ( typeof ajax === 'string' ) {
  3586. ajax = { url: ajax };
  3587. }
  3588. // Handle the case when the ajax data is given as a function
  3589. if ( typeof ajax.data === 'function' ) {
  3590. var d = {};
  3591. var ret = ajax.data( d );
  3592. // Allow the return to be used, or the object passed in
  3593. if ( ret !== undefined && typeof ret !== 'string' ) {
  3594. d = ret;
  3595. }
  3596. $.each( d, function ( key, value ) {
  3597. data.append( key, value );
  3598. } );
  3599. }
  3600. else if ( $.isPlainObject(ajax.data) ) {
  3601. throw new Exception('Upload feature cannot use `ajax.data` with an object. Please use it as a function instead.');
  3602. }
  3603. // Dev cancellable event
  3604. var preRet = editor._event( 'preUpload', [ conf.name, files[ counter ], data ] );
  3605. if ( preRet === false ) {
  3606. if ( counter < files.length-1 ) {
  3607. counter++;
  3608. reader.readAsDataURL( files[counter] );
  3609. }
  3610. else {
  3611. completeCallback.call( editor, ids );
  3612. }
  3613. return;
  3614. }
  3615. // Use preSubmit to stop form submission during an upload, since the
  3616. // value won't be known until that point.
  3617. var submit = false;
  3618. editor
  3619. .on( 'preSubmit.DTE_Upload', function () {
  3620. submit = true;
  3621. return false;
  3622. } );
  3623. $.ajax( $.extend( {}, ajax, {
  3624. type: 'post',
  3625. data: data,
  3626. dataType: 'json',
  3627. contentType: false,
  3628. processData: false,
  3629. xhr: function () {
  3630. var xhr = $.ajaxSettings.xhr();
  3631. if ( xhr.upload ) {
  3632. xhr.upload.onprogress = function ( e ) {
  3633. if ( e.lengthComputable ) {
  3634. var percent = (e.loaded/e.total*100).toFixed(0)+"%";
  3635. progressCallback( conf, files.length === 1 ?
  3636. percent :
  3637. counter+':'+files.length+' '+percent
  3638. );
  3639. }
  3640. };
  3641. xhr.upload.onloadend = function ( e ) {
  3642. progressCallback( conf, conf.processingText || 'Processing' );
  3643. };
  3644. }
  3645. return xhr;
  3646. },
  3647. success: function ( json ) {
  3648. editor.off( 'preSubmit.DTE_Upload' );
  3649. editor._event( 'uploadXhrSuccess', [ conf.name, json ] );
  3650. if ( json.fieldErrors && json.fieldErrors.length ) {
  3651. var errors = json.fieldErrors;
  3652. for ( var i=0, ien=errors.length ; i<ien ; i++ ) {
  3653. editor.error( errors[i].name, errors[i].status );
  3654. }
  3655. }
  3656. else if ( json.error ) {
  3657. editor.error( json.error );
  3658. }
  3659. else if ( ! json.upload || ! json.upload.id ) {
  3660. editor.error( conf.name, generalError );
  3661. }
  3662. else {
  3663. if ( json.files ) {
  3664. // Loop over the tables that are defined
  3665. $.each( json.files, function ( table, files ) {
  3666. if ( ! Editor.files[ table ] ) {
  3667. Editor.files[ table ] = {};
  3668. }
  3669. $.extend( Editor.files[ table ], files );
  3670. } );
  3671. }
  3672. ids.push( json.upload.id );
  3673. if ( counter < files.length-1 ) {
  3674. counter++;
  3675. reader.readAsDataURL( files[counter] );
  3676. }
  3677. else {
  3678. completeCallback.call( editor, ids );
  3679. if ( submit ) {
  3680. editor.submit();
  3681. }
  3682. }
  3683. }
  3684. progressCallback( conf );
  3685. },
  3686. error: function ( xhr ) {
  3687. editor._event( 'uploadXhrError', [ conf.name, xhr ] );
  3688. editor.error( conf.name, generalError );
  3689. progressCallback( conf );
  3690. }
  3691. } ) );
  3692. };
  3693. // Convert to a plain array
  3694. files = $.map( files, function (val) {
  3695. return val;
  3696. } );
  3697. // Truncate the selected files if needed
  3698. if ( conf._limitLeft !== undefined ) {
  3699. files.splice( conf._limitLeft, files.length );
  3700. }
  3701. reader.readAsDataURL( files[0] );
  3702. };
  3703. /**
  3704. * Editor constructor - take the developer configuration and apply it to the instance.
  3705. * @param {object} init The initialisation options provided by the developer - see
  3706. * {@link Editor.defaults} for a full list of options.
  3707. * @private
  3708. */
  3709. Editor.prototype._constructor = function(init) {
  3710. init = $.extend(true, {}, Editor.defaults, init);
  3711. this.s = $.extend(true, {}, Editor.models.settings, {
  3712. actionName: init.actionName,
  3713. table: init.domTable || init.table,
  3714. dbTable: init.dbTable || null, // legacy
  3715. ajaxUrl: init.ajaxUrl,
  3716. ajax: init.ajax,
  3717. idSrc: init.idSrc,
  3718. dataSource: init.domTable || init.table ?
  3719. Editor.dataSources.dataTable : Editor.dataSources.html,
  3720. formOptions: init.formOptions,
  3721. legacyAjax: init.legacyAjax,
  3722. template: init.template ?
  3723. $(init.template).detach() : null
  3724. });
  3725. this.classes = $.extend(true, {}, Editor.classes);
  3726. this.i18n = init.i18n;
  3727. // Increment the unique counter for the next instance
  3728. Editor.models.settings.unique++;
  3729. var that = this;
  3730. var classes = this.classes;
  3731. this.dom = {
  3732. "wrapper": $(
  3733. '<div class="' + classes.wrapper + '">' +
  3734. '<div data-dte-e="processing" class="' + classes.processing.indicator + '"><span/></div>' +
  3735. '<div data-dte-e="body" class="' + classes.body.wrapper + '">' +
  3736. '<div data-dte-e="body_content" class="' + classes.body.content + '"/>' +
  3737. '</div>' +
  3738. '<div data-dte-e="foot" class="' + classes.footer.wrapper + '">' +
  3739. '<div class="' + classes.footer.content + '"/>' +
  3740. '</div>' +
  3741. '</div>'
  3742. )[0],
  3743. "form": $(
  3744. '<form data-dte-e="form" class="' + classes.form.tag + '">' +
  3745. '<div data-dte-e="form_content" class="' + classes.form.content + '"/>' +
  3746. '</form>'
  3747. )[0],
  3748. "formError": $('<div data-dte-e="form_error" class="' + classes.form.error + '"/>')[0],
  3749. "formInfo": $('<div data-dte-e="form_info" class="' + classes.form.info + '"/>')[0],
  3750. "header": $('<div data-dte-e="head" class="' + classes.header.wrapper + '"><div class="' + classes.header.content + '"/></div>')[0],
  3751. "buttons": $('<div data-dte-e="form_buttons" class="' + classes.form.buttons + '"/>')[0]
  3752. };
  3753. // Customise the TableTools buttons with the i18n settings - worth noting that
  3754. // this could easily be done outside the Editor instance, but this makes things
  3755. // a bit easier to understand and more cohesive. Also worth noting that when
  3756. // there are two or more Editor instances, the init sequence should be
  3757. // Editor / DataTables, Editor / DataTables etc, since the value of these button
  3758. // instances matter when you create the TableTools buttons for the DataTable.
  3759. if ($.fn.dataTable.TableTools) {
  3760. var ttButtons = $.fn.dataTable.TableTools.BUTTONS;
  3761. var i18n = this.i18n;
  3762. $.each(['create', 'edit', 'remove'], function(i, val) {
  3763. ttButtons['editor_' + val].sButtonText = i18n[val].button;
  3764. });
  3765. }
  3766. // Bind callback methods
  3767. $.each(init.events, function(evt, fn) {
  3768. that.on(evt, function() {
  3769. // When giving events in the constructor the event argument was not
  3770. // given in 1.2-, so we remove it here. This is solely for
  3771. // backwards compatibility as the events in the initialisation are
  3772. // not documented in 1.3+.
  3773. var args = Array.prototype.slice.call(arguments);
  3774. args.shift();
  3775. fn.apply(that, args);
  3776. });
  3777. });
  3778. // Cache the DOM nodes
  3779. var dom = this.dom;
  3780. var wrapper = dom.wrapper;
  3781. dom.formContent = _editor_el('form_content', dom.form)[0];
  3782. dom.footer = _editor_el('foot', wrapper)[0];
  3783. dom.body = _editor_el('body', wrapper)[0];
  3784. dom.bodyContent = _editor_el('body_content', wrapper)[0];
  3785. dom.processing = _editor_el('processing', wrapper)[0];
  3786. // Add any fields which are given on initialisation
  3787. if (init.fields) {
  3788. this.add(init.fields);
  3789. }
  3790. $(document)
  3791. .on('init.dt.dte' + this.s.unique, function(e, settings, json) {
  3792. // Attempt to attach to a DataTable automatically when the table is
  3793. // initialised
  3794. if (that.s.table && settings.nTable === $(that.s.table).get(0)) {
  3795. settings._editor = that;
  3796. }
  3797. })
  3798. .on('xhr.dt.dte' + this.s.unique, function(e, settings, json) {
  3799. // Automatically update fields which have a field name defined in
  3800. // the returned json - saves an `initComplete` for the user
  3801. if (json && that.s.table && settings.nTable === $(that.s.table).get(0)) {
  3802. that._optionsUpdate(json);
  3803. }
  3804. });
  3805. // Prep the display controller
  3806. try {
  3807. this.s.displayController = Editor.display[init.display].init(this);
  3808. }
  3809. catch (e) {
  3810. throw 'Cannot find display controller '+init.display;
  3811. }
  3812. this._event('initComplete', []);
  3813. $(document).trigger('initEditor', [this]);
  3814. };
  3815. /*global __inlineCounter*/
  3816. /**
  3817. * Set the class on the form to relate to the action that is being performed.
  3818. * This allows styling to be applied to the form to reflect the state that
  3819. * it is in.
  3820. *
  3821. * @private
  3822. */
  3823. Editor.prototype._actionClass = function ()
  3824. {
  3825. var classesActions = this.classes.actions;
  3826. var action = this.s.action;
  3827. var wrapper = $(this.dom.wrapper);
  3828. wrapper.removeClass( [classesActions.create, classesActions.edit, classesActions.remove].join(' ') );
  3829. if ( action === "create" ) {
  3830. wrapper.addClass( classesActions.create );
  3831. }
  3832. else if ( action === "edit" ) {
  3833. wrapper.addClass( classesActions.edit );
  3834. }
  3835. else if ( action === "remove" ) {
  3836. wrapper.addClass( classesActions.remove );
  3837. }
  3838. };
  3839. /**
  3840. * Create an Ajax request in the same style as DataTables 1.10, with full
  3841. * backwards compatibility for Editor 1.2.
  3842. *
  3843. * @param {object} data Data to submit
  3844. * @param {function} success Success callback
  3845. * @param {function} error Error callback
  3846. * @param {object} submitParams Submitted data
  3847. * @private
  3848. */
  3849. Editor.prototype._ajax = function ( data, success, error, submitParams )
  3850. {
  3851. var that = this;
  3852. var action = this.s.action;
  3853. var thrown;
  3854. var opts = {
  3855. type: 'POST',
  3856. dataType: 'json',
  3857. data: null,
  3858. error: [ function ( xhr, text, err ) {
  3859. thrown = err;
  3860. } ],
  3861. success: [],
  3862. complete: [ function ( xhr, text ) {
  3863. // Use `complete` rather than `success` so that all status codes are
  3864. // caught and can return valid JSON (useful when working with REST
  3865. // services).
  3866. var json = null;
  3867. if ( xhr.status === 204 || xhr.responseText === 'null' ) {
  3868. json = {};
  3869. }
  3870. else {
  3871. try {
  3872. // jQuery 1.12 or newer for responseJSON, but its the only
  3873. // way to get the JSON from a JSONP. So if you want to use
  3874. // JSONP with Editor you have to use jQuery 1.12+.
  3875. json = xhr.responseJSON ?
  3876. xhr.responseJSON :
  3877. $.parseJSON( xhr.responseText );
  3878. }
  3879. catch (e) {}
  3880. }
  3881. if ( $.isPlainObject( json ) || $.isArray( json ) ) {
  3882. success( json, xhr.status >= 400, xhr );
  3883. }
  3884. else {
  3885. error( xhr, text, thrown );
  3886. }
  3887. } ]
  3888. };
  3889. var a;
  3890. var ajaxSrc = this.s.ajax || this.s.ajaxUrl;
  3891. var id = action === 'edit' || action === 'remove' ?
  3892. _pluck( this.s.editFields, 'idSrc' ) :
  3893. null;
  3894. if ( $.isArray( id ) ) {
  3895. id = id.join(',');
  3896. }
  3897. // Get the correct object for rest style
  3898. if ( $.isPlainObject( ajaxSrc ) && ajaxSrc[ action ] ) {
  3899. ajaxSrc = ajaxSrc[ action ];
  3900. }
  3901. if ( typeof ajaxSrc === 'function' ) {
  3902. // As a function, execute it, passing in the required parameters
  3903. var uri = null;
  3904. var method = null;
  3905. // If the old style ajaxSrc is given, we need to process it for
  3906. // backwards compatibility with 1.2-. Unfortunate otherwise this would
  3907. // be a very simply function!
  3908. if ( this.s.ajaxUrl ) {
  3909. var url = this.s.ajaxUrl;
  3910. if ( url.create ) {
  3911. uri = url[ action ];
  3912. }
  3913. if ( uri.indexOf(' ') !== -1 ) {
  3914. a = uri.split(' ');
  3915. method = a[0];
  3916. uri = a[1];
  3917. }
  3918. uri = uri.replace( /_id_/, id );
  3919. }
  3920. ajaxSrc( method, uri, data, success, error );
  3921. return;
  3922. }
  3923. else if ( typeof ajaxSrc === 'string' ) {
  3924. // As a string it gives the URL. For backwards compatibility it can also
  3925. // give the method.
  3926. if ( ajaxSrc.indexOf(' ') !== -1 ) {
  3927. a = ajaxSrc.split(' ');
  3928. opts.type = a[0];
  3929. opts.url = a[1];
  3930. }
  3931. else {
  3932. opts.url = ajaxSrc;
  3933. }
  3934. }
  3935. else {
  3936. // As an object, we extend the Editor defaults - with the exception of
  3937. // the error and complete functions which get added in so the user can
  3938. // specify their own in addition to ours
  3939. var optsCopy = $.extend( {}, ajaxSrc || {} );
  3940. if ( optsCopy.complete ) {
  3941. opts.complete.unshift( optsCopy.complete );
  3942. delete optsCopy.complete;
  3943. }
  3944. if ( optsCopy.error ) {
  3945. opts.error.unshift( optsCopy.error );
  3946. delete optsCopy.error;
  3947. }
  3948. opts = $.extend( {}, opts, optsCopy );
  3949. }
  3950. // URL macros
  3951. opts.url = opts.url.replace( /_id_/, id );
  3952. // Data processing option like in DataTables
  3953. if ( opts.data ) {
  3954. var isFn = typeof opts.data === 'function';
  3955. var newData = isFn ?
  3956. opts.data( data ) : // fn can manipulate data or return an object
  3957. opts.data; // object or array to merge
  3958. // If the function returned something, use that alone
  3959. data = isFn && newData ?
  3960. newData :
  3961. $.extend( true, data, newData );
  3962. }
  3963. opts.data = data;
  3964. // If a DELETE method is used there are a number of servers which will
  3965. // reject the request if it has a body. So we need to append to the URL.
  3966. //
  3967. // http://stackoverflow.com/questions/15088955
  3968. // http://bugs.jquery.com/ticket/11586
  3969. if ( opts.type === 'DELETE' && (opts.deleteBody === undefined || opts.deleteBody === true) ) {
  3970. var params = $.param( opts.data );
  3971. opts.url += opts.url.indexOf('?') === -1 ?
  3972. '?'+params :
  3973. '&'+params;
  3974. delete opts.data;
  3975. }
  3976. // Finally, make the ajax call
  3977. $.ajax( opts );
  3978. };
  3979. /**
  3980. * Abstraction for jQuery's animate method, to support jQuery slim which doesn't
  3981. * include the animate module
  3982. *
  3983. * @private
  3984. */
  3985. Editor.prototype._animate = function ( target, style, time, callback )
  3986. {
  3987. if ( $.fn.animate ) {
  3988. target
  3989. .stop()
  3990. .animate( style, time, callback );
  3991. }
  3992. else {
  3993. target.css( style );
  3994. if ( typeof time === 'function' ) {
  3995. time.call( target );
  3996. }
  3997. else if ( callback ) {
  3998. callback.call( target );
  3999. }
  4000. }
  4001. };
  4002. /**
  4003. * Create the DOM structure from the source elements for the main form.
  4004. * This is required since the elements can be moved around for other form types
  4005. * (bubble).
  4006. *
  4007. * @private
  4008. */
  4009. Editor.prototype._assembleMain = function ()
  4010. {
  4011. var dom = this.dom;
  4012. $(dom.wrapper)
  4013. .prepend( dom.header );
  4014. $(dom.footer)
  4015. .append( dom.formError )
  4016. .append( dom.buttons );
  4017. $(dom.bodyContent)
  4018. .append( dom.formInfo )
  4019. .append( dom.form );
  4020. };
  4021. /**
  4022. * Blur the editing window. A blur is different from a close in that it might
  4023. * cause either a close or the form to be submitted. A typical example of a
  4024. * blur would be clicking on the background of the bubble or main editing forms
  4025. * - i.e. it might be a close, or it might submit depending upon the
  4026. * configuration, while a click on the close box is a very definite close.
  4027. *
  4028. * @private
  4029. */
  4030. Editor.prototype._blur = function ()
  4031. {
  4032. var opts = this.s.editOpts;
  4033. var onBlur = opts.onBlur;
  4034. if ( this._event( 'preBlur' ) === false ) {
  4035. return;
  4036. }
  4037. if ( typeof onBlur === 'function' ) {
  4038. onBlur( this );
  4039. }
  4040. else if ( onBlur === 'submit' ) {
  4041. this.submit();
  4042. }
  4043. else if ( onBlur === 'close' ) {
  4044. this._close();
  4045. }
  4046. };
  4047. /**
  4048. * Clear all of the information that might have been dynamically set while
  4049. * the form was visible - specifically errors and dynamic messages
  4050. *
  4051. * @private
  4052. */
  4053. Editor.prototype._clearDynamicInfo = function ()
  4054. {
  4055. // Can be triggered due to a destroy if the editor is open
  4056. if ( ! this.s ) {
  4057. return;
  4058. }
  4059. var errorClass = this.classes.field.error;
  4060. var fields = this.s.fields;
  4061. $('div.'+errorClass, this.dom.wrapper).removeClass( errorClass );
  4062. $.each( fields, function (name, field) {
  4063. field
  4064. .error('')
  4065. .message('');
  4066. } );
  4067. this
  4068. .error('')
  4069. .message('');
  4070. };
  4071. /**
  4072. * Close an editing display, firing callbacks and events as needed
  4073. *
  4074. * @param {function} submitComplete Function to call after the preClose event
  4075. * @param {string} mode Editing mode that is just finished
  4076. * @private
  4077. */
  4078. Editor.prototype._close = function ( submitComplete, mode )
  4079. {
  4080. var closed;
  4081. // Allow preClose event to cancel the opening of the display
  4082. if ( this._event( 'preClose' ) === false ) {
  4083. return;
  4084. }
  4085. if ( this.s.closeCb ) {
  4086. closed = this.s.closeCb( submitComplete, mode );
  4087. this.s.closeCb = null;
  4088. }
  4089. if ( this.s.closeIcb ) {
  4090. this.s.closeIcb();
  4091. this.s.closeIcb = null;
  4092. }
  4093. // Remove focus control
  4094. $('body').off( 'focus.editor-focus' );
  4095. this.s.displayed = false;
  4096. this._event( 'close' );
  4097. if ( closed ) {
  4098. // Note that `bubble` will call this itself due to the animation
  4099. this._event( 'closed', [closed] );
  4100. }
  4101. };
  4102. /**
  4103. * Register a function to be called when the editing display is closed. This is
  4104. * used by function that create the editing display to tidy up the display on
  4105. * close - for example removing event handlers to prevent memory leaks.
  4106. *
  4107. * @param {function} fn Function to call on close
  4108. * @private
  4109. */
  4110. Editor.prototype._closeReg = function ( fn )
  4111. {
  4112. this.s.closeCb = fn;
  4113. };
  4114. /**
  4115. * Argument shifting for the create(), edit() and remove() methods. In Editor
  4116. * 1.3 the preferred form of calling those three methods is with just two
  4117. * parameters (one in the case of create() - the id and the show flag), while in
  4118. * previous versions four / three parameters could be passed in, including the
  4119. * buttons and title options. In 1.3 the chaining API is preferred, but we want
  4120. * to support the old form as well, so this function is provided to perform
  4121. * that argument shifting, common to all three.
  4122. *
  4123. * @private
  4124. */
  4125. Editor.prototype._crudArgs = function ( arg1, arg2, arg3, arg4 )
  4126. {
  4127. var that = this;
  4128. var title;
  4129. var buttons;
  4130. var show;
  4131. var opts;
  4132. if ( $.isPlainObject( arg1 ) ) {
  4133. // Form options passed in as the first option
  4134. opts = arg1;
  4135. }
  4136. else if ( typeof arg1 === 'boolean' ) {
  4137. // Show / hide passed in as the first option - form options second
  4138. show = arg1;
  4139. opts = arg2; // can be undefined
  4140. }
  4141. else {
  4142. // Old style arguments
  4143. title = arg1; // can be undefined
  4144. buttons = arg2; // can be undefined
  4145. show = arg3; // can be undefined
  4146. opts = arg4; // can be undefined
  4147. }
  4148. // If all undefined, then fall into here
  4149. if ( show === undefined ) {
  4150. show = true;
  4151. }
  4152. if ( title ) {
  4153. that.title( title );
  4154. }
  4155. if ( buttons ) {
  4156. that.buttons( buttons );
  4157. }
  4158. return {
  4159. opts: $.extend( {}, this.s.formOptions.main, opts ),
  4160. maybeOpen: function () {
  4161. if ( show ) {
  4162. that.open();
  4163. }
  4164. }
  4165. };
  4166. };
  4167. /**
  4168. * Execute the data source abstraction layer functions. This is simply a case
  4169. * of executing the function with the Editor scope, passing in the remaining
  4170. * parameters.
  4171. *
  4172. * @param {string} name Function name to execute
  4173. * @private
  4174. */
  4175. Editor.prototype._dataSource = function ( name /*, ... */ )
  4176. {
  4177. // Remove the name from the arguments list, so the rest can be passed
  4178. // straight into the field type
  4179. var args = Array.prototype.slice.call( arguments );
  4180. args.shift();
  4181. var fn = this.s.dataSource[ name ];
  4182. if ( fn ) {
  4183. return fn.apply( this, args );
  4184. }
  4185. };
  4186. /**
  4187. * Insert the fields into the DOM, in the correct order
  4188. *
  4189. * @private
  4190. */
  4191. Editor.prototype._displayReorder = function ( includeFields )
  4192. {
  4193. var that = this;
  4194. var formContent = $(this.dom.formContent);
  4195. var fields = this.s.fields;
  4196. var order = this.s.order;
  4197. var template = this.s.template;
  4198. var mode = this.s.mode || 'main';
  4199. if ( includeFields ) {
  4200. this.s.includeFields = includeFields;
  4201. }
  4202. else {
  4203. includeFields = this.s.includeFields;
  4204. }
  4205. // Empty before adding in the required fields
  4206. formContent.children().detach();
  4207. $.each( order, function (i, fieldOrName) {
  4208. var name = fieldOrName instanceof Editor.Field ?
  4209. fieldOrName.name() :
  4210. fieldOrName;
  4211. if ( that._weakInArray( name, includeFields ) !== -1 ) {
  4212. if ( template && mode === 'main' ) {
  4213. template.find('editor-field[name="'+name+'"]').after(
  4214. fields[ name ].node()
  4215. );
  4216. template.find( '[data-editor-template="'+name+'"]').append(
  4217. fields[ name ].node()
  4218. );
  4219. }
  4220. else {
  4221. formContent.append( fields[ name ].node() );
  4222. }
  4223. }
  4224. } );
  4225. if ( template && mode === 'main' ) {
  4226. template.appendTo( formContent );
  4227. }
  4228. this._event( 'displayOrder', [
  4229. this.s.displayed,
  4230. this.s.action,
  4231. formContent
  4232. ] );
  4233. };
  4234. /**
  4235. * Generic editing handler. This can be called by the three editing modes (main,
  4236. * bubble and inline) to configure Editor for a row edit, and fire the required
  4237. * events to ensure that the editing interfaces all provide a common API.
  4238. *
  4239. * @param {*} rows Identifier for the item(s) to be edited
  4240. * @param {string} type Editing type - for the initEdit event
  4241. * @private
  4242. */
  4243. Editor.prototype._edit = function ( items, editFields, type, formOptions, setupDone )
  4244. {
  4245. var that = this;
  4246. var fields = this.s.fields;
  4247. var usedFields = [];
  4248. var includeInOrder;
  4249. var editData = {};
  4250. this.s.editFields = editFields;
  4251. this.s.editData = editData;
  4252. this.s.modifier = items;
  4253. this.s.action = "edit";
  4254. this.dom.form.style.display = 'block';
  4255. this.s.mode = type;
  4256. this._actionClass();
  4257. // Setup the field values for editing
  4258. $.each( fields, function ( name, field ) {
  4259. field.multiReset();
  4260. includeInOrder = false;
  4261. editData[ name ] = {};
  4262. $.each( editFields, function ( idSrc, edit ) {
  4263. if ( edit.fields[ name ] ) {
  4264. var val = field.valFromData( edit.data );
  4265. // Save the set data values so we can decided in submit if data has changed
  4266. // Note that `null` is stored as an empty string since fields do not currently
  4267. // have the ability to store a null value - when they are read back (in the
  4268. // submit) they would be an empty string. When null handling is added to
  4269. // fields, this will need to be removed.
  4270. editData[ name ][ idSrc ] = val === null ?
  4271. '' :
  4272. $.isArray(val) ?
  4273. val.slice() :
  4274. val;
  4275. // If scoped to edit the whole row, then set all of the fields
  4276. if ( !formOptions || formOptions.scope === 'row' ) {
  4277. field.multiSet( idSrc, val !== undefined ?
  4278. val :
  4279. field.def()
  4280. );
  4281. if ( ! edit.displayFields || edit.displayFields[ name ] ) {
  4282. includeInOrder = true;
  4283. }
  4284. }
  4285. else {
  4286. // Limit editing to only those fields selected if any are selected
  4287. if ( ! edit.displayFields || edit.displayFields[ name ] ) {
  4288. field.multiSet( idSrc, val !== undefined ?
  4289. val :
  4290. field.def()
  4291. );
  4292. includeInOrder = true;
  4293. }
  4294. }
  4295. }
  4296. } );
  4297. // If the field is used, then add it to the fields to be shown
  4298. if ( field.multiIds().length !== 0 && includeInOrder ) {
  4299. usedFields.push( name );
  4300. }
  4301. } );
  4302. // Remove the fields that are not required from the display
  4303. var currOrder = this.order().slice();
  4304. for ( var i=currOrder.length-1 ; i >= 0 ; i-- ) {
  4305. // Use `toString()` to convert numbers to strings, since usedFields
  4306. // contains strings (object property names)
  4307. if ( $.inArray( currOrder[i].toString(), usedFields ) === -1 ) {
  4308. currOrder.splice( i, 1 );
  4309. }
  4310. }
  4311. this._displayReorder( currOrder );
  4312. // Events
  4313. this._event(
  4314. 'initEdit',
  4315. [
  4316. _pluck( editFields, 'node' )[0],
  4317. _pluck( editFields, 'data' )[0],
  4318. items,
  4319. type
  4320. ],
  4321. function () {
  4322. that._event(
  4323. 'initMultiEdit', // undocumented and to be removed in v2
  4324. [ editFields, items, type ],
  4325. function () {
  4326. setupDone();
  4327. }
  4328. );
  4329. }
  4330. );
  4331. };
  4332. /**
  4333. * Fire callback functions and trigger events.
  4334. *
  4335. * @param {string|array} trigger Name(s) of the jQuery custom event to trigger
  4336. * @param {array} args Array of arguments to pass to the triggered event
  4337. * @return {*} Return from the event
  4338. * @private
  4339. */
  4340. Editor.prototype._event = function ( trigger, args, promiseComplete )
  4341. {
  4342. if ( ! args ) {
  4343. args = [];
  4344. }
  4345. // Allow an array to be passed in for the trigger to fire multiple events
  4346. if ( $.isArray( trigger ) ) {
  4347. for ( var i=0, ien=trigger.length ; i<ien ; i++ ) {
  4348. this._event( trigger[i], args );
  4349. }
  4350. }
  4351. else {
  4352. var e = $.Event( trigger );
  4353. $(this).triggerHandler( e, args );
  4354. // Automatically trigger a cancelled event if a `pre` event handler
  4355. // was cancelled by the callback
  4356. if ( trigger.indexOf('pre') === 0 && e.result === false ) {
  4357. $(this).triggerHandler(
  4358. $.Event( trigger + 'Cancelled' ),
  4359. args
  4360. );
  4361. }
  4362. // Allow for a promise to be returned and execute a callback
  4363. if ( promiseComplete ) {
  4364. if ( e.result && typeof e.result === 'object' && e.result.then ) {
  4365. // jQuery and "real" promises both provide "then"
  4366. e.result.then( promiseComplete );
  4367. }
  4368. else {
  4369. // If there wasn't a promise returned, then execute immediately
  4370. promiseComplete( e.result );
  4371. }
  4372. }
  4373. return e.result;
  4374. }
  4375. };
  4376. /**
  4377. * 'Modernise' event names, from the old style `on[A-Z]` names to camelCase.
  4378. * This is done to provide backwards compatibility with Editor 1.2- event names.
  4379. * The names themselves were updated for consistency with DataTables.
  4380. *
  4381. * @param {string} Event name to modernise
  4382. * @return {string} String with new event name structure
  4383. * @private
  4384. */
  4385. Editor.prototype._eventName = function ( input )
  4386. {
  4387. var name;
  4388. var names = input.split( ' ' );
  4389. for ( var i=0, ien=names.length ; i<ien ; i++ ) {
  4390. name = names[i];
  4391. // Strip the 'on' part and lowercase the first character
  4392. var onStyle = name.match(/^on([A-Z])/);
  4393. if ( onStyle ) {
  4394. name = onStyle[1].toLowerCase() + name.substring( 3 );
  4395. }
  4396. names[i] = name;
  4397. }
  4398. return names.join( ' ' );
  4399. };
  4400. /**
  4401. * Find a field from a DOM node. All children are searched.
  4402. *
  4403. * @param {node} node DOM node to search for
  4404. * @return {Field} Field instance
  4405. */
  4406. Editor.prototype._fieldFromNode = function ( node )
  4407. {
  4408. var foundField = null;
  4409. $.each( this.s.fields, function ( name, field ) {
  4410. if ( $( field.node() ).find( node ).length ) {
  4411. foundField = field;
  4412. }
  4413. } );
  4414. return foundField;
  4415. };
  4416. /**
  4417. * Convert a field name input parameter to an array of field names.
  4418. *
  4419. * Many of the API methods provide the ability to pass `undefined` a string or
  4420. * array of strings to identify fields. This method harmonises that.
  4421. *
  4422. * @param {array|string} [fieldNames] Field names to get
  4423. * @return {array} Field names
  4424. * @private
  4425. */
  4426. Editor.prototype._fieldNames = function ( fieldNames )
  4427. {
  4428. if ( fieldNames === undefined ) {
  4429. return this.fields();
  4430. }
  4431. else if ( ! $.isArray( fieldNames ) ) {
  4432. return [ fieldNames ];
  4433. }
  4434. return fieldNames;
  4435. };
  4436. /**
  4437. * Focus on a field. Providing the logic to allow complex focus expressions
  4438. *
  4439. * @param {array} fields Array of Field instances or field names for the fields
  4440. * that are shown
  4441. * @param {null|string|integer} focus Field identifier to focus on
  4442. * @private
  4443. */
  4444. Editor.prototype._focus = function ( fieldsIn, focus )
  4445. {
  4446. var that = this;
  4447. var field;
  4448. var fields = $.map( fieldsIn, function ( fieldOrName ) {
  4449. return typeof fieldOrName === 'string' ?
  4450. that.s.fields[ fieldOrName ] :
  4451. fieldOrName;
  4452. } );
  4453. if ( typeof focus === 'number' ) {
  4454. field = fields[ focus ];
  4455. }
  4456. else if ( focus ) {
  4457. if ( focus.indexOf( 'jq:' ) === 0 ) {
  4458. field = $('div.DTE '+focus.replace(/^jq:/, ''));
  4459. }
  4460. else {
  4461. field = this.s.fields[ focus ];
  4462. }
  4463. }
  4464. else {
  4465. document.activeElement.blur();
  4466. }
  4467. this.s.setFocus = field;
  4468. if ( field ) {
  4469. field.focus();
  4470. }
  4471. };
  4472. /**
  4473. * Form options - common function so all editing methods can provide the same
  4474. * basic options, DRY.
  4475. *
  4476. * @param {object} opts Editing options. See model.formOptions
  4477. * @private
  4478. */
  4479. Editor.prototype._formOptions = function ( opts )
  4480. {
  4481. var that = this;
  4482. var inlineCount = __inlineCounter++;
  4483. var namespace = '.dteInline'+inlineCount;
  4484. // Backwards compatibility with 1.4
  4485. if ( opts.closeOnComplete !== undefined ) {
  4486. opts.onComplete = opts.closeOnComplete ? 'close' : 'none';
  4487. }
  4488. if ( opts.submitOnBlur !== undefined ) {
  4489. opts.onBlur = opts.submitOnBlur ? 'submit' : 'close';
  4490. }
  4491. if ( opts.submitOnReturn !== undefined ) {
  4492. opts.onReturn = opts.submitOnReturn ? 'submit' : 'none';
  4493. }
  4494. if ( opts.blurOnBackground !== undefined ) {
  4495. opts.onBackground = opts.blurOnBackground ? 'blur' : 'none';
  4496. }
  4497. this.s.editOpts = opts;
  4498. // When submitting by Ajax we don't want to close a form that has been
  4499. // opened during the ajax request, so we keep a count of the form opening
  4500. this.s.editCount = inlineCount;
  4501. if ( typeof opts.title === 'string' || typeof opts.title === 'function' ) {
  4502. this.title( opts.title );
  4503. opts.title = true;
  4504. }
  4505. if ( typeof opts.message === 'string' || typeof opts.message === 'function' ) {
  4506. this.message( opts.message );
  4507. opts.message = true;
  4508. }
  4509. if ( typeof opts.buttons !== 'boolean' ) {
  4510. this.buttons( opts.buttons );
  4511. opts.buttons = true;
  4512. }
  4513. // Prevent submit by a host `<form>`
  4514. $(document).on( 'keydown'+namespace, function ( e ) {
  4515. if ( e.keyCode === 13 && that.s.displayed ) { // return
  4516. var el = $(document.activeElement);
  4517. if ( el ) {
  4518. var field = that._fieldFromNode( el );
  4519. if ( field && typeof field.canReturnSubmit === 'function' && field.canReturnSubmit( el ) ) {
  4520. e.preventDefault();
  4521. }
  4522. }
  4523. }
  4524. } );
  4525. $(document).on( 'keyup'+namespace, function ( e ) {
  4526. var el = $(document.activeElement);
  4527. if ( e.keyCode === 13 && that.s.displayed ) { // return
  4528. var field = that._fieldFromNode( el );
  4529. // Allow the field plug-in to say if we can submit or not
  4530. if ( field && typeof field.canReturnSubmit === 'function' && field.canReturnSubmit( el ) ) {
  4531. if ( opts.onReturn === 'submit' ) {
  4532. e.preventDefault();
  4533. that.submit();
  4534. }
  4535. else if ( typeof opts.onReturn === 'function' ) {
  4536. e.preventDefault();
  4537. opts.onReturn( that, e );
  4538. }
  4539. }
  4540. }
  4541. else if ( e.keyCode === 27 ) { // esc
  4542. e.preventDefault();
  4543. if ( typeof opts.onEsc === 'function' ) {
  4544. opts.onEsc( that, e );
  4545. }
  4546. else if ( opts.onEsc === 'blur' ) {
  4547. that.blur();
  4548. }
  4549. else if ( opts.onEsc === 'close' ) {
  4550. that.close();
  4551. }
  4552. else if ( opts.onEsc === 'submit' ) {
  4553. that.submit();
  4554. }
  4555. }
  4556. else if ( el.parents('.DTE_Form_Buttons').length ) {
  4557. if ( e.keyCode === 37 ) { // left
  4558. el.prev( 'button' ).focus();
  4559. }
  4560. else if ( e.keyCode === 39 ) { // right
  4561. el.next( 'button' ).focus();
  4562. }
  4563. }
  4564. } );
  4565. this.s.closeIcb = function () {
  4566. $(document).off( 'keydown'+namespace );
  4567. $(document).off( 'keyup'+namespace );
  4568. };
  4569. return namespace;
  4570. };
  4571. /**
  4572. * Convert from the 1.5+ data interchange format to the 1.4- format if suitable.
  4573. *
  4574. * @param {string} direction 'send' or 'receive'
  4575. * @param {string} action CRUD action
  4576. * @param {object} data Data object to transform
  4577. * @private
  4578. */
  4579. Editor.prototype._legacyAjax = function ( direction, action, data )
  4580. {
  4581. if ( ! this.s.legacyAjax || ! data ) {
  4582. return;
  4583. }
  4584. if ( direction === 'send' ) {
  4585. if ( action === 'create' || action === 'edit' ) {
  4586. var id;
  4587. $.each( data.data, function ( rowId, values ) {
  4588. if ( id !== undefined ) {
  4589. throw 'Editor: Multi-row editing is not supported by the legacy Ajax data format';
  4590. }
  4591. id = rowId;
  4592. } );
  4593. data.data = data.data[ id ];
  4594. if ( action === 'edit' ) {
  4595. data.id = id;
  4596. }
  4597. }
  4598. else {
  4599. data.id = $.map( data.data, function ( values, id ) {
  4600. return id;
  4601. } );
  4602. delete data.data;
  4603. }
  4604. }
  4605. else {
  4606. if ( ! data.data && data.row ) {
  4607. // 1.4 libraries retuned data in the `row` property
  4608. data.data = [ data.row ];
  4609. }
  4610. else if ( ! data.data ) {
  4611. // 1.4- allowed data not to be returned - 1.5 requires it
  4612. data.data = [];
  4613. }
  4614. }
  4615. };
  4616. /**
  4617. * Update the field options from a JSON data source
  4618. *
  4619. * @param {object} json JSON object from the server
  4620. * @private
  4621. */
  4622. Editor.prototype._optionsUpdate = function ( json )
  4623. {
  4624. var that = this;
  4625. if ( json.options ) {
  4626. $.each( this.s.fields, function (name, field) {
  4627. if ( json.options[ name ] !== undefined ) {
  4628. var fieldInst = that.field( name );
  4629. if ( fieldInst && fieldInst.update ) {
  4630. fieldInst.update( json.options[ name ] );
  4631. }
  4632. }
  4633. } );
  4634. }
  4635. };
  4636. /**
  4637. * Show a message in the form. This can be used for error messages or dynamic
  4638. * messages (information display) as the structure for each is basically the
  4639. * same. This method will take into account if the form is visible or not - if
  4640. * so then the message is shown with an effect for the end user, otherwise
  4641. * it is just set immediately.
  4642. *
  4643. * @param {element} el The field display node to use
  4644. * @param {string|function} msg The message to show
  4645. * @private
  4646. */
  4647. Editor.prototype._message = function ( el, msg )
  4648. {
  4649. // Allow for jQuery slim
  4650. var canAnimate = $.fn.animate ? true : false;
  4651. if ( typeof msg === 'function' ) {
  4652. msg = msg( this, new DataTable.Api(this.s.table) );
  4653. }
  4654. el = $(el);
  4655. if ( canAnimate ) {
  4656. el.stop();
  4657. }
  4658. if ( ! msg ) {
  4659. if ( this.s.displayed && canAnimate ) {
  4660. // Clear the message with visual effect since the form is visible
  4661. el
  4662. .fadeOut( function () {
  4663. el.html( '' );
  4664. } );
  4665. }
  4666. else {
  4667. // Clear the message without visual effect
  4668. el
  4669. .html( '' )
  4670. .css('display', 'none');
  4671. }
  4672. }
  4673. else {
  4674. if ( this.s.displayed && canAnimate ) {
  4675. // Show the message with visual effect
  4676. el
  4677. .html( msg )
  4678. .fadeIn();
  4679. }
  4680. else {
  4681. // Show the message without visual effect
  4682. el
  4683. .html( msg )
  4684. .css('display', 'block');
  4685. }
  4686. }
  4687. };
  4688. /**
  4689. * Update the multi-value information display to not show redundant information
  4690. *
  4691. * @private
  4692. */
  4693. Editor.prototype._multiInfo = function ()
  4694. {
  4695. var fields = this.s.fields;
  4696. var include = this.s.includeFields;
  4697. var show = true;
  4698. var state;
  4699. if ( ! include ) {
  4700. return;
  4701. }
  4702. for ( var i=0, ien=include.length ; i<ien ; i++ ) {
  4703. var field = fields[ include[i] ];
  4704. var multiEditable = field.multiEditable();
  4705. if ( field.isMultiValue() && multiEditable && show ) {
  4706. // Multi-row editable. Only show first message
  4707. state = true;
  4708. show = false;
  4709. }
  4710. else if ( field.isMultiValue() && ! multiEditable ) {
  4711. // Not multi-row editable. Always show message
  4712. state = true;
  4713. }
  4714. else {
  4715. state = false;
  4716. }
  4717. fields[ include[i] ].multiInfoShown( state );
  4718. }
  4719. };
  4720. /**
  4721. * Common display editing form method called by all editing methods after the
  4722. * form has been configured and displayed. This is to ensure all fire the same
  4723. * events.
  4724. *
  4725. * @param {string} type Editing type
  4726. * @param {boolean} immediate indicate if the open is immediate (in which case
  4727. * `opened` is also triggered).
  4728. * @return {boolean} `true`
  4729. * @private
  4730. */
  4731. Editor.prototype._postopen = function ( type, immediate )
  4732. {
  4733. var that = this;
  4734. var focusCapture = this.s.displayController.captureFocus;
  4735. if ( focusCapture === undefined ) {
  4736. focusCapture = true;
  4737. }
  4738. $(this.dom.form)
  4739. .off( 'submit.editor-internal' )
  4740. .on( 'submit.editor-internal', function (e) {
  4741. e.preventDefault();
  4742. } );
  4743. // Focus capture - when the Editor form is shown we capture the browser's
  4744. // focus action. Without doing this is would result in the user being able
  4745. // to control items under the Editor display - triggering actions that
  4746. // shouldn't be possible while the editing is shown.
  4747. if ( focusCapture && (type === 'main' || type === 'bubble') ) {
  4748. $('body').on( 'focus.editor-focus', function () {
  4749. if ( $(document.activeElement).parents('.DTE').length === 0 &&
  4750. $(document.activeElement).parents('.DTED').length === 0
  4751. ) {
  4752. if ( that.s.setFocus ) {
  4753. that.s.setFocus.focus();
  4754. }
  4755. }
  4756. } );
  4757. }
  4758. this._multiInfo();
  4759. this._event( 'open', [type, this.s.action] );
  4760. if ( immediate ) {
  4761. this._event( 'opened', [type, this.s.action] );
  4762. }
  4763. return true;
  4764. };
  4765. /**
  4766. * Common display editing form method called by all editing methods before the
  4767. * form has been configured and displayed. This is to ensure all fire the same
  4768. * events.
  4769. *
  4770. * @param {string} Editing type
  4771. * @return {boolean} `false` if the open is cancelled by the preOpen event,
  4772. * otherwise `true`
  4773. * @private
  4774. */
  4775. Editor.prototype._preopen = function ( type )
  4776. {
  4777. // Allow preOpen event to cancel the opening of the display
  4778. if ( this._event( 'preOpen', [type, this.s.action] ) === false ) {
  4779. // Tidy- this would normally be done on close, but we never get that far
  4780. this._clearDynamicInfo();
  4781. this._event( 'cancelOpen', [type, this.s.action] );
  4782. // inline and bubble methods cannot be opened using `open()`, they
  4783. // have to be called again, so we need to clean up the event
  4784. // listener added by _formOptions
  4785. if ( (this.s.mode === 'inline' || this.s.mode === 'bubble') && this.s.closeIcb ) {
  4786. this.s.closeIcb();
  4787. }
  4788. this.s.closeIcb = null;
  4789. return false;
  4790. }
  4791. this.s.displayed = type;
  4792. return true;
  4793. };
  4794. /**
  4795. * Set the form into processing mode or take it out of processing mode. In
  4796. * processing mode a processing indicator is shown and user interaction with the
  4797. * form buttons is blocked
  4798. *
  4799. * @param {boolean} processing true if to go into processing mode and false if
  4800. * to come out of processing mode
  4801. * @private
  4802. */
  4803. Editor.prototype._processing = function ( processing )
  4804. {
  4805. var procClass = this.classes.processing.active;
  4806. $(['div.DTE', this.dom.wrapper]).toggleClass( procClass, processing );
  4807. this.s.processing = processing;
  4808. this._event( 'processing', [processing] );
  4809. };
  4810. /**
  4811. * Submit a form to the server for processing. This is the private method that is used
  4812. * by the 'submit' API method, which should always be called in preference to calling
  4813. * this method directly.
  4814. *
  4815. * @param {function} [successCallback] Callback function that is executed once the
  4816. * form has been successfully submitted to the server and no errors occurred.
  4817. * @param {function} [errorCallback] Callback function that is executed if the
  4818. * server reports an error due to the submission (this includes a JSON formatting
  4819. * error should the error return invalid JSON).
  4820. * @param {function} [formatdata] Callback function that is passed in the data
  4821. * that will be submitted to the server, allowing pre-formatting of the data,
  4822. * removal of data or adding of extra fields.
  4823. * @param {boolean} [hide=true] When the form is successfully submitted, by default
  4824. * the form display will be hidden - this option allows that to be overridden.
  4825. * @private
  4826. */
  4827. Editor.prototype._submit = function ( successCallback, errorCallback, formatdata, hide )
  4828. {
  4829. var that = this;
  4830. var i, iLen, eventRet, errorNodes;
  4831. var changed = false, allData = {}, changedData = {};
  4832. var setBuilder = DataTable.ext.oApi._fnSetObjectDataFn;
  4833. var dataSource = this.s.dataSource;
  4834. var fields = this.s.fields;
  4835. var editCount = this.s.editCount;
  4836. var modifier = this.s.modifier;
  4837. var editFields = this.s.editFields;
  4838. var editData = this.s.editData;
  4839. var opts = this.s.editOpts;
  4840. var changedSubmit = opts.submit;
  4841. var submitParamsLocal;
  4842. // After initSubmit to allow `mode()` to be used as a setter
  4843. var action = this.s.action;
  4844. var submitParams = {
  4845. "data": {}
  4846. };
  4847. submitParams[this.s.actionName] = action;
  4848. // For backwards compatibility
  4849. if ( this.s.dbTable ) {
  4850. submitParams.table = this.s.dbTable;
  4851. }
  4852. // Gather the data that is to be submitted
  4853. if ( action === "create" || action === "edit" ) {
  4854. $.each( editFields, function ( idSrc, edit ) {
  4855. var allRowData = {};
  4856. var changedRowData = {};
  4857. $.each( fields, function (name, field) {
  4858. if ( edit.fields[ name ] && field.submittable() ) {
  4859. var multiGet = field.multiGet();
  4860. var builder = setBuilder( name );
  4861. // If it wasn't an edit field, we still need to get the original
  4862. // data, so we can submit it if `all` or `allIfChanged`
  4863. if ( multiGet[ idSrc ] === undefined ) {
  4864. var originalVal = field.valFromData( edit.data );
  4865. builder( allRowData, originalVal );
  4866. return;
  4867. }
  4868. var value = multiGet[ idSrc ];
  4869. var manyBuilder = $.isArray( value ) && name.indexOf('[]') !== -1 ?
  4870. setBuilder( name.replace(/\[.*$/,'')+'-many-count' ) :
  4871. null;
  4872. builder( allRowData, value );
  4873. // We need to tell the server-side if an array submission
  4874. // actually has no elements so it knows if the array was
  4875. // being submitted or not (since otherwise it doesn't know
  4876. // if the array was empty, or just not being submitted)
  4877. if ( manyBuilder ) {
  4878. manyBuilder( allRowData, value.length );
  4879. }
  4880. // Build a changed object for if that is the selected data
  4881. // type
  4882. if ( action === 'edit' && (!editData[ name ] || ! field.compare( value, editData[ name ][ idSrc ]) ) ) {
  4883. builder( changedRowData, value );
  4884. changed = true;
  4885. if ( manyBuilder ) {
  4886. manyBuilder( changedRowData, value.length );
  4887. }
  4888. }
  4889. }
  4890. } );
  4891. if ( ! $.isEmptyObject( allRowData ) ) {
  4892. allData[ idSrc ] = allRowData;
  4893. }
  4894. if ( ! $.isEmptyObject( changedRowData ) ) {
  4895. changedData[ idSrc ] = changedRowData;
  4896. }
  4897. } );
  4898. // Decide what data to submit to the server for edit (create is all, always)
  4899. if ( action === 'create' || changedSubmit === 'all' || (changedSubmit === 'allIfChanged' && changed) ) {
  4900. submitParams.data = allData;
  4901. }
  4902. else if ( changedSubmit === 'changed' && changed ) {
  4903. submitParams.data = changedData;
  4904. }
  4905. else {
  4906. // Nothing to submit
  4907. this.s.action = null;
  4908. if ( opts.onComplete === 'close' && (hide === undefined || hide) ) {
  4909. this._close( false );
  4910. }
  4911. else if ( typeof opts.onComplete === 'function' ) {
  4912. opts.onComplete( this );
  4913. }
  4914. if ( successCallback ) {
  4915. successCallback.call( this );
  4916. }
  4917. this._processing( false );
  4918. this._event( 'submitComplete' );
  4919. return;
  4920. }
  4921. }
  4922. else if ( action === "remove" ) {
  4923. $.each( editFields, function ( idSrc, edit ) {
  4924. submitParams.data[ idSrc ] = edit.data;
  4925. } );
  4926. }
  4927. this._legacyAjax( 'send', action, submitParams );
  4928. // Local copy of the submit parameters, needed for the data lib prep since
  4929. // the preSubmit can modify the format and we need to know what the format is
  4930. submitParamsLocal = $.extend( true, {}, submitParams );
  4931. // Allow the data to be submitted to the server to be preprocessed by callback
  4932. // and event functions
  4933. if ( formatdata ) {
  4934. formatdata( submitParams );
  4935. }
  4936. if ( this._event( 'preSubmit', [submitParams, action] ) === false ) {
  4937. this._processing( false );
  4938. return;
  4939. }
  4940. // Submit to the server (or whatever method is defined in the settings)
  4941. var submitWire = this.s.ajax || this.s.ajaxUrl ?
  4942. this._ajax :
  4943. this._submitTable;
  4944. submitWire.call(
  4945. this,
  4946. submitParams,
  4947. function (json, notGood, xhr) {
  4948. that._submitSuccess(
  4949. json, notGood, submitParams, submitParamsLocal, that.s.action,
  4950. editCount, hide, successCallback, errorCallback, xhr
  4951. );
  4952. },
  4953. function (xhr, err, thrown) {
  4954. that._submitError( xhr, err, thrown, errorCallback, submitParams, that.s.action );
  4955. },
  4956. submitParams
  4957. );
  4958. };
  4959. /**
  4960. * Save submitted data without an Ajax request. This will write to a local
  4961. * table only - not saving it permanently, but rather using the DataTable itself
  4962. * as a data store.
  4963. *
  4964. * @param {object} data Data to submit
  4965. * @param {function} success Success callback
  4966. * @param {function} error Error callback
  4967. * @param {object} submitParams Submitted data
  4968. * @private
  4969. */
  4970. Editor.prototype._submitTable = function ( data, success, error, submitParams )
  4971. {
  4972. var that = this;
  4973. var action = data.action;
  4974. var out = { data: [] };
  4975. var idGet = DataTable.ext.oApi._fnGetObjectDataFn( this.s.idSrc );
  4976. var idSet = DataTable.ext.oApi._fnSetObjectDataFn( this.s.idSrc );
  4977. // Nothing required for remove - create and edit get a copy of the data
  4978. if ( action !== 'remove' ) {
  4979. var originalData = this.s.mode === 'main' ?
  4980. this._dataSource( 'fields', this.modifier() ) :
  4981. this._dataSource( 'individual', this.modifier() );
  4982. $.each( data.data, function ( key, vals ) {
  4983. var toSave;
  4984. var extender = $.fn.dataTableExt.oApi._fnExtend;
  4985. // Get the original row's data, so we can modify it with new values.
  4986. // This allows Editor to not need to submit all fields
  4987. if ( action === 'edit' ) {
  4988. var rowData = originalData[ key ].data;
  4989. toSave = extender( {}, rowData, true );
  4990. toSave = extender( toSave, vals, true );
  4991. }
  4992. else {
  4993. toSave = extender( {}, vals, true );
  4994. }
  4995. // If create and there isn't an id for the new row, create
  4996. // one. An id could be creased by `preSubmit`
  4997. var overrideId = idGet( toSave );
  4998. if ( action === 'create' && overrideId === undefined ) {
  4999. idSet( toSave, +new Date() +''+ key );
  5000. }
  5001. else {
  5002. idSet( toSave, overrideId );
  5003. }
  5004. out.data.push( toSave );
  5005. } );
  5006. }
  5007. success( out );
  5008. };
  5009. /**
  5010. * Submit success callback function
  5011. * @param {object} json Payload
  5012. * @param {bool} notGood True if the returned status code was
  5013. * >=400 (i.e. processing failed). This is called `notGood` rather than
  5014. * `success` since the request was successfully processed, just not written to
  5015. * the db. It is also inverted from "good" to make it optional when overriding
  5016. * the `ajax` function.
  5017. * @param {object} submitParams Submitted data
  5018. * @param {object} submitParamsLocal Unmodified copy of submitted data
  5019. * (before it could be modified by the user)
  5020. * @param {string} action CRUD action being taken
  5021. * @param {int} editCount Protection against async errors
  5022. * @param {bool} hide Hide the form flag
  5023. * @param {function} successCallback Success callback
  5024. * @param {function} errorCallback Error callback
  5025. * @private
  5026. */
  5027. Editor.prototype._submitSuccess = function ( json, notGood, submitParams, submitParamsLocal, action, editCount, hide, successCallback, errorCallback, xhr )
  5028. {
  5029. var that = this;
  5030. var setData;
  5031. var fields = this.s.fields;
  5032. var opts = this.s.editOpts;
  5033. var modifier = this.s.modifier;
  5034. this._legacyAjax( 'receive', action, json );
  5035. this._event( 'postSubmit', [json, submitParams, action, xhr] );
  5036. if ( !json.error ) {
  5037. json.error = "";
  5038. }
  5039. if ( !json.fieldErrors ) {
  5040. json.fieldErrors = [];
  5041. }
  5042. if ( notGood || json.error || json.fieldErrors.length ) {
  5043. // Global form error
  5044. var globalError = [];
  5045. if ( json.error ) {
  5046. globalError.push( json.error );
  5047. }
  5048. // Field specific errors
  5049. $.each( json.fieldErrors, function (i, err) {
  5050. var field = fields[ err.name ];
  5051. if ( ! field ) {
  5052. throw new Error('Unknown field: '+ err.name);
  5053. }
  5054. else if ( field.displayed() ) {
  5055. field.error( err.status || "Error" );
  5056. if ( i === 0 ) {
  5057. if ( opts.onFieldError === 'focus' ) {
  5058. // Scroll the display to the first error and focus
  5059. that._animate(
  5060. $(that.dom.bodyContent, that.s.wrapper),
  5061. { scrollTop: $(field.node()).position().top },
  5062. 500
  5063. );
  5064. field.focus();
  5065. }
  5066. else if ( typeof opts.onFieldError === 'function' ) {
  5067. opts.onFieldError( that, err );
  5068. }
  5069. }
  5070. }
  5071. else {
  5072. // If the field isn't visible, we need to make it display as a global error
  5073. // This _shouldn't_ happen - it means there is invalid data if it does
  5074. globalError.push( field.name()+': '+ (err.status || "Error") );
  5075. }
  5076. } );
  5077. this.error( globalError.join('<br>') );
  5078. this._event( 'submitUnsuccessful', [json] );
  5079. if ( errorCallback ) {
  5080. errorCallback.call( that, json );
  5081. }
  5082. }
  5083. else {
  5084. // Create a data store that the data source can use, which is
  5085. // unique to this action
  5086. var store = {};
  5087. if ( json.data && (action === "create" || action === "edit") ) {
  5088. this._dataSource( 'prep', action, modifier, submitParamsLocal, json, store );
  5089. for ( var i=0 ; i<json.data.length ; i++ ) {
  5090. setData = json.data[ i ];
  5091. var id = this._dataSource( 'id', setData );
  5092. this._event( 'setData', [json, setData, action] ); // legacy
  5093. if ( action === "create" ) {
  5094. // New row was created to add it to the DT
  5095. this._event( 'preCreate', [json, setData, id] );
  5096. this._dataSource( 'create', fields, setData, store );
  5097. this._event( ['create', 'postCreate'], [json, setData, id] );
  5098. }
  5099. else if ( action === "edit" ) {
  5100. // Row was updated, so tell the DT
  5101. this._event( 'preEdit', [json, setData, id] );
  5102. this._dataSource( 'edit', modifier, fields, setData, store );
  5103. this._event( ['edit', 'postEdit'], [json, setData, id] );
  5104. }
  5105. }
  5106. this._dataSource( 'commit', action, modifier, json.data, store );
  5107. }
  5108. else if ( action === "remove" ) {
  5109. this._dataSource( 'prep', action, modifier, submitParamsLocal, json, store );
  5110. // Remove the rows given and then redraw the table
  5111. this._event( 'preRemove', [json, this.ids()] );
  5112. this._dataSource( 'remove', modifier, fields, store );
  5113. this._event( ['remove', 'postRemove'], [json, this.ids()] );
  5114. this._dataSource( 'commit', action, modifier, json.data, store );
  5115. }
  5116. // Submission complete
  5117. if ( editCount === this.s.editCount ) {
  5118. var action = this.s.action;
  5119. this.s.action = null; // Must do before close, in case close starts a new edit
  5120. if ( opts.onComplete === 'close' && (hide === undefined || hide) ) {
  5121. // If no data returned, then treat as not complete
  5122. this._close( json.data ? true : false, action );
  5123. }
  5124. else if ( typeof opts.onComplete === 'function' ) {
  5125. opts.onComplete( this );
  5126. }
  5127. }
  5128. // All done - fire off the callbacks and events
  5129. if ( successCallback ) {
  5130. successCallback.call( that, json );
  5131. }
  5132. this._event( 'submitSuccess', [json, setData, action] );
  5133. }
  5134. this._processing( false );
  5135. this._event( 'submitComplete', [json, setData, action] );
  5136. };
  5137. /**
  5138. * Submit error callback function
  5139. * @private
  5140. */
  5141. Editor.prototype._submitError = function ( xhr, err, thrown, errorCallback, submitParams, action )
  5142. {
  5143. this._event( 'postSubmit', [null, submitParams, action, xhr] );
  5144. this.error( this.i18n.error.system );
  5145. this._processing( false );
  5146. if ( errorCallback ) {
  5147. errorCallback.call( this, xhr, err, thrown );
  5148. }
  5149. this._event( ['submitError', 'submitComplete'], [xhr, err, thrown, submitParams] );
  5150. };
  5151. /**
  5152. * Check to see if the form needs to be tidied before a new action can be performed.
  5153. * This includes if the from is currently processing an old action and if it
  5154. * is inline editing.
  5155. *
  5156. * @param {function} fn Callback function
  5157. * @returns {boolean} `true` if was in inline mode, `false` otherwise
  5158. * @private
  5159. */
  5160. Editor.prototype._tidy = function ( fn )
  5161. {
  5162. var that = this;
  5163. var dt = this.s.table ?
  5164. new $.fn.dataTable.Api( this.s.table ) :
  5165. null;
  5166. var ssp = false;
  5167. if ( dt ) {
  5168. ssp = dt.settings()[0].oFeatures.bServerSide;
  5169. }
  5170. if ( this.s.processing ) {
  5171. // If currently processing, wait until the action is complete
  5172. this.one( 'submitComplete', function () {
  5173. // If server-side processing is being used in DataTables, first
  5174. // check that we are still processing (might not be if nothing was
  5175. // submitted) and then wait for the draw to finished
  5176. if ( ssp ) {
  5177. dt.one( 'draw', fn );
  5178. }
  5179. else {
  5180. setTimeout( function () {
  5181. fn();
  5182. }, 10 );
  5183. }
  5184. } );
  5185. return true;
  5186. }
  5187. else if ( this.display() === 'inline' || this.display() === 'bubble' ) {
  5188. // If there is an inline edit box, it needs to be tidied
  5189. this
  5190. .one( 'close', function () {
  5191. // On close if processing then we need to wait for the submit to
  5192. // complete before running the callback as onBlur was set to
  5193. // submit
  5194. if ( ! that.s.processing ) {
  5195. // IE needs a small timeout, otherwise it may not focus on a
  5196. // field if one already has focus
  5197. setTimeout( function () {
  5198. // Check that Editor wasn't destroyed
  5199. if ( that.s ) {
  5200. fn();
  5201. }
  5202. }, 10 );
  5203. }
  5204. else {
  5205. // Need to wait for the submit to finish
  5206. that.one( 'submitComplete', function ( e, json ) {
  5207. // If SSP then need to wait for the draw
  5208. if ( ssp && json ) {
  5209. dt.one( 'draw', fn );
  5210. }
  5211. else {
  5212. setTimeout( function () {
  5213. if ( that.s ) {
  5214. fn();
  5215. }
  5216. }, 10 );
  5217. }
  5218. } );
  5219. }
  5220. } )
  5221. .blur();
  5222. return true;
  5223. }
  5224. return false;
  5225. };
  5226. /**
  5227. * Same as $.inArray but with weak type checking
  5228. * @param {any} name Value to look for in the array
  5229. * @param {array} arr Array to scan through
  5230. * @returns {number} -1 if not found, index otherwise
  5231. */
  5232. Editor.prototype._weakInArray = function ( name, arr )
  5233. {
  5234. for ( var i=0, ien=arr.length ; i<ien ; i++ ) {
  5235. if ( name == arr[i] ) {
  5236. return i;
  5237. }
  5238. }
  5239. return -1;
  5240. };
  5241. /*
  5242. * Defaults
  5243. */
  5244. // Dev node - although this file is held in the models directory (because it
  5245. // really is a model, it is assigned to Editor.defaults for easy
  5246. // and sensible access to set the defaults for Editor.
  5247. /**
  5248. * Initialisation options that can be given to Editor at initialisation time.
  5249. * @namespace
  5250. */
  5251. Editor.defaults = {
  5252. /**
  5253. * jQuery selector that can be used to identify the table you wish to apply
  5254. * this editor instance to.
  5255. *
  5256. * In previous versions of Editor (1.2 and earlier), this parameter was
  5257. * called `table`. The name has been altered in 1.3+ to simplify the
  5258. * initialisation. This is a backwards compatible change - if you pass in
  5259. * a `table` option it will be used.
  5260. * @type string
  5261. * @default <i>Empty string</i>
  5262. *
  5263. * @example
  5264. * $(document).ready(function() {
  5265. * var editor = new $.fn.Editor( {
  5266. * "ajax": "php/index.php",
  5267. * "table": "#example"
  5268. * } );
  5269. * } );
  5270. */
  5271. "table": null,
  5272. /**
  5273. * The URL, or collection of URLs when using a REST interface, which will accept
  5274. * the data for the create, edit and remove functions. The target script / program
  5275. * must accept data in the format defined by Editor and return the expected JSON as
  5276. * required by Editor. When given as an object, the `create`, `edit` and `remove`
  5277. * properties should be defined, each being the URL to send the data to for that
  5278. * action. When used as an object, the string `_id_` will be replaced for the edit
  5279. * and remove actions, allowing a URL to be dynamically created for those actions.
  5280. * @type string|object
  5281. * @default <i>Empty string</i>
  5282. * @deprecated This option has been deprecated in favour of the `ajax` option.
  5283. * It can still be used, but it is recommended that you use the `ajax` option
  5284. * which provides all of the abilities of this old option and more.
  5285. */
  5286. "ajaxUrl": null,
  5287. /**
  5288. * Fields to initialise the form with - see {@link Editor.models.field} for
  5289. * a full list of the options available to each field. Note that if fields are not
  5290. * added to the form at initialisation time using this option, they can be added using
  5291. * the {@link Editor#add} API method.
  5292. * @type array
  5293. * @default []
  5294. *
  5295. * @example
  5296. * $(document).ready(function() {
  5297. * var editor = new $.fn.Editor( {
  5298. * "ajax": "php/index.php",
  5299. * "table": "#example",
  5300. * "fields": [ {
  5301. * "label": "User name:",
  5302. * "name": "username"
  5303. * }
  5304. * // More fields would typically be added here!
  5305. * } ]
  5306. * } );
  5307. * } );
  5308. */
  5309. "fields": [],
  5310. /**
  5311. * The display controller for the form. The form itself is just a collection of
  5312. * DOM elements which require a display container. This display controller allows
  5313. * the visual appearance of the form to be significantly altered without major
  5314. * alterations to the Editor code. There are two display controllers built into
  5315. * Editor *lightbox* and *envelope*. The value of this property will
  5316. * be used to access the display controller defined in {@link Editor.display}
  5317. * for the given name. Additional display controllers can be added by adding objects
  5318. * to that object, through extending the displayController model:
  5319. * {@link Editor.models.displayController}.
  5320. * @type string
  5321. * @default lightbox
  5322. *
  5323. * @example
  5324. * $(document).ready(function() {
  5325. * var editor = new $.fn.Editor( {
  5326. * "ajax": "php/index.php",
  5327. * "table": "#example",
  5328. * "display": 'envelope'
  5329. * } );
  5330. * } );
  5331. */
  5332. "display": 'lightbox',
  5333. /**
  5334. * Control how the Ajax call to update data on the server.
  5335. *
  5336. * This option matches the `dt-init ajax` option in that is can be provided
  5337. * in one of three different ways:
  5338. *
  5339. * * string - As a string, the value given is used as the url to target
  5340. * the Ajax request to, using the default Editor Ajax options. Note that
  5341. * for backwards compatibility you can use the form "METHOD URL" - for
  5342. * example: `"PUT api/users"`, although it is recommended you use the
  5343. * object form described below.
  5344. * * object - As an object, the `ajax` property has two forms:
  5345. * * Used to extend and override the default Ajax options that Editor
  5346. * uses. This can be very useful for adding extra data for example, or
  5347. * changing the HTTP request type.
  5348. * * With `create`, `edit` and `remove` properties, Editor will use the
  5349. * option for the action that it is taking, which can be useful for
  5350. * REST style interfaces. The value of each property can be a string,
  5351. * object or function, using exactly the same options as the main `ajax`
  5352. * option. All three options must be defined if this form is to be used.
  5353. * * function - As a function this gives complete control over the method
  5354. * used to update the server (if indeed a server is being used!). For
  5355. * example, you could use a different data store such as localStorage,
  5356. * Firebase or route the data through a web-socket.
  5357. *
  5358. * @example
  5359. * // As a string - all actions are submitted to this URI as POST requests
  5360. * $(document).ready(function() {
  5361. * var editor = new $.fn.Editor( {
  5362. * "ajax": 'php/index.php',
  5363. * "table": "#example"
  5364. * } );
  5365. * } );
  5366. *
  5367. * @example
  5368. * // As an object - using GET rather than POST
  5369. * $(document).ready(function() {
  5370. * var editor = new $.fn.Editor( {
  5371. * "ajax": {
  5372. * "type": 'GET',
  5373. * "url": 'php/index.php
  5374. * },
  5375. * "table": "#example"
  5376. * } );
  5377. * } );
  5378. *
  5379. * @example
  5380. * // As an object - each action is submitted to a different URI as POST requests
  5381. * $(document).ready(function() {
  5382. * var editor = new $.fn.Editor( {
  5383. * "ajax": {
  5384. * "create": "/rest/user/create",
  5385. * "edit": "/rest/user/_id_/edit",
  5386. * "remove": "/rest/user/_id_/delete"
  5387. * },
  5388. * "table": "#example"
  5389. * } );
  5390. * } );
  5391. *
  5392. * @example
  5393. * // As an object - with different HTTP methods for each action
  5394. * $(document).ready(function() {
  5395. * var editor = new $.fn.Editor( {
  5396. * "ajax": {
  5397. * "create": {
  5398. * type: 'POST',
  5399. * url: '/rest/user/create'
  5400. * },
  5401. * "edit": {
  5402. * type: 'PUT',
  5403. * url: '/rest/user/edit/_id_'
  5404. * },
  5405. * "remove": {
  5406. * type: 'DELETE',
  5407. * url: '/rest/user/delete'
  5408. * }
  5409. * },
  5410. * "table": "#example"
  5411. * } );
  5412. * } );
  5413. *
  5414. * // As a function - Making a custom `$.ajax` call
  5415. * $(document).ready(function() {
  5416. * var editor = new $.fn.Editor( {
  5417. * "ajax": "php/index.php",
  5418. * "table": "#example",
  5419. * "ajax": function ( method, url, data, successCallback, errorCallback ) {
  5420. * $.ajax( {
  5421. * "type": method,
  5422. * "url": url,
  5423. * "data": data,
  5424. * "dataType": "json",
  5425. * "success": function (json) {
  5426. * successCallback( json );
  5427. * },
  5428. * "error": function (xhr, error, thrown) {
  5429. * errorCallback( xhr, error, thrown );
  5430. * }
  5431. * } );
  5432. * }
  5433. * } );
  5434. * } );
  5435. */
  5436. "ajax": null,
  5437. /**
  5438. * JSON property from which to read / write the row's ID property (i.e. its
  5439. * unique column index that identifies the row to the database). By default
  5440. * Editor will use the `DT_RowId` property from the data source object
  5441. * (DataTable's magic property to set the DOM id for the row).
  5442. *
  5443. * If you want to read a parameter from the data source object instead of
  5444. * using `DT_RowId`, set this option to the property name to use.
  5445. *
  5446. * Like other data source options the `srcId` option can be given in dotted
  5447. * object notation to read nested objects.
  5448. * @type null|string
  5449. * @default DT_RowId
  5450. *
  5451. * @example
  5452. * // Using a data source such as:
  5453. * // { "id":12, "browser":"Chrome", ... }
  5454. * $(document).ready(function() {
  5455. * var editor = new $.fn.Editor( {
  5456. * "ajax": "php/index.php",
  5457. * "table": "#example",
  5458. * "idSrc": "id"
  5459. * } );
  5460. * } );
  5461. */
  5462. "idSrc": 'DT_RowId',
  5463. /**
  5464. * Events / callbacks - event handlers can be assigned as an individual function
  5465. * during initialisation using the parameters in this name space. The names, and
  5466. * the parameters passed to each callback match their event equivalent in the
  5467. * {@link Editor} object.
  5468. * @namespace
  5469. * @deprecated Since 1.3. Use the `on()` API method instead. Note that events
  5470. * passed in do still operate as they did in 1.2- but are no longer
  5471. * individually documented.
  5472. */
  5473. "events": {},
  5474. /**
  5475. * Internationalisation options for Editor. All client-side strings that the
  5476. * end user can see in the interface presented by Editor can be modified here.
  5477. *
  5478. * You may also wish to refer to the <a href="http://datatables.net/usage/i18n">
  5479. * DataTables internationalisation options</a> to provide a fully language
  5480. * customised table interface.
  5481. * @namespace
  5482. *
  5483. * @example
  5484. * // Set the 'create' button text. All other strings used are the
  5485. * // default values.
  5486. * var editor = new $.fn.Editor( {
  5487. * "ajax": "data/source",
  5488. * "table": "#example",
  5489. * "i18n": {
  5490. * "create": {
  5491. * "button": "New user"
  5492. * }
  5493. * }
  5494. * } );
  5495. *
  5496. * @example
  5497. * // Set the submit text for all three actions
  5498. * var editor = new $.fn.Editor( {
  5499. * "ajax": "data/source",
  5500. * "table": "#example",
  5501. * "i18n": {
  5502. * "create": {
  5503. * "submit": "Create new user"
  5504. * },
  5505. * "edit": {
  5506. * "submit": "Update user"
  5507. * },
  5508. * "remove": {
  5509. * "submit": "Remove user"
  5510. * }
  5511. * }
  5512. * } );
  5513. */
  5514. "i18n": {
  5515. /**
  5516. * Strings used when working with the Editor 'create' action (creating new
  5517. * records).
  5518. * @namespace
  5519. */
  5520. "create": {
  5521. /**
  5522. * TableTools button text
  5523. * @type string
  5524. * @default New
  5525. */
  5526. "button": "New",
  5527. /**
  5528. * Display container title (when showing the editor display)
  5529. * @type string
  5530. * @default Create new entry
  5531. */
  5532. "title": "Create new entry",
  5533. /**
  5534. * Submit button text
  5535. * @type string
  5536. * @default Create
  5537. */
  5538. "submit": "Create"
  5539. },
  5540. /**
  5541. * Strings used when working with the Editor 'edit' action (editing existing
  5542. * records).
  5543. * @namespace
  5544. */
  5545. "edit": {
  5546. /**
  5547. * TableTools button text
  5548. * @type string
  5549. * @default Edit
  5550. */
  5551. "button": "Edit",
  5552. /**
  5553. * Display container title (when showing the editor display)
  5554. * @type string
  5555. * @default Edit entry
  5556. */
  5557. "title": "Edit entry",
  5558. /**
  5559. * Submit button text
  5560. * @type string
  5561. * @default Update
  5562. */
  5563. "submit": "Update"
  5564. },
  5565. /**
  5566. * Strings used when working with the Editor 'delete' action (deleting
  5567. * existing records).
  5568. * @namespace
  5569. */
  5570. "remove": {
  5571. /**
  5572. * TableTools button text
  5573. * @type string
  5574. * @default Delete
  5575. */
  5576. "button": "Delete",
  5577. /**
  5578. * Display container title (when showing the editor display)
  5579. * @type string
  5580. * @default Delete
  5581. */
  5582. "title": "Delete",
  5583. /**
  5584. * Submit button text
  5585. * @type string
  5586. * @default Delete
  5587. */
  5588. "submit": "Delete",
  5589. /**
  5590. * Deletion confirmation message.
  5591. *
  5592. * As Editor has the ability to delete either a single or multiple rows
  5593. * at a time, this option can be given as either a string (which will be
  5594. * used regardless of how many records are selected) or as an object
  5595. * where the property "_" will be used (with %d substituted for the number
  5596. * of records to be deleted) as the delete message, unless there is a
  5597. * key with the number of records to be deleted. This allows Editor
  5598. * to consider the different pluralisation characteristics of different
  5599. * languages.
  5600. * @type object|string
  5601. * @default Are you sure you wish to delete %d rows?
  5602. *
  5603. * @example
  5604. * // String - no plural consideration
  5605. * var editor = new $.fn.Editor( {
  5606. * "ajax": "data/source",
  5607. * "table": "#example",
  5608. * "i18n": {
  5609. * "remove": {
  5610. * "confirm": "Are you sure you wish to delete %d record(s)?"
  5611. * }
  5612. * }
  5613. * } );
  5614. *
  5615. * @example
  5616. * // Basic 1 (singular) or _ (plural)
  5617. * var editor = new $.fn.Editor( {
  5618. * "ajax": "data/source",
  5619. * "table": "#example",
  5620. * "i18n": {
  5621. * "remove": {
  5622. * "confirm": {
  5623. * "_": "Confirm deletion of %d records.",
  5624. * "1": "Confirm deletion of record."
  5625. * }
  5626. * }
  5627. * } );
  5628. *
  5629. * @example
  5630. * // Singular, dual and plural
  5631. * var editor = new $.fn.Editor( {
  5632. * "ajax": "data/source",
  5633. * "table": "#example",
  5634. * "i18n": {
  5635. * "remove": {
  5636. * "confirm": {
  5637. * "_": "Confirm deletion of %d records.",
  5638. * "1": "Confirm deletion of record.",
  5639. * "2": "Confirm deletion of both record."
  5640. * }
  5641. * }
  5642. * } );
  5643. *
  5644. */
  5645. "confirm": {
  5646. "_": "Are you sure you wish to delete %d rows?",
  5647. "1": "Are you sure you wish to delete 1 row?"
  5648. }
  5649. },
  5650. /**
  5651. * Strings used for error conditions.
  5652. * @namespace
  5653. */
  5654. "error": {
  5655. /**
  5656. * Generic server error message
  5657. * @type string
  5658. * @default A system error has occurred (<a target=\"_blank\" href=\"//datatables.net/tn/12\">More information</a>)
  5659. */
  5660. "system": "A system error has occurred (<a target=\"_blank\" href=\"//datatables.net/tn/12\">More information</a>)."
  5661. },
  5662. /**
  5663. * Strings used for multi-value editing
  5664. * @namespace
  5665. */
  5666. multi: {
  5667. /**
  5668. * Shown in place of the field value when a field has multiple values
  5669. */
  5670. title: "Multiple values",
  5671. /**
  5672. * Shown below the multi title text, although only the first
  5673. * instance of this text is shown in the form to reduce redundancy
  5674. */
  5675. info: "The selected items contain different values for this input. To edit and set all items for this input to the same value, click or tap here, otherwise they will retain their individual values.",
  5676. /**
  5677. * Shown below the field input when group editing a value to allow
  5678. * the user to return to the original multiple values
  5679. */
  5680. restore: "Undo changes",
  5681. /**
  5682. * Disabled for multi-row editing
  5683. */
  5684. noMulti: "This input can be edited individually, but not part of a group."
  5685. },
  5686. datetime: {
  5687. previous: 'Previous',
  5688. next: 'Next',
  5689. months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
  5690. weekdays: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
  5691. amPm: [ 'am', 'pm' ],
  5692. hours: 'Hour',
  5693. minutes: 'Minute',
  5694. seconds: 'Second',
  5695. unknown: '-'
  5696. }
  5697. },
  5698. formOptions: {
  5699. bubble: $.extend( {}, Editor.models.formOptions, {
  5700. title: false,
  5701. message: false,
  5702. buttons: '_basic',
  5703. submit: 'changed'
  5704. } ),
  5705. inline: $.extend( {}, Editor.models.formOptions, {
  5706. buttons: false,
  5707. submit: 'changed'
  5708. } ),
  5709. main: $.extend( {}, Editor.models.formOptions )
  5710. },
  5711. /**
  5712. * Submit data to the server in the 1.4- data format (`true`) or in the 1.5+
  5713. * data format (`false` - default).
  5714. *
  5715. * @type Boolean
  5716. */
  5717. legacyAjax: false,
  5718. /**
  5719. * Parameter name to use to submit data to the server.
  5720. * @type string
  5721. */
  5722. actionName: 'action'
  5723. };
  5724. /*
  5725. * Extensions
  5726. */
  5727. (function(){
  5728. var __dataSources = Editor.dataSources = {};
  5729. /* - - - - - - - - - -
  5730. * DataTables editor interface
  5731. */
  5732. var __dtIsSsp = function ( dt, editor ) {
  5733. // If the draw type is `none`, then we still need to use the DT API to
  5734. // update the display with the new data
  5735. return dt.settings()[0].oFeatures.bServerSide &&
  5736. editor.s.editOpts.drawType !== 'none';
  5737. };
  5738. var __dtApi = function ( table ) {
  5739. return $(table).DataTable();
  5740. };
  5741. var __dtHighlight = function ( node ) {
  5742. // Highlight a row using CSS transitions. The timeouts need to match the
  5743. // transition duration from the CSS
  5744. node = $(node);
  5745. setTimeout( function () {
  5746. node.addClass( 'highlight' );
  5747. setTimeout( function () {
  5748. node
  5749. .addClass( 'noHighlight' )
  5750. .removeClass( 'highlight' );
  5751. setTimeout( function () {
  5752. node.removeClass( 'noHighlight' );
  5753. }, 550 );
  5754. }, 500 );
  5755. }, 20 );
  5756. };
  5757. var __dtRowSelector = function ( out, dt, identifier, fields, idFn )
  5758. {
  5759. dt.rows( identifier ).indexes().each( function ( idx ) {
  5760. var row = dt.row( idx );
  5761. var data = row.data();
  5762. var idSrc = idFn( data );
  5763. if ( idSrc === undefined ) {
  5764. Editor.error( 'Unable to find row identifier', 14 );
  5765. }
  5766. out[ idSrc ] = {
  5767. idSrc: idSrc,
  5768. data: data,
  5769. node: row.node(),
  5770. fields: fields,
  5771. type: 'row'
  5772. };
  5773. } );
  5774. };
  5775. var __dtFieldsFromIdx = function ( dt, fields, idx )
  5776. {
  5777. var field;
  5778. var col = dt.settings()[0].aoColumns[ idx ];
  5779. var dataSrc = col.editField !== undefined ?
  5780. col.editField :
  5781. col.mData;
  5782. var resolvedFields = {};
  5783. var run = function ( field, dataSrc ) {
  5784. if ( field.name() === dataSrc ) {
  5785. resolvedFields[ field.name() ] = field;
  5786. }
  5787. };
  5788. $.each( fields, function ( name, fieldInst ) {
  5789. if ( $.isArray( dataSrc ) ) {
  5790. for ( var i=0 ; i<dataSrc.length ; i++ ) {
  5791. run( fieldInst, dataSrc[i] );
  5792. }
  5793. }
  5794. else {
  5795. run( fieldInst, dataSrc );
  5796. }
  5797. } );
  5798. if ( $.isEmptyObject( resolvedFields ) ) {
  5799. Editor.error('Unable to automatically determine field from source. Please specify the field name.', 11);
  5800. }
  5801. return resolvedFields;
  5802. };
  5803. var __dtCellSelector = function ( out, dt, identifier, allFields, idFn, forceFields )
  5804. {
  5805. dt.cells( identifier ).indexes().each( function ( idx ) {
  5806. var cell = dt.cell( idx );
  5807. var row = dt.row( idx.row );
  5808. var data = row.data();
  5809. var idSrc = idFn( data );
  5810. var fields = forceFields || __dtFieldsFromIdx( dt, allFields, idx.column );
  5811. var isNode = (typeof identifier === 'object' && identifier.nodeName) || identifier instanceof $;
  5812. var prevDisplayFields, prevAttach;
  5813. // The row selector will create a new `out` object for the identifier, and the
  5814. // cell selector might be called multiple times for a row, so we need to save
  5815. // our specific items
  5816. if ( out[ idSrc ] ) {
  5817. prevAttach = out[ idSrc ].attach;
  5818. prevDisplayFields = out[ idSrc ].displayFields;
  5819. }
  5820. // Use the row selector to get the row information
  5821. __dtRowSelector(out, dt, idx.row, allFields, idFn);
  5822. // Need to check if `attach / displayFields is present before writing
  5823. out[ idSrc ].attach = prevAttach || [];
  5824. out[ idSrc ].attach.push( isNode ?
  5825. $(identifier).get(0) :
  5826. cell.fixedNode ? // If its under a fixed column, get the floating node
  5827. cell.fixedNode() :
  5828. cell.node()
  5829. );
  5830. out[ idSrc ].displayFields = prevDisplayFields || {};
  5831. $.extend( out[ idSrc ].displayFields, fields );
  5832. } );
  5833. };
  5834. var __dtColumnSelector = function ( out, dt, identifier, fields, idFn )
  5835. {
  5836. dt.cells( null, identifier ).indexes().each( function ( idx ) {
  5837. __dtCellSelector( out, dt, idx, fields, idFn );
  5838. } );
  5839. };
  5840. var __dtjqId = function ( id ) {
  5841. return typeof id === 'string' ?
  5842. '#'+id.replace( /(:|\.|\[|\]|,)/g, '\\$1' ) :
  5843. '#'+id;
  5844. };
  5845. __dataSources.dataTable = {
  5846. id: function ( data ) {
  5847. var idFn = DataTable.ext.oApi._fnGetObjectDataFn( this.s.idSrc );
  5848. return idFn( data );
  5849. },
  5850. individual: function ( identifier, fieldNames ) {
  5851. var idFn = DataTable.ext.oApi._fnGetObjectDataFn( this.s.idSrc );
  5852. var dt = __dtApi( this.s.table );
  5853. var fields = this.s.fields;
  5854. var out = {};
  5855. var forceFields;
  5856. var responsiveNode;
  5857. if ( fieldNames ) {
  5858. if ( ! $.isArray( fieldNames ) ) {
  5859. fieldNames = [ fieldNames ];
  5860. }
  5861. forceFields = {};
  5862. $.each( fieldNames, function ( i, name ) {
  5863. forceFields[ name ] = fields[ name ];
  5864. } );
  5865. }
  5866. __dtCellSelector( out, dt, identifier, fields, idFn, forceFields );
  5867. return out;
  5868. },
  5869. // get idSrc, fields to edit, data and node for each item
  5870. fields: function ( identifier )
  5871. {
  5872. var idFn = DataTable.ext.oApi._fnGetObjectDataFn( this.s.idSrc );
  5873. var dt = __dtApi( this.s.table );
  5874. var fields = this.s.fields;
  5875. var out = {};
  5876. if ( $.isPlainObject( identifier ) && ( identifier.rows !== undefined || identifier.columns !== undefined || identifier.cells !== undefined ) ) {
  5877. // Multi-item type selector
  5878. if ( identifier.rows !== undefined ) {
  5879. __dtRowSelector( out, dt, identifier.rows, fields, idFn );
  5880. }
  5881. if ( identifier.columns !== undefined ) {
  5882. __dtColumnSelector( out, dt, identifier.columns, fields, idFn );
  5883. }
  5884. if ( identifier.cells !== undefined ) {
  5885. __dtCellSelector( out, dt, identifier.cells, fields, idFn );
  5886. }
  5887. }
  5888. else {
  5889. // Just a rows selector
  5890. __dtRowSelector( out, dt, identifier, fields, idFn );
  5891. }
  5892. return out;
  5893. },
  5894. create: function ( fields, data ) {
  5895. var dt = __dtApi( this.s.table );
  5896. if ( ! __dtIsSsp( dt, this ) ) {
  5897. var row = dt.row.add( data );
  5898. __dtHighlight( row.node() );
  5899. }
  5900. },
  5901. edit: function ( identifier, fields, data, store ) {
  5902. var that = this;
  5903. var dt = __dtApi( this.s.table );
  5904. // No point in doing anything when server-side processing - the commit
  5905. // will redraw the table
  5906. if ( ! __dtIsSsp( dt, this ) || this.s.editOpts.drawType === 'none' ) {
  5907. // The identifier can select one or more rows, but the data will
  5908. // refer to just a single row. We need to determine which row from
  5909. // the set is the one to operator on.
  5910. var rowId = __dataSources.dataTable.id.call( this, data );
  5911. var row;
  5912. // Find the row to edit - attempt to do an id look up first for speed
  5913. try {
  5914. row = dt.row( __dtjqId(rowId) );
  5915. }
  5916. catch (e) {
  5917. row = dt;
  5918. }
  5919. // If not found, then we need to do it the slow way
  5920. if ( ! row.any() ) {
  5921. row = dt.row( function ( rowIdx, rowData, rowNode ) {
  5922. return rowId == __dataSources.dataTable.id.call( that, rowData );
  5923. } );
  5924. }
  5925. if ( row.any() ) {
  5926. // Merge data to allow for a sub-set to be returned
  5927. var extender = $.fn.dataTableExt.oApi._fnExtend;
  5928. var toSave = extender( {}, row.data(), true );
  5929. toSave = extender( toSave, data, true );
  5930. row.data( toSave );
  5931. // Remove the item from the list of indexes now that is has been
  5932. // updated
  5933. var idx = $.inArray( rowId, store.rowIds );
  5934. store.rowIds.splice( idx, 1 );
  5935. }
  5936. else {
  5937. // If not found, then its a new row (change in pkey possibly)
  5938. row = dt.row.add( data );
  5939. }
  5940. __dtHighlight( row.node() );
  5941. }
  5942. },
  5943. remove: function ( identifier, fields, store ) {
  5944. // No confirmation from the server
  5945. var that = this;
  5946. var dt = __dtApi( this.s.table );
  5947. var cancelled = store.cancelled;
  5948. if ( cancelled.length === 0 ) {
  5949. // No rows were cancelled on the server-side, remove them all
  5950. dt.rows( identifier ).remove();
  5951. }
  5952. else {
  5953. // One or more rows were cancelled, so we need to identify them
  5954. // and not remove those rows
  5955. var indexes = [];
  5956. dt.rows( identifier ).every( function () {
  5957. var id = __dataSources.dataTable.id.call( that, this.data() );
  5958. if ( $.inArray( id, cancelled ) === -1 ) {
  5959. // Don't use `remove` here - it messes up the indexes
  5960. indexes.push( this.index() );
  5961. }
  5962. } );
  5963. dt.rows( indexes ).remove();
  5964. }
  5965. },
  5966. prep: function ( action, identifier, submit, json, store ) {
  5967. // On edit we store the ids of the rows that are being edited
  5968. if ( action === 'edit' ) {
  5969. var cancelled = json.cancelled || [];
  5970. store.rowIds = $.map( submit.data, function ( val, key ) {
  5971. return ! $.isEmptyObject( submit.data[ key ] ) && // was submitted
  5972. $.inArray( key, cancelled ) === -1 ? // was not cancelled on the server-side
  5973. key :
  5974. undefined;
  5975. } );
  5976. }
  5977. else if ( action === 'remove' ) {
  5978. store.cancelled = json.cancelled || [];
  5979. }
  5980. },
  5981. commit: function ( action, identifier, data, store ) {
  5982. // Updates complete - redraw
  5983. var that = this;
  5984. var dt = __dtApi( this.s.table );
  5985. // On edit, if there are any rows left in the `store.rowIds`, then they
  5986. // were not returned by the server and should be removed (they might not
  5987. // meet filtering requirements any more for example)
  5988. if ( ! __dtIsSsp( dt, this ) && action === 'edit' && store.rowIds.length ) {
  5989. var ids = store.rowIds;
  5990. var row;
  5991. var compare = function ( id ) {
  5992. return function ( rowIdx, rowData, rowNode ) {
  5993. return id == __dataSources.dataTable.id.call( that, rowData );
  5994. };
  5995. };
  5996. for ( var i=0, ien=ids.length ; i<ien ; i++ ) {
  5997. // Find the row to edit - attempt to do an id look up first for speed
  5998. try {
  5999. row = dt.row( __dtjqId(ids[i]) );
  6000. }
  6001. catch (e) {
  6002. row = dt;
  6003. }
  6004. // If not found, then we need to do it the slow way
  6005. if ( ! row.any() ) {
  6006. row = dt.row( compare( ids[i] ) );
  6007. }
  6008. if ( row.any() && ! dt.settings()[0].oFeatures.bServerSide ) {
  6009. row.remove();
  6010. }
  6011. }
  6012. }
  6013. var drawType = this.s.editOpts.drawType;
  6014. if ( drawType !== 'none' ) {
  6015. dt.draw( drawType );
  6016. }
  6017. }
  6018. };
  6019. /* - - - - - - - -
  6020. * HTML editor interface
  6021. */
  6022. function __html_id ( identifier ) {
  6023. var context = document;
  6024. if ( identifier !== 'keyless' ) {
  6025. context = $('[data-editor-id="'+identifier+'"]');
  6026. if ( context.length === 0 ) {
  6027. context = typeof identifier === 'string' ?
  6028. $(__dtjqId(identifier)) :
  6029. $(identifier);
  6030. }
  6031. if ( context.length === 0 ) {
  6032. throw 'Could not find an element with `data-editor-id` or `id` of: '+identifier;
  6033. }
  6034. }
  6035. return context;
  6036. }
  6037. function __html_el ( identifier, name ) {
  6038. var context = __html_id( identifier );
  6039. return $('[data-editor-field="'+name+'"]', context);
  6040. }
  6041. function __html_els ( identifier, names ) {
  6042. var out = $();
  6043. for ( var i=0, ien=names.length ; i<ien ; i++ ) {
  6044. out = out.add( __html_el( identifier, names[i] ) );
  6045. }
  6046. return out;
  6047. }
  6048. function __html_get( identifier, dataSrc ) {
  6049. var el = __html_el( identifier, dataSrc );
  6050. return el.filter('[data-editor-value]').length ?
  6051. el.attr( 'data-editor-value' ) :
  6052. el.html();
  6053. }
  6054. function __html_set( identifier, fields, data ) {
  6055. $.each( fields, function ( name, field ) {
  6056. var val = field.valFromData( data );
  6057. if ( val !== undefined ) {
  6058. var el = __html_el( identifier, field.dataSrc() );
  6059. if ( el.filter('[data-editor-value]').length ) {
  6060. el.attr( 'data-editor-value', val );
  6061. }
  6062. else {
  6063. el.each( function () {
  6064. // This is very frustrating, but in IE if you just write directly
  6065. // to innerHTML, and elements that are overwritten are GC'ed,
  6066. // even if there is a reference to them elsewhere
  6067. while ( this.childNodes.length ) {
  6068. this.removeChild( this.firstChild );
  6069. }
  6070. } )
  6071. .html( val );
  6072. }
  6073. }
  6074. } );
  6075. }
  6076. __dataSources.html = {
  6077. id: function ( data ) {
  6078. var idFn = DataTable.ext.oApi._fnGetObjectDataFn( this.s.idSrc );
  6079. return idFn( data );
  6080. },
  6081. initField: function ( cfg ) {
  6082. // This is before the field has been initialised so can't use it API
  6083. var label = $('[data-editor-label="'+(cfg.data || cfg.name)+'"]');
  6084. if ( ! cfg.label && label.length ) {
  6085. cfg.label = label.html();
  6086. }
  6087. },
  6088. individual: function ( identifier, fieldNames ) {
  6089. var attachEl;
  6090. // Auto detection of the field name and id
  6091. if ( identifier instanceof $ || identifier.nodeName ) {
  6092. attachEl = identifier;
  6093. if ( ! fieldNames ) {
  6094. fieldNames = [ $( identifier ).attr('data-editor-field') ];
  6095. }
  6096. var back = $.fn.addBack ? 'addBack' : 'andSelf';
  6097. identifier = $( identifier ).parents('[data-editor-id]')[ back ]().data('editor-id');
  6098. }
  6099. // no id given and none found
  6100. if ( ! identifier ) {
  6101. identifier = 'keyless';
  6102. }
  6103. // no field name - cannot continue
  6104. if ( fieldNames && ! $.isArray( fieldNames ) ) {
  6105. fieldNames = [ fieldNames ];
  6106. }
  6107. if ( ! fieldNames || fieldNames.length === 0 ) {
  6108. throw 'Cannot automatically determine field name from data source';
  6109. }
  6110. var out = __dataSources.html.fields.call( this, identifier );
  6111. var fields = this.s.fields;
  6112. var forceFields = {};
  6113. $.each( fieldNames, function ( i, name ) {
  6114. forceFields[ name ] = fields[ name ];
  6115. } );
  6116. $.each( out, function ( id, set ) {
  6117. set.type = 'cell';
  6118. set.attach = attachEl ?
  6119. $(attachEl) :
  6120. __html_els( identifier, fieldNames ).toArray();
  6121. set.fields = fields;
  6122. set.displayFields = forceFields;
  6123. } );
  6124. return out;
  6125. },
  6126. // get idSrc, fields to edit, data and node for each item
  6127. fields: function ( identifier )
  6128. {
  6129. var out = {};
  6130. var self = __dataSources.html;
  6131. // Allow multi-point editing
  6132. if ( $.isArray( identifier ) ) {
  6133. for ( var i=0, ien=identifier.length ; i<ien ; i++ ) {
  6134. var res = self.fields.call( this, identifier[i] );
  6135. out[ identifier[i] ] = res[ identifier[i] ];
  6136. }
  6137. return out;
  6138. }
  6139. // else
  6140. var data = {};
  6141. var fields = this.s.fields;
  6142. if ( ! identifier ) {
  6143. identifier = 'keyless';
  6144. }
  6145. $.each( fields, function ( name, field ) {
  6146. var val = __html_get( identifier, field.dataSrc() );
  6147. // If no HTML element is present, jQuery returns null. We want undefined
  6148. field.valToData( data, val === null ? undefined : val );
  6149. } );
  6150. out[ identifier ] = {
  6151. idSrc: identifier,
  6152. data: data,
  6153. node: document,
  6154. fields: fields,
  6155. type: 'row'
  6156. };
  6157. return out;
  6158. },
  6159. create: function ( fields, data ) {
  6160. // If there is an element with the id that has been created, then use it
  6161. // to assign the values
  6162. if ( data ) {
  6163. var id = __dataSources.html.id.call( this, data );
  6164. try {
  6165. if ( __html_id(id).length ) {
  6166. __html_set( id, fields, data );
  6167. }
  6168. }
  6169. catch (e) {
  6170. // noop - use `postCreate` to add items to the DOM
  6171. }
  6172. }
  6173. },
  6174. edit: function ( identifier, fields, data ) {
  6175. // Get the ids from the returned data or `keyless` if not found
  6176. var id = __dataSources.html.id.call( this, data ) || 'keyless';
  6177. __html_set( id, fields, data );
  6178. },
  6179. remove: function ( identifier, fields ) {
  6180. // If there is an element with an ID property matching the identifier,
  6181. // remove it
  6182. __html_id(identifier).remove();
  6183. }
  6184. };
  6185. }());
  6186. /**
  6187. * Class names that are used by Editor for its various display components.
  6188. * A copy of this object is taken when an Editor instance is initialised, thus
  6189. * allowing different classes to be used in different instances if required.
  6190. * Class name changes can be useful for easy integration with CSS frameworks,
  6191. * for example Twitter Bootstrap.
  6192. * @namespace
  6193. */
  6194. Editor.classes = {
  6195. /**
  6196. * Applied to the base DIV element that contains all other Editor elements
  6197. */
  6198. "wrapper": "DTE",
  6199. /**
  6200. * Processing classes
  6201. * @namespace
  6202. */
  6203. "processing": {
  6204. /**
  6205. * Processing indicator element
  6206. */
  6207. "indicator": "DTE_Processing_Indicator",
  6208. /**
  6209. * Added to the base element ("wrapper") when the form is "processing"
  6210. */
  6211. "active": "processing"
  6212. },
  6213. /**
  6214. * Display header classes
  6215. * @namespace
  6216. */
  6217. "header": {
  6218. /**
  6219. * Container for the header elements
  6220. */
  6221. "wrapper": "DTE_Header",
  6222. /**
  6223. * Liner for the header content
  6224. */
  6225. "content": "DTE_Header_Content"
  6226. },
  6227. /**
  6228. * Display body classes
  6229. * @namespace
  6230. */
  6231. "body": {
  6232. /**
  6233. * Container for the body elements
  6234. */
  6235. "wrapper": "DTE_Body",
  6236. /**
  6237. * Liner for the body content
  6238. */
  6239. "content": "DTE_Body_Content"
  6240. },
  6241. /**
  6242. * Display footer classes
  6243. * @namespace
  6244. */
  6245. "footer": {
  6246. /**
  6247. * Container for the footer elements
  6248. */
  6249. "wrapper": "DTE_Footer",
  6250. /**
  6251. * Liner for the footer content
  6252. */
  6253. "content": "DTE_Footer_Content"
  6254. },
  6255. /**
  6256. * Form classes
  6257. * @namespace
  6258. */
  6259. "form": {
  6260. /**
  6261. * Container for the form elements
  6262. */
  6263. "wrapper": "DTE_Form",
  6264. /**
  6265. * Liner for the form content
  6266. */
  6267. "content": "DTE_Form_Content",
  6268. /**
  6269. * Applied to the <form> tag
  6270. */
  6271. "tag": "",
  6272. /**
  6273. * Global form information
  6274. */
  6275. "info": "DTE_Form_Info",
  6276. /**
  6277. * Global error imformation
  6278. */
  6279. "error": "DTE_Form_Error",
  6280. /**
  6281. * Buttons container
  6282. */
  6283. "buttons": "DTE_Form_Buttons",
  6284. /**
  6285. * Button
  6286. */
  6287. "button": "btn",
  6288. /**
  6289. * Button inside the form
  6290. */
  6291. "buttonInternal": "btn"
  6292. },
  6293. /**
  6294. * Field classes
  6295. * @namespace
  6296. */
  6297. "field": {
  6298. /**
  6299. * Container for each field
  6300. */
  6301. "wrapper": "DTE_Field",
  6302. /**
  6303. * Class prefix for the field type - field type is added to the end allowing
  6304. * styling based on field type.
  6305. */
  6306. "typePrefix": "DTE_Field_Type_",
  6307. /**
  6308. * Class prefix for the field name - field name is added to the end allowing
  6309. * styling based on field name.
  6310. */
  6311. "namePrefix": "DTE_Field_Name_",
  6312. /**
  6313. * Field label
  6314. */
  6315. "label": "DTE_Label",
  6316. /**
  6317. * Field input container
  6318. */
  6319. "input": "DTE_Field_Input",
  6320. /**
  6321. * Input elements wrapper
  6322. */
  6323. "inputControl": "DTE_Field_InputControl",
  6324. /**
  6325. * Field error state (added to the field.wrapper element when in error state
  6326. */
  6327. "error": "DTE_Field_StateError",
  6328. /**
  6329. * Label information text
  6330. */
  6331. "msg-label": "DTE_Label_Info",
  6332. /**
  6333. * Error information text
  6334. */
  6335. "msg-error": "DTE_Field_Error",
  6336. /**
  6337. * Live messaging (API) information text
  6338. */
  6339. "msg-message": "DTE_Field_Message",
  6340. /**
  6341. * General information text
  6342. */
  6343. "msg-info": "DTE_Field_Info",
  6344. /**
  6345. * Multi-value information display wrapper
  6346. */
  6347. "multiValue": "multi-value",
  6348. /**
  6349. * Multi-value information descriptive text
  6350. */
  6351. "multiInfo": "multi-info",
  6352. /**
  6353. * Multi-value information display
  6354. */
  6355. "multiRestore": "multi-restore",
  6356. /**
  6357. * Multi-value not editable (field.multiEditable)
  6358. */
  6359. "multiNoEdit": "multi-noEdit",
  6360. /**
  6361. * Field is disabled
  6362. */
  6363. "disabled": "disabled",
  6364. /**
  6365. * Field's processing element
  6366. */
  6367. "processing": "DTE_Processing_Indicator"
  6368. },
  6369. /**
  6370. * Action classes - these are added to the Editor base element ("wrapper")
  6371. * and allows styling based on the type of form view that is being employed.
  6372. * @namespace
  6373. */
  6374. "actions": {
  6375. /**
  6376. * Editor is in 'create' state
  6377. */
  6378. "create": "DTE_Action_Create",
  6379. /**
  6380. * Editor is in 'edit' state
  6381. */
  6382. "edit": "DTE_Action_Edit",
  6383. /**
  6384. * Editor is in 'remove' state
  6385. */
  6386. "remove": "DTE_Action_Remove"
  6387. },
  6388. /**
  6389. * Inline editing classes - these are used to display the inline editor
  6390. * @namespace
  6391. */
  6392. "inline": {
  6393. "wrapper": "DTE DTE_Inline",
  6394. "liner": "DTE_Inline_Field",
  6395. "buttons": "DTE_Inline_Buttons"
  6396. },
  6397. /**
  6398. * Bubble editing classes - these are used to display the bubble editor
  6399. * @namespace
  6400. */
  6401. "bubble": {
  6402. /**
  6403. * Bubble container element
  6404. */
  6405. "wrapper": "DTE DTE_Bubble",
  6406. /**
  6407. * Bubble content liner
  6408. */
  6409. "liner": "DTE_Bubble_Liner",
  6410. /**
  6411. * Bubble table display wrapper, so the buttons and form can be shown
  6412. * as table cells (via css)
  6413. */
  6414. "table": "DTE_Bubble_Table",
  6415. /**
  6416. * Close button
  6417. */
  6418. "close": "icon close",
  6419. /**
  6420. * Pointer shown which node is being edited
  6421. */
  6422. "pointer": "DTE_Bubble_Triangle",
  6423. /**
  6424. * Fixed background
  6425. */
  6426. "bg": "DTE_Bubble_Background"
  6427. }
  6428. };
  6429. /*
  6430. * Add helpful buttons to make life easier
  6431. *
  6432. * Note that the values that require a string to make any sense (the button text
  6433. * for example) are set by Editor when Editor is initialised through the i18n
  6434. * options.
  6435. */
  6436. (function () {
  6437. if ( DataTable.TableTools ) {
  6438. var ttButtons = DataTable.TableTools.BUTTONS;
  6439. var ttButtonBase = {
  6440. sButtonText: null,
  6441. editor: null,
  6442. formTitle: null
  6443. };
  6444. ttButtons.editor_create = $.extend( true, ttButtons.text, ttButtonBase, {
  6445. formButtons: [ {
  6446. label: null,
  6447. fn: function (e) { this.submit(); }
  6448. } ],
  6449. fnClick: function( button, config ) {
  6450. var editor = config.editor;
  6451. var i18nCreate = editor.i18n.create;
  6452. var buttons = config.formButtons;
  6453. if ( ! buttons[0].label ) {
  6454. buttons[0].label = i18nCreate.submit;
  6455. }
  6456. editor.create( {
  6457. title: i18nCreate.title,
  6458. buttons: buttons
  6459. } );
  6460. }
  6461. } );
  6462. ttButtons.editor_edit = $.extend( true, ttButtons.select_single, ttButtonBase, {
  6463. formButtons: [ {
  6464. label: null,
  6465. fn: function (e) { this.submit(); }
  6466. } ],
  6467. fnClick: function( button, config ) {
  6468. var selected = this.fnGetSelectedIndexes();
  6469. if ( selected.length !== 1 ) {
  6470. return;
  6471. }
  6472. var editor = config.editor;
  6473. var i18nEdit = editor.i18n.edit;
  6474. var buttons = config.formButtons;
  6475. if ( ! buttons[0].label ) {
  6476. buttons[0].label = i18nEdit.submit;
  6477. }
  6478. editor.edit( selected[0], {
  6479. title: i18nEdit.title,
  6480. buttons: buttons
  6481. } );
  6482. }
  6483. } );
  6484. ttButtons.editor_remove = $.extend( true, ttButtons.select, ttButtonBase, {
  6485. question: null,
  6486. formButtons: [
  6487. {
  6488. label: null,
  6489. fn: function (e) {
  6490. // Executed in the Form instance's scope
  6491. var that = this;
  6492. this.submit( function ( json ) {
  6493. var tt = $.fn.dataTable.TableTools.fnGetInstance(
  6494. $(that.s.table).DataTable().table().node()
  6495. );
  6496. tt.fnSelectNone();
  6497. } );
  6498. }
  6499. }
  6500. ],
  6501. fnClick: function( button, config ) {
  6502. var rows = this.fnGetSelectedIndexes();
  6503. if ( rows.length === 0 ) {
  6504. return;
  6505. }
  6506. var editor = config.editor;
  6507. var i18nRemove = editor.i18n.remove;
  6508. var buttons = config.formButtons;
  6509. var question = typeof i18nRemove.confirm === 'string' ?
  6510. i18nRemove.confirm :
  6511. i18nRemove.confirm[rows.length] ?
  6512. i18nRemove.confirm[rows.length] : i18nRemove.confirm._;
  6513. if ( ! buttons[0].label ) {
  6514. buttons[0].label = i18nRemove.submit;
  6515. }
  6516. editor.remove( rows, {
  6517. message: question.replace( /%d/g, rows.length ),
  6518. title: i18nRemove.title,
  6519. buttons: buttons
  6520. } );
  6521. }
  6522. } );
  6523. }
  6524. var _buttons = DataTable.ext.buttons;
  6525. $.extend( _buttons, {
  6526. create: {
  6527. text: function ( dt, node, config ) {
  6528. return dt.i18n( 'buttons.create', config.editor.i18n.create.button );
  6529. },
  6530. className: 'buttons-create',
  6531. editor: null,
  6532. formButtons: {
  6533. text: function ( editor ) {
  6534. return editor.i18n.create.submit;
  6535. },
  6536. action: function (e) {
  6537. this.submit();
  6538. }
  6539. },
  6540. formMessage: null,
  6541. formTitle: null,
  6542. action: function( e, dt, node, config ) {
  6543. var that = this;
  6544. var editor = config.editor;
  6545. this.processing(true);
  6546. editor
  6547. .one( 'preOpen', function () {
  6548. that.processing(false);
  6549. })
  6550. .create( {
  6551. buttons: config.formButtons,
  6552. message: config.formMessage || editor.i18n.create.message,
  6553. title: config.formTitle || editor.i18n.create.title
  6554. } );
  6555. }
  6556. },
  6557. edit: {
  6558. extend: 'selected',
  6559. text: function ( dt, node, config ) {
  6560. return dt.i18n( 'buttons.edit', config.editor.i18n.edit.button );
  6561. },
  6562. className: 'buttons-edit',
  6563. editor: null,
  6564. formButtons: {
  6565. text: function ( editor ) {
  6566. return editor.i18n.edit.submit;
  6567. },
  6568. action: function (e) {
  6569. this.submit();
  6570. }
  6571. },
  6572. formMessage: null,
  6573. formTitle: null,
  6574. action: function( e, dt, node, config ) {
  6575. var that = this;
  6576. var editor = config.editor;
  6577. var rows = dt.rows( { selected: true } ).indexes();
  6578. var columns = dt.columns( { selected: true } ).indexes();
  6579. var cells = dt.cells( { selected: true } ).indexes();
  6580. var items = columns.length || cells.length ?
  6581. {
  6582. rows: rows,
  6583. columns: columns,
  6584. cells: cells
  6585. } :
  6586. rows;
  6587. this.processing(true);
  6588. editor
  6589. .one( 'preOpen', function () {
  6590. that.processing(false);
  6591. })
  6592. .edit( items, {
  6593. buttons: config.formButtons,
  6594. message: config.formMessage || editor.i18n.edit.message,
  6595. title: config.formTitle || editor.i18n.edit.title
  6596. } );
  6597. }
  6598. },
  6599. remove: {
  6600. extend: 'selected',
  6601. limitTo: ['rows'],
  6602. text: function ( dt, node, config ) {
  6603. return dt.i18n( 'buttons.remove', config.editor.i18n.remove.button );
  6604. },
  6605. className: 'buttons-remove',
  6606. editor: null,
  6607. formButtons: {
  6608. text: function ( editor ) {
  6609. return editor.i18n.remove.submit;
  6610. },
  6611. action: function (e) {
  6612. this.submit();
  6613. }
  6614. },
  6615. formMessage: function ( editor, dt ) {
  6616. var rows = dt.rows( { selected: true } ).indexes();
  6617. var i18n = editor.i18n.remove;
  6618. var question = typeof i18n.confirm === 'string' ?
  6619. i18n.confirm :
  6620. i18n.confirm[rows.length] ?
  6621. i18n.confirm[rows.length] : i18n.confirm._;
  6622. return question.replace( /%d/g, rows.length );
  6623. },
  6624. formTitle: null,
  6625. action: function( e, dt, node, config ) {
  6626. var that = this;
  6627. var editor = config.editor;
  6628. this.processing(true);
  6629. editor
  6630. .one( 'preOpen', function () {
  6631. that.processing(false);
  6632. })
  6633. .remove( dt.rows( { selected: true } ).indexes(), {
  6634. buttons: config.formButtons,
  6635. message: config.formMessage,
  6636. title: config.formTitle || editor.i18n.remove.title
  6637. } );
  6638. }
  6639. }
  6640. } );
  6641. // Reuse the standard edit and remove buttons for their singular equivalent,
  6642. // but set it to extend the single selected button only
  6643. _buttons.editSingle = $.extend( {}, _buttons.edit );
  6644. _buttons.editSingle.extend = 'selectedSingle';
  6645. _buttons.removeSingle = $.extend( {}, _buttons.remove );
  6646. _buttons.removeSingle.extend = 'selectedSingle';
  6647. }());
  6648. /**
  6649. * Field types array - this can be used to add field types or modify the pre-
  6650. * defined options. See the online Editor documentation for information about
  6651. * the built in field types.
  6652. *
  6653. * Note that we use a DataTables ext object to allow plug-ins to be loaded
  6654. * before Editor itself. This is useful for the online DataTables download
  6655. * builder.
  6656. *
  6657. * @namespace
  6658. */
  6659. Editor.fieldTypes = {};
  6660. /*
  6661. * This file provides a DateTime GUI picker (calendar and time input). Only the
  6662. * format YYYY-MM-DD is supported without additional software, but the end user
  6663. * experience can be greatly enhanced by including the momentjs library which
  6664. * provides date / time parsing and formatting options.
  6665. *
  6666. * This functionality is required because the HTML5 date and datetime input
  6667. * types are not widely supported in desktop browsers.
  6668. *
  6669. * Constructed by using:
  6670. *
  6671. * new Editor.DateTime( input, opts )
  6672. *
  6673. * where `input` is the HTML input element to use and `opts` is an object of
  6674. * options based on the `Editor.DateTime.defaults` object.
  6675. */
  6676. Editor.DateTime = function ( input, opts ) {
  6677. this.c = $.extend( true, {}, Editor.DateTime.defaults, opts );
  6678. var classPrefix = this.c.classPrefix;
  6679. var i18n = this.c.i18n;
  6680. // Only IS8601 dates are supported without moment
  6681. if ( ! window.moment && this.c.format !== 'YYYY-MM-DD' ) {
  6682. throw "Editor datetime: Without momentjs only the format 'YYYY-MM-DD' can be used";
  6683. }
  6684. var timeBlock = function ( type ) {
  6685. return '<div class="'+classPrefix+'-timeblock">'+
  6686. '</div>';
  6687. };
  6688. var gap = function () {
  6689. return '<span>:</span>';
  6690. };
  6691. // DOM structure
  6692. var structure = $(
  6693. '<div class="'+classPrefix+'">'+
  6694. '<div class="'+classPrefix+'-date">'+
  6695. '<div class="'+classPrefix+'-title">'+
  6696. '<div class="'+classPrefix+'-iconLeft">'+
  6697. '<button>'+i18n.previous+'</button>'+
  6698. '</div>'+
  6699. '<div class="'+classPrefix+'-iconRight">'+
  6700. '<button>'+i18n.next+'</button>'+
  6701. '</div>'+
  6702. '<div class="'+classPrefix+'-label">'+
  6703. '<span/>'+
  6704. '<select class="'+classPrefix+'-month"/>'+
  6705. '</div>'+
  6706. '<div class="'+classPrefix+'-label">'+
  6707. '<span/>'+
  6708. '<select class="'+classPrefix+'-year"/>'+
  6709. '</div>'+
  6710. '</div>'+
  6711. '<div class="'+classPrefix+'-calendar"/>'+
  6712. '</div>'+
  6713. '<div class="'+classPrefix+'-time">'+
  6714. '<div class="'+classPrefix+'-hours"/>'+
  6715. '<div class="'+classPrefix+'-minutes"/>'+
  6716. '<div class="'+classPrefix+'-seconds"/>'+
  6717. '</div>'+
  6718. '<div class="'+classPrefix+'-error"/>'+
  6719. '</div>'
  6720. );
  6721. this.dom = {
  6722. container: structure,
  6723. date: structure.find( '.'+classPrefix+'-date' ),
  6724. title: structure.find( '.'+classPrefix+'-title' ),
  6725. calendar: structure.find( '.'+classPrefix+'-calendar' ),
  6726. time: structure.find( '.'+classPrefix+'-time' ),
  6727. error: structure.find( '.'+classPrefix+'-error' ),
  6728. input: $(input)
  6729. };
  6730. this.s = {
  6731. /** @type {Date} Date value that the picker has currently selected */
  6732. d: null,
  6733. /** @type {Date} Date of the calendar - might not match the value */
  6734. display: null,
  6735. /** @type {number} Used to select minutes in a range where the range base is itself unavailable */
  6736. minutesRange: null,
  6737. /** @type {number} Used to select minutes in a range where the range base is itself unavailable */
  6738. secondsRange: null,
  6739. /** @type {String} Unique namespace string for this instance */
  6740. namespace: 'editor-dateime-'+(Editor.DateTime._instance++),
  6741. /** @type {Object} Parts of the picker that should be shown */
  6742. parts: {
  6743. date: this.c.format.match( /[YMD]|L(?!T)|l/ ) !== null,
  6744. time: this.c.format.match( /[Hhm]|LT|LTS/ ) !== null,
  6745. seconds: this.c.format.indexOf( 's' ) !== -1,
  6746. hours12: this.c.format.match( /[haA]/ ) !== null
  6747. }
  6748. };
  6749. this.dom.container
  6750. .append( this.dom.date )
  6751. .append( this.dom.time )
  6752. .append( this.dom.error );
  6753. this.dom.date
  6754. .append( this.dom.title )
  6755. .append( this.dom.calendar );
  6756. this._constructor();
  6757. };
  6758. $.extend( Editor.DateTime.prototype, {
  6759. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  6760. * Public
  6761. */
  6762. /**
  6763. * Destroy the control
  6764. */
  6765. destroy: function () {
  6766. this._hide();
  6767. this.dom.container.off().empty();
  6768. this.dom.input.off('.editor-datetime');
  6769. },
  6770. errorMsg: function ( msg ) {
  6771. var error = this.dom.error;
  6772. if ( msg ) {
  6773. error.html( msg );
  6774. }
  6775. else {
  6776. error.empty();
  6777. }
  6778. },
  6779. hide: function () {
  6780. this._hide();
  6781. },
  6782. max: function ( date ) {
  6783. this.c.maxDate = date;
  6784. this._optionsTitle();
  6785. this._setCalander();
  6786. },
  6787. min: function ( date ) {
  6788. this.c.minDate = date;
  6789. this._optionsTitle();
  6790. this._setCalander();
  6791. },
  6792. /**
  6793. * Check if an element belongs to this control
  6794. *
  6795. * @param {node} node Element to check
  6796. * @return {boolean} true if owned by this control, false otherwise
  6797. */
  6798. owns: function ( node ) {
  6799. return $(node).parents().filter( this.dom.container ).length > 0;
  6800. },
  6801. /**
  6802. * Get / set the value
  6803. *
  6804. * @param {string|Date} set Value to set
  6805. * @param {boolean} [write=true] Flag to indicate if the formatted value
  6806. * should be written into the input element
  6807. */
  6808. val: function ( set, write ) {
  6809. if ( set === undefined ) {
  6810. return this.s.d;
  6811. }
  6812. if ( set instanceof Date ) {
  6813. this.s.d = this._dateToUtc( set );
  6814. }
  6815. else if ( set === null || set === '' ) {
  6816. this.s.d = null;
  6817. }
  6818. else if ( set === '--now' ) {
  6819. this.s.d = new Date();
  6820. }
  6821. else if ( typeof set === 'string' ) {
  6822. if ( window.moment ) {
  6823. // Use moment if possible (even for ISO8601 strings, since it
  6824. // will correctly handle 0000-00-00 and the like)
  6825. var m = window.moment.utc( set, this.c.format, this.c.momentLocale, this.c.momentStrict );
  6826. this.s.d = m.isValid() ? m.toDate() : null;
  6827. }
  6828. else {
  6829. // Else must be using ISO8601 without moment (constructor would
  6830. // have thrown an error otherwise)
  6831. var match = set.match(/(\d{4})\-(\d{2})\-(\d{2})/ );
  6832. this.s.d = match ?
  6833. new Date( Date.UTC(match[1], match[2]-1, match[3]) ) :
  6834. null;
  6835. }
  6836. }
  6837. if ( write || write === undefined ) {
  6838. if ( this.s.d ) {
  6839. this._writeOutput();
  6840. }
  6841. else {
  6842. // The input value was not valid...
  6843. this.dom.input.val( set );
  6844. }
  6845. }
  6846. // We need a date to be able to display the calendar at all
  6847. if ( ! this.s.d ) {
  6848. this.s.d = this._dateToUtc( new Date() );
  6849. }
  6850. this.s.display = new Date( this.s.d.toString() );
  6851. // Set the day of the month to be 1 so changing between months doesn't
  6852. // run into issues when going from day 31 to 28 (for example)
  6853. this.s.display.setUTCDate( 1 );
  6854. // Update the display elements for the new value
  6855. this._setTitle();
  6856. this._setCalander();
  6857. this._setTime();
  6858. },
  6859. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  6860. * Constructor
  6861. */
  6862. /**
  6863. * Build the control and assign initial event handlers
  6864. *
  6865. * @private
  6866. */
  6867. _constructor: function () {
  6868. var that = this;
  6869. var classPrefix = this.c.classPrefix;
  6870. var onChange = function () {
  6871. that.c.onChange.call( that, that.dom.input.val(), that.s.d, that.dom.input );
  6872. };
  6873. if ( ! this.s.parts.date ) {
  6874. this.dom.date.css( 'display', 'none' );
  6875. }
  6876. if ( ! this.s.parts.time ) {
  6877. this.dom.time.css( 'display', 'none' );
  6878. }
  6879. if ( ! this.s.parts.seconds ) {
  6880. this.dom.time.children('div.'+classPrefix+'-seconds').remove();
  6881. this.dom.time.children('span').eq(1).remove();
  6882. }
  6883. // Render the options
  6884. this._optionsTitle();
  6885. // Trigger the display of the widget when clicking or focusing on the
  6886. // input element
  6887. this.dom.input
  6888. .attr('autocomplete', 'off')
  6889. .on('focus.editor-datetime click.editor-datetime', function () {
  6890. // If already visible - don't do anything
  6891. if ( that.dom.container.is(':visible') || that.dom.input.is(':disabled') ) {
  6892. return;
  6893. }
  6894. // In case the value has changed by text
  6895. that.val( that.dom.input.val(), false );
  6896. that._show();
  6897. } )
  6898. .on('keyup.editor-datetime', function () {
  6899. // Update the calendar's displayed value as the user types
  6900. if ( that.dom.container.is(':visible') ) {
  6901. that.val( that.dom.input.val(), false );
  6902. }
  6903. } );
  6904. // Main event handlers for input in the widget
  6905. this.dom.container
  6906. .on( 'change', 'select', function () {
  6907. var select = $(this);
  6908. var val = select.val();
  6909. if ( select.hasClass(classPrefix+'-month') ) {
  6910. // Month select
  6911. that._correctMonth( that.s.display, val );
  6912. that._setTitle();
  6913. that._setCalander();
  6914. }
  6915. else if ( select.hasClass(classPrefix+'-year') ) {
  6916. // Year select
  6917. that.s.display.setUTCFullYear( val );
  6918. that._setTitle();
  6919. that._setCalander();
  6920. }
  6921. else if ( select.hasClass(classPrefix+'-hours') || select.hasClass(classPrefix+'-ampm') ) {
  6922. // Hours - need to take account of AM/PM input if present
  6923. if ( that.s.parts.hours12 ) {
  6924. var hours = $(that.dom.container).find('.'+classPrefix+'-hours').val() * 1;
  6925. var pm = $(that.dom.container).find('.'+classPrefix+'-ampm').val() === 'pm';
  6926. that.s.d.setUTCHours( hours === 12 && !pm ?
  6927. 0 :
  6928. pm && hours !== 12 ?
  6929. hours + 12 :
  6930. hours
  6931. );
  6932. }
  6933. else {
  6934. that.s.d.setUTCHours( val );
  6935. }
  6936. that._setTime();
  6937. that._writeOutput( true );
  6938. onChange();
  6939. }
  6940. else if ( select.hasClass(classPrefix+'-minutes') ) {
  6941. // Minutes select
  6942. that.s.d.setUTCMinutes( val );
  6943. that._setTime();
  6944. that._writeOutput( true );
  6945. onChange();
  6946. }
  6947. else if ( select.hasClass(classPrefix+'-seconds') ) {
  6948. // Seconds select
  6949. that.s.d.setSeconds( val );
  6950. that._setTime();
  6951. that._writeOutput( true );
  6952. onChange();
  6953. }
  6954. that.dom.input.focus();
  6955. that._position();
  6956. } )
  6957. .on( 'click', function (e) {
  6958. var d = that.s.d;
  6959. var nodeName = e.target.nodeName.toLowerCase();
  6960. var target = nodeName === 'span' ?
  6961. e.target.parentNode :
  6962. e.target;
  6963. nodeName = target.nodeName.toLowerCase();
  6964. if ( nodeName === 'select' ) {
  6965. return;
  6966. }
  6967. e.stopPropagation();
  6968. if ( nodeName === 'button' ) {
  6969. var button = $(target);
  6970. var parent = button.parent();
  6971. if ( parent.hasClass('disabled') && ! parent.hasClass('range') ) {
  6972. button.blur();
  6973. return;
  6974. }
  6975. if ( parent.hasClass(classPrefix+'-iconLeft') ) {
  6976. // Previous month
  6977. that.s.display.setUTCMonth( that.s.display.getUTCMonth()-1 );
  6978. that._setTitle();
  6979. that._setCalander();
  6980. that.dom.input.focus();
  6981. }
  6982. else if ( parent.hasClass(classPrefix+'-iconRight') ) {
  6983. // Next month
  6984. that._correctMonth( that.s.display, that.s.display.getUTCMonth()+1 );
  6985. that._setTitle();
  6986. that._setCalander();
  6987. that.dom.input.focus();
  6988. }
  6989. else if ( button.parents('.'+classPrefix+'-time').length ) {
  6990. var val = button.data('value');
  6991. var unit = button.data('unit');
  6992. if ( unit === 'minutes' ) {
  6993. if ( parent.hasClass('disabled') && parent.hasClass('range') ) {
  6994. that.s.minutesRange = val;
  6995. that._setTime();
  6996. return;
  6997. }
  6998. else {
  6999. that.s.minutesRange = null;
  7000. }
  7001. }
  7002. if ( unit === 'seconds' ) {
  7003. if ( parent.hasClass('disabled') && parent.hasClass('range') ) {
  7004. that.s.secondsRange = val;
  7005. that._setTime();
  7006. return;
  7007. }
  7008. else {
  7009. that.s.secondsRange = null;
  7010. }
  7011. }
  7012. // Specific to hours for 12h clock
  7013. if ( val === 'am' ) {
  7014. if ( d.getUTCHours() >= 12 ) {
  7015. val = d.getUTCHours() - 12;
  7016. }
  7017. else {
  7018. return;
  7019. }
  7020. }
  7021. else if ( val === 'pm' ) {
  7022. if ( d.getUTCHours() < 12 ) {
  7023. val = d.getUTCHours() + 12;
  7024. }
  7025. else {
  7026. return;
  7027. }
  7028. }
  7029. var set = unit === 'hours' ?
  7030. 'setUTCHours' :
  7031. unit === 'minutes' ?
  7032. 'setUTCMinutes' :
  7033. 'setSeconds';
  7034. d[set]( val );
  7035. that._setTime();
  7036. that._writeOutput( true );
  7037. onChange();
  7038. }
  7039. else {
  7040. // Calendar click
  7041. if ( ! d ) {
  7042. d = that._dateToUtc( new Date() );
  7043. }
  7044. // Can't be certain that the current day will exist in
  7045. // the new month, and likewise don't know that the
  7046. // new day will exist in the old month, But 1 always
  7047. // does, so we can change the month without worry of a
  7048. // recalculation being done automatically by `Date`
  7049. d.setUTCDate( 1 );
  7050. d.setUTCFullYear( button.data('year') );
  7051. d.setUTCMonth( button.data('month') );
  7052. d.setUTCDate( button.data('day') );
  7053. that._writeOutput( true );
  7054. // Don't hide if there is a time picker, since we want to
  7055. // be able to select a time as well.
  7056. if ( ! that.s.parts.time ) {
  7057. // This is annoying but IE has some kind of async
  7058. // behaviour with focus and the focus from the above
  7059. // write would occur after this hide - resulting in the
  7060. // calendar opening immediately
  7061. setTimeout( function () {
  7062. that._hide();
  7063. }, 10 );
  7064. }
  7065. else {
  7066. that._setCalander();
  7067. }
  7068. onChange();
  7069. }
  7070. }
  7071. else {
  7072. // Click anywhere else in the widget - return focus to the
  7073. // input element
  7074. that.dom.input.focus();
  7075. }
  7076. } );
  7077. },
  7078. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  7079. * Private
  7080. */
  7081. /**
  7082. * Compare the date part only of two dates - this is made super easy by the
  7083. * toDateString method!
  7084. *
  7085. * @param {Date} a Date 1
  7086. * @param {Date} b Date 2
  7087. * @private
  7088. */
  7089. _compareDates: function( a, b ) {
  7090. // Can't use toDateString as that converts to local time
  7091. return this._dateToUtcString(a) === this._dateToUtcString(b);
  7092. },
  7093. /**
  7094. * When changing month, take account of the fact that some months don't have
  7095. * the same number of days. For example going from January to February you
  7096. * can have the 31st of Jan selected and just add a month since the date
  7097. * would still be 31, and thus drop you into March.
  7098. *
  7099. * @param {Date} date Date - will be modified
  7100. * @param {integer} month Month to set
  7101. * @private
  7102. */
  7103. _correctMonth: function ( date, month ) {
  7104. var days = this._daysInMonth( date.getUTCFullYear(), month );
  7105. var correctDays = date.getUTCDate() > days;
  7106. date.setUTCMonth( month );
  7107. if ( correctDays ) {
  7108. date.setUTCDate( days );
  7109. date.setUTCMonth( month );
  7110. }
  7111. },
  7112. /**
  7113. * Get the number of days in a method. Based on
  7114. * http://stackoverflow.com/a/4881951 by Matti Virkkunen
  7115. *
  7116. * @param {integer} year Year
  7117. * @param {integer} month Month (starting at 0)
  7118. * @private
  7119. */
  7120. _daysInMonth: function ( year, month ) {
  7121. //
  7122. var isLeap = ((year % 4) === 0 && ((year % 100) !== 0 || (year % 400) === 0));
  7123. var months = [31, (isLeap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  7124. return months[month];
  7125. },
  7126. /**
  7127. * Create a new date object which has the UTC values set to the local time.
  7128. * This allows the local time to be used directly for the library which
  7129. * always bases its calculations and display on UTC.
  7130. *
  7131. * @param {Date} s Date to "convert"
  7132. * @return {Date} Shifted date
  7133. */
  7134. _dateToUtc: function ( s ) {
  7135. return new Date( Date.UTC(
  7136. s.getFullYear(), s.getMonth(), s.getDate(),
  7137. s.getHours(), s.getMinutes(), s.getSeconds()
  7138. ) );
  7139. },
  7140. /**
  7141. * Create a UTC ISO8601 date part from a date object
  7142. *
  7143. * @param {Date} d Date to "convert"
  7144. * @return {string} ISO formatted date
  7145. */
  7146. _dateToUtcString: function ( d ) {
  7147. return d.getUTCFullYear()+'-'+
  7148. this._pad(d.getUTCMonth()+1)+'-'+
  7149. this._pad(d.getUTCDate());
  7150. },
  7151. /**
  7152. * Hide the control and remove events related to its display
  7153. *
  7154. * @private
  7155. */
  7156. _hide: function () {
  7157. var namespace = this.s.namespace;
  7158. this.dom.container.detach();
  7159. $(window).off( '.'+namespace );
  7160. $(document).off( 'keydown.'+namespace );
  7161. $('div.dataTables_scrollBody').off( 'scroll.'+namespace );
  7162. $('div.DTE_Body_Content').off( 'scroll.'+namespace );
  7163. $('body').off( 'click.'+namespace );
  7164. },
  7165. /**
  7166. * Convert a 24 hour value to a 12 hour value
  7167. *
  7168. * @param {integer} val 24 hour value
  7169. * @return {integer} 12 hour value
  7170. * @private
  7171. */
  7172. _hours24To12: function ( val ) {
  7173. return val === 0 ?
  7174. 12 :
  7175. val > 12 ?
  7176. val - 12 :
  7177. val;
  7178. },
  7179. /**
  7180. * Generate the HTML for a single day in the calendar - this is basically
  7181. * and HTML cell with a button that has data attributes so we know what was
  7182. * clicked on (if it is clicked on) and a bunch of classes for styling.
  7183. *
  7184. * @param {object} day Day object from the `_htmlMonth` method
  7185. * @return {string} HTML cell
  7186. */
  7187. _htmlDay: function( day )
  7188. {
  7189. if ( day.empty ) {
  7190. return '<td class="empty"></td>';
  7191. }
  7192. var classes = [ 'selectable' ];
  7193. var classPrefix = this.c.classPrefix;
  7194. if ( day.disabled ) {
  7195. classes.push( 'disabled' );
  7196. }
  7197. if ( day.today ) {
  7198. classes.push( 'now' );
  7199. }
  7200. if ( day.selected ) {
  7201. classes.push( 'selected' );
  7202. }
  7203. return '<td data-day="' + day.day + '" class="' + classes.join(' ') + '">' +
  7204. '<button class="'+classPrefix+'-button '+classPrefix+'-day" type="button" ' +'data-year="' + day.year + '" data-month="' + day.month + '" data-day="' + day.day + '">' +
  7205. '<span>'+day.day+'</span>'+
  7206. '</button>' +
  7207. '</td>';
  7208. },
  7209. /**
  7210. * Create the HTML for a month to be displayed in the calendar table.
  7211. *
  7212. * Based upon the logic used in Pikaday - MIT licensed
  7213. * Copyright (c) 2014 David Bushell
  7214. * https://github.com/dbushell/Pikaday
  7215. *
  7216. * @param {integer} year Year
  7217. * @param {integer} month Month (starting at 0)
  7218. * @return {string} Calendar month HTML
  7219. * @private
  7220. */
  7221. _htmlMonth: function ( year, month ) {
  7222. var now = this._dateToUtc( new Date() ),
  7223. days = this._daysInMonth( year, month ),
  7224. before = new Date( Date.UTC(year, month, 1) ).getUTCDay(),
  7225. data = [],
  7226. row = [];
  7227. if ( this.c.firstDay > 0 ) {
  7228. before -= this.c.firstDay;
  7229. if (before < 0) {
  7230. before += 7;
  7231. }
  7232. }
  7233. var cells = days + before,
  7234. after = cells;
  7235. while ( after > 7 ) {
  7236. after -= 7;
  7237. }
  7238. cells += 7 - after;
  7239. var minDate = this.c.minDate;
  7240. var maxDate = this.c.maxDate;
  7241. if ( minDate ) {
  7242. minDate.setUTCHours(0);
  7243. minDate.setUTCMinutes(0);
  7244. minDate.setSeconds(0);
  7245. }
  7246. if ( maxDate ) {
  7247. maxDate.setUTCHours(23);
  7248. maxDate.setUTCMinutes(59);
  7249. maxDate.setSeconds(59);
  7250. }
  7251. for ( var i=0, r=0 ; i<cells ; i++ ) {
  7252. var day = new Date( Date.UTC(year, month, 1 + (i - before)) ),
  7253. selected = this.s.d ? this._compareDates(day, this.s.d) : false,
  7254. today = this._compareDates(day, now),
  7255. empty = i < before || i >= (days + before),
  7256. disabled = (minDate && day < minDate) ||
  7257. (maxDate && day > maxDate);
  7258. var disableDays = this.c.disableDays;
  7259. if ( $.isArray( disableDays ) && $.inArray( day.getUTCDay(), disableDays ) !== -1 ) {
  7260. disabled = true;
  7261. }
  7262. else if ( typeof disableDays === 'function' && disableDays( day ) === true ) {
  7263. disabled = true;
  7264. }
  7265. var dayConfig = {
  7266. day: 1 + (i - before),
  7267. month: month,
  7268. year: year,
  7269. selected: selected,
  7270. today: today,
  7271. disabled: disabled,
  7272. empty: empty
  7273. };
  7274. row.push( this._htmlDay(dayConfig) );
  7275. if ( ++r === 7 ) {
  7276. if ( this.c.showWeekNumber ) {
  7277. row.unshift( this._htmlWeekOfYear(i - before, month, year) );
  7278. }
  7279. data.push( '<tr>'+row.join('')+'</tr>' );
  7280. row = [];
  7281. r = 0;
  7282. }
  7283. }
  7284. var classPrefix = this.c.classPrefix;
  7285. var className = classPrefix+'-table';
  7286. if ( this.c.showWeekNumber ) {
  7287. className += ' weekNumber';
  7288. }
  7289. // Show / hide month icons based on min/max
  7290. if ( minDate ) {
  7291. var underMin = minDate >= new Date( Date.UTC(year, month, 1, 0, 0, 0 ) );
  7292. this.dom.title.find('div.'+classPrefix+'-iconLeft')
  7293. .css( 'display', underMin ? 'none' : 'block' );
  7294. }
  7295. if ( maxDate ) {
  7296. var overMax = maxDate < new Date( Date.UTC(year, month+1, 1, 0, 0, 0 ) );
  7297. this.dom.title.find('div.'+classPrefix+'-iconRight')
  7298. .css( 'display', overMax ? 'none' : 'block' );
  7299. }
  7300. return '<table class="'+className+'">' +
  7301. '<thead>'+
  7302. this._htmlMonthHead() +
  7303. '</thead>'+
  7304. '<tbody>'+
  7305. data.join('') +
  7306. '</tbody>'+
  7307. '</table>';
  7308. },
  7309. /**
  7310. * Create the calendar table's header (week days)
  7311. *
  7312. * @return {string} HTML cells for the row
  7313. * @private
  7314. */
  7315. _htmlMonthHead: function () {
  7316. var a = [];
  7317. var firstDay = this.c.firstDay;
  7318. var i18n = this.c.i18n;
  7319. // Take account of the first day shift
  7320. var dayName = function ( day ) {
  7321. day += firstDay;
  7322. while (day >= 7) {
  7323. day -= 7;
  7324. }
  7325. return i18n.weekdays[day];
  7326. };
  7327. // Empty cell in the header
  7328. if ( this.c.showWeekNumber ) {
  7329. a.push( '<th></th>' );
  7330. }
  7331. for ( var i=0 ; i<7 ; i++ ) {
  7332. a.push( '<th>'+dayName( i )+'</th>' );
  7333. }
  7334. return a.join('');
  7335. },
  7336. /**
  7337. * Create a cell that contains week of the year - ISO8601
  7338. *
  7339. * Based on https://stackoverflow.com/questions/6117814/ and
  7340. * http://techblog.procurios.nl/k/n618/news/view/33796/14863/
  7341. *
  7342. * @param {integer} d Day of month
  7343. * @param {integer} m Month of year (zero index)
  7344. * @param {integer} y Year
  7345. * @return {string}
  7346. * @private
  7347. */
  7348. _htmlWeekOfYear: function ( d, m, y ) {
  7349. var date = new Date( y, m, d, 0, 0, 0, 0 );
  7350. // First week of the year always has 4th January in it
  7351. date.setDate( date.getDate() + 4 - (date.getDay() || 7) );
  7352. var oneJan = new Date( y, 0, 1 );
  7353. var weekNum = Math.ceil( ( ( (date - oneJan) / 86400000) + 1)/7 );
  7354. return '<td class="'+this.c.classPrefix+'-week">' + weekNum + '</td>';
  7355. },
  7356. /**
  7357. * Create option elements from a range in an array
  7358. *
  7359. * @param {string} selector Class name unique to the select element to use
  7360. * @param {array} values Array of values
  7361. * @param {array} [labels] Array of labels. If given must be the same
  7362. * length as the values parameter.
  7363. * @private
  7364. */
  7365. _options: function ( selector, values, labels ) {
  7366. if ( ! labels ) {
  7367. labels = values;
  7368. }
  7369. var select = this.dom.container.find('select.'+this.c.classPrefix+'-'+selector);
  7370. select.empty();
  7371. for ( var i=0, ien=values.length ; i<ien ; i++ ) {
  7372. select.append( '<option value="'+values[i]+'">'+labels[i]+'</option>' );
  7373. }
  7374. },
  7375. /**
  7376. * Set an option and update the option's span pair (since the select element
  7377. * has opacity 0 for styling)
  7378. *
  7379. * @param {string} selector Class name unique to the select element to use
  7380. * @param {*} val Value to set
  7381. * @private
  7382. */
  7383. _optionSet: function ( selector, val ) {
  7384. var select = this.dom.container.find('select.'+this.c.classPrefix+'-'+selector);
  7385. var span = select.parent().children('span');
  7386. select.val( val );
  7387. var selected = select.find('option:selected');
  7388. span.html( selected.length !== 0 ?
  7389. selected.text() :
  7390. this.c.i18n.unknown
  7391. );
  7392. },
  7393. /**
  7394. * Create time options list.
  7395. *
  7396. * @param {string} unit Time unit - hours, minutes or seconds
  7397. * @param {integer} count Count range - 12, 24 or 60
  7398. * @param {integer} val Existing value for this unit
  7399. * @param {integer[]} allowed Values allow for selection
  7400. * @param {integer} range Override range
  7401. * @private
  7402. */
  7403. _optionsTime: function ( unit, count, val, allowed, range ) {
  7404. var classPrefix = this.c.classPrefix;
  7405. var container = this.dom.container.find('div.'+classPrefix+'-'+unit);
  7406. var i, j;
  7407. var render = count === 12 ?
  7408. function (i) { return i; } :
  7409. this._pad;
  7410. var classPrefix = this.c.classPrefix;
  7411. var className = classPrefix+'-table';
  7412. var i18n = this.c.i18n;
  7413. if ( ! container.length ) {
  7414. return;
  7415. }
  7416. var a = '';
  7417. var span = 10;
  7418. var button = function (value, label, className) {
  7419. // Shift the value for PM
  7420. if ( count === 12 && val >= 12 && typeof value === 'number' ) {
  7421. value += 12;
  7422. }
  7423. var selected = val === value || (value === 'am' && val < 12) || (value === 'pm' && val >= 12) ?
  7424. 'selected' :
  7425. '';
  7426. if (allowed && $.inArray(value, allowed) === -1) {
  7427. selected += ' disabled';
  7428. }
  7429. if ( className ) {
  7430. selected += ' '+className;
  7431. }
  7432. return '<td class="selectable '+selected+'">' +
  7433. '<button class="'+classPrefix+'-button '+classPrefix+'-day" type="button" data-unit="'+unit+'" data-value="'+value+ '">' +
  7434. '<span>'+label+'</span>'+
  7435. '</button>' +
  7436. '</td>';
  7437. }
  7438. if ( count === 12 ) {
  7439. // Hours with AM/PM
  7440. a += '<tr>';
  7441. for ( i=1 ; i<=6 ; i++ ) {
  7442. a += button(i, render(i));
  7443. }
  7444. a += button('am', i18n.amPm[0]);
  7445. a += '</tr>';
  7446. a += '<tr>';
  7447. for ( i=7 ; i<=12 ; i++ ) {
  7448. a += button(i, render(i));
  7449. }
  7450. a += button('pm', i18n.amPm[1]);
  7451. a += '</tr>';
  7452. span = 7;
  7453. }
  7454. else if ( count === 24 ) {
  7455. // Hours - 24
  7456. var c = 0;
  7457. for (j=0 ; j<4 ; j++ ) {
  7458. a += '<tr>';
  7459. for ( i=0 ; i<6 ; i++ ) {
  7460. a += button(c, render(c));
  7461. c++;
  7462. }
  7463. a += '</tr>';
  7464. }
  7465. span = 6;
  7466. }
  7467. else {
  7468. // Minutes and seconds
  7469. a += '<tr>';
  7470. for (j=0 ; j<60 ; j+=10 ) {
  7471. a += button(j, render(j), 'range');
  7472. }
  7473. a += '</tr>';
  7474. // Slight hack to allow for the different number of columns
  7475. a += '</tbody></thead><table class="'+className+' '+className+'-nospace"><tbody>';
  7476. var start = range !== null ?
  7477. range :
  7478. Math.floor( val / 10 )*10;
  7479. a += '<tr>';
  7480. for (j=start+1 ; j<start+10 ; j++ ) {
  7481. a += button(j, render(j));
  7482. }
  7483. a += '</tr>';
  7484. span = 6;
  7485. }
  7486. container
  7487. .empty()
  7488. .append(
  7489. '<table class="'+className+'">'+
  7490. '<thead><tr><th colspan="'+span+'">'+
  7491. i18n[unit] +
  7492. '</th></tr></thead>'+
  7493. '<tbody>'+
  7494. a+
  7495. '</tbody>'+
  7496. '</table>'
  7497. );
  7498. },
  7499. /**
  7500. * Create the options for the month and year
  7501. *
  7502. * @param {integer} year Year
  7503. * @param {integer} month Month (starting at 0)
  7504. * @private
  7505. */
  7506. _optionsTitle: function () {
  7507. var i18n = this.c.i18n;
  7508. var min = this.c.minDate;
  7509. var max = this.c.maxDate;
  7510. var minYear = min ? min.getFullYear() : null;
  7511. var maxYear = max ? max.getFullYear() : null;
  7512. var i = minYear !== null ? minYear : new Date().getFullYear() - this.c.yearRange;
  7513. var j = maxYear !== null ? maxYear : new Date().getFullYear() + this.c.yearRange;
  7514. this._options( 'month', this._range( 0, 11 ), i18n.months );
  7515. this._options( 'year', this._range( i, j ) );
  7516. },
  7517. /**
  7518. * Simple two digit pad
  7519. *
  7520. * @param {integer} i Value that might need padding
  7521. * @return {string|integer} Padded value
  7522. * @private
  7523. */
  7524. _pad: function ( i ) {
  7525. return i<10 ? '0'+i : i;
  7526. },
  7527. /**
  7528. * Position the calendar to look attached to the input element
  7529. * @private
  7530. */
  7531. _position: function () {
  7532. var offset = this.dom.input.offset();
  7533. var container = this.dom.container;
  7534. var inputHeight = this.dom.input.outerHeight();
  7535. if ( this.s.parts.date && this.s.parts.time && $(window).width() > 550 ) {
  7536. container.addClass('horizontal');
  7537. }
  7538. else {
  7539. container.removeClass('horizontal');
  7540. }
  7541. container
  7542. .css( {
  7543. top: offset.top + inputHeight,
  7544. left: offset.left
  7545. } )
  7546. .appendTo( 'body' );
  7547. var calHeight = container.outerHeight();
  7548. var calWidth = container.outerWidth();
  7549. var scrollTop = $(window).scrollTop();
  7550. // Correct to the bottom
  7551. if ( offset.top + inputHeight + calHeight - scrollTop > $(window).height() ) {
  7552. var newTop = offset.top - calHeight;
  7553. container.css( 'top', newTop < 0 ? 0 : newTop );
  7554. }
  7555. // Correct to the right
  7556. if ( calWidth + offset.left > $(window).width() ) {
  7557. var newLeft = $(window).width() - calWidth;
  7558. container.css( 'left', newLeft < 0 ? 0 : newLeft );
  7559. }
  7560. },
  7561. /**
  7562. * Create a simple array with a range of values
  7563. *
  7564. * @param {integer} start Start value (inclusive)
  7565. * @param {integer} end End value (inclusive)
  7566. * @param {integer} [inc=1] Increment value
  7567. * @return {array} Created array
  7568. * @private
  7569. */
  7570. _range: function ( start, end, inc ) {
  7571. var a = [];
  7572. if ( ! inc ) {
  7573. inc = 1;
  7574. }
  7575. for ( var i=start ; i<=end ; i+=inc ) {
  7576. a.push( i );
  7577. }
  7578. return a;
  7579. },
  7580. /**
  7581. * Redraw the calendar based on the display date - this is a destructive
  7582. * operation
  7583. *
  7584. * @private
  7585. */
  7586. _setCalander: function () {
  7587. if ( this.s.display ) {
  7588. this.dom.calendar
  7589. .empty()
  7590. .append( this._htmlMonth(
  7591. this.s.display.getUTCFullYear(),
  7592. this.s.display.getUTCMonth()
  7593. ) );
  7594. }
  7595. },
  7596. /**
  7597. * Set the month and year for the calendar based on the current display date
  7598. *
  7599. * @private
  7600. */
  7601. _setTitle: function () {
  7602. this._optionSet( 'month', this.s.display.getUTCMonth() );
  7603. this._optionSet( 'year', this.s.display.getUTCFullYear() );
  7604. },
  7605. /**
  7606. * Set the time based on the current value of the widget
  7607. *
  7608. * @private
  7609. */
  7610. _setTime: function () {
  7611. var that = this;
  7612. var d = this.s.d;
  7613. var hours = d ? d.getUTCHours() : 0;
  7614. var allowed = function ( prop ) { // Backwards compt with `Increment` option
  7615. return that.c[prop+'Available'] ?
  7616. that.c[prop+'Available'] :
  7617. that._range( 0, 59, that.c[prop+'Increment'] );
  7618. }
  7619. this._optionsTime( 'hours', this.s.parts.hours12 ? 12 : 24, hours, this.c.hoursAvailable )
  7620. this._optionsTime( 'minutes', 60, d ? d.getUTCMinutes() : 0, allowed('minutes'), this.s.minutesRange );
  7621. this._optionsTime( 'seconds', 60, d ? d.getSeconds() : 0, allowed('seconds'), this.s.secondsRange );
  7622. },
  7623. /**
  7624. * Show the widget and add events to the document required only while it
  7625. * is displayed
  7626. *
  7627. * @private
  7628. */
  7629. _show: function () {
  7630. var that = this;
  7631. var namespace = this.s.namespace;
  7632. this._position();
  7633. // Need to reposition on scroll
  7634. $(window).on( 'scroll.'+namespace+' resize.'+namespace, function () {
  7635. that._hide();
  7636. } );
  7637. $('div.DTE_Body_Content').on( 'scroll.'+namespace, function () {
  7638. that._hide();
  7639. } );
  7640. $('div.dataTables_scrollBody').on( 'scroll.'+namespace, function () {
  7641. that._hide();
  7642. } );
  7643. // On tab focus will move to a different field (no keyboard navigation
  7644. // in the date picker - this might need to be changed).
  7645. // On esc the Editor might close. Even if it doesn't the date picker
  7646. // should
  7647. $(document).on( 'keydown.'+namespace, function (e) {
  7648. if (
  7649. e.keyCode === 9 || // tab
  7650. e.keyCode === 27 || // esc
  7651. e.keyCode === 13 // return
  7652. ) {
  7653. that._hide();
  7654. }
  7655. } );
  7656. // Hide if clicking outside of the widget - but in a different click
  7657. // event from the one that was used to trigger the show (bubble and
  7658. // inline)
  7659. setTimeout( function () {
  7660. $('body').on( 'click.'+namespace, function (e) {
  7661. var parents = $(e.target).parents();
  7662. if ( ! parents.filter( that.dom.container ).length && e.target !== that.dom.input[0] ) {
  7663. that._hide();
  7664. }
  7665. } );
  7666. }, 10 );
  7667. },
  7668. /**
  7669. * Write the formatted string to the input element this control is attached
  7670. * to
  7671. *
  7672. * @private
  7673. */
  7674. _writeOutput: function ( focus ) {
  7675. var date = this.s.d;
  7676. // Use moment if possible - otherwise it must be ISO8601 (or the
  7677. // constructor would have thrown an error)
  7678. var out = window.moment ?
  7679. window.moment.utc( date, undefined, this.c.momentLocale, this.c.momentStrict ).format( this.c.format ) :
  7680. date.getUTCFullYear() +'-'+
  7681. this._pad(date.getUTCMonth() + 1) +'-'+
  7682. this._pad(date.getUTCDate());
  7683. this.dom.input.val( out );
  7684. if ( focus ) {
  7685. this.dom.input.focus();
  7686. }
  7687. }
  7688. } );
  7689. /**
  7690. * For generating unique namespaces
  7691. *
  7692. * @type {Number}
  7693. * @private
  7694. */
  7695. Editor.DateTime._instance = 0;
  7696. /**
  7697. * Defaults for the date time picker
  7698. *
  7699. * @type {Object}
  7700. */
  7701. Editor.DateTime.defaults = {
  7702. // Not documented - could be an internal property
  7703. classPrefix: 'editor-datetime',
  7704. // function or array of ints
  7705. disableDays: null,
  7706. // first day of the week (0: Sunday, 1: Monday, etc)
  7707. firstDay: 1,
  7708. format: 'YYYY-MM-DD',
  7709. hoursAvailable: null,
  7710. // Not documented as i18n is done by the Editor.defaults.i18n obj
  7711. i18n: Editor.defaults.i18n.datetime,
  7712. maxDate: null,
  7713. minDate: null,
  7714. minutesAvailable: null,
  7715. minutesIncrement: 1, // deprecated
  7716. momentStrict: true,
  7717. momentLocale: 'en',
  7718. onChange: function () {},
  7719. secondsAvailable: null,
  7720. secondsIncrement: 1, // deprecated
  7721. // show the ISO week number at the head of the row
  7722. showWeekNumber: false,
  7723. // overruled by max / min date
  7724. yearRange: 10
  7725. };
  7726. (function() {
  7727. var fieldTypes = Editor.fieldTypes;
  7728. // Upload private helper method
  7729. function _buttonText ( conf, text )
  7730. {
  7731. if ( text === null || text === undefined ) {
  7732. text = conf.uploadText || "Choose file...";
  7733. }
  7734. conf._input.find('div.upload button').html( text );
  7735. }
  7736. function _commonUpload ( editor, conf, dropCallback, multiple )
  7737. {
  7738. var btnClass = editor.classes.form.buttonInternal;
  7739. var container = $(
  7740. '<div class="editor_upload">'+
  7741. '<div class="eu_table">'+
  7742. '<div class="row">'+
  7743. '<div class="cell upload limitHide">'+
  7744. '<button class="'+btnClass+'" />'+
  7745. '<input type="file" '+(multiple ? 'multiple' : '')+'/>'+
  7746. '</div>'+
  7747. '<div class="cell clearValue">'+
  7748. '<button class="'+btnClass+'" />'+
  7749. '</div>'+
  7750. '</div>'+
  7751. '<div class="row second">'+
  7752. '<div class="cell limitHide">'+
  7753. '<div class="drop"><span/></div>'+
  7754. '</div>'+
  7755. '<div class="cell">'+
  7756. '<div class="rendered"/>'+
  7757. '</div>'+
  7758. '</div>'+
  7759. '</div>'+
  7760. '</div>'
  7761. );
  7762. conf._input = container;
  7763. conf._enabled = true;
  7764. if ( conf.id ) {
  7765. container.find('input[type=file]').attr('id', Editor.safeId( conf.id ));
  7766. }
  7767. if ( conf.attr ) {
  7768. container.find('input[type=file]').attr(conf.attr);
  7769. }
  7770. _buttonText( conf );
  7771. if ( window.FileReader && conf.dragDrop !== false ) {
  7772. container.find('div.drop span').text(
  7773. conf.dragDropText || "Drag and drop a file here to upload"
  7774. );
  7775. var dragDrop = container.find('div.drop');
  7776. dragDrop
  7777. .on( 'drop', function (e) {
  7778. if ( conf._enabled ) {
  7779. Editor.upload( editor, conf, e.originalEvent.dataTransfer.files, _buttonText, dropCallback );
  7780. dragDrop.removeClass('over');
  7781. }
  7782. return false;
  7783. } )
  7784. .on( 'dragleave dragexit', function (e) {
  7785. if ( conf._enabled ) {
  7786. dragDrop.removeClass('over');
  7787. }
  7788. return false;
  7789. } )
  7790. .on( 'dragover', function (e) {
  7791. if ( conf._enabled ) {
  7792. dragDrop.addClass('over');
  7793. }
  7794. return false;
  7795. } );
  7796. // When an Editor is open with a file upload input there is a
  7797. // reasonable chance that the user will miss the drop point when
  7798. // dragging and dropping. Rather than loading the file in the browser,
  7799. // we want nothing to happen, otherwise the form will be lost.
  7800. editor
  7801. .on( 'open', function () {
  7802. $('body').on( 'dragover.DTE_Upload drop.DTE_Upload', function (e) {
  7803. return false;
  7804. } );
  7805. } )
  7806. .on( 'close', function () {
  7807. $('body').off( 'dragover.DTE_Upload drop.DTE_Upload' );
  7808. } );
  7809. }
  7810. else {
  7811. container.addClass( 'noDrop' );
  7812. container.append( container.find('div.rendered') );
  7813. }
  7814. container.find('div.clearValue button').on( 'click', function (e) {
  7815. e.preventDefault();
  7816. if ( conf._enabled ) {
  7817. Editor.fieldTypes.upload.set.call( editor, conf, '' );
  7818. }
  7819. } );
  7820. container.find('input[type=file]').on('change', function () {
  7821. Editor.upload( editor, conf, this.files, _buttonText, function (ids) {
  7822. dropCallback.call( editor, ids );
  7823. // Clear the value so change will happen on the next file select,
  7824. // even if it is the same file
  7825. container.find('input[type=file]').val('');
  7826. } );
  7827. } );
  7828. return container;
  7829. }
  7830. // Typically a change event caused by the end user will be added to a queue that
  7831. // the browser will handle when no other script is running. However, using
  7832. // `$().trigger()` will cause it to happen immediately, so in order to simulate
  7833. // the standard browser behaviour we use setTimeout. This also means that
  7834. // `dependent()` and other change event listeners will trigger when the field
  7835. // values have all been set, rather than as they are being set - 31594
  7836. function _triggerChange ( input ) {
  7837. setTimeout( function () {
  7838. input.trigger( 'change', {editor: true, editorSet: true} ); // editorSet legacy
  7839. }, 0 );
  7840. }
  7841. // A number of the fields in this file use the same get, set, enable and disable
  7842. // methods (specifically the text based controls), so in order to reduce the code
  7843. // size, we just define them once here in our own local base model for the field
  7844. // types.
  7845. var baseFieldType = $.extend( true, {}, Editor.models.fieldType, {
  7846. get: function ( conf ) {
  7847. return conf._input.val();
  7848. },
  7849. set: function ( conf, val ) {
  7850. conf._input.val( val );
  7851. _triggerChange( conf._input );
  7852. },
  7853. enable: function ( conf ) {
  7854. conf._input.prop( 'disabled', false );
  7855. },
  7856. disable: function ( conf ) {
  7857. conf._input.prop( 'disabled', true );
  7858. },
  7859. canReturnSubmit: function ( conf, node ) {
  7860. return true;
  7861. }
  7862. } );
  7863. fieldTypes.hidden = {
  7864. create: function ( conf ) {
  7865. conf._val = conf.value;
  7866. return null;
  7867. },
  7868. get: function ( conf ) {
  7869. return conf._val;
  7870. },
  7871. set: function ( conf, val ) {
  7872. conf._val = val;
  7873. }
  7874. };
  7875. fieldTypes.readonly = $.extend( true, {}, baseFieldType, {
  7876. create: function ( conf ) {
  7877. conf._input = $('<input/>').attr( $.extend( {
  7878. id: Editor.safeId( conf.id ),
  7879. type: 'text',
  7880. readonly: 'readonly'
  7881. }, conf.attr || {} ) );
  7882. return conf._input[0];
  7883. }
  7884. } );
  7885. fieldTypes.text = $.extend( true, {}, baseFieldType, {
  7886. create: function ( conf ) {
  7887. conf._input = $('<input/>').attr( $.extend( {
  7888. id: Editor.safeId( conf.id ),
  7889. type: 'text'
  7890. }, conf.attr || {} ) );
  7891. return conf._input[0];
  7892. }
  7893. } );
  7894. fieldTypes.password = $.extend( true, {}, baseFieldType, {
  7895. create: function ( conf ) {
  7896. conf._input = $('<input/>').attr( $.extend( {
  7897. id: Editor.safeId( conf.id ),
  7898. type: 'password'
  7899. }, conf.attr || {} ) );
  7900. return conf._input[0];
  7901. }
  7902. } );
  7903. fieldTypes.textarea = $.extend( true, {}, baseFieldType, {
  7904. create: function ( conf ) {
  7905. conf._input = $('<textarea/>').attr( $.extend( {
  7906. id: Editor.safeId( conf.id )
  7907. }, conf.attr || {} ) );
  7908. return conf._input[0];
  7909. },
  7910. canReturnSubmit: function ( conf, node ) {
  7911. return false;
  7912. }
  7913. } );
  7914. fieldTypes.select = $.extend( true, {}, baseFieldType, {
  7915. // Locally "private" function that can be reused for the create and update methods
  7916. _addOptions: function ( conf, opts, append ) {
  7917. var elOpts = conf._input[0].options;
  7918. var countOffset = 0;
  7919. if ( ! append ) {
  7920. elOpts.length = 0;
  7921. if ( conf.placeholder !== undefined ) {
  7922. var placeholderValue = conf.placeholderValue !== undefined ?
  7923. conf.placeholderValue :
  7924. '';
  7925. countOffset += 1;
  7926. elOpts[0] = new Option( conf.placeholder, placeholderValue );
  7927. var disabled = conf.placeholderDisabled !== undefined ?
  7928. conf.placeholderDisabled :
  7929. true;
  7930. elOpts[0].hidden = disabled; // can't be hidden if not disabled!
  7931. elOpts[0].disabled = disabled;
  7932. elOpts[0]._editor_val = placeholderValue;
  7933. }
  7934. }
  7935. else {
  7936. countOffset = elOpts.length;
  7937. }
  7938. if ( opts ) {
  7939. Editor.pairs( opts, conf.optionsPair, function ( val, label, i, attr ) {
  7940. var option = new Option( label, val );
  7941. option._editor_val = val;
  7942. if ( attr ) {
  7943. $(option).attr( attr );
  7944. }
  7945. elOpts[ i+countOffset ] = option;
  7946. } );
  7947. }
  7948. },
  7949. create: function ( conf ) {
  7950. conf._input = $('<select/>')
  7951. .attr( $.extend( {
  7952. id: Editor.safeId( conf.id ),
  7953. multiple: conf.multiple === true
  7954. }, conf.attr || {} ) )
  7955. .on( 'change.dte', function (e, d) {
  7956. // On change, get the user selected value and store it as the
  7957. // last set, so `update` can reflect it. This way `_lastSet`
  7958. // always gives the intended value, be it set via the API or by
  7959. // the end user.
  7960. if ( ! d || ! d.editor ) {
  7961. conf._lastSet = fieldTypes.select.get( conf );
  7962. }
  7963. } );
  7964. fieldTypes.select._addOptions( conf, conf.options || conf.ipOpts );
  7965. return conf._input[0];
  7966. },
  7967. update: function ( conf, options, append ) {
  7968. fieldTypes.select._addOptions( conf, options, append );
  7969. // Attempt to set the last selected value (set by the API or the end
  7970. // user, they get equal priority)
  7971. var lastSet = conf._lastSet;
  7972. if ( lastSet !== undefined ) {
  7973. fieldTypes.select.set( conf, lastSet, true );
  7974. }
  7975. _triggerChange( conf._input );
  7976. },
  7977. get: function ( conf ) {
  7978. var val = conf._input.find('option:selected').map( function () {
  7979. return this._editor_val;
  7980. } ).toArray();
  7981. if ( conf.multiple ) {
  7982. return conf.separator ?
  7983. val.join( conf.separator ) :
  7984. val;
  7985. }
  7986. return val.length ? val[0] : null;
  7987. },
  7988. set: function ( conf, val, localUpdate ) {
  7989. if ( ! localUpdate ) {
  7990. conf._lastSet = val;
  7991. }
  7992. // Can't just use `$().val()` because it won't work with strong types
  7993. if ( conf.multiple && conf.separator && ! $.isArray( val ) ) {
  7994. val = typeof val === 'string' ?
  7995. val.split( conf.separator ) :
  7996. [];
  7997. }
  7998. else if ( ! $.isArray( val ) ) {
  7999. val = [ val ];
  8000. }
  8001. var i, len=val.length, found, allFound = false;
  8002. var options = conf._input.find('option');
  8003. conf._input.find('option').each( function () {
  8004. found = false;
  8005. for ( i=0 ; i<len ; i++ ) {
  8006. // Weak typing
  8007. if ( this._editor_val == val[i] ) {
  8008. found = true;
  8009. allFound = true;
  8010. break;
  8011. }
  8012. }
  8013. this.selected = found;
  8014. } );
  8015. // If there is a placeholder, we might need to select it if nothing else
  8016. // was selected. It doesn't make sense to select when multi is enabled
  8017. if ( conf.placeholder && ! allFound && ! conf.multiple && options.length ) {
  8018. options[0].selected = true;
  8019. }
  8020. // Update will call change itself, otherwise multiple might be called
  8021. if ( ! localUpdate ) {
  8022. _triggerChange( conf._input );
  8023. }
  8024. return allFound;
  8025. },
  8026. destroy: function ( conf ) {
  8027. conf._input.off( 'change.dte' );
  8028. }
  8029. } );
  8030. fieldTypes.checkbox = $.extend( true, {}, baseFieldType, {
  8031. // Locally "private" function that can be reused for the create and update methods
  8032. _addOptions: function ( conf, opts, append ) {
  8033. var val, label;
  8034. var jqInput = conf._input;
  8035. var offset = 0;
  8036. if ( ! append ) {
  8037. jqInput.empty();
  8038. }
  8039. else {
  8040. offset = $('input', jqInput).length;
  8041. }
  8042. if ( opts ) {
  8043. Editor.pairs( opts, conf.optionsPair, function ( val, label, i, attr ) {
  8044. jqInput.append(
  8045. '<div>'+
  8046. '<input id="'+Editor.safeId( conf.id )+'_'+(i+offset)+'" type="checkbox" />'+
  8047. '<label for="'+Editor.safeId( conf.id )+'_'+(i+offset)+'">'+label+'</label>'+
  8048. '</div>'
  8049. );
  8050. $('input:last', jqInput).attr('value', val)[0]._editor_val = val;
  8051. if ( attr ) {
  8052. $('input:last', jqInput).attr( attr );
  8053. }
  8054. } );
  8055. }
  8056. },
  8057. create: function ( conf ) {
  8058. conf._input = $('<div />');
  8059. fieldTypes.checkbox._addOptions( conf, conf.options || conf.ipOpts );
  8060. return conf._input[0];
  8061. },
  8062. get: function ( conf ) {
  8063. var out = [];
  8064. var selected = conf._input.find('input:checked');
  8065. if ( selected.length ) {
  8066. selected.each( function () {
  8067. out.push( this._editor_val );
  8068. } );
  8069. }
  8070. else if ( conf.unselectedValue !== undefined ) {
  8071. out.push( conf.unselectedValue );
  8072. }
  8073. return conf.separator === undefined || conf.separator === null ?
  8074. out :
  8075. out.join(conf.separator);
  8076. },
  8077. set: function ( conf, val ) {
  8078. var jqInputs = conf._input.find('input');
  8079. if ( ! $.isArray(val) && typeof val === 'string' ) {
  8080. val = val.split( conf.separator || '|' );
  8081. }
  8082. else if ( ! $.isArray(val) ) {
  8083. val = [ val ];
  8084. }
  8085. var i, len=val.length, found;
  8086. jqInputs.each( function () {
  8087. found = false;
  8088. for ( i=0 ; i<len ; i++ ) {
  8089. if ( this._editor_val == val[i] ) {
  8090. found = true;
  8091. break;
  8092. }
  8093. }
  8094. this.checked = found;
  8095. } );
  8096. _triggerChange( jqInputs );
  8097. },
  8098. enable: function ( conf ) {
  8099. conf._input.find('input').prop('disabled', false);
  8100. },
  8101. disable: function ( conf ) {
  8102. conf._input.find('input').prop('disabled', true);
  8103. },
  8104. update: function ( conf, options, append ) {
  8105. // Get the current value
  8106. var checkbox = fieldTypes.checkbox;
  8107. var currVal = checkbox.get( conf );
  8108. checkbox._addOptions( conf, options, append );
  8109. checkbox.set( conf, currVal );
  8110. }
  8111. } );
  8112. fieldTypes.radio = $.extend( true, {}, baseFieldType, {
  8113. // Locally "private" function that can be reused for the create and update methods
  8114. _addOptions: function ( conf, opts, append ) {
  8115. var val, label;
  8116. var jqInput = conf._input;
  8117. var offset = 0;
  8118. if ( ! append ) {
  8119. jqInput.empty();
  8120. }
  8121. else {
  8122. offset = $('input', jqInput).length;
  8123. }
  8124. if ( opts ) {
  8125. Editor.pairs( opts, conf.optionsPair, function ( val, label, i, attr ) {
  8126. jqInput.append(
  8127. '<div>'+
  8128. '<input id="'+Editor.safeId( conf.id )+'_'+(i+offset)+'" type="radio" name="'+conf.name+'" />'+
  8129. '<label for="'+Editor.safeId( conf.id )+'_'+(i+offset)+'">'+label+'</label>'+
  8130. '</div>'
  8131. );
  8132. $('input:last', jqInput).attr('value', val)[0]._editor_val = val;
  8133. if ( attr ) {
  8134. $('input:last', jqInput).attr( attr );
  8135. }
  8136. } );
  8137. }
  8138. },
  8139. create: function ( conf ) {
  8140. conf._input = $('<div />');
  8141. fieldTypes.radio._addOptions( conf, conf.options || conf.ipOpts );
  8142. // this is ugly, but IE6/7 has a problem with radio elements that are created
  8143. // and checked before being added to the DOM! Basically it doesn't check them. As
  8144. // such we use the _preChecked property to set cache the checked button and then
  8145. // check it again when the display is shown. This has no effect on other browsers
  8146. // other than to cook a few clock cycles.
  8147. this.on('open', function () {
  8148. conf._input.find('input').each( function () {
  8149. if ( this._preChecked ) {
  8150. this.checked = true;
  8151. }
  8152. } );
  8153. } );
  8154. return conf._input[0];
  8155. },
  8156. get: function ( conf ) {
  8157. var el = conf._input.find('input:checked');
  8158. return el.length ? el[0]._editor_val : undefined;
  8159. },
  8160. set: function ( conf, val ) {
  8161. var that = this;
  8162. conf._input.find('input').each( function () {
  8163. this._preChecked = false;
  8164. if ( this._editor_val == val ) {
  8165. this.checked = true;
  8166. this._preChecked = true;
  8167. }
  8168. else {
  8169. // In a detached DOM tree, there is no relationship between the
  8170. // input elements, so we need to uncheck any element that does
  8171. // not match the value
  8172. this.checked = false;
  8173. this._preChecked = false;
  8174. }
  8175. } );
  8176. _triggerChange( conf._input.find('input:checked') );
  8177. },
  8178. enable: function ( conf ) {
  8179. conf._input.find('input').prop('disabled', false);
  8180. },
  8181. disable: function ( conf ) {
  8182. conf._input.find('input').prop('disabled', true);
  8183. },
  8184. update: function ( conf, options, append ) {
  8185. var radio = fieldTypes.radio;
  8186. var currVal = radio.get( conf );
  8187. radio._addOptions( conf, options, append );
  8188. // Select the current value if it exists in the new data set, otherwise
  8189. // select the first radio input so there is always a value selected
  8190. var inputs = conf._input.find('input');
  8191. radio.set( conf, inputs.filter('[value="'+currVal+'"]').length ?
  8192. currVal :
  8193. inputs.eq(0).attr('value')
  8194. );
  8195. }
  8196. } );
  8197. fieldTypes.date = $.extend( true, {}, baseFieldType, {
  8198. create: function ( conf ) {
  8199. conf._input = $('<input />').attr( $.extend( {
  8200. id: Editor.safeId( conf.id ),
  8201. type: 'text'
  8202. }, conf.attr ) );
  8203. if ( $.datepicker ) {
  8204. // jQuery UI date picker
  8205. conf._input.addClass( 'jqueryui' );
  8206. if ( ! conf.dateFormat ) {
  8207. conf.dateFormat = $.datepicker.RFC_2822;
  8208. }
  8209. // Allow the element to be attached to the DOM
  8210. setTimeout( function () {
  8211. $( conf._input ).datepicker( $.extend( {
  8212. dateFormat: conf.dateFormat,
  8213. buttonImage: conf.dateImage,
  8214. buttonImageOnly: true,
  8215. onSelect: function () {
  8216. conf._input.focus().click();
  8217. }
  8218. }, conf.opts ) );
  8219. $('#ui-datepicker-div').css('display','none');
  8220. }, 10 );
  8221. }
  8222. else {
  8223. // HTML5 (only Chrome and Edge on the desktop support this atm)
  8224. conf._input.attr( 'type', 'date' );
  8225. }
  8226. return conf._input[0];
  8227. },
  8228. // use default get method as will work for all
  8229. set: function ( conf, val ) {
  8230. if ( $.datepicker && conf._input.hasClass('hasDatepicker') ) {
  8231. // Due to the async init of the control it is possible that we might
  8232. // try to set a value before it has been initialised!
  8233. conf._input.datepicker( "setDate" , val ).change();
  8234. }
  8235. else {
  8236. $(conf._input).val( val );
  8237. }
  8238. },
  8239. enable: function ( conf ) {
  8240. if ( $.datepicker ) {
  8241. conf._input.datepicker( "enable" );
  8242. }
  8243. else {
  8244. $(conf._input).prop( 'disabled', false );
  8245. }
  8246. },
  8247. disable: function ( conf ) {
  8248. if ( $.datepicker ) {
  8249. conf._input.datepicker( "disable" );
  8250. }
  8251. else {
  8252. $(conf._input).prop( 'disabled', true );
  8253. }
  8254. },
  8255. owns: function ( conf, node ) {
  8256. return $(node).parents('div.ui-datepicker').length || $(node).parents('div.ui-datepicker-header').length ?
  8257. true :
  8258. false;
  8259. }
  8260. } );
  8261. fieldTypes.datetime = $.extend( true, {}, baseFieldType, {
  8262. create: function ( conf ) {
  8263. conf._input = $('<input />').attr( $.extend( true, {
  8264. id: Editor.safeId( conf.id ),
  8265. type: 'text'
  8266. }, conf.attr ) );
  8267. conf._picker = new Editor.DateTime( conf._input, $.extend( {
  8268. format: conf.displayFormat || conf.format, // can be undefined
  8269. i18n: this.i18n.datetime,
  8270. onChange: function () {
  8271. setTimeout( function () {
  8272. conf._input.trigger( 'change' );
  8273. }, 0 );
  8274. }
  8275. }, conf.opts ) );
  8276. conf._closeFn = function () {
  8277. conf._picker.hide();
  8278. };
  8279. if ( conf.keyInput === false ) {
  8280. conf._input.on('keydown', function (e) {
  8281. e.preventDefault();
  8282. } );
  8283. }
  8284. this.on( 'close', conf._closeFn );
  8285. return conf._input[0];
  8286. },
  8287. get: function ( conf ) {
  8288. let val = conf._input.val();
  8289. let inst = conf._picker.c;
  8290. // If a wire format is specified, convert the display format to the wire
  8291. return conf.wireFormat && moment ?
  8292. moment(val, inst.format, inst.momentLocale, inst.momentStrict).format(conf.wireFormat) :
  8293. val;
  8294. },
  8295. set: function ( conf, val ) {
  8296. let inst = conf._picker.c;
  8297. // If there is a wire format, convert it to the display format
  8298. conf._picker.val( conf.wireFormat && moment ?
  8299. moment(val, conf.wireFormat, inst.momentLocale, inst.momentStrict).format(inst.format) :
  8300. val
  8301. );
  8302. _triggerChange( conf._input );
  8303. },
  8304. // default disable and enable options are okay
  8305. owns: function ( conf, node ) {
  8306. return conf._picker.owns( node );
  8307. },
  8308. errorMessage: function ( conf, msg ) {
  8309. conf._picker.errorMsg( msg );
  8310. },
  8311. destroy: function ( conf ) {
  8312. this.off( 'close', conf._closeFn );
  8313. conf._input.off( 'keydown' );
  8314. conf._picker.destroy();
  8315. },
  8316. minDate: function ( conf, min ) {
  8317. conf._picker.min( min );
  8318. },
  8319. maxDate: function ( conf, max ) {
  8320. conf._picker.max( max );
  8321. }
  8322. } );
  8323. fieldTypes.upload = $.extend( true, {}, baseFieldType, {
  8324. create: function ( conf ) {
  8325. var editor = this;
  8326. var container = _commonUpload( editor, conf, function ( val ) {
  8327. Editor.fieldTypes.upload.set.call( editor, conf, val[0] );
  8328. editor._event( 'postUpload', [ conf.name, val[0] ] );
  8329. } );
  8330. return container;
  8331. },
  8332. get: function ( conf ) {
  8333. return conf._val;
  8334. },
  8335. set: function ( conf, val ) {
  8336. conf._val = val;
  8337. var container = conf._input;
  8338. if ( conf.display ) {
  8339. var rendered = container.find('div.rendered');
  8340. if ( conf._val ) {
  8341. rendered.html( conf.display( conf._val ) );
  8342. }
  8343. else {
  8344. rendered
  8345. .empty()
  8346. .append( '<span>'+( conf.noFileText || 'No file' )+'</span>' );
  8347. }
  8348. }
  8349. var button = container.find('div.clearValue button');
  8350. if ( val && conf.clearText ) {
  8351. button.html( conf.clearText );
  8352. container.removeClass( 'noClear' );
  8353. }
  8354. else {
  8355. container.addClass( 'noClear' );
  8356. }
  8357. conf._input.find('input').triggerHandler( 'upload.editor', [ conf._val ] );
  8358. },
  8359. enable: function ( conf ) {
  8360. conf._input.find('input').prop('disabled', false);
  8361. conf._enabled = true;
  8362. },
  8363. disable: function ( conf ) {
  8364. conf._input.find('input').prop('disabled', true);
  8365. conf._enabled = false;
  8366. },
  8367. canReturnSubmit: function ( conf, node ) {
  8368. return false;
  8369. }
  8370. } );
  8371. fieldTypes.uploadMany = $.extend( true, {}, baseFieldType, {
  8372. _showHide: function ( conf ) {
  8373. if ( ! conf.limit ) {
  8374. return;
  8375. }
  8376. conf._container.find('div.limitHide').css(
  8377. 'display',
  8378. conf._val.length >= conf.limit ?
  8379. 'none' :
  8380. 'block'
  8381. );
  8382. // Used by the Editor.upload static function to truncate if too many
  8383. // files are selected for upload
  8384. conf._limitLeft = conf.limit - conf._val.length;
  8385. },
  8386. create: function ( conf ) {
  8387. var editor = this;
  8388. var container = _commonUpload( editor, conf, function ( val ) {
  8389. conf._val = conf._val.concat( val );
  8390. Editor.fieldTypes.uploadMany.set.call( editor, conf, conf._val );
  8391. editor._event( 'postUpload', [ conf.name, conf._val ] );
  8392. }, true );
  8393. container
  8394. .addClass( 'multi' )
  8395. .on( 'click', 'button.remove', function (e) {
  8396. e.stopPropagation();
  8397. if ( conf._enabled ) {
  8398. var idx = $(this).data('idx');
  8399. conf._val.splice( idx, 1 );
  8400. Editor.fieldTypes.uploadMany.set.call( editor, conf, conf._val );
  8401. }
  8402. } );
  8403. conf._container = container;
  8404. return container;
  8405. },
  8406. get: function ( conf ) {
  8407. return conf._val;
  8408. },
  8409. set: function ( conf, val ) {
  8410. // Default value for fields is an empty string, whereas we want []
  8411. if ( ! val ) {
  8412. val = [];
  8413. }
  8414. if ( ! $.isArray( val ) ) {
  8415. throw 'Upload collections must have an array as a value';
  8416. }
  8417. conf._val = val;
  8418. var that = this;
  8419. var container = conf._input;
  8420. if ( conf.display ) {
  8421. var rendered = container.find('div.rendered').empty();
  8422. if ( val.length ) {
  8423. var list = $('<ul/>').appendTo( rendered );
  8424. $.each( val, function ( i, file ) {
  8425. list.append(
  8426. '<li>'+
  8427. conf.display( file, i )+
  8428. ' <button class="'+that.classes.form.button+' remove" data-idx="'+i+'">&times;</button>'+
  8429. '</li>'
  8430. );
  8431. } );
  8432. }
  8433. else {
  8434. rendered.append( '<span>'+( conf.noFileText || 'No files' )+'</span>' );
  8435. }
  8436. }
  8437. Editor.fieldTypes.uploadMany._showHide( conf );
  8438. conf._input.find('input').triggerHandler( 'upload.editor', [ conf._val ] );
  8439. },
  8440. enable: function ( conf ) {
  8441. conf._input.find('input').prop('disabled', false);
  8442. conf._enabled = true;
  8443. },
  8444. disable: function ( conf ) {
  8445. conf._input.find('input').prop('disabled', true);
  8446. conf._enabled = false;
  8447. },
  8448. canReturnSubmit: function ( conf, node ) {
  8449. return false;
  8450. }
  8451. } );
  8452. }());
  8453. // If there are field types available on DataTables we copy them in (after the
  8454. // built in ones to allow overrides) and then expose the field types object.
  8455. if ( DataTable.ext.editorFields ) {
  8456. $.extend( Editor.fieldTypes, DataTable.ext.editorFields );
  8457. }
  8458. DataTable.ext.editorFields = Editor.fieldTypes;
  8459. /**
  8460. * File information for uploads
  8461. */
  8462. Editor.files = {};
  8463. /**
  8464. * Name of this class
  8465. * @constant CLASS
  8466. * @type String
  8467. * @default Editor
  8468. */
  8469. Editor.prototype.CLASS = "Editor";
  8470. /**
  8471. * DataTables Editor version
  8472. * @constant Editor.VERSION
  8473. * @type String
  8474. * @default See code
  8475. * @static
  8476. */
  8477. Editor.version = "1.9.2";
  8478. // Event documentation for JSDoc
  8479. /**
  8480. * Processing event, fired when Editor submits data to the server for processing.
  8481. * This can be used to provide your own processing indicator if your UI framework
  8482. * already has one.
  8483. * @name Editor#processing
  8484. * @event
  8485. * @param {event} e jQuery event object
  8486. * @param {boolean} processing Flag for if the processing is running (true) or
  8487. * not (false).
  8488. */
  8489. /**
  8490. * Form displayed event, fired when the form is made available in the DOM. This
  8491. * can be useful for fields that require height and width calculations to be
  8492. * performed since the element is not available in the document until the
  8493. * form is displayed.
  8494. * @name Editor#open
  8495. * @event
  8496. * @param {event} e jQuery event object
  8497. * @param {string} type Editing type
  8498. */
  8499. /**
  8500. * Before a form is displayed, this event is fired. It allows the open action to be
  8501. * cancelled by returning false from the function.
  8502. * @name Editor#preOpen
  8503. * @event
  8504. * @param {event} e jQuery event object
  8505. */
  8506. /**
  8507. * Form hidden event, fired when the form is removed from the document. The
  8508. * of the compliment `open` event.
  8509. * @name Editor#close
  8510. * @event
  8511. * @param {event} e jQuery event object
  8512. */
  8513. /**
  8514. * Before a form is closed, this event is fired. It allows the close action to be
  8515. * cancelled by returning false from the function. This can be useful for confirming
  8516. * that the user actually wants to close the display (if they have unsaved changes
  8517. * for example).
  8518. * @name Editor#preClose
  8519. * @event
  8520. * @param {event} e jQuery event object
  8521. * @param {string} trigger Action that caused the close event - can be undefined.
  8522. * Typically defined by the display controller.
  8523. */
  8524. /**
  8525. * Emitted before a form blur occurs. A form blur is similar to a close, but
  8526. * is triggered by a user, typically, clicking on the background, while a close
  8527. * occurs due to a click on the close button. A blur can precede a close.
  8528. * @name Editor#preBlur
  8529. * @event
  8530. * @param {event} e jQuery event object
  8531. */
  8532. /**
  8533. * Pre-submit event for the form, fired just before the data is submitted to
  8534. * the server. This event allows you to modify the data that will be submitted
  8535. * to the server. Note that this event runs after the 'formatdata' callback
  8536. * function of the {@link Editor#submit} API method.
  8537. * @name Editor#preSubmit
  8538. * @event
  8539. * @param {event} e jQuery event object
  8540. * @param {object} data The data object that will be submitted to the server
  8541. * @param {string} action The action type for this submit - `create`, `edit` or
  8542. * `remove`.
  8543. */
  8544. /**
  8545. * Post-submit event for the form, fired immediately after the data has been
  8546. * loaded by the Ajax call, allowing modification or any other interception
  8547. * of the data returned form the server.
  8548. * @name Editor#postSubmit
  8549. * @event
  8550. * @param {event} e jQuery event object
  8551. * @param {object} json The JSON object returned from the server
  8552. * @param {object} data The data object that was be submitted to the server
  8553. * @param {string} action The action type for this submit - `create`, `edit` or
  8554. * `remove`.
  8555. */
  8556. /**
  8557. * Submission complete event, fired when data has been submitted to the server and
  8558. * after any of the return handling code has been run (updating the DataTable
  8559. * for example). Note that unlike `submitSuccess` and `submitError`, `submitComplete`
  8560. * will be fired for both a successful submission and an error. Additionally this
  8561. * event will be fired after `submitSuccess` or `submitError`.
  8562. * @name Editor#submitComplete
  8563. * @event
  8564. * @param {event} e jQuery event object
  8565. * @param {object} json The JSON object returned from the server
  8566. * @param {object} data The data that was used to update the DataTable
  8567. */
  8568. /**
  8569. * Submission complete and successful event, fired when data has been successfully
  8570. * submitted to the server and all actions required by the returned data (inserting
  8571. * or updating a row) have been completed.
  8572. * @name Editor#submitSuccess
  8573. * @event
  8574. * @param {event} e jQuery event object
  8575. * @param {object} json The JSON object returned from the server
  8576. * @param {object} data The data that was used to update the DataTable
  8577. */
  8578. /**
  8579. * Submission complete, but in error event, fired when data has been submitted to
  8580. * the server but an error occurred on the server (typically a JSON formatting error)
  8581. * @name Editor#submitError
  8582. * @event
  8583. * @param {event} e jQuery event object
  8584. * @param {object} xhr The Ajax object
  8585. * @param {string} err The error message from jQuery
  8586. * @param {object} thrown The exception thrown by jQuery
  8587. * @param {object} data The data that was used to update the DataTable
  8588. */
  8589. /**
  8590. * Create method activated event, fired when the create API method has been called,
  8591. * just prior to the form being shown. Useful for manipulating the form specifically
  8592. * for the create state.
  8593. * @name Editor#initCreate
  8594. * @event
  8595. * @param {event} e jQuery event object
  8596. */
  8597. /**
  8598. * Pre-create new row event, fired just before DataTables calls the fnAddData method
  8599. * to add new data to the DataTable, allowing modification of the data that will be
  8600. * used to insert into the table.
  8601. * @name Editor#preCreate
  8602. * @event
  8603. * @param {event} e jQuery event object
  8604. * @param {object} json The JSON object returned from the server
  8605. * @param {object} data The data that will be used to update the DataTable
  8606. */
  8607. /**
  8608. * Create new row event, fired when a new row has been created in the DataTable by
  8609. * a form submission. This is called just after the fnAddData call to the DataTable.
  8610. * @name Editor#create
  8611. * @event
  8612. * @param {event} e jQuery event object
  8613. * @param {object} json The JSON object returned from the server
  8614. * @param {object} data The data that was used to update the DataTable
  8615. */
  8616. /**
  8617. * As per the `create` event - included for naming consistency.
  8618. * @name Editor#postCreate
  8619. * @event
  8620. * @param {event} e jQuery event object
  8621. * @param {object} json The JSON object returned from the server
  8622. * @param {object} data The data that was used to update the DataTable
  8623. */
  8624. /**
  8625. * Edit method activated event, fired when the edit API method has been called,
  8626. * just prior to the form being shown. Useful for manipulating the form specifically
  8627. * for the edit state.
  8628. * @name Editor#initEdit
  8629. * @event
  8630. * @param {event} e jQuery event object
  8631. * @param {node} tr TR element of the row to be edited
  8632. * @param {array|object} data Data source array / object for the row to be
  8633. * edited
  8634. */
  8635. /**
  8636. * Pre-edit row event, fired just before DataTables calls the fnUpdate method
  8637. * to edit data in a DataTables row, allowing modification of the data that will be
  8638. * used to update the table.
  8639. * @name Editor#preEdit
  8640. * @event
  8641. * @param {event} e jQuery event object
  8642. * @param {object} json The JSON object returned from the server
  8643. * @param {object} data The data that will be used to update the DataTable
  8644. */
  8645. /**
  8646. * Edit row event, fired when a row has been edited in the DataTable by a form
  8647. * submission. This is called just after the fnUpdate call to the DataTable.
  8648. * @name Editor#edit
  8649. * @event
  8650. * @param {event} e jQuery event object
  8651. * @param {object} json The JSON object returned from the server
  8652. * @param {object} data The data that was used to update the DataTable
  8653. */
  8654. /**
  8655. * As per the `edit` event - included for naming consistency.
  8656. * @name Editor#postEdit
  8657. * @event
  8658. * @param {event} e jQuery event object
  8659. * @param {object} json The JSON object returned from the server
  8660. * @param {object} data The data that was used to update the DataTable
  8661. */
  8662. /**
  8663. * Remove method activated event, fired when the remove API method has been
  8664. * called, just prior to the form being shown. Useful for manipulating the form
  8665. * specifically for the remove state.
  8666. * @name Editor#initRemove
  8667. * @event
  8668. * @param {event} e jQuery event object
  8669. * @param {array} trs Array of the TR elements for the removed to be deleted
  8670. * @param {array} data Array of the data source array / objects for the rows to
  8671. * be deleted. This is in the same index order as the TR nodes in the second
  8672. * parameter.
  8673. */
  8674. /**
  8675. * Pre-remove row event, fired just before DataTables calls the fnDeleteRow method
  8676. * to delete a DataTables row.
  8677. * @name Editor#preRemove
  8678. * @event
  8679. * @param {event} e jQuery event object
  8680. * @param {object} json The JSON object returned from the server
  8681. */
  8682. /**
  8683. * Row removed event, fired when a row has been removed in the DataTable by a form
  8684. * submission. This is called just after the fnDeleteRow call to the DataTable.
  8685. * @name Editor#remove
  8686. * @event
  8687. * @param {event} e jQuery event object
  8688. * @param {object} json The JSON object returned from the server
  8689. */
  8690. /**
  8691. * As per the `postRemove` event - included for naming consistency.
  8692. * @name Editor#postRemove
  8693. * @event
  8694. * @param {event} e jQuery event object
  8695. * @param {object} json The JSON object returned from the server
  8696. */
  8697. /**
  8698. * Set data event, fired when the data is gathered from the form to be used
  8699. * to update the DataTable. This is a "global" version of `preCreate`, `preEdit`
  8700. * and `preRemove` and can be used to manipulate the data that will be added
  8701. * to the DataTable for all three actions
  8702. * @name Editor#setData
  8703. * @event
  8704. * @param {event} e jQuery event object
  8705. * @param {object} json The JSON object returned from the server
  8706. * @param {object} data The data that will be used to update the DataTable
  8707. * @param {string} action The action being performed by the form - 'create',
  8708. * 'edit' or 'remove'.
  8709. */
  8710. /**
  8711. * Initialisation of the Editor instance has been completed.
  8712. * @name Editor#initComplete
  8713. * @event
  8714. * @param {event} e jQuery event object
  8715. */
  8716. return Editor;
  8717. }));