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