webrouter.pp 38 KB

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