webrouter.pp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518
  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. if Assigned(Result) then
  520. Result.HandleRequest(Self,aURL,AParams)
  521. else
  522. Raise EHTTPRoute.CreateFmt('No route for URL: %s',[aURL]);
  523. end;
  524. function TRouter.DoRouteRequest(AURL: String): TRoute;
  525. Var
  526. APath : String;
  527. Params : TStrings;
  528. begin
  529. APath:=AURL;
  530. Params:=TStringList.Create;
  531. try
  532. Result:=GetRoute(APath,Params);
  533. Result:=DoRouteRequest(Result,aPath,Params);
  534. finally
  535. Params.Free;
  536. end;
  537. end;
  538. function TRouter.GetRequestPath(const URL: String): String;
  539. begin
  540. Result:=SanitizeRoute(URL);
  541. end;
  542. function TRouter.GetCurrentLocation: String;
  543. begin
  544. end;
  545. function TRouter.Push(location: TRawLocation): TTransitionResult;
  546. begin
  547. Result:=History.Push(location);
  548. end;
  549. function TRouter.Replace(location: TRawLocation): TTransitionResult;
  550. begin
  551. Result:=History.Replace(location);
  552. end;
  553. function TRouter.Go(N: integer): TTransitionResult;
  554. begin
  555. Result:=History.Go(N);
  556. end;
  557. function TRouter.NavigateForward: TTransitionResult;
  558. begin
  559. Result:=Go(1);
  560. end;
  561. function TRouter.NavigateBack: TTransitionResult;
  562. begin
  563. Result:=Go(-1);
  564. end;
  565. constructor TRouter.Create(AOwner: TComponent);
  566. begin
  567. inherited Create(AOwner);
  568. froutes:=CreateRouteList;
  569. end;
  570. destructor TRouter.Destroy;
  571. begin
  572. FreeAndNil(FRoutes);
  573. inherited Destroy;
  574. end;
  575. procedure TRouter.InitHistory(aKind: THistoryKind; aBase : String = '');
  576. begin
  577. FreeAndNil(FHistory);
  578. case aKind of
  579. hkAbstract : FHistory:=TAbstractHistory.Create(Self,aBase);
  580. hkhash : FHistory:=THashHistory.Create(Self,aBase);
  581. hkHTML5 : FHistory:=THTMLHistory.Create(Self,aBase);
  582. hkAuto :
  583. if TBrowserState.supportsPushState then
  584. FHistory:=THTMLHistory.Create(Self,aBase)
  585. else
  586. FHistory:=THashHistory.Create(Self,aBase);
  587. end;
  588. FHistory.SetupListeners;
  589. end;
  590. procedure TRouter.DeleteRoute(AIndex: Integer);
  591. begin
  592. FRoutes.Delete(Aindex)
  593. end;
  594. procedure TRouter.DeleteRouteByID(AID: Integer);
  595. Var
  596. R : TCollectionItem;
  597. begin
  598. R:=FRoutes.FindItemID(AID);
  599. R.Free;
  600. end;
  601. procedure TRouter.DeleteRoute(ARoute: TRoute);
  602. begin
  603. ARoute.Free;
  604. end;
  605. class function TRouter.Service: TRouter;
  606. begin
  607. if FService=Nil then
  608. FService:=ServiceClass.Create(Nil);
  609. Result:=FService;
  610. end;
  611. class function TRouter.ServiceClass: TRouterClass;
  612. begin
  613. If FServiceClass=nil then
  614. FServiceClass:=TRouter;
  615. Result:=FServiceClass;
  616. end;
  617. class procedure TRouter.SetServiceClass(AClass: TRouterClass);
  618. begin
  619. if Assigned(FService) then
  620. FreeAndNil(FService);
  621. FServiceClass:=AClass;
  622. end;
  623. function TRouter.RegisterRoute(const APattern: String; AEvent: TRouteEvent; IsDefault: Boolean): TRoute;
  624. begin
  625. Result:=CreateHTTPRoute(TRouteEventHandler,APattern,IsDefault);
  626. TRouteEventHandler(Result).Event:=AEvent;
  627. end;
  628. function TRouter.CreateHTTPRoute(AClass : TRouteClass; const APattern: String;IsDefault: Boolean) : TRoute;
  629. begin
  630. CheckDuplicate(APattern,isDefault);
  631. Result:=AClass.Create(FRoutes);
  632. With Result do
  633. begin
  634. URLPattern:=APattern;
  635. Default:=IsDefault;
  636. end;
  637. end;
  638. function TRouter.RegisterRoute(const APattern: String; const AObjectClass: TRouteObjectClass; IsDefault: Boolean): TRoute;
  639. begin
  640. Result:=CreateHTTPRoute(TRouteObjectHandler,APattern,IsDefault);
  641. TRouteObjectHandler(Result).RouteObjectCLass:=AObjectClass;
  642. end;
  643. class function TRouter.SanitizeRoute(const Path: String): String;
  644. begin
  645. Result:=Path;
  646. end;
  647. function TRouter.FindHTTPRoute(const Path: String; Params : TStrings): TRoute;
  648. Var
  649. I : Integer;
  650. APathInfo : String;
  651. begin
  652. APathInfo:=SanitizeRoute(Path);
  653. Result:=Nil;
  654. I:=0;
  655. While (Result=Nil) and (I<FRoutes.Count) do
  656. begin
  657. Result:=FRoutes[i];
  658. If Not Result.MatchPattern(APathInfo,Params) then
  659. Result:=Nil;
  660. Inc(I);
  661. end;
  662. end;
  663. function TRouter.GetRoute(const Path: String; Params : TStrings): TRoute;
  664. begin
  665. Result:=FindHTTPRoute(Path,Params);
  666. if Not Assigned(Result) then
  667. Raise EHTTPRoute.Create('Not found');
  668. end;
  669. function TRouter.RouteRequest(const ARouteURL: String): TRoute;
  670. Var
  671. AURL : String;
  672. begin
  673. AURL:=ARouteURL;
  674. If Assigned(FBeforeRequest) then
  675. FBeforeRequest(Self,AURL);
  676. Result:=DoRouteRequest(AURL);
  677. If Assigned(FAfterRequest) then
  678. FAfterRequest(Self,AURL);
  679. end;
  680. { TRouteList }
  681. function TRouteList.GetR(AIndex : Integer): TRoute;
  682. begin
  683. Result:=Items[AIndex] as TRoute;
  684. end;
  685. procedure TRouteList.SetR(AIndex : Integer; AValue: TRoute);
  686. begin
  687. Items[AIndex]:=AValue;
  688. end;
  689. { TRoute }
  690. Class Function TRoute.NormalizeURLPattern(AValue: String) : String;
  691. Var
  692. V : String;
  693. begin
  694. V:=IncludeHTTPPathDelimiter(AValue);
  695. if (V<>'/') and (V[1]='/') then
  696. Delete(V,1,1);
  697. Result:=V;
  698. end;
  699. procedure TRoute.SetURLPattern(AValue: String);
  700. Var
  701. V : String;
  702. begin
  703. V:=NormalizeURLPattern(AValue);
  704. if (FURLPattern=V) then Exit;
  705. FURLPattern:=V;
  706. end;
  707. function TRoute.Matches(const APattern: String): Boolean;
  708. begin
  709. Result:=(CompareText(URLPattern,NormalizeURLPattern(APattern))=0)
  710. end;
  711. Function TRoute.MatchPattern(Const Path : String; L : TStrings) : Boolean;
  712. Function StartsWith(C : Char; S : String): Boolean;
  713. begin
  714. Result:=(Length(S)>0) and (S[1]=C);
  715. end;
  716. Function EndsWith(C : Char; S : String): Boolean;
  717. Var
  718. L : Integer;
  719. begin
  720. L:=Length(S);
  721. Result:=(L>0) and (S[L]=C);
  722. end;
  723. procedure ExtractNextPathLevel(var ALeft: string;
  724. var ALvl: string; var ARight: string; const ADelim: Char = '/');
  725. var
  726. P: Integer;
  727. begin
  728. Writeln('ExtractNextPathLevel >:',Aleft,' (',aLvl,') ',aRight);
  729. if (ALvl<>ADelim) then
  730. begin
  731. ALeft:=ALeft+ALvl;
  732. if StartsWith(ADelim,ARight) then
  733. begin
  734. ALeft:=ALeft+ADelim;
  735. Delete(ARight,1,1);
  736. end;
  737. end;
  738. P:=Pos(ADelim,ARight);
  739. if P=0 then
  740. P:=Length(ARight)+1;
  741. ALvl:=Copy(ARight,1,P-1);
  742. ARight:=Copy(ARight,P,MaxInt);
  743. Writeln('ExtractNextPathLevel <:',Aleft,' (',aLvl,') ',aRight);
  744. end;
  745. procedure ExtractPrevPathLevel(var ALeft: string;
  746. var ALvl: string; var ARight: string; const ADelim: Char = '/');
  747. var
  748. P,L: Integer;
  749. begin
  750. Writeln('ExtractPrevPathLevel >:',Aleft,' (',aLvl,') ',aRight);
  751. if (ALvl<>ADelim) then
  752. begin
  753. ARight:=ALvl+ARight;
  754. L:=Length(ALeft);
  755. if EndsWith(ADelim,ALeft) then
  756. begin
  757. ARight:=ADelim+ARight;
  758. Delete(ALeft,L,1);
  759. end;
  760. end;
  761. P:=RPos(ADelim,ALeft);
  762. ALvl:=Copy(ALeft,P+1,MaxInt);
  763. ALeft:=Copy(ALeft,1,P);
  764. Writeln('ExtractPrevPathLevel <:',Aleft,' (',aLvl,') ',aRight);
  765. end;
  766. Procedure AddParam(aName,AValue : String);
  767. begin
  768. if Assigned(L) then
  769. L.Values[aName]:=aValue;
  770. end;
  771. var
  772. APathInfo : String;
  773. APattern : String;
  774. VLeftPat, VRightPat, VLeftVal, VRightVal, VVal, VPat, VName: string;
  775. begin
  776. Result:= False;
  777. if (URLPattern='') then
  778. Exit; // Maybe empty pattern should match any path?
  779. APathInfo:=Path;
  780. APattern:=URLPattern;
  781. Delete(APattern, Pos('?', APattern), MaxInt);
  782. Delete(APathInfo, Pos('?', APathInfo), MaxInt);
  783. if StartsWith('/',APattern) then
  784. Delete(APattern,1,1);
  785. if StartsWith('/',APathInfo) then
  786. Delete(APathInfo,1,1);
  787. VLeftPat := '';
  788. VLeftVal := '';
  789. VPat := '/'; // init value is '/', not ''
  790. VVal := '/'; // init value is '/', not ''
  791. VRightPat := APattern;
  792. VRightVal := APathInfo;
  793. Writeln('Check match on ',URLPattern);
  794. repeat
  795. // Extract next part
  796. ExtractNextPathLevel(VLeftPat, VPat, VRightPat);
  797. ExtractNextPathLevel(VLeftVal, VVal, VRightVal);
  798. Writeln('Pat: ',VPat,' Val: ',VVal);
  799. if StartsWith(':',VPat) then
  800. AddParam(Copy(VPat,2,Maxint),VVal)
  801. else
  802. if StartsWith('*',VPat) then
  803. begin
  804. // *path
  805. VName := Copy(VPat, 2, MaxInt);
  806. VLeftPat := VRightPat;
  807. VLeftVal := VVal + VRightVal;
  808. VPat := '/'; // init value is '/', not ''
  809. VVal := '/'; // init value is '/', not ''
  810. VRightPat := '';
  811. VRightVal := '';
  812. // if AutoAddSlash ...
  813. if EndsWith('/',VLeftPat) and not EndsWith('/',VLeftVal) then
  814. Delete(VLeftPat, Length(VLeftPat), 1);
  815. repeat
  816. // Extract backwards
  817. ExtractPrevPathLevel(VLeftPat, VPat, VRightPat);
  818. ExtractPrevPathLevel(VLeftVal, VVal, VRightVal);
  819. if StartsWith(':', VPat) then
  820. begin
  821. // *path/:field
  822. AddParam(Copy(VPat,2,Maxint),VVal);
  823. end
  824. else
  825. // *path/const
  826. if not ((VPat='') and (VLeftPat='')) and (VPat<>VVal) then
  827. Exit;
  828. // Check if we already done
  829. if (VLeftPat='') or (VLeftVal='') then
  830. begin
  831. if VLeftPat='' then
  832. begin
  833. if (VName<>'') then
  834. AddParam(VName,VLeftVal+VVal);
  835. Result:=True;
  836. end;
  837. Exit;
  838. end;
  839. until False;
  840. end
  841. else
  842. // const
  843. if (VPat <> VVal) then
  844. Exit;
  845. // Check if we already done
  846. if (VRightPat='') or (VRightVal='') then
  847. begin
  848. if (VRightPat='') and (VRightVal='') then
  849. Result:=True
  850. else if (VRightPat='/') then
  851. Result := True;
  852. Exit;
  853. end;
  854. until False;
  855. end;
  856. function TRoute.FullPath: String;
  857. begin
  858. Result:=URLPattern;
  859. end;
  860. class function THistory.getLocation(base: string): string;
  861. Var
  862. path : string;
  863. begin
  864. path:=window.location.pathname;
  865. if (base<>'') and (Pos(base,path)=1) then
  866. path:=Copy(Path,Length(Base)+1,Length(Path)-Length(Base));
  867. Result:=Path;
  868. if Result='' then
  869. Result:='/';
  870. Result:=Result+window.location.search+window.location.hash
  871. end;
  872. class function THistory.cleanPath(aPath: string): string;
  873. begin
  874. Result:=StringReplace(aPath,'//','/',[rfReplaceAll]);
  875. end;
  876. function THistory.Push(location: TRawLocation): TTransitionResult;
  877. Var
  878. Old : TRoute;
  879. begin
  880. Old:=Current;
  881. Result:=TransitionTo(location);
  882. if Result=trOK then
  883. begin
  884. Result:=doPush(Location);
  885. if Result=trOK then
  886. TWebScroll.Handle(router, Current, Old, false)
  887. end;
  888. end;
  889. function THistory.Replace(location: TRawLocation): TTransitionResult;
  890. Var
  891. Old : TRoute;
  892. begin
  893. Old:=Current;
  894. Result:=TransitionTo(location);
  895. if Result=trOK then
  896. begin
  897. Result:=doReplace(Location);
  898. TWebScroll.Handle(Router,Current,Old,false);
  899. end;
  900. end;
  901. function THistory.Go(N: integer): TTransitionResult;
  902. begin
  903. Result:=doGo(N);
  904. end;
  905. function THistory.NavigateForward: TTransitionResult;
  906. begin
  907. Result:=Go(1);
  908. end;
  909. function THistory.NavigateBack: TTransitionResult;
  910. begin
  911. Result:=Go(-1);
  912. end;
  913. procedure THistory.SetupListeners;
  914. begin
  915. // Do nothing
  916. end;
  917. function DoScroll(Event: TEventListenerEvent): boolean;
  918. begin
  919. TWebScroll.SaveScrollPosition;
  920. Result:=True;
  921. end;
  922. Class Function TWebScroll.GetStateKey : string;
  923. begin
  924. Result:=TJSDate.New().toString;
  925. end;
  926. Class Procedure TWebScroll.Setup;
  927. begin
  928. // web.window.;
  929. window.history.replaceState(New(['key', GetStateKey]), '');
  930. window.addEventListener('popstate',@DoScroll);
  931. end;
  932. Class Procedure TWebScroll.handle (router: TRouter; ato: TRoute; afrom: TRoute; isPop: boolean) ;
  933. Var
  934. Position : TScrollPoint;
  935. ScrollParams : TScrollParams;
  936. begin
  937. if Not Assigned(Router.OnScroll) then
  938. Exit;
  939. position:=getScrollPosition();
  940. ScrollParams:=Router.OnScroll(Router, aTo, aFrom, position);
  941. scrollToPosition(ScrollParams);
  942. end;
  943. Var
  944. positionStore : TJSObject;
  945. Class procedure TWebScroll.saveScrollPosition;
  946. Var
  947. Key : string;
  948. begin
  949. key:=getStateKey();
  950. if Key<>'' then
  951. positionStore.properties[key]:=New(['x',window.ScrollX,'y',window.ScrollY]);
  952. end;
  953. Class function TWebScroll.getScrollPosition : TScrollPoint;
  954. Var
  955. Key : string;
  956. O : JSValue;
  957. begin
  958. key:=getStateKey();
  959. Result.X:=0;
  960. Result.Y:=0;
  961. if (Key<>'') then
  962. begin
  963. O:=positionStore[key];
  964. if isObject(O) then
  965. begin
  966. Result.X:=Double(TJSOBject(O).Properties['x']);
  967. Result.Y:=Double(TJSOBject(O).Properties['y']);
  968. end;
  969. end;
  970. end;
  971. function getElementPosition (el: TJSElement; offset: TScrollPoint): TScrollPoint ;
  972. Var
  973. DocEl : TJSElement;
  974. docRect,elRect : TJSDOMRect;
  975. begin
  976. docEl:=document.documentElement;
  977. docRect := docEl.getBoundingClientRect();
  978. elRect := el.getBoundingClientRect();
  979. Result.x:= elRect.left - docRect.left - offset.x;
  980. Result.Y:= elRect.top - docRect.top - offset.y;
  981. end;
  982. Class Procedure TWebScroll.ScrollToPosition(AScroll : TScrollParams);
  983. Var
  984. el : TJSElement;
  985. P : TScrollPoint;
  986. begin
  987. if (AScroll.Selector<>'') then
  988. begin
  989. el:=document.querySelector(AScroll.Selector);
  990. if Assigned(el) then
  991. P:=getElementPosition(el,AScroll.Position)
  992. else
  993. P:=AScroll.Position;
  994. end
  995. else
  996. P:=AScroll.Position;
  997. Window.scrollTo(Round(P.x),Round(P.y));
  998. end;
  999. Class function TBrowserState.genKey (): string ;
  1000. begin
  1001. Result:=IntToStr(TJSDate.now);
  1002. end;
  1003. Class function TBrowserState.getStateKey : string;
  1004. begin
  1005. if (TheKey='') then
  1006. TheKey:=genKey;
  1007. Result:=Thekey;
  1008. end;
  1009. Class Procedure TBrowserState.SetStateKey (akey: string);
  1010. begin
  1011. Thekey:=akey;
  1012. end;
  1013. Class Procedure TBrowserState.pushState (aurl: string; replace: boolean);
  1014. Var
  1015. O : TJSObject;
  1016. begin
  1017. TWebScroll.SaveScrollPosition;
  1018. try
  1019. if (Not replace) then
  1020. SetStateKey(GenKey);
  1021. O:=New(['key', GetStateKey()]);
  1022. if replace then
  1023. window.history.replaceState(o, '', aUrl)
  1024. else
  1025. window.history.pushState(o, '', aUrl);
  1026. except
  1027. if Replace then
  1028. window.location.replace(aUrl)
  1029. else
  1030. window.location.Assign(aUrl);
  1031. end;
  1032. end;
  1033. Class Procedure TBrowserState.replaceState(aUrl: string);
  1034. begin
  1035. pushState(aUrl, true)
  1036. end;
  1037. Class Function TBrowserState.supportsPushState : Boolean;
  1038. Var
  1039. UA : String;
  1040. Function isB(B : String) : Boolean;
  1041. begin
  1042. Result:=Pos(B,UA)<>0;
  1043. end;
  1044. begin
  1045. Result:=False;
  1046. if isDefined(Window) and isDefined(Window.Navigator) then
  1047. begin
  1048. ua:=Window.Navigator.userAgent;
  1049. Result:=Not (
  1050. IsB('Android 2.')
  1051. or IsB('Android 4.0')
  1052. or IsB('Mobile Safari')
  1053. or IsB('Chrome')
  1054. or isB('Windows Phone')
  1055. );
  1056. end;
  1057. If Result then
  1058. Result:=isDefined(Window.history) and isDefined(Window.history);
  1059. end;
  1060. { ---------------------------------------------------------------------
  1061. THashHistory
  1062. ---------------------------------------------------------------------}
  1063. procedure THashHistory.DoHashChange;
  1064. Var
  1065. NewHash : String;
  1066. Old : TRoute;
  1067. begin
  1068. NewHash:=NormalizeHash(GetHash);
  1069. if (NewHash=FLastHash) then
  1070. exit;
  1071. old:=Current;
  1072. if TransitionTo(NewHash)=trOK then
  1073. begin
  1074. TWebScroll.Handle(router, Current, old, true);
  1075. FLastHash:=NewHash;
  1076. end
  1077. else
  1078. replaceHash(FLastHash);
  1079. end;
  1080. procedure THashHistory.SetupListeners;
  1081. begin
  1082. if SupportsScroll then
  1083. TWebScroll.Setup;
  1084. if TBrowserState.SupportsPushState then
  1085. Window.addEventListener('popstate',@DoHashChange)
  1086. else
  1087. Window.addEventListener('hashchange',@DoHashChange);
  1088. end;
  1089. function THashHistory.doPush (location: TRawLocation) : TTransitionResult;
  1090. Var
  1091. L : String;
  1092. begin
  1093. L:=NormalizeHash(location);
  1094. FLastHash:=L;
  1095. pushHash(L);
  1096. Result:=trOK;
  1097. end;
  1098. function THashHistory.doreplace(location: TRawLocation): TTransitionResult;
  1099. Var
  1100. L : String;
  1101. begin
  1102. L:=NormalizeHash(location);
  1103. FLastHash:=L;
  1104. replaceHash(L);
  1105. Result:=trOK;
  1106. end;
  1107. function THashHistory.doGo(N: integer): TTransitionResult;
  1108. begin
  1109. Window.history.go(n);
  1110. result:=trOK;
  1111. end;
  1112. procedure THashHistory.ensureURL (push : boolean = false);
  1113. var
  1114. aHash,CURL: string;
  1115. begin
  1116. CURL:=NormalizeHash(FLastHash);
  1117. aHash:=getHash;
  1118. if (aHash<>CURL) then
  1119. if Push then
  1120. pushHash(CURL)
  1121. else
  1122. replaceHash(CURL)
  1123. end;
  1124. function THashHistory.getCurrentLocation: String;
  1125. begin
  1126. Result:=getHash()
  1127. end;
  1128. class function THashHistory.getHash: string;
  1129. Var
  1130. HRef : String;
  1131. Idx : Integer;
  1132. begin
  1133. // We can't use window.location.hash here because it's not
  1134. // consistent across browsers - Firefox will pre-decode it!
  1135. HRef:=window.location.href;
  1136. Idx:=Pos('#',HRef);
  1137. if (Idx=0) then
  1138. Result:=''
  1139. else
  1140. Result:=Copy(HRef,Idx+1,Length(HRef)-Idx);
  1141. end;
  1142. function THashHistory.Kind : THistoryKind;
  1143. begin
  1144. Result:=hkHash;
  1145. end;
  1146. class function THashHistory.getUrl (APath : string) : string;
  1147. Var
  1148. HRef : String;
  1149. Idx : Integer;
  1150. begin
  1151. HRef:=window.location.href;
  1152. Idx:=Pos('#',HRef);
  1153. if Idx=0 then
  1154. Result:=HRef
  1155. else
  1156. Result:=Copy(HRef,1,Idx-1);
  1157. Result:=Result+'#'+aPath;
  1158. end;
  1159. class procedure THashHistory.pushHash(path: string);
  1160. begin
  1161. if (TBrowserState.supportsPushState) then
  1162. TBrowserState.pushState(getUrl(path),false)
  1163. else
  1164. window.location.hash:=path
  1165. end;
  1166. class procedure THashHistory.replaceHash(path: string);
  1167. Var
  1168. H : String;
  1169. begin
  1170. H:=GetHash;
  1171. if (H=Path) then exit;
  1172. if (TBrowserState.supportsPushState) then
  1173. TBrowserState.replaceState(getUrl(path))
  1174. else
  1175. window.location.replace(getUrl(path))
  1176. end;
  1177. { ---------------------------------------------------------------------
  1178. TAbstractHistory
  1179. ---------------------------------------------------------------------}
  1180. constructor TAbstractHistory.Create (router: TRouter; base: string = '');
  1181. begin
  1182. Inherited;
  1183. SetLength(FStack,0);
  1184. FIndex:=-1;
  1185. end;
  1186. procedure TAbstractHistory.MaybeGrow(AIndex: Integer);
  1187. begin
  1188. if AIndex+1>Length(FStack) then
  1189. Setlength(FStack,AIndex+1);
  1190. end;
  1191. function TAbstractHistory.doPush(location: TRawLocation): TTransitionResult;
  1192. begin
  1193. Inc(FIndex);
  1194. MaybeGrow(FIndex);
  1195. FStack[FIndex]:=Location;
  1196. Result:=trOK;
  1197. end;
  1198. function TAbstractHistory.doReplace(location: TRawLocation): TTransitionResult;
  1199. begin
  1200. FStack[FIndex]:=Location;
  1201. Result:=trOK;
  1202. end;
  1203. function TAbstractHistory.doGo(N: integer): TTransitionResult;
  1204. Var
  1205. I : Integer;
  1206. Route : TRoute;
  1207. begin
  1208. I:=FIndex+N;
  1209. if (I<0) or (I>=Length(FStack)) then
  1210. Result:=trAbort
  1211. else
  1212. begin
  1213. // Route:=FStack[i];
  1214. // Result:=confirmTransition(Route);
  1215. if (Result=trOK) then
  1216. begin
  1217. FIndex:=0;
  1218. updateRoute(Route);
  1219. end;
  1220. end;
  1221. end;
  1222. procedure THistory.UpdateRoute(aRoute: TRoute);
  1223. begin
  1224. FCurrent:=aRoute;
  1225. if Assigned(FOnChange) then
  1226. FOnChange(aRoute);
  1227. end;
  1228. function TAbstractHistory.getCurrentLocation: String;
  1229. Var
  1230. I : Integer;
  1231. Route : string;
  1232. begin
  1233. I:=Length(FStack)-1;
  1234. if (I>=0) then
  1235. Route:=FStack[I]
  1236. else
  1237. Result:='/';
  1238. Result:=Route;
  1239. end;
  1240. procedure TAbstractHistory.ensureURL(Push: Boolean);
  1241. begin
  1242. // Noop
  1243. if Push then ;
  1244. end;
  1245. function TAbstractHistory.Kind: THistoryKind;
  1246. begin
  1247. Result:=hkAbstract;
  1248. end;
  1249. begin
  1250. positionStore:=new([]);
  1251. end.