12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514 |
- {
- This file is part of the Free Component Library (FCL)
- Copyright (c) 2017 by the Free Pascal development team
- HTTPRoute: HTTP request router
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- }
- {
- Note:
- The MatchPattern routine was taken from Brook Framework's router unit, by Silvio Clecio.
- }
- {$mode objfpc}
- unit webrouter;
- interface
- uses
- Classes, SysUtils, web;
- Type
- EHTTPRoute = Class(Exception);
- TRawLocation = String;
- TScrollPoint = record
- X,Y : Double;
- end;
- // Forward definitions;
- TRouter = Class;
- TRoute = class;
- THistory = Class;
- TRouterClass = Class of TRouter;
- TRouteEvent = Reference to Procedure (URl : String; aRoute : TRoute; Params: TStrings);
- TTransitionResult = (trOK,trError,trAbort);
- THistoryKind = (hkAuto,hkAbstract,hkHash,hkHTML5);
- TTransitionNotifyEvent = Reference to Procedure (Sender : THistory; aLocation : String; aRoute : TRoute);
- TAllowTransitionEvent = Reference to Procedure (Sender : THistory; aOld, aNew : TRoute; Params : TStrings; var Allow : Boolean);
- { THistory }
- THistory = Class(TObject)
- Private
- FOnAllow: TAllowTransitionEvent;
- FRouter: TRouter;
- FOnChange : TNotifyEvent;
- FOnReady : TTransitionNotifyEvent;
- FOnError : TTransitionNotifyEvent;
- FCurrent : TRoute;
- FBase : String;
- function GetCurrent: TRoute;
- Protected
- procedure SetupListeners; virtual;
- Function doPush (location: TRawLocation) : TTransitionResult; virtual; abstract;
- Function doreplace (location: TRawLocation) : TTransitionResult; virtual; abstract;
- function doGo(N: integer): TTransitionResult; virtual; abstract;
- procedure ensureURL (push : boolean = false); virtual; abstract;
- Public
- Constructor Create(aRouter : TRouter); reintroduce;
- Constructor Create(aRouter : TRouter; aBase : String); virtual;
- Class Function NormalizeHash(aHash : String) : string;
- Procedure UpdateRoute (aRoute : TRoute);
- Destructor Destroy; override;
- Function ExpectScroll : Boolean;
- Function SupportsScroll : Boolean;
- Class function getLocation (base: string): string;
- Class function cleanPath(aPath : string): string;
- // Navigation
- function GetCurrentLocation: String; virtual; abstract;
- Function Push (location: TRawLocation) : TTransitionResult;
- Function Replace (location: TRawLocation) : TTransitionResult;
- function Go(N: integer): TTransitionResult;
- Function NavigateForward: TTransitionResult;
- Function NavigateBack: TTransitionResult;
- Function TransitionTo(aLocation: TRawLocation) : TTransitionResult;
- function ConfirmTransition(aRoute: TRoute; Params: TStrings) : TTransitionResult;
- Property Current : TRoute Read GetCurrent;
- Property Router : TRouter Read FRouter;
- Property OnReady : TTransitionNotifyEvent Read FOnReady Write FOnReady;
- Property OnError : TTransitionNotifyEvent Read FOnError Write FOnError;
- Property OnChange : TNotifyEvent Read FOnChange Write FOnChange;
- Property OnAllowTransition : TAllowTransitionEvent Read FOnAllow Write FOnAllow;
- property Base : String Read FBase;
- function Kind : THistoryKind; virtual; abstract;
- end;
- { TAbstractHistory }
- TAbstractHistory = Class(THistory)
- Private
- FIndex: Integer;
- FStack: Array of TRawLocation;
- procedure MaybeGrow(AIndex: Integer);
- Protected
- Function doPush (location: TRawLocation) : TTransitionResult; override;
- Function doReplace (location: TRawLocation) : TTransitionResult; override;
- function doGo(N: integer): TTransitionResult; override;
- Public
- constructor Create (router: TRouter; base: string = ''); override;
- function getCurrentLocation: String; override;
- Procedure ensureURL (Push: Boolean = False); override;
- function Kind : THistoryKind; override;
- end;
- { THashHistory }
- THashHistory = Class(THistory)
- Protected
- FlastHash : String;
- procedure DoHashChange; virtual;
- procedure SetupListeners; override;
- Function doPush (location: TRawLocation) : TTransitionResult; override;
- Function doreplace (location: TRawLocation) : TTransitionResult; override;
- function doGo(N: integer): TTransitionResult; override;
- procedure ensureURL (push : boolean = false); override;
- Public
- function getCurrentLocation: String; override;
- Class Procedure pushHash (path : string);
- Class Procedure replaceHash (path : string);
- class function getUrl (APath : string) : string;
- Class function getHash : string;
- function Kind : THistoryKind; override;
- end;
- { THTMLHistory }
- THTMLHistory = Class(THistory)
- Protected
- FlastLocation : String;
- procedure DoStateChange; virtual;
- procedure SetupListeners; override;
- Function doPush (location: TRawLocation) : TTransitionResult; override;
- Function doreplace (location: TRawLocation) : TTransitionResult; override;
- function doGo(N: integer): TTransitionResult; override;
- procedure ensureURL (push : boolean = false); override;
- Public
- function getCurrentLocation: String; override;
- Class Procedure pushState (path : string; doReplace : boolean = false);
- Class Procedure replaceState (path : string);
- function getUrl (ALocation : string) : string;
- function Kind : THistoryKind; override;
- end;
- { TRoute }
- TRoute = Class(TCollectionItem)
- private
- FDefault: Boolean;
- FEvent: TRouteEvent;
- FURLPattern: String;
- procedure SetURLPattern(AValue: String);
- Public
- Class function NormalizeURLPattern(AValue: String): String;
- Function Matches(Const APattern : String) : Boolean;
- Function MatchPattern(Const Path : String; L : TStrings) : Boolean;
- Procedure HandleRequest(ARouter : TRouter; Const URL : String; L : TStrings); virtual; abstract;
- Function FullPath : String;
- Published
- Property Default : Boolean Read FDefault Write FDefault;
- Property URLPattern : String Read FURLPattern Write SetURLPattern;
- Property Event : TRouteEvent Read FEvent Write FEvent;
- end;
- TRouteClass = Class of TRoute;
- { TRouteList }
- TRouteList = Class(TCollection)
- private
- function GetR(AIndex : Integer): TRoute;
- procedure SetR(AIndex : Integer; AValue: TRoute);
- Public
- Property Routes[AIndex : Integer] : TRoute Read GetR Write SetR; default;
- end;
- TRouteObject = Class(TObject)
- Procedure HandleRoute (Const URL : String; Params : TStrings); virtual; abstract;
- end;
- TRouteObjectClass = Class of TRouteObject;
- { TRouter }
- TBeforeRouteEvent = reference to procedure(Sender : TObject; Var ARouteURL : String);
- TAfterRouteEvent = reference to procedure(Sender : TObject; const ARouteURL : String);
- TScrollParams = Record
- selector : string;
- Position : TScrollPoint;
- end;
- TPageScrollEvent = reference to Function(Sender : TObject; aTo,aFrom : TRoute; aPosition : TScrollPoint) : TScrollParams;
- TRouter = Class(TComponent)
- Private
- Class Procedure DoneService;
- Class
- Var FService : TRouter;
- FServiceClass : TRouterClass;
- private
- FAfterRequest: TAfterRouteEvent;
- FBeforeRequest: TBeforeRouteEvent;
- FHistory: THistory;
- FOnScroll: TPageScrollEvent;
- FRoutes : TRouteList;
- function GetHistory: THistory;
- function GetHistoryKind: THistoryKind;
- function GetR(AIndex : Integer): TRoute;
- function GetRouteCount: Integer;
- Protected
- // Return an instance of given class with Pattern, Method, IsDefault filled in.
- function CreateHTTPRoute(AClass: TRouteClass; const APattern: String; IsDefault: Boolean ): TRoute; virtual;
- // Override this if you want to use another collection class.
- Function CreateRouteList : TRouteList; virtual;
- Procedure CheckDuplicate(APattern : String; isDefault : Boolean);
- // Actually route request. Override this for customized behaviour.
- function DoRouteRequest(ARoute : TRoute; Const AURL : String; AParams : TStrings) : TRoute; virtual;
- function DoRouteRequest(AURL : String) : TRoute;
- Public
- Constructor Create(AOwner: TComponent); override;
- Destructor Destroy; override;
- Procedure InitHistory(aKind : THistoryKind; aBase : String = '');
- // Delete given route by index.
- Procedure DeleteRoute(AIndex : Integer);
- // Delete given route by index.
- Procedure DeleteRouteByID(AID : Integer);
- // Delete given route by index. The route object will be freed.
- Procedure DeleteRoute(ARoute : TRoute);
- // Sanitize route path. Strips of query parameters and makes sure it ends in /
- class function SanitizeRoute(const Path: String): String;
- // Global instance.
- Class Function Service : TRouter;
- // Class for global instance when it is created;
- Class Function ServiceClass : TRouterClass;
- // This will destroy the service
- Class Procedure SetServiceClass(AClass : TRouterClass);
- // Register event based route
- Function RegisterRoute(Const APattern : String; AEvent: TRouteEvent; IsDefault : Boolean = False) : TRoute;overload;
- // Object class based route. The router is responsible for the lifetime of the object instance
- Function RegisterRoute(Const APattern : String; const AObjectClass: TRouteObjectClass; IsDefault : Boolean = False) : TRoute; overload;
- // Find route. Matches Path on the various patterns. If a pattern is found, then the method is tested.
- // Returns the route that matches the pattern and method.
- function FindHTTPRoute(const Path: String; Params: TStrings): TRoute;
- function GetRoute(const Path: String; Params: TStrings): TRoute;
- // Do actual routing. Exceptions raised will not be caught.
- // This bypasses the history mechanism.
- Function RouteRequest(Const ARouteURL : String) : TRoute;
- // Extract request path from URL. By default, returns the URL
- function GetRequestPath(const URL: String): String; virtual;
- // Navigation. These are easy-access methods for history.
- function GetCurrentLocation: String;
- // These pass by the history mechanism
- Function Push (location: TRawLocation) : TTransitionResult;
- Function Replace (location: TRawLocation) : TTransitionResult;
- function Go(N: integer): TTransitionResult;
- Function NavigateForward : TTransitionResult;
- Function NavigateBack :TTransitionResult;
- // Indexed access to the registered routes.
- Property Routes [AIndex : Integer] : TRoute Read GetR; Default;
- // Number of registered routes.
- Property RouteCount : Integer Read GetRouteCount;
- // Events executed before and after request. In case of exception, after is not executed.
- Property BeforeRequest : TBeforeRouteEvent Read FBeforeRequest Write FBeforeRequest;
- Property AfterRequest : TAfterRouteEvent Read FAfterRequest Write FAfterRequest;
- // OnScroll
- Property OnScroll : TPageScrollEvent Read FOnScroll Write FOnScroll;
- // Currently used history mechanism
- Property History : THistory Read GetHistory;
- // Kind of current history. Shortcut for History.Kind, returns hkauto if History is not assigned
- Property HistoryKind : THistoryKind Read GetHistoryKind;
- end;
- TWebScroll = Class
- Class Procedure scrollToPosition (AScroll : TScrollParams);
- Class function getScrollPosition : TScrollPoint;
- Class Procedure SaveScrollPosition;
- Class Procedure Setup;
- Class Procedure handle (router: TRouter; ato: TRoute; afrom: TRoute; isPop: boolean) ;
- Class Function GetStateKey : String;
- end;
- TBrowserState = Class
- Private
- Class var
- TheKey : String;
- Public
- Class Function GenKey : String;
- Class Function supportsPushState : Boolean;
- Class function GetStateKey : string;
- Class Procedure SetStateKey (akey: string);
- Class Procedure PushState (aUrl : string; replace : boolean);
- Class Procedure ReplaceState(aUrl: string);
- end;
- // Shortcut for TRouter.Service;
- Function Router : TRouter;
- Function IncludeHTTPPathDelimiter (S : String) : String;
- implementation
- uses strutils, js;
- Resourcestring
- EDuplicateRoute = 'Duplicate route pattern: %s';
- EDuplicateDefaultRoute = 'Duplicate default route registered with pattern: %s';
- function Router: TRouter;
- begin
- Result:=TRouter.Service;
- end;
- function IncludeHTTPPathDelimiter(S: String): String;
- begin
- If (Copy(S,Length(S),1)='/') then
- Result:=S
- else
- Result:=S+'/';
- end;
- { THTMLHistory }
- procedure THTMLHistory.DoStateChange;
- Var
- NewLocation : String;
- Old : TRoute;
- begin
- NewLocation:=getLocation(FBase);
- if (NewLocation=FLastLocation) then
- exit;
- old:=Current;
- if TransitionTo(NewLocation)=trOK then
- begin
- TWebScroll.Handle(router, Current, old, true);
- FLastLocation:=NewLocation;
- end
- else
- replaceState(FLastLocation);
- end;
- procedure THTMLHistory.SetupListeners;
- begin
- Window.addEventListener('popstate',@DoStateChange)
- end;
- function THTMLHistory.doPush(location: TRawLocation): TTransitionResult;
- begin
- pushState(GetURL(Location));
- Result:=trOK;
- end;
- function THTMLHistory.doreplace(location: TRawLocation): TTransitionResult;
- begin
- ReplaceState(GetURL(Location));
- Result:=trOK;
- end;
- function THTMLHistory.doGo(N: integer): TTransitionResult;
- begin
- window.history.go(n);
- Result:=trOK;
- end;
- procedure THTMLHistory.ensureURL(push: boolean);
- var
- URL,Actual,Expected : string;
- begin
- Actual:=getCurrentLocation;
- Expected:=FlastLocation;
- if (Actual<>Expected) then
- begin
- url:=getUrl(Expected);
- if Push then
- pushState(url)
- else
- replaceState(url)
- end;
- end;
- function THTMLHistory.getCurrentLocation: String;
- begin
- Result:=window.locationString;
- end;
- class procedure THTMLHistory.pushState(path: string; doReplace: boolean);
- begin
- TBrowserState.pushState(Path,doReplace);
- end;
- class procedure THTMLHistory.replaceState(path: string);
- begin
- pushState(Path,True);
- end;
- function THTMLHistory.getUrl(ALocation : string): string;
- begin
- Result:=IncludeHTTPPathDelimiter(FBase);
- While (ALocation<>'') and (Copy(ALocation,1,1)='/') do
- ALocation:=Copy(ALocation,2,Length(ALocation)-1);
- Result:=FBase+Alocation;
- end;
- function THTMLHistory.Kind: THistoryKind;
- begin
- Result:=hkHTML5;
- end;
- { THistory }
- function THistory.GetCurrent: TRoute;
- begin
- Result:=FCurrent;
- end;
- constructor THistory.Create(aRouter: TRouter);
- begin
- Create(aRouter,'');
- end;
- constructor THistory.Create(aRouter: TRouter; aBase: String);
- begin
- FRouter:=aRouter;
- FBase:=aBase;
- end;
- class function THistory.NormalizeHash(aHash: String): string;
- begin
- Result:=aHash;
- if Copy(Result,1,1)<>'/' then
- Result:='/'+Result;
- end;
- destructor THistory.Destroy;
- begin
- inherited Destroy;
- end;
- function THistory.ExpectScroll: Boolean;
- begin
- Result:=Assigned(Router) and Assigned(Router.OnScroll);
- end;
- function THistory.SupportsScroll: Boolean;
- begin
- Result:=TBrowserState.supportsPushState and ExpectScroll;
- end;
- function THistory.TransitionTo(aLocation: TRawLocation): TTransitionResult;
- Var
- Params : TStrings;
- R : TRoute;
- begin
- Params:=TStringList.Create;
- try
- R:=Router.FindHTTPRoute(aLocation,Params);
- Case ConfirmTransition(R,Params) of
- trOK :
- begin
- R:=Router.DoRouteRequest(R,aLocation,Params);
- UpdateRoute(R);
- if Assigned(OnReady) then
- OnReady(Self,aLocation,R);
- end;
- trError:
- if Assigned(OnError) then
- FOnError(Self,aLocation,R);
- end;
- Finally
- Params.Free;
- end;
- Result:=trOK;
- end;
- function THistory.ConfirmTransition(aRoute: TRoute; Params : TStrings): TTransitionResult;
- Var
- Old : TRoute;
- allow : Boolean;
- begin
- Old:=Current;
- Allow:=True;
- if Assigned(FOnAllow) then
- FOnAllow(Self,old,aRoute,Params,Allow);
- if Not Allow then
- begin
- ensureURL();
- Result:=trAbort;
- end;
- Result:=trOK;
- end;
- { TRouteObjectHandler }
- Type
- TRouteObjectHandler = Class(TRoute)
- private
- FObjectClass: TRouteObjectClass;
- Public
- Procedure HandleRequest(ARouter : TRouter; Const URL : String; Params : TStrings); override;
- Property RouteObjectClass : TRouteObjectClass Read FObjectClass Write FObjectClass;
- end;
- { TRouteEventHandler }
- TRouteEventHandler = Class(TRoute)
- Public
- Procedure HandleRequest(ARouter : TRouter; Const URL : String; Params : TStrings); override;
- Property Event : TRouteEvent Read FEvent Write FEvent;
- end;
- { TRouteEventHandler }
- procedure TRouteEventHandler.HandleRequest(ARouter : TRouter; const URL: String; Params: TStrings);
- begin
- If Assigned(Event) then
- Event(URL,Self,Params);
- end;
- procedure TRouteObjectHandler.HandleRequest(ARouter : TRouter; Const URL : String; Params : TStrings);
- Var
- O : TRouteObject;
- begin
- O:=RouteObjectClass.Create;
- try
- O.HandleRoute(URL,Params);
- finally
- O.Free;
- end;
- end;
- { TRouter }
- function TRouter.GetR(AIndex : Integer): TRoute;
- begin
- Result:=FRoutes[AIndex]
- end;
- function TRouter.GetHistory: THistory;
- begin
- If (FHistory=Nil) then
- InitHistory(hkAuto,'');
- Result:=FHistory;
- end;
- function TRouter.GetHistoryKind: THistoryKind;
- begin
- if Not assigned(History) then
- Result:=hkAuto
- else
- Result:=History.Kind;
- end;
- class procedure TRouter.DoneService;
- begin
- FreeAndNil(FService);
- end;
- function TRouter.GetRouteCount: Integer;
- begin
- Result:=FRoutes.Count;
- end;
- function TRouter.CreateRouteList: TRouteList;
- begin
- Result:=TRouteList.Create(TRoute);
- end;
- procedure TRouter.CheckDuplicate(APattern: String; isDefault: Boolean);
- Var
- I,DI : Integer;
- R : TRoute;
- begin
- DI:=-1;
- For I:=0 to FRoutes.Count-1 do
- begin
- R:=FRoutes[I];
- if R.Default then
- DI:=I;
- if R.Matches(APattern) then
- Raise EHTTPRoute.CreateFmt(EDuplicateRoute,[APattern]);
- end;
- if isDefault and (DI<>-1) then
- Raise EHTTPRoute.CreateFmt(EDuplicateDefaultRoute,[APattern]);
- end;
- function TRouter.DoRouteRequest(ARoute: TRoute; const AURL: String;
- AParams: TStrings): TRoute;
- begin
- Result:=aRoute;
- Result.HandleRequest(Self,aURL,AParams);
- end;
- function TRouter.DoRouteRequest(AURL: String): TRoute;
- Var
- APath : String;
- Params : TStrings;
- begin
- APath:=AURL;
- Params:=TStringList.Create;
- try
- Result:=GetRoute(APath,Params);
- Result:=DoRouteRequest(Result,aPath,Params);
- finally
- Params.Free;
- end;
- end;
- function TRouter.GetRequestPath(const URL: String): String;
- begin
- Result:=SanitizeRoute(URL);
- end;
- function TRouter.GetCurrentLocation: String;
- begin
- end;
- function TRouter.Push(location: TRawLocation): TTransitionResult;
- begin
- Result:=History.Push(location);
- end;
- function TRouter.Replace(location: TRawLocation): TTransitionResult;
- begin
- Result:=History.Replace(location);
- end;
- function TRouter.Go(N: integer): TTransitionResult;
- begin
- Result:=History.Go(N);
- end;
- function TRouter.NavigateForward: TTransitionResult;
- begin
- Result:=Go(1);
- end;
- function TRouter.NavigateBack: TTransitionResult;
- begin
- Result:=Go(-1);
- end;
- constructor TRouter.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- froutes:=CreateRouteList;
- end;
- destructor TRouter.Destroy;
- begin
- FreeAndNil(FRoutes);
- inherited Destroy;
- end;
- procedure TRouter.InitHistory(aKind: THistoryKind; aBase : String = '');
- begin
- FreeAndNil(FHistory);
- case aKind of
- hkAbstract : FHistory:=TAbstractHistory.Create(Self,aBase);
- hkhash : FHistory:=THashHistory.Create(Self,aBase);
- hkHTML5 : FHistory:=THTMLHistory.Create(Self,aBase);
- hkAuto :
- if TBrowserState.supportsPushState then
- FHistory:=THTMLHistory.Create(Self,aBase)
- else
- FHistory:=THashHistory.Create(Self,aBase);
- end;
- FHistory.SetupListeners;
- end;
- procedure TRouter.DeleteRoute(AIndex: Integer);
- begin
- FRoutes.Delete(Aindex)
- end;
- procedure TRouter.DeleteRouteByID(AID: Integer);
- Var
- R : TCollectionItem;
- begin
- R:=FRoutes.FindItemID(AID);
- R.Free;
- end;
- procedure TRouter.DeleteRoute(ARoute: TRoute);
- begin
- ARoute.Free;
- end;
- class function TRouter.Service: TRouter;
- begin
- if FService=Nil then
- FService:=ServiceClass.Create(Nil);
- Result:=FService;
- end;
- class function TRouter.ServiceClass: TRouterClass;
- begin
- If FServiceClass=nil then
- FServiceClass:=TRouter;
- Result:=FServiceClass;
- end;
- class procedure TRouter.SetServiceClass(AClass: TRouterClass);
- begin
- if Assigned(FService) then
- FreeAndNil(FService);
- FServiceClass:=AClass;
- end;
- function TRouter.RegisterRoute(const APattern: String; AEvent: TRouteEvent; IsDefault: Boolean): TRoute;
- begin
- Result:=CreateHTTPRoute(TRouteEventHandler,APattern,IsDefault);
- TRouteEventHandler(Result).Event:=AEvent;
- end;
- function TRouter.CreateHTTPRoute(AClass : TRouteClass; const APattern: String;IsDefault: Boolean) : TRoute;
- begin
- CheckDuplicate(APattern,isDefault);
- Result:=AClass.Create(FRoutes);
- With Result do
- begin
- URLPattern:=APattern;
- Default:=IsDefault;
- end;
- end;
- function TRouter.RegisterRoute(const APattern: String; const AObjectClass: TRouteObjectClass; IsDefault: Boolean): TRoute;
- begin
- Result:=CreateHTTPRoute(TRouteObjectHandler,APattern,IsDefault);
- TRouteObjectHandler(Result).RouteObjectCLass:=AObjectClass;
- end;
- class function TRouter.SanitizeRoute(const Path: String): String;
- begin
- Result:=Path;
- end;
- function TRouter.FindHTTPRoute(const Path: String; Params : TStrings): TRoute;
- Var
- I : Integer;
- APathInfo : String;
- begin
- APathInfo:=SanitizeRoute(Path);
- Result:=Nil;
- I:=0;
- While (Result=Nil) and (I<FRoutes.Count) do
- begin
- Result:=FRoutes[i];
- If Not Result.MatchPattern(APathInfo,Params) then
- Result:=Nil;
- Inc(I);
- end;
- end;
- function TRouter.GetRoute(const Path: String; Params : TStrings): TRoute;
- begin
- Result:=FindHTTPRoute(Path,Params);
- if Not Assigned(Result) then
- Raise EHTTPRoute.Create('Not found');
- end;
- function TRouter.RouteRequest(const ARouteURL: String): TRoute;
- Var
- AURL : String;
- begin
- AURL:=ARouteURL;
- If Assigned(FBeforeRequest) then
- FBeforeRequest(Self,AURL);
- Result:=DoRouteRequest(AURL);
- If Assigned(FAfterRequest) then
- FAfterRequest(Self,AURL);
- end;
- { TRouteList }
- function TRouteList.GetR(AIndex : Integer): TRoute;
- begin
- Result:=Items[AIndex] as TRoute;
- end;
- procedure TRouteList.SetR(AIndex : Integer; AValue: TRoute);
- begin
- Items[AIndex]:=AValue;
- end;
- { TRoute }
- Class Function TRoute.NormalizeURLPattern(AValue: String) : String;
- Var
- V : String;
- begin
- V:=IncludeHTTPPathDelimiter(AValue);
- if (V<>'/') and (V[1]='/') then
- Delete(V,1,1);
- Result:=V;
- end;
- procedure TRoute.SetURLPattern(AValue: String);
- Var
- V : String;
- begin
- V:=NormalizeURLPattern(AValue);
- if (FURLPattern=V) then Exit;
- FURLPattern:=V;
- end;
- function TRoute.Matches(const APattern: String): Boolean;
- begin
- Result:=(CompareText(URLPattern,NormalizeURLPattern(APattern))=0)
- end;
- Function TRoute.MatchPattern(Const Path : String; L : TStrings) : Boolean;
- Function StartsWith(C : Char; S : String): Boolean;
-
- begin
- Result:=(Length(S)>0) and (S[1]=C);
- end;
-
- Function EndsWith(C : Char; S : String): Boolean;
-
- Var
- L : Integer;
-
- begin
- L:=Length(S);
- Result:=(L>0) and (S[L]=C);
- end;
- procedure ExtractNextPathLevel(var ALeft: string;
- var ALvl: string; var ARight: string; const ADelim: Char = '/');
- var
- P: Integer;
- begin
- Writeln('ExtractNextPathLevel >:',Aleft,' (',aLvl,') ',aRight);
- if (ALvl<>ADelim) then
- begin
- ALeft:=ALeft+ALvl;
- if StartsWith(ADelim,ARight) then
- begin
- ALeft:=ALeft+ADelim;
- Delete(ARight,1,1);
- end;
- end;
- P:=Pos(ADelim,ARight);
- if P=0 then
- P:=Length(ARight)+1;
- ALvl:=Copy(ARight,1,P-1);
- ARight:=Copy(ARight,P,MaxInt);
- Writeln('ExtractNextPathLevel <:',Aleft,' (',aLvl,') ',aRight);
- end;
- procedure ExtractPrevPathLevel(var ALeft: string;
- var ALvl: string; var ARight: string; const ADelim: Char = '/');
- var
- P,L: Integer;
- begin
- Writeln('ExtractPrevPathLevel >:',Aleft,' (',aLvl,') ',aRight);
- if (ALvl<>ADelim) then
- begin
- ARight:=ALvl+ARight;
- L:=Length(ALeft);
- if EndsWith(ADelim,ALeft) then
- begin
- ARight:=ADelim+ARight;
- Delete(ALeft,L,1);
- end;
- end;
- P:=RPos(ADelim,ALeft);
- ALvl:=Copy(ALeft,P+1,MaxInt);
- ALeft:=Copy(ALeft,1,P);
- Writeln('ExtractPrevPathLevel <:',Aleft,' (',aLvl,') ',aRight);
- end;
- Procedure AddParam(aName,AValue : String);
- begin
- if Assigned(L) then
- L.Values[aName]:=aValue;
- end;
- var
- APathInfo : String;
- APattern : String;
- VLeftPat, VRightPat, VLeftVal, VRightVal, VVal, VPat, VName: string;
- begin
- Result:= False;
- if (URLPattern='') then
- Exit; // Maybe empty pattern should match any path?
- APathInfo:=Path;
- APattern:=URLPattern;
- Delete(APattern, Pos('?', APattern), MaxInt);
- Delete(APathInfo, Pos('?', APathInfo), MaxInt);
- if StartsWith('/',APattern) then
- Delete(APattern,1,1);
- if StartsWith('/',APathInfo) then
- Delete(APathInfo,1,1);
- VLeftPat := '';
- VLeftVal := '';
- VPat := '/'; // init value is '/', not ''
- VVal := '/'; // init value is '/', not ''
- VRightPat := APattern;
- VRightVal := APathInfo;
- Writeln('Check match on ',URLPattern);
- repeat
- // Extract next part
- ExtractNextPathLevel(VLeftPat, VPat, VRightPat);
- ExtractNextPathLevel(VLeftVal, VVal, VRightVal);
- Writeln('Pat: ',VPat,' Val: ',VVal);
- if StartsWith(':',VPat) then
- AddParam(Copy(VPat,2,Maxint),VVal)
- else
- if StartsWith('*',VPat) then
- begin
- // *path
- VName := Copy(VPat, 2, MaxInt);
- VLeftPat := VRightPat;
- VLeftVal := VVal + VRightVal;
- VPat := '/'; // init value is '/', not ''
- VVal := '/'; // init value is '/', not ''
- VRightPat := '';
- VRightVal := '';
- // if AutoAddSlash ...
- if EndsWith('/',VLeftPat) and not EndsWith('/',VLeftVal) then
- Delete(VLeftPat, Length(VLeftPat), 1);
- repeat
- // Extract backwards
- ExtractPrevPathLevel(VLeftPat, VPat, VRightPat);
- ExtractPrevPathLevel(VLeftVal, VVal, VRightVal);
- if StartsWith(':', VPat) then
- begin
- // *path/:field
- AddParam(Copy(VPat,2,Maxint),VVal);
- end
- else
- // *path/const
- if not ((VPat='') and (VLeftPat='')) and (VPat<>VVal) then
- Exit;
- // Check if we already done
- if (VLeftPat='') or (VLeftVal='') then
- begin
- if VLeftPat='' then
- begin
- if (VName<>'') then
- AddParam(VName,VLeftVal+VVal);
- Result:=True;
- end;
- Exit;
- end;
- until False;
- end
- else
- // const
- if (VPat <> VVal) then
- Exit;
- // Check if we already done
- if (VRightPat='') or (VRightVal='') then
- begin
- if (VRightPat='') and (VRightVal='') then
- Result:=True
- else if (VRightPat='/') then
- Result := True;
- Exit;
- end;
- until False;
- end;
- function TRoute.FullPath: String;
- begin
- Result:=URLPattern;
- end;
- class function THistory.getLocation(base: string): string;
- Var
- path : string;
- begin
- path:=window.location.pathname;
- if (base<>'') and (Pos(base,path)=1) then
- path:=Copy(Path,Length(Base)+1,Length(Path)-Length(Base));
- Result:=Path;
- if Result='' then
- Result:='/';
- Result:=Result+window.location.search+window.location.hash
- end;
- class function THistory.cleanPath(aPath: string): string;
- begin
- Result:=StringReplace(aPath,'//','/',[rfReplaceAll]);
- end;
- function THistory.Push(location: TRawLocation): TTransitionResult;
- Var
- Old : TRoute;
- begin
- Old:=Current;
- Result:=TransitionTo(location);
- if Result=trOK then
- begin
- Result:=doPush(Location);
- if Result=trOK then
- TWebScroll.Handle(router, Current, Old, false)
- end;
- end;
- function THistory.Replace(location: TRawLocation): TTransitionResult;
- Var
- Old : TRoute;
- begin
- Old:=Current;
- Result:=TransitionTo(location);
- if Result=trOK then
- begin
- Result:=doReplace(Location);
- TWebScroll.Handle(Router,Current,Old,false);
- end;
- end;
- function THistory.Go(N: integer): TTransitionResult;
- begin
- Result:=doGo(N);
- end;
- function THistory.NavigateForward: TTransitionResult;
- begin
- Result:=Go(1);
- end;
- function THistory.NavigateBack: TTransitionResult;
- begin
- Result:=Go(-1);
- end;
- procedure THistory.SetupListeners;
- begin
- // Do nothing
- end;
- function DoScroll(Event: TEventListenerEvent): boolean;
- begin
- TWebScroll.SaveScrollPosition;
- Result:=True;
- end;
- Class Function TWebScroll.GetStateKey : string;
- begin
- Result:=TJSDate.New().toString;
- end;
- Class Procedure TWebScroll.Setup;
- begin
- // web.window.;
- window.history.replaceState(New(['key', GetStateKey]), '');
- window.addEventListener('popstate',@DoScroll);
- end;
- Class Procedure TWebScroll.handle (router: TRouter; ato: TRoute; afrom: TRoute; isPop: boolean) ;
- Var
- Position : TScrollPoint;
- ScrollParams : TScrollParams;
- begin
- if Not Assigned(Router.OnScroll) then
- Exit;
- position:=getScrollPosition();
- ScrollParams:=Router.OnScroll(Router, aTo, aFrom, position);
- scrollToPosition(ScrollParams);
- end;
- Var
- positionStore : TJSObject;
- Class procedure TWebScroll.saveScrollPosition;
- Var
- Key : string;
- begin
- key:=getStateKey();
- if Key<>'' then
- positionStore.properties[key]:=New(['x',window.ScrollX,'y',window.ScrollY]);
- end;
- Class function TWebScroll.getScrollPosition : TScrollPoint;
- Var
- Key : string;
- O : JSValue;
- begin
- key:=getStateKey();
- Result.X:=0;
- Result.Y:=0;
- if (Key<>'') then
- begin
- O:=positionStore[key];
- if isObject(O) then
- begin
- Result.X:=Double(TJSOBject(O).Properties['x']);
- Result.Y:=Double(TJSOBject(O).Properties['y']);
- end;
- end;
- end;
- function getElementPosition (el: TJSElement; offset: TScrollPoint): TScrollPoint ;
- Var
- DocEl : TJSElement;
- docRect,elRect : TJSDOMRect;
- begin
- docEl:=document.documentElement;
- docRect := docEl.getBoundingClientRect();
- elRect := el.getBoundingClientRect();
- Result.x:= elRect.left - docRect.left - offset.x;
- Result.Y:= elRect.top - docRect.top - offset.y;
- end;
- Class Procedure TWebScroll.ScrollToPosition(AScroll : TScrollParams);
- Var
- el : TJSElement;
- P : TScrollPoint;
- begin
- if (AScroll.Selector<>'') then
- begin
- el:=document.querySelector(AScroll.Selector);
- if Assigned(el) then
- P:=getElementPosition(el,AScroll.Position)
- else
- P:=AScroll.Position;
- end
- else
- P:=AScroll.Position;
- Window.scrollTo(Round(P.x),Round(P.y));
- end;
- Class function TBrowserState.genKey (): string ;
- begin
- Result:=IntToStr(TJSDate.now);
- end;
- Class function TBrowserState.getStateKey : string;
- begin
- if (TheKey='') then
- TheKey:=genKey;
- Result:=Thekey;
- end;
- Class Procedure TBrowserState.SetStateKey (akey: string);
- begin
- Thekey:=akey;
- end;
- Class Procedure TBrowserState.pushState (aurl: string; replace: boolean);
- Var
- O : TJSObject;
- begin
- TWebScroll.SaveScrollPosition;
- try
- if (Not replace) then
- SetStateKey(GenKey);
- O:=New(['key', GetStateKey()]);
- if replace then
- window.history.replaceState(o, '', aUrl)
- else
- window.history.pushState(o, '', aUrl);
- except
- if Replace then
- window.location.replace(aUrl)
- else
- window.location.Assign(aUrl);
- end;
- end;
- Class Procedure TBrowserState.replaceState(aUrl: string);
- begin
- pushState(aUrl, true)
- end;
- Class Function TBrowserState.supportsPushState : Boolean;
- Var
- UA : String;
- Function isB(B : String) : Boolean;
- begin
- Result:=Pos(B,UA)<>0;
- end;
- begin
- if Result and isDefined(Window) and isDefined(Window.Navigator) then
- begin
- ua:=Window.Navigator.userAgent;
- Result:=Not (
- IsB('Android 2.')
- or IsB('Android 4.0')
- or IsB('Mobile Safari')
- or IsB('Chrome')
- or isB('Windows Phone')
- );
- If Result then
- Result:=isDefined(Window.history) and isDefined(Window.history);
- end;
- end;
- { ---------------------------------------------------------------------
- THashHistory
- ---------------------------------------------------------------------}
- procedure THashHistory.DoHashChange;
- Var
- NewHash : String;
- Old : TRoute;
- begin
- NewHash:=NormalizeHash(GetHash);
- if (NewHash=FLastHash) then
- exit;
- old:=Current;
- if TransitionTo(NewHash)=trOK then
- begin
- TWebScroll.Handle(router, Current, old, true);
- FLastHash:=NewHash;
- end
- else
- replaceHash(FLastHash);
- end;
- procedure THashHistory.SetupListeners;
- begin
- if SupportsScroll then
- TWebScroll.Setup;
- if TBrowserState.SupportsPushState then
- Window.addEventListener('popstate',@DoHashChange)
- else
- Window.addEventListener('hashchange',@DoHashChange);
- end;
- function THashHistory.doPush (location: TRawLocation) : TTransitionResult;
- Var
- L : String;
- begin
- L:=NormalizeHash(location);
- FLastHash:=L;
- pushHash(L);
- Result:=trOK;
- end;
- function THashHistory.doreplace(location: TRawLocation): TTransitionResult;
- Var
- L : String;
- begin
- L:=NormalizeHash(location);
- FLastHash:=L;
- replaceHash(L);
- Result:=trOK;
- end;
- function THashHistory.doGo(N: integer): TTransitionResult;
- begin
- Window.history.go(n);
- result:=trOK;
- end;
- procedure THashHistory.ensureURL (push : boolean = false);
- var
- aHash,CURL: string;
- begin
- CURL:=NormalizeHash(FLastHash);
- aHash:=getHash;
- if (aHash<>CURL) then
- if Push then
- pushHash(CURL)
- else
- replaceHash(CURL)
- end;
- function THashHistory.getCurrentLocation: String;
- begin
- Result:=getHash()
- end;
- class function THashHistory.getHash: string;
- Var
- HRef : String;
- Idx : Integer;
- begin
- // We can't use window.location.hash here because it's not
- // consistent across browsers - Firefox will pre-decode it!
- HRef:=window.location.href;
- Idx:=Pos('#',HRef);
- if (Idx=0) then
- Result:=''
- else
- Result:=Copy(HRef,Idx+1,Length(HRef)-Idx);
- end;
- function THashHistory.Kind : THistoryKind;
- begin
- Result:=hkHash;
- end;
- class function THashHistory.getUrl (APath : string) : string;
- Var
- HRef : String;
- Idx : Integer;
- begin
- HRef:=window.location.href;
- Idx:=Pos('#',HRef);
- if Idx=0 then
- Result:=HRef
- else
- Result:=Copy(HRef,1,Idx-1);
- Result:=Result+'#'+aPath;
- end;
- class procedure THashHistory.pushHash(path: string);
- begin
- if (TBrowserState.supportsPushState) then
- TBrowserState.pushState(getUrl(path),false)
- else
- window.location.hash:=path
- end;
- class procedure THashHistory.replaceHash(path: string);
- Var
- H : String;
- begin
- H:=GetHash;
- if (H=Path) then exit;
- if (TBrowserState.supportsPushState) then
- TBrowserState.replaceState(getUrl(path))
- else
- window.location.replace(getUrl(path))
- end;
- { ---------------------------------------------------------------------
- TAbstractHistory
- ---------------------------------------------------------------------}
- constructor TAbstractHistory.Create (router: TRouter; base: string = '');
- begin
- Inherited;
- SetLength(FStack,0);
- FIndex:=-1;
- end;
- procedure TAbstractHistory.MaybeGrow(AIndex: Integer);
- begin
- if AIndex+1>Length(FStack) then
- Setlength(FStack,AIndex+1);
- end;
- function TAbstractHistory.doPush(location: TRawLocation): TTransitionResult;
- begin
- Inc(FIndex);
- MaybeGrow(FIndex);
- FStack[FIndex]:=Location;
- Result:=trOK;
- end;
- function TAbstractHistory.doReplace(location: TRawLocation): TTransitionResult;
- begin
- FStack[FIndex]:=Location;
- Result:=trOK;
- end;
- function TAbstractHistory.doGo(N: integer): TTransitionResult;
- Var
- I : Integer;
- Route : TRoute;
- begin
- I:=FIndex+N;
- if (I<0) or (I>=Length(FStack)) then
- Result:=trAbort
- else
- begin
- // Route:=FStack[i];
- // Result:=confirmTransition(Route);
- if (Result=trOK) then
- begin
- FIndex:=0;
- updateRoute(Route);
- end;
- end;
- end;
- procedure THistory.UpdateRoute(aRoute: TRoute);
- begin
- FCurrent:=aRoute;
- if Assigned(FOnChange) then
- FOnChange(aRoute);
- end;
- function TAbstractHistory.getCurrentLocation: String;
- Var
- I : Integer;
- Route : string;
- begin
- I:=Length(FStack)-1;
- if (I>=0) then
- Route:=FStack[I]
- else
- Result:='/';
- Result:=Route;
- end;
- procedure TAbstractHistory.ensureURL(Push: Boolean);
- begin
- // Noop
- if Push then ;
- end;
- function TAbstractHistory.Kind: THistoryKind;
- begin
- Result:=hkAbstract;
- end;
- begin
- positionStore:=new([]);
- end.
|