webrouter.pp 37 KB

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