webrouter.pp 36 KB

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