webrouter.pp 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2017 by the Free Pascal development team
  4. HTTPRoute: HTTP request router
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. }
  11. {
  12. Note:
  13. The MatchPattern routine was taken from Brook Framework's router unit, by Silvio Clecio.
  14. }
  15. {$mode objfpc}
  16. unit webrouter;
  17. interface
  18. uses
  19. Classes, SysUtils, web;
  20. Type
  21. EHTTPRoute = Class(Exception);
  22. TRawLocation = String;
  23. TScrollPoint = record
  24. X,Y : Double;
  25. end;
  26. // Forward definitions;
  27. TRouter = Class;
  28. TRoute = class;
  29. THistory = Class;
  30. TRouterClass = Class of TRouter;
  31. TRouteEvent = Reference to Procedure (URl : String; aRoute : TRoute; Params: TStrings);
  32. TTransitionResult = (trOK,trError,trAbort);
  33. THistoryKind = (hkAuto,hkAbstract,hkHash,hkHTML5);
  34. TTransitionNotifyEvent = Reference to Procedure (Sender : THistory; aLocation : String; aRoute : TRoute);
  35. TAllowTransitionEvent = Reference to Procedure (Sender : THistory; aOld, aNew : TRoute; Params : TStrings; var Allow : Boolean);
  36. { THistory }
  37. THistory = Class(TObject)
  38. Private
  39. FOnAllow: TAllowTransitionEvent;
  40. FRouter: TRouter;
  41. FOnChange : TNotifyEvent;
  42. FOnReady : TTransitionNotifyEvent;
  43. FOnError : TTransitionNotifyEvent;
  44. FCurrent : TRoute;
  45. FBase : String;
  46. function GetCurrent: TRoute;
  47. Protected
  48. procedure SetupListeners; virtual;
  49. Function doPush (location: TRawLocation) : TTransitionResult; virtual; abstract;
  50. Function doreplace (location: TRawLocation) : TTransitionResult; virtual; abstract;
  51. function doGo(N: integer): TTransitionResult; virtual; abstract;
  52. procedure ensureURL (push : boolean = false); virtual; abstract;
  53. Public
  54. Constructor Create(aRouter : TRouter); reintroduce;
  55. Constructor Create(aRouter : TRouter; aBase : String); virtual;
  56. Class Function NormalizeHash(aHash : String) : string;
  57. Procedure UpdateRoute (aRoute : TRoute);
  58. Destructor Destroy; override;
  59. Function ExpectScroll : Boolean;
  60. Function SupportsScroll : Boolean;
  61. Class function getLocation (base: string): string;
  62. Class function cleanPath(aPath : string): string;
  63. // Navigation
  64. function GetCurrentLocation: String; virtual; abstract;
  65. Function Push (location: TRawLocation) : TTransitionResult;
  66. Function Replace (location: TRawLocation) : TTransitionResult;
  67. function Go(N: integer): TTransitionResult;
  68. Function NavigateForward: TTransitionResult;
  69. Function NavigateBack: TTransitionResult;
  70. Function TransitionTo(aLocation: TRawLocation) : TTransitionResult;
  71. function ConfirmTransition(aRoute: TRoute; Params: TStrings) : TTransitionResult;
  72. Property Current : TRoute Read GetCurrent;
  73. Property Router : TRouter Read FRouter;
  74. Property OnReady : TTransitionNotifyEvent Read FOnReady Write FOnReady;
  75. Property OnError : TTransitionNotifyEvent Read FOnError Write FOnError;
  76. Property OnChange : TNotifyEvent Read FOnChange Write FOnChange;
  77. Property OnAllowTransition : TAllowTransitionEvent Read FOnAllow Write FOnAllow;
  78. property Base : String Read FBase;
  79. function Kind : THistoryKind; virtual; abstract;
  80. end;
  81. { TAbstractHistory }
  82. TAbstractHistory = Class(THistory)
  83. Private
  84. FIndex: Integer;
  85. FStack: Array of TRawLocation;
  86. procedure MaybeGrow(AIndex: Integer);
  87. Protected
  88. Function doPush (location: TRawLocation) : TTransitionResult; override;
  89. Function doReplace (location: TRawLocation) : TTransitionResult; override;
  90. function doGo(N: integer): TTransitionResult; override;
  91. Public
  92. constructor Create (router: TRouter; base: string = ''); override;
  93. function getCurrentLocation: String; override;
  94. Procedure ensureURL (Push: Boolean = False); override;
  95. function Kind : THistoryKind; override;
  96. end;
  97. { THashHistory }
  98. THashHistory = Class(THistory)
  99. Protected
  100. FlastHash : String;
  101. procedure DoHashChange; virtual;
  102. procedure SetupListeners; override;
  103. Function doPush (location: TRawLocation) : TTransitionResult; override;
  104. Function doreplace (location: TRawLocation) : TTransitionResult; override;
  105. function doGo(N: integer): TTransitionResult; override;
  106. procedure ensureURL (push : boolean = false); override;
  107. Public
  108. function getCurrentLocation: String; override;
  109. Class Procedure pushHash (path : string);
  110. Class Procedure replaceHash (path : string);
  111. class function getUrl (APath : string) : string;
  112. Class function getHash : string;
  113. function Kind : THistoryKind; override;
  114. end;
  115. { THTMLHistory }
  116. THTMLHistory = Class(THistory)
  117. Protected
  118. FlastLocation : String;
  119. procedure DoStateChange; virtual;
  120. procedure SetupListeners; override;
  121. Function doPush (location: TRawLocation) : TTransitionResult; override;
  122. Function doreplace (location: TRawLocation) : TTransitionResult; override;
  123. function doGo(N: integer): TTransitionResult; override;
  124. procedure ensureURL (push : boolean = false); override;
  125. Public
  126. function getCurrentLocation: String; override;
  127. Class Procedure pushState (path : string; doReplace : boolean = false);
  128. Class Procedure replaceState (path : string);
  129. function getUrl (ALocation : string) : string;
  130. function Kind : THistoryKind; override;
  131. end;
  132. { TRoute }
  133. TRoute = Class(TCollectionItem)
  134. private
  135. FDefault: Boolean;
  136. FEvent: TRouteEvent;
  137. FURLPattern: String;
  138. procedure SetURLPattern(AValue: String);
  139. Public
  140. Class function NormalizeURLPattern(AValue: String): String;
  141. Function Matches(Const APattern : String) : Boolean;
  142. Function MatchPattern(Const Path : String; L : TStrings) : Boolean;
  143. Procedure HandleRequest(ARouter : TRouter; Const URL : String; L : TStrings); virtual; abstract;
  144. Function FullPath : String;
  145. Published
  146. Property Default : Boolean Read FDefault Write FDefault;
  147. Property URLPattern : String Read FURLPattern Write SetURLPattern;
  148. Property Event : TRouteEvent Read FEvent Write FEvent;
  149. end;
  150. TRouteClass = Class of TRoute;
  151. { TRouteList }
  152. TRouteList = Class(TCollection)
  153. private
  154. function GetR(AIndex : Integer): TRoute;
  155. procedure SetR(AIndex : Integer; AValue: TRoute);
  156. Public
  157. Property Routes[AIndex : Integer] : TRoute Read GetR Write SetR; default;
  158. end;
  159. TRouteObject = Class(TObject)
  160. Procedure HandleRoute (Const URL : String; Params : TStrings); virtual; abstract;
  161. end;
  162. TRouteObjectClass = Class of TRouteObject;
  163. { TRouter }
  164. TBeforeRouteEvent = reference to procedure(Sender : TObject; Var ARouteURL : String);
  165. TAfterRouteEvent = reference to procedure(Sender : TObject; const ARouteURL : String);
  166. TScrollParams = Record
  167. selector : string;
  168. Position : TScrollPoint;
  169. end;
  170. TPageScrollEvent = reference to Function(Sender : TObject; aTo,aFrom : TRoute; aPosition : TScrollPoint) : TScrollParams;
  171. TRouter = Class(TComponent)
  172. Private
  173. Class Procedure DoneService;
  174. Class
  175. Var FService : TRouter;
  176. FServiceClass : TRouterClass;
  177. private
  178. FAfterRequest: TAfterRouteEvent;
  179. FBeforeRequest: TBeforeRouteEvent;
  180. FHistory: THistory;
  181. FOnScroll: TPageScrollEvent;
  182. FRoutes : TRouteList;
  183. function GetHistory: THistory;
  184. function GetHistoryKind: THistoryKind;
  185. function GetR(AIndex : Integer): TRoute;
  186. function GetRouteCount: Integer;
  187. Protected
  188. // Return an instance of given class with Pattern, Method, IsDefault filled in.
  189. function CreateHTTPRoute(AClass: TRouteClass; const APattern: String; IsDefault: Boolean ): TRoute; virtual;
  190. // Override this if you want to use another collection class.
  191. Function CreateRouteList : TRouteList; virtual;
  192. Procedure CheckDuplicate(APattern : String; isDefault : Boolean);
  193. // Actually route request. Override this for customized behaviour.
  194. function DoRouteRequest(ARoute : TRoute; Const AURL : String; AParams : TStrings) : TRoute; virtual;
  195. function DoRouteRequest(AURL : String) : TRoute;
  196. Public
  197. Constructor Create(AOwner: TComponent); override;
  198. Destructor Destroy; override;
  199. Procedure InitHistory(aKind : THistoryKind; aBase : String = '');
  200. // Delete given route by index.
  201. Procedure DeleteRoute(AIndex : Integer);
  202. // Delete given route by index.
  203. Procedure DeleteRouteByID(AID : Integer);
  204. // Delete given route by index. The route object will be freed.
  205. Procedure DeleteRoute(ARoute : TRoute);
  206. // Sanitize route path. Strips of query parameters and makes sure it ends in /
  207. class function SanitizeRoute(const Path: String): String;
  208. // Global instance.
  209. Class Function Service : TRouter;
  210. // Class for global instance when it is created;
  211. Class Function ServiceClass : TRouterClass;
  212. // This will destroy the service
  213. Class Procedure SetServiceClass(AClass : TRouterClass);
  214. // Register event based route
  215. Function RegisterRoute(Const APattern : String; AEvent: TRouteEvent; IsDefault : Boolean = False) : TRoute;overload;
  216. // Object class based route. The router is responsible for the lifetime of the object instance
  217. Function RegisterRoute(Const APattern : String; const AObjectClass: TRouteObjectClass; IsDefault : Boolean = False) : TRoute; overload;
  218. // Find route. Matches Path on the various patterns. If a pattern is found, then the method is tested.
  219. // Returns the route that matches the pattern and method.
  220. function FindHTTPRoute(const Path: String; Params: TStrings): TRoute;
  221. function GetRoute(const Path: String; Params: TStrings): TRoute;
  222. // Do actual routing. Exceptions raised will not be caught.
  223. // This bypasses the history mechanism.
  224. Function RouteRequest(Const ARouteURL : String) : TRoute;
  225. // Extract request path from URL. By default, returns the URL
  226. function GetRequestPath(const URL: String): String; virtual;
  227. // Navigation. These are easy-access methods for history.
  228. function GetCurrentLocation: String;
  229. // These pass by the history mechanism
  230. Function Push (location: TRawLocation) : TTransitionResult;
  231. Function Replace (location: TRawLocation) : TTransitionResult;
  232. function Go(N: integer): TTransitionResult;
  233. Function NavigateForward : TTransitionResult;
  234. Function NavigateBack :TTransitionResult;
  235. // Indexed access to the registered routes.
  236. Property Routes [AIndex : Integer] : TRoute Read GetR; Default;
  237. // Number of registered routes.
  238. Property RouteCount : Integer Read GetRouteCount;
  239. // Events executed before and after request. In case of exception, after is not executed.
  240. Property BeforeRequest : TBeforeRouteEvent Read FBeforeRequest Write FBeforeRequest;
  241. Property AfterRequest : TAfterRouteEvent Read FAfterRequest Write FAfterRequest;
  242. // OnScroll
  243. Property OnScroll : TPageScrollEvent Read FOnScroll Write FOnScroll;
  244. // Currently used history mechanism
  245. Property History : THistory Read GetHistory;
  246. // Kind of current history. Shortcut for History.Kind, returns hkauto if History is not assigned
  247. Property HistoryKind : THistoryKind Read GetHistoryKind;
  248. end;
  249. TWebScroll = Class
  250. Class Procedure scrollToPosition (AScroll : TScrollParams);
  251. Class function getScrollPosition : TScrollPoint;
  252. Class Procedure SaveScrollPosition;
  253. Class Procedure Setup;
  254. Class Procedure handle (router: TRouter; ato: TRoute; afrom: TRoute; isPop: boolean) ;
  255. Class Function GetStateKey : String;
  256. end;
  257. TBrowserState = Class
  258. Private
  259. Class var
  260. TheKey : String;
  261. Public
  262. Class Function GenKey : String;
  263. Class Function supportsPushState : Boolean;
  264. Class function GetStateKey : string;
  265. Class Procedure SetStateKey (akey: string);
  266. Class Procedure PushState (aUrl : string; replace : boolean);
  267. Class Procedure ReplaceState(aUrl: string);
  268. end;
  269. // Shortcut for TRouter.Service;
  270. Function Router : TRouter;
  271. Function IncludeHTTPPathDelimiter (S : String) : String;
  272. implementation
  273. uses strutils, js;
  274. Resourcestring
  275. EDuplicateRoute = 'Duplicate route pattern: %s';
  276. EDuplicateDefaultRoute = 'Duplicate default route registered with pattern: %s';
  277. function Router: TRouter;
  278. begin
  279. Result:=TRouter.Service;
  280. end;
  281. function IncludeHTTPPathDelimiter(S: String): String;
  282. begin
  283. If (Copy(S,Length(S),1)='/') then
  284. Result:=S
  285. else
  286. Result:=S+'/';
  287. end;
  288. { THTMLHistory }
  289. procedure THTMLHistory.DoStateChange;
  290. Var
  291. NewLocation : String;
  292. Old : TRoute;
  293. begin
  294. NewLocation:=getLocation(FBase);
  295. if (NewLocation=FLastLocation) then
  296. exit;
  297. old:=Current;
  298. if TransitionTo(NewLocation)=trOK then
  299. begin
  300. TWebScroll.Handle(router, Current, old, true);
  301. FLastLocation:=NewLocation;
  302. end
  303. else
  304. replaceState(FLastLocation);
  305. end;
  306. procedure THTMLHistory.SetupListeners;
  307. begin
  308. Window.addEventListener('popstate',@DoStateChange)
  309. end;
  310. function THTMLHistory.doPush(location: TRawLocation): TTransitionResult;
  311. begin
  312. pushState(GetURL(Location));
  313. Result:=trOK;
  314. end;
  315. function THTMLHistory.doreplace(location: TRawLocation): TTransitionResult;
  316. begin
  317. ReplaceState(GetURL(Location));
  318. Result:=trOK;
  319. end;
  320. function THTMLHistory.doGo(N: integer): TTransitionResult;
  321. begin
  322. window.history.go(n);
  323. Result:=trOK;
  324. end;
  325. procedure THTMLHistory.ensureURL(push: boolean);
  326. var
  327. URL,Actual,Expected : string;
  328. begin
  329. Actual:=getCurrentLocation;
  330. Expected:=FlastLocation;
  331. if (Actual<>Expected) then
  332. begin
  333. url:=getUrl(Expected);
  334. if Push then
  335. pushState(url)
  336. else
  337. replaceState(url)
  338. end;
  339. end;
  340. function THTMLHistory.getCurrentLocation: String;
  341. begin
  342. Result:=window.locationString;
  343. end;
  344. class procedure THTMLHistory.pushState(path: string; doReplace: boolean);
  345. begin
  346. TBrowserState.pushState(Path,doReplace);
  347. end;
  348. class procedure THTMLHistory.replaceState(path: string);
  349. begin
  350. pushState(Path,True);
  351. end;
  352. function THTMLHistory.getUrl(ALocation : string): string;
  353. begin
  354. Result:=IncludeHTTPPathDelimiter(FBase);
  355. While (ALocation<>'') and (Copy(ALocation,1,1)='/') do
  356. ALocation:=Copy(ALocation,2,Length(ALocation)-1);
  357. Result:=FBase+Alocation;
  358. end;
  359. function THTMLHistory.Kind: THistoryKind;
  360. begin
  361. Result:=hkHTML5;
  362. end;
  363. { THistory }
  364. function THistory.GetCurrent: TRoute;
  365. begin
  366. Result:=FCurrent;
  367. end;
  368. constructor THistory.Create(aRouter: TRouter);
  369. begin
  370. Create(aRouter,'');
  371. end;
  372. constructor THistory.Create(aRouter: TRouter; aBase: String);
  373. begin
  374. FRouter:=aRouter;
  375. FBase:=aBase;
  376. end;
  377. class function THistory.NormalizeHash(aHash: String): string;
  378. begin
  379. Result:=aHash;
  380. if Copy(Result,1,1)<>'/' then
  381. Result:='/'+Result;
  382. end;
  383. destructor THistory.Destroy;
  384. begin
  385. inherited Destroy;
  386. end;
  387. function THistory.ExpectScroll: Boolean;
  388. begin
  389. Result:=Assigned(Router) and Assigned(Router.OnScroll);
  390. end;
  391. function THistory.SupportsScroll: Boolean;
  392. begin
  393. Result:=TBrowserState.supportsPushState and ExpectScroll;
  394. end;
  395. function THistory.TransitionTo(aLocation: TRawLocation): TTransitionResult;
  396. Var
  397. Params : TStrings;
  398. R : TRoute;
  399. begin
  400. Params:=TStringList.Create;
  401. try
  402. R:=Router.FindHTTPRoute(aLocation,Params);
  403. Case ConfirmTransition(R,Params) of
  404. trOK :
  405. begin
  406. R:=Router.DoRouteRequest(R,aLocation,Params);
  407. UpdateRoute(R);
  408. if Assigned(OnReady) then
  409. OnReady(Self,aLocation,R);
  410. end;
  411. trError:
  412. if Assigned(OnError) then
  413. FOnError(Self,aLocation,R);
  414. end;
  415. Finally
  416. Params.Free;
  417. end;
  418. Result:=trOK;
  419. end;
  420. function THistory.ConfirmTransition(aRoute: TRoute; Params : TStrings): TTransitionResult;
  421. Var
  422. Old : TRoute;
  423. allow : Boolean;
  424. begin
  425. Old:=Current;
  426. Allow:=True;
  427. if Assigned(FOnAllow) then
  428. FOnAllow(Self,old,aRoute,Params,Allow);
  429. if Not Allow then
  430. begin
  431. ensureURL();
  432. Result:=trAbort;
  433. end;
  434. Result:=trOK;
  435. end;
  436. { TRouteObjectHandler }
  437. Type
  438. TRouteObjectHandler = Class(TRoute)
  439. private
  440. FObjectClass: TRouteObjectClass;
  441. Public
  442. Procedure HandleRequest(ARouter : TRouter; Const URL : String; Params : TStrings); override;
  443. Property RouteObjectClass : TRouteObjectClass Read FObjectClass Write FObjectClass;
  444. end;
  445. { TRouteEventHandler }
  446. TRouteEventHandler = Class(TRoute)
  447. Public
  448. Procedure HandleRequest(ARouter : TRouter; Const URL : String; Params : TStrings); override;
  449. Property Event : TRouteEvent Read FEvent Write FEvent;
  450. end;
  451. { TRouteEventHandler }
  452. procedure TRouteEventHandler.HandleRequest(ARouter : TRouter; const URL: String; Params: TStrings);
  453. begin
  454. If Assigned(Event) then
  455. Event(URL,Self,Params);
  456. end;
  457. procedure TRouteObjectHandler.HandleRequest(ARouter : TRouter; Const URL : String; Params : TStrings);
  458. Var
  459. O : TRouteObject;
  460. begin
  461. O:=RouteObjectClass.Create;
  462. try
  463. O.HandleRoute(URL,Params);
  464. finally
  465. O.Free;
  466. end;
  467. end;
  468. { TRouter }
  469. function TRouter.GetR(AIndex : Integer): TRoute;
  470. begin
  471. Result:=FRoutes[AIndex]
  472. end;
  473. function TRouter.GetHistory: THistory;
  474. begin
  475. If (FHistory=Nil) then
  476. InitHistory(hkAuto,'');
  477. Result:=FHistory;
  478. end;
  479. function TRouter.GetHistoryKind: THistoryKind;
  480. begin
  481. if Not assigned(History) then
  482. Result:=hkAuto
  483. else
  484. Result:=History.Kind;
  485. end;
  486. class procedure TRouter.DoneService;
  487. begin
  488. FreeAndNil(FService);
  489. end;
  490. function TRouter.GetRouteCount: Integer;
  491. begin
  492. Result:=FRoutes.Count;
  493. end;
  494. function TRouter.CreateRouteList: TRouteList;
  495. begin
  496. Result:=TRouteList.Create(TRoute);
  497. end;
  498. procedure TRouter.CheckDuplicate(APattern: String; isDefault: Boolean);
  499. Var
  500. I,DI : Integer;
  501. R : TRoute;
  502. begin
  503. DI:=-1;
  504. For I:=0 to FRoutes.Count-1 do
  505. begin
  506. R:=FRoutes[I];
  507. if R.Default then
  508. DI:=I;
  509. if R.Matches(APattern) then
  510. Raise EHTTPRoute.CreateFmt(EDuplicateRoute,[APattern]);
  511. end;
  512. if isDefault and (DI<>-1) then
  513. Raise EHTTPRoute.CreateFmt(EDuplicateDefaultRoute,[APattern]);
  514. end;
  515. function TRouter.DoRouteRequest(ARoute: TRoute; const AURL: String;
  516. AParams: TStrings): TRoute;
  517. begin
  518. Result:=aRoute;
  519. Result.HandleRequest(Self,aURL,AParams);
  520. end;
  521. function TRouter.DoRouteRequest(AURL: String): TRoute;
  522. Var
  523. APath : String;
  524. Params : TStrings;
  525. begin
  526. APath:=AURL;
  527. Params:=TStringList.Create;
  528. try
  529. Result:=GetRoute(APath,Params);
  530. Result:=DoRouteRequest(Result,aPath,Params);
  531. finally
  532. Params.Free;
  533. end;
  534. end;
  535. function TRouter.GetRequestPath(const URL: String): String;
  536. begin
  537. Result:=SanitizeRoute(URL);
  538. end;
  539. function TRouter.GetCurrentLocation: String;
  540. begin
  541. end;
  542. function TRouter.Push(location: TRawLocation): TTransitionResult;
  543. begin
  544. Result:=History.Push(location);
  545. end;
  546. function TRouter.Replace(location: TRawLocation): TTransitionResult;
  547. begin
  548. Result:=History.Replace(location);
  549. end;
  550. function TRouter.Go(N: integer): TTransitionResult;
  551. begin
  552. Result:=History.Go(N);
  553. end;
  554. function TRouter.NavigateForward: TTransitionResult;
  555. begin
  556. Result:=Go(1);
  557. end;
  558. function TRouter.NavigateBack: TTransitionResult;
  559. begin
  560. Result:=Go(-1);
  561. end;
  562. constructor TRouter.Create(AOwner: TComponent);
  563. begin
  564. inherited Create(AOwner);
  565. froutes:=CreateRouteList;
  566. end;
  567. destructor TRouter.Destroy;
  568. begin
  569. FreeAndNil(FRoutes);
  570. inherited Destroy;
  571. end;
  572. procedure TRouter.InitHistory(aKind: THistoryKind; aBase : String = '');
  573. begin
  574. FreeAndNil(FHistory);
  575. case aKind of
  576. hkAbstract : FHistory:=TAbstractHistory.Create(Self,aBase);
  577. hkhash : FHistory:=THashHistory.Create(Self,aBase);
  578. hkHTML5 : FHistory:=THTMLHistory.Create(Self,aBase);
  579. hkAuto :
  580. if TBrowserState.supportsPushState then
  581. FHistory:=THTMLHistory.Create(Self,aBase)
  582. else
  583. FHistory:=THashHistory.Create(Self,aBase);
  584. end;
  585. FHistory.SetupListeners;
  586. end;
  587. procedure TRouter.DeleteRoute(AIndex: Integer);
  588. begin
  589. FRoutes.Delete(Aindex)
  590. end;
  591. procedure TRouter.DeleteRouteByID(AID: Integer);
  592. Var
  593. R : TCollectionItem;
  594. begin
  595. R:=FRoutes.FindItemID(AID);
  596. R.Free;
  597. end;
  598. procedure TRouter.DeleteRoute(ARoute: TRoute);
  599. begin
  600. ARoute.Free;
  601. end;
  602. class function TRouter.Service: TRouter;
  603. begin
  604. if FService=Nil then
  605. FService:=ServiceClass.Create(Nil);
  606. Result:=FService;
  607. end;
  608. class function TRouter.ServiceClass: TRouterClass;
  609. begin
  610. If FServiceClass=nil then
  611. FServiceClass:=TRouter;
  612. Result:=FServiceClass;
  613. end;
  614. class procedure TRouter.SetServiceClass(AClass: TRouterClass);
  615. begin
  616. if Assigned(FService) then
  617. FreeAndNil(FService);
  618. FServiceClass:=AClass;
  619. end;
  620. function TRouter.RegisterRoute(const APattern: String; AEvent: TRouteEvent; IsDefault: Boolean): TRoute;
  621. begin
  622. Result:=CreateHTTPRoute(TRouteEventHandler,APattern,IsDefault);
  623. TRouteEventHandler(Result).Event:=AEvent;
  624. end;
  625. function TRouter.CreateHTTPRoute(AClass : TRouteClass; const APattern: String;IsDefault: Boolean) : TRoute;
  626. begin
  627. CheckDuplicate(APattern,isDefault);
  628. Result:=AClass.Create(FRoutes);
  629. With Result do
  630. begin
  631. URLPattern:=APattern;
  632. Default:=IsDefault;
  633. end;
  634. end;
  635. function TRouter.RegisterRoute(const APattern: String; const AObjectClass: TRouteObjectClass; IsDefault: Boolean): TRoute;
  636. begin
  637. Result:=CreateHTTPRoute(TRouteObjectHandler,APattern,IsDefault);
  638. TRouteObjectHandler(Result).RouteObjectCLass:=AObjectClass;
  639. end;
  640. class function TRouter.SanitizeRoute(const Path: String): String;
  641. begin
  642. Result:=Path;
  643. end;
  644. function TRouter.FindHTTPRoute(const Path: String; Params : TStrings): TRoute;
  645. Var
  646. I : Integer;
  647. APathInfo : String;
  648. begin
  649. APathInfo:=SanitizeRoute(Path);
  650. Result:=Nil;
  651. I:=0;
  652. While (Result=Nil) and (I<FRoutes.Count) do
  653. begin
  654. Result:=FRoutes[i];
  655. If Not Result.MatchPattern(APathInfo,Params) then
  656. Result:=Nil;
  657. Inc(I);
  658. end;
  659. end;
  660. function TRouter.GetRoute(const Path: String; Params : TStrings): TRoute;
  661. begin
  662. Result:=FindHTTPRoute(Path,Params);
  663. if Not Assigned(Result) then
  664. Raise EHTTPRoute.Create('Not found');
  665. end;
  666. function TRouter.RouteRequest(const ARouteURL: String): TRoute;
  667. Var
  668. AURL : String;
  669. begin
  670. AURL:=ARouteURL;
  671. If Assigned(FBeforeRequest) then
  672. FBeforeRequest(Self,AURL);
  673. Result:=DoRouteRequest(AURL);
  674. If Assigned(FAfterRequest) then
  675. FAfterRequest(Self,AURL);
  676. end;
  677. { TRouteList }
  678. function TRouteList.GetR(AIndex : Integer): TRoute;
  679. begin
  680. Result:=Items[AIndex] as TRoute;
  681. end;
  682. procedure TRouteList.SetR(AIndex : Integer; AValue: TRoute);
  683. begin
  684. Items[AIndex]:=AValue;
  685. end;
  686. { TRoute }
  687. Class Function TRoute.NormalizeURLPattern(AValue: String) : String;
  688. Var
  689. V : String;
  690. begin
  691. V:=IncludeHTTPPathDelimiter(AValue);
  692. if (V<>'/') and (V[1]='/') then
  693. Delete(V,1,1);
  694. Result:=V;
  695. end;
  696. procedure TRoute.SetURLPattern(AValue: String);
  697. Var
  698. V : String;
  699. begin
  700. V:=NormalizeURLPattern(AValue);
  701. if (FURLPattern=V) then Exit;
  702. FURLPattern:=V;
  703. end;
  704. function TRoute.Matches(const APattern: String): Boolean;
  705. begin
  706. Result:=(CompareText(URLPattern,NormalizeURLPattern(APattern))=0)
  707. end;
  708. Function TRoute.MatchPattern(Const Path : String; L : TStrings) : Boolean;
  709. Function StartsWith(C : Char; S : String): Boolean;
  710. begin
  711. Result:=(Length(S)>0) and (S[1]=C);
  712. end;
  713. Function EndsWith(C : Char; S : String): Boolean;
  714. Var
  715. L : Integer;
  716. begin
  717. L:=Length(S);
  718. Result:=(L>0) and (S[L]=C);
  719. end;
  720. procedure ExtractNextPathLevel(var ALeft: string;
  721. var ALvl: string; var ARight: string; const ADelim: Char = '/');
  722. var
  723. P: Integer;
  724. begin
  725. Writeln('ExtractNextPathLevel >:',Aleft,' (',aLvl,') ',aRight);
  726. if (ALvl<>ADelim) then
  727. begin
  728. ALeft:=ALeft+ALvl;
  729. if StartsWith(ADelim,ARight) then
  730. begin
  731. ALeft:=ALeft+ADelim;
  732. Delete(ARight,1,1);
  733. end;
  734. end;
  735. P:=Pos(ADelim,ARight);
  736. if P=0 then
  737. P:=Length(ARight)+1;
  738. ALvl:=Copy(ARight,1,P-1);
  739. ARight:=Copy(ARight,P,MaxInt);
  740. Writeln('ExtractNextPathLevel <:',Aleft,' (',aLvl,') ',aRight);
  741. end;
  742. procedure ExtractPrevPathLevel(var ALeft: string;
  743. var ALvl: string; var ARight: string; const ADelim: Char = '/');
  744. var
  745. P,L: Integer;
  746. begin
  747. Writeln('ExtractPrevPathLevel >:',Aleft,' (',aLvl,') ',aRight);
  748. if (ALvl<>ADelim) then
  749. begin
  750. ARight:=ALvl+ARight;
  751. L:=Length(ALeft);
  752. if EndsWith(ADelim,ALeft) then
  753. begin
  754. ARight:=ADelim+ARight;
  755. Delete(ALeft,L,1);
  756. end;
  757. end;
  758. P:=RPos(ADelim,ALeft);
  759. ALvl:=Copy(ALeft,P+1,MaxInt);
  760. ALeft:=Copy(ALeft,1,P);
  761. Writeln('ExtractPrevPathLevel <:',Aleft,' (',aLvl,') ',aRight);
  762. end;
  763. Procedure AddParam(aName,AValue : String);
  764. begin
  765. if Assigned(L) then
  766. L.Values[aName]:=aValue;
  767. end;
  768. var
  769. APathInfo : String;
  770. APattern : String;
  771. VLeftPat, VRightPat, VLeftVal, VRightVal, VVal, VPat, VName: string;
  772. begin
  773. Result:= False;
  774. if (URLPattern='') then
  775. Exit; // Maybe empty pattern should match any path?
  776. APathInfo:=Path;
  777. APattern:=URLPattern;
  778. Delete(APattern, Pos('?', APattern), MaxInt);
  779. Delete(APathInfo, Pos('?', APathInfo), MaxInt);
  780. if StartsWith('/',APattern) then
  781. Delete(APattern,1,1);
  782. if StartsWith('/',APathInfo) then
  783. Delete(APathInfo,1,1);
  784. VLeftPat := '';
  785. VLeftVal := '';
  786. VPat := '/'; // init value is '/', not ''
  787. VVal := '/'; // init value is '/', not ''
  788. VRightPat := APattern;
  789. VRightVal := APathInfo;
  790. Writeln('Check match on ',URLPattern);
  791. repeat
  792. // Extract next part
  793. ExtractNextPathLevel(VLeftPat, VPat, VRightPat);
  794. ExtractNextPathLevel(VLeftVal, VVal, VRightVal);
  795. Writeln('Pat: ',VPat,' Val: ',VVal);
  796. if StartsWith(':',VPat) then
  797. AddParam(Copy(VPat,2,Maxint),VVal)
  798. else
  799. if StartsWith('*',VPat) then
  800. begin
  801. // *path
  802. VName := Copy(VPat, 2, MaxInt);
  803. VLeftPat := VRightPat;
  804. VLeftVal := VVal + VRightVal;
  805. VPat := '/'; // init value is '/', not ''
  806. VVal := '/'; // init value is '/', not ''
  807. VRightPat := '';
  808. VRightVal := '';
  809. // if AutoAddSlash ...
  810. if EndsWith('/',VLeftPat) and not EndsWith('/',VLeftVal) then
  811. Delete(VLeftPat, Length(VLeftPat), 1);
  812. repeat
  813. // Extract backwards
  814. ExtractPrevPathLevel(VLeftPat, VPat, VRightPat);
  815. ExtractPrevPathLevel(VLeftVal, VVal, VRightVal);
  816. if StartsWith(':', VPat) then
  817. begin
  818. // *path/:field
  819. AddParam(Copy(VPat,2,Maxint),VVal);
  820. end
  821. else
  822. // *path/const
  823. if not ((VPat='') and (VLeftPat='')) and (VPat<>VVal) then
  824. Exit;
  825. // Check if we already done
  826. if (VLeftPat='') or (VLeftVal='') then
  827. begin
  828. if VLeftPat='' then
  829. begin
  830. if (VName<>'') then
  831. AddParam(VName,VLeftVal+VVal);
  832. Result:=True;
  833. end;
  834. Exit;
  835. end;
  836. until False;
  837. end
  838. else
  839. // const
  840. if (VPat <> VVal) then
  841. Exit;
  842. // Check if we already done
  843. if (VRightPat='') or (VRightVal='') then
  844. begin
  845. if (VRightPat='') and (VRightVal='') then
  846. Result:=True
  847. else if (VRightPat='/') then
  848. Result := True;
  849. Exit;
  850. end;
  851. until False;
  852. end;
  853. function TRoute.FullPath: String;
  854. begin
  855. Result:=URLPattern;
  856. end;
  857. class function THistory.getLocation(base: string): string;
  858. Var
  859. path : string;
  860. begin
  861. path:=window.location.pathname;
  862. if (base<>'') and (Pos(base,path)=1) then
  863. path:=Copy(Path,Length(Base)+1,Length(Path)-Length(Base));
  864. Result:=Path;
  865. if Result='' then
  866. Result:='/';
  867. Result:=Result+window.location.search+window.location.hash
  868. end;
  869. class function THistory.cleanPath(aPath: string): string;
  870. begin
  871. Result:=StringReplace(aPath,'//','/',[rfReplaceAll]);
  872. end;
  873. function THistory.Push(location: TRawLocation): TTransitionResult;
  874. Var
  875. Old : TRoute;
  876. begin
  877. Old:=Current;
  878. Result:=TransitionTo(location);
  879. if Result=trOK then
  880. begin
  881. Result:=doPush(Location);
  882. if Result=trOK then
  883. TWebScroll.Handle(router, Current, Old, false)
  884. end;
  885. end;
  886. function THistory.Replace(location: TRawLocation): TTransitionResult;
  887. Var
  888. Old : TRoute;
  889. begin
  890. Old:=Current;
  891. Result:=TransitionTo(location);
  892. if Result=trOK then
  893. begin
  894. Result:=doReplace(Location);
  895. TWebScroll.Handle(Router,Current,Old,false);
  896. end;
  897. end;
  898. function THistory.Go(N: integer): TTransitionResult;
  899. begin
  900. Result:=doGo(N);
  901. end;
  902. function THistory.NavigateForward: TTransitionResult;
  903. begin
  904. Result:=Go(1);
  905. end;
  906. function THistory.NavigateBack: TTransitionResult;
  907. begin
  908. Result:=Go(-1);
  909. end;
  910. procedure THistory.SetupListeners;
  911. begin
  912. // Do nothing
  913. end;
  914. function DoScroll(Event: TEventListenerEvent): boolean;
  915. begin
  916. TWebScroll.SaveScrollPosition;
  917. Result:=True;
  918. end;
  919. Class Function TWebScroll.GetStateKey : string;
  920. begin
  921. Result:=TJSDate.New().toString;
  922. end;
  923. Class Procedure TWebScroll.Setup;
  924. begin
  925. // web.window.;
  926. window.history.replaceState(New(['key', GetStateKey]), '');
  927. window.addEventListener('popstate',@DoScroll);
  928. end;
  929. Class Procedure TWebScroll.handle (router: TRouter; ato: TRoute; afrom: TRoute; isPop: boolean) ;
  930. Var
  931. Position : TScrollPoint;
  932. ScrollParams : TScrollParams;
  933. begin
  934. if Not Assigned(Router.OnScroll) then
  935. Exit;
  936. position:=getScrollPosition();
  937. ScrollParams:=Router.OnScroll(Router, aTo, aFrom, position);
  938. scrollToPosition(ScrollParams);
  939. end;
  940. Var
  941. positionStore : TJSObject;
  942. Class procedure TWebScroll.saveScrollPosition;
  943. Var
  944. Key : string;
  945. begin
  946. key:=getStateKey();
  947. if Key<>'' then
  948. positionStore.properties[key]:=New(['x',window.ScrollX,'y',window.ScrollY]);
  949. end;
  950. Class function TWebScroll.getScrollPosition : TScrollPoint;
  951. Var
  952. Key : string;
  953. O : JSValue;
  954. begin
  955. key:=getStateKey();
  956. Result.X:=0;
  957. Result.Y:=0;
  958. if (Key<>'') then
  959. begin
  960. O:=positionStore[key];
  961. if isObject(O) then
  962. begin
  963. Result.X:=Double(TJSOBject(O).Properties['x']);
  964. Result.Y:=Double(TJSOBject(O).Properties['y']);
  965. end;
  966. end;
  967. end;
  968. function getElementPosition (el: TJSElement; offset: TScrollPoint): TScrollPoint ;
  969. Var
  970. DocEl : TJSElement;
  971. docRect,elRect : TJSDOMRect;
  972. begin
  973. docEl:=document.documentElement;
  974. docRect := docEl.getBoundingClientRect();
  975. elRect := el.getBoundingClientRect();
  976. Result.x:= elRect.left - docRect.left - offset.x;
  977. Result.Y:= elRect.top - docRect.top - offset.y;
  978. end;
  979. Class Procedure TWebScroll.ScrollToPosition(AScroll : TScrollParams);
  980. Var
  981. el : TJSElement;
  982. P : TScrollPoint;
  983. begin
  984. if (AScroll.Selector<>'') then
  985. begin
  986. el:=document.querySelector(AScroll.Selector);
  987. if Assigned(el) then
  988. P:=getElementPosition(el,AScroll.Position)
  989. else
  990. P:=AScroll.Position;
  991. end
  992. else
  993. P:=AScroll.Position;
  994. Window.scrollTo(Round(P.x),Round(P.y));
  995. end;
  996. Class function TBrowserState.genKey (): string ;
  997. begin
  998. Result:=IntToStr(TJSDate.now);
  999. end;
  1000. Class function TBrowserState.getStateKey : string;
  1001. begin
  1002. if (TheKey='') then
  1003. TheKey:=genKey;
  1004. Result:=Thekey;
  1005. end;
  1006. Class Procedure TBrowserState.SetStateKey (akey: string);
  1007. begin
  1008. Thekey:=akey;
  1009. end;
  1010. Class Procedure TBrowserState.pushState (aurl: string; replace: boolean);
  1011. Var
  1012. O : TJSObject;
  1013. begin
  1014. TWebScroll.SaveScrollPosition;
  1015. try
  1016. if (Not replace) then
  1017. SetStateKey(GenKey);
  1018. O:=New(['key', GetStateKey()]);
  1019. if replace then
  1020. window.history.replaceState(o, '', aUrl)
  1021. else
  1022. window.history.pushState(o, '', aUrl);
  1023. except
  1024. if Replace then
  1025. window.location.replace(aUrl)
  1026. else
  1027. window.location.Assign(aUrl);
  1028. end;
  1029. end;
  1030. Class Procedure TBrowserState.replaceState(aUrl: string);
  1031. begin
  1032. pushState(aUrl, true)
  1033. end;
  1034. Class Function TBrowserState.supportsPushState : Boolean;
  1035. Var
  1036. UA : String;
  1037. Function isB(B : String) : Boolean;
  1038. begin
  1039. Result:=Pos(B,UA)<>0;
  1040. end;
  1041. begin
  1042. if Result and isDefined(Window) and isDefined(Window.Navigator) then
  1043. begin
  1044. ua:=Window.Navigator.userAgent;
  1045. Result:=Not (
  1046. IsB('Android 2.')
  1047. or IsB('Android 4.0')
  1048. or IsB('Mobile Safari')
  1049. or IsB('Chrome')
  1050. or isB('Windows Phone')
  1051. );
  1052. If Result then
  1053. Result:=isDefined(Window.history) and isDefined(Window.history);
  1054. end;
  1055. end;
  1056. { ---------------------------------------------------------------------
  1057. THashHistory
  1058. ---------------------------------------------------------------------}
  1059. procedure THashHistory.DoHashChange;
  1060. Var
  1061. NewHash : String;
  1062. Old : TRoute;
  1063. begin
  1064. NewHash:=NormalizeHash(GetHash);
  1065. if (NewHash=FLastHash) then
  1066. exit;
  1067. old:=Current;
  1068. if TransitionTo(NewHash)=trOK then
  1069. begin
  1070. TWebScroll.Handle(router, Current, old, true);
  1071. FLastHash:=NewHash;
  1072. end
  1073. else
  1074. replaceHash(FLastHash);
  1075. end;
  1076. procedure THashHistory.SetupListeners;
  1077. begin
  1078. if SupportsScroll then
  1079. TWebScroll.Setup;
  1080. if TBrowserState.SupportsPushState then
  1081. Window.addEventListener('popstate',@DoHashChange)
  1082. else
  1083. Window.addEventListener('hashchange',@DoHashChange);
  1084. end;
  1085. function THashHistory.doPush (location: TRawLocation) : TTransitionResult;
  1086. Var
  1087. L : String;
  1088. begin
  1089. L:=NormalizeHash(location);
  1090. FLastHash:=L;
  1091. pushHash(L);
  1092. Result:=trOK;
  1093. end;
  1094. function THashHistory.doreplace(location: TRawLocation): TTransitionResult;
  1095. Var
  1096. L : String;
  1097. begin
  1098. L:=NormalizeHash(location);
  1099. FLastHash:=L;
  1100. replaceHash(L);
  1101. Result:=trOK;
  1102. end;
  1103. function THashHistory.doGo(N: integer): TTransitionResult;
  1104. begin
  1105. Window.history.go(n);
  1106. result:=trOK;
  1107. end;
  1108. procedure THashHistory.ensureURL (push : boolean = false);
  1109. var
  1110. aHash,CURL: string;
  1111. begin
  1112. CURL:=NormalizeHash(FLastHash);
  1113. aHash:=getHash;
  1114. if (aHash<>CURL) then
  1115. if Push then
  1116. pushHash(CURL)
  1117. else
  1118. replaceHash(CURL)
  1119. end;
  1120. function THashHistory.getCurrentLocation: String;
  1121. begin
  1122. Result:=getHash()
  1123. end;
  1124. class function THashHistory.getHash: string;
  1125. Var
  1126. HRef : String;
  1127. Idx : Integer;
  1128. begin
  1129. // We can't use window.location.hash here because it's not
  1130. // consistent across browsers - Firefox will pre-decode it!
  1131. HRef:=window.location.href;
  1132. Idx:=Pos('#',HRef);
  1133. if (Idx=0) then
  1134. Result:=''
  1135. else
  1136. Result:=Copy(HRef,Idx+1,Length(HRef)-Idx);
  1137. end;
  1138. function THashHistory.Kind : THistoryKind;
  1139. begin
  1140. Result:=hkHash;
  1141. end;
  1142. class function THashHistory.getUrl (APath : string) : string;
  1143. Var
  1144. HRef : String;
  1145. Idx : Integer;
  1146. begin
  1147. HRef:=window.location.href;
  1148. Idx:=Pos('#',HRef);
  1149. if Idx=0 then
  1150. Result:=HRef
  1151. else
  1152. Result:=Copy(HRef,1,Idx-1);
  1153. Result:=Result+'#'+aPath;
  1154. end;
  1155. class procedure THashHistory.pushHash(path: string);
  1156. begin
  1157. if (TBrowserState.supportsPushState) then
  1158. TBrowserState.pushState(getUrl(path),false)
  1159. else
  1160. window.location.hash:=path
  1161. end;
  1162. class procedure THashHistory.replaceHash(path: string);
  1163. Var
  1164. H : String;
  1165. begin
  1166. H:=GetHash;
  1167. if (H=Path) then exit;
  1168. if (TBrowserState.supportsPushState) then
  1169. TBrowserState.replaceState(getUrl(path))
  1170. else
  1171. window.location.replace(getUrl(path))
  1172. end;
  1173. { ---------------------------------------------------------------------
  1174. TAbstractHistory
  1175. ---------------------------------------------------------------------}
  1176. constructor TAbstractHistory.Create (router: TRouter; base: string = '');
  1177. begin
  1178. Inherited;
  1179. SetLength(FStack,0);
  1180. FIndex:=-1;
  1181. end;
  1182. procedure TAbstractHistory.MaybeGrow(AIndex: Integer);
  1183. begin
  1184. if AIndex+1>Length(FStack) then
  1185. Setlength(FStack,AIndex+1);
  1186. end;
  1187. function TAbstractHistory.doPush(location: TRawLocation): TTransitionResult;
  1188. begin
  1189. Inc(FIndex);
  1190. MaybeGrow(FIndex);
  1191. FStack[FIndex]:=Location;
  1192. Result:=trOK;
  1193. end;
  1194. function TAbstractHistory.doReplace(location: TRawLocation): TTransitionResult;
  1195. begin
  1196. FStack[FIndex]:=Location;
  1197. Result:=trOK;
  1198. end;
  1199. function TAbstractHistory.doGo(N: integer): TTransitionResult;
  1200. Var
  1201. I : Integer;
  1202. Route : TRoute;
  1203. begin
  1204. I:=FIndex+N;
  1205. if (I<0) or (I>=Length(FStack)) then
  1206. Result:=trAbort
  1207. else
  1208. begin
  1209. // Route:=FStack[i];
  1210. // Result:=confirmTransition(Route);
  1211. if (Result=trOK) then
  1212. begin
  1213. FIndex:=0;
  1214. updateRoute(Route);
  1215. end;
  1216. end;
  1217. end;
  1218. procedure THistory.UpdateRoute(aRoute: TRoute);
  1219. begin
  1220. FCurrent:=aRoute;
  1221. if Assigned(FOnChange) then
  1222. FOnChange(aRoute);
  1223. end;
  1224. function TAbstractHistory.getCurrentLocation: String;
  1225. Var
  1226. I : Integer;
  1227. Route : string;
  1228. begin
  1229. I:=Length(FStack)-1;
  1230. if (I>=0) then
  1231. Route:=FStack[I]
  1232. else
  1233. Result:='/';
  1234. Result:=Route;
  1235. end;
  1236. procedure TAbstractHistory.ensureURL(Push: Boolean);
  1237. begin
  1238. // Noop
  1239. if Push then ;
  1240. end;
  1241. function TAbstractHistory.Kind: THistoryKind;
  1242. begin
  1243. Result:=hkAbstract;
  1244. end;
  1245. begin
  1246. positionStore:=new([]);
  1247. end.