This commit is contained in:
Ezequiel Juliano Müller 2017-02-07 09:24:39 -02:00
commit 18e4412f73
177 changed files with 4026 additions and 824 deletions

View File

@ -32,6 +32,7 @@
* Messaging extension using STOMP (beta) * Messaging extension using STOMP (beta)
* Automatic documentation through /system/describeserver.info * Automatic documentation through /system/describeserver.info
* Driven by its huge community (Facebook group https://www.facebook.com/groups/delphimvcframework) * Driven by its huge community (Facebook group https://www.facebook.com/groups/delphimvcframework)
* Semantic Versioning
* Simple and [documented](https://github.com/danieleteti/delphimvcframework/blob/master/docs/ITDevCON%202013%20-%20Introduction%20to%20DelphiMVCFramework.pdf) * Simple and [documented](https://github.com/danieleteti/delphimvcframework/blob/master/docs/ITDevCON%202013%20-%20Introduction%20to%20DelphiMVCFramework.pdf)
* Check the [DMVCFramework Developer Guide](https://danieleteti.gitbooks.io/delphimvcframework/content/) (work in progress) * Check the [DMVCFramework Developer Guide](https://danieleteti.gitbooks.io/delphimvcframework/content/) (work in progress)
@ -56,6 +57,31 @@ These are the most notable:
* DelphiRedisClient (https://github.com/danieleteti/delphiredisclient) * DelphiRedisClient (https://github.com/danieleteti/delphiredisclient)
* LoggerPro (https://github.com/danieleteti/loggerpro) * LoggerPro (https://github.com/danieleteti/loggerpro)
### Using ObjectsMappers in Delphi Starter Edition
A lot of users ask about it, now is possible to use the Mapper also in Delphi Started Edition. To enable the "StarterEditionMode" open ```sources\dmvcframework.inc``` and remove the dot (.) after the curly brace in the following line
```{.$DEFINE STARTEREDITION}```
become
```{$DEFINE STARTEREDITION}```
## Release Notes
**2.1.3 (lithium)**
- FIX https://github.com/danieleteti/delphimvcframework/issues/64
- Added unit tests to avoid regressions
**2.1.2 (helium)**
- FIX for Delphi versions who don't have ```TJSONBool``` (Delphi XE8 or older)
- Added new conditional define in dmvcframework.inc: JSONBOOL (defined for Delphi Seattle+)
**2.1.1 (hydrogen)**
- Updated the IDE Expert to show the current version of the framework
- FIX to the mapper about the datasets null values (needs to be checked in old Delphi versions)
- ADDED support for boolean values in datasets serialization
- ADDED unit tests about Mapper and dataset fields nullability
- The current version is available in constant ```DMVCFRAMEWORK_VERSION``` defined in ```MVCFramework.Commons.pas```
##Samples and documentation ##Samples and documentation
DMVCFramework is provided with a lot of examples focused on specific functionality. DMVCFramework is provided with a lot of examples focused on specific functionality.
All samples are in [Samples](https://github.com/danieleteti/delphimvcframework/tree/master/samples) folder. All samples are in [Samples](https://github.com/danieleteti/delphimvcframework/tree/master/samples) folder.
@ -66,7 +92,7 @@ Check the [DMVCFramework Developer Guide](https://danieleteti.gitbooks.io/delphi
Below the is a basic sample of a DMVCFramework server wich can be deployed as standa-alone application, as an Apache module or as ISAPI dll. This flexibility is provided by the Delphi WebBroker framework (built-in in Delphi since Delphi 4). Below the is a basic sample of a DMVCFramework server wich can be deployed as standa-alone application, as an Apache module or as ISAPI dll. This flexibility is provided by the Delphi WebBroker framework (built-in in Delphi since Delphi 4).
The project containes an IDE Expert which make creating DMVCFramework project a breeze. However not all the Delphi version are supported, so here's the manual version (which is not complicated at all). The project containes an IDE Expert which make creating DMVCFramework project a breeze. However not all the Delphi version are supported, so here's the manual version (which is not complicated at all).
To create this server, you have to create a new Delphi Projects -> WebBroker -> WebServerApplication. Then add the following changes to the webmodule. To create this server, you have to create a new ```Delphi Projects -> WebBroker -> WebServerApplication```. Then add the following changes to the webmodule.
```delphi ```delphi
unit WebModuleUnit1; unit WebModuleUnit1;

View File

@ -8,7 +8,7 @@ init()
################################################################################# #################################################################################
def buildProject(project): def buildProject(project, platform = 'Win32'):
print(Fore.YELLOW + "Building " + project) print(Fore.YELLOW + "Building " + project)
p = project.replace('.dproj', '.cfg') p = project.replace('.dproj', '.cfg')
if os.path.isfile(p): if os.path.isfile(p):
@ -16,7 +16,7 @@ def buildProject(project):
os.remove(p + '.unused') os.remove(p + '.unused')
os.rename(p, p + '.unused') os.rename(p, p + '.unused')
# print os.system("msbuild /t:Build /p:Config=Debug \"" + project + "\"") # print os.system("msbuild /t:Build /p:Config=Debug \"" + project + "\"")
return subprocess.call("rsvars.bat & msbuild /t:Build /p:Config=Debug /p:Platform=Win32 \"" + project + "\"", shell=True) == 0 return subprocess.call("rsvars.bat & msbuild /t:Build /p:Config=Debug /p:Platform=" + platform + " \"" + project + "\"", shell=True) == 0
def summaryTable(builds): def summaryTable(builds):
@ -26,19 +26,14 @@ def summaryTable(builds):
print(Fore.YELLOW + "=" * 90) print(Fore.YELLOW + "=" * 90)
good = bad = 0 good = bad = 0
for item in builds: for item in builds:
if item['status'] == 'ok': if item['status'].startswith('ok'):
#WConio.textcolor(WConio.LIGHTGREEN)
good += 1 good += 1
else: else:
#WConio.textcolor(WConio.RED)
bad += 1 bad += 1
print(Fore.BLUE + item['project'].ljust(80) + (Fore.WHITE if item['status'] == 'ok' else Fore.RED) + item['status'].ljust(4)) print(Fore.BLUE + item['project'].ljust(80) + (Fore.WHITE if item['status'].startswith('ok') else Fore.RED) + item['status'].ljust(4))
#WConio.textcolor(WConio.WHITE)
print(Fore.YELLOW + "=" * 90) print(Fore.YELLOW + "=" * 90)
#WConio.textcolor(WConio.GREEN)
print(Fore.WHITE + "GOOD :".rjust(80) + str(good).rjust(10, '.')) print(Fore.WHITE + "GOOD :".rjust(80) + str(good).rjust(10, '.'))
#WConio.textcolor(WConio.RED)
print(Fore.RED + "BAD :".rjust(80) + str(bad).rjust(10, '.')) print(Fore.RED + "BAD :".rjust(80) + str(bad).rjust(10, '.'))
@ -49,6 +44,7 @@ def main(projects):
builds = [] builds = []
for project in projects: for project in projects:
filename = '\\'.join(project.split('\\')[-3:]) filename = '\\'.join(project.split('\\')[-3:])
list = {'project': filename}
if project.find('delphistompclient') > -1 or project.find('contribsamples') > -1: if project.find('delphistompclient') > -1 or project.find('contribsamples') > -1:
list['status'] = 'skip' list['status'] = 'skip'
continue continue
@ -59,6 +55,14 @@ def main(projects):
else: else:
list["status"] = "ko" list["status"] = "ko"
builds.append(list) builds.append(list)
if (os.path.exists(project + '.android')):
list = {'project': filename}
if buildProject(project, 'Android'):
list["status"] = "okandroid"
else:
list["status"] = "koandroid"
builds.append(list)
summaryTable(builds) summaryTable(builds)
# Store current attribute settings # Store current attribute settings
@ -67,7 +71,7 @@ def main(projects):
def dmvc_copyright(): def dmvc_copyright():
print(Style.BRIGHT + Fore.WHITE + "----------------------------------------------------------------------------------------") print(Style.BRIGHT + Fore.WHITE + "----------------------------------------------------------------------------------------")
print(Fore.RED + " ** Delphi MVC Framework Building System **") print(Fore.RED + " ** Delphi MVC Framework Building System **")
print(Fore.WHITE + "Delphi MVC Framework is CopyRight (2010-2016) of Daniele Teti and the DMVCFramework TEAM") print(Fore.WHITE + "Delphi MVC Framework is CopyRight (2010-2017) of Daniele Teti and the DMVCFramework TEAM")
print(Fore.RESET + "----------------------------------------------------------------------------------------\n") print(Fore.RESET + "----------------------------------------------------------------------------------------\n")
## MAIN ## ## MAIN ##

BIN
docs/periodic_table.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

View File

@ -1,32 +1,32 @@
{ *************************************************************************** } // ***************************************************************************
{ } //
{ Delphi MVC Framework } // Delphi MVC Framework
{ } //
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } // Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team
{ } //
{ https://github.com/danieleteti/delphimvcframework } // https://github.com/danieleteti/delphimvcframework
{ } //
{ *************************************************************************** } // ***************************************************************************
{ } //
{ Licensed under the Apache License, Version 2.0 (the "License"); } // Licensed under the Apache License, Version 2.0 (the "License");
{ you may not use this file except in compliance with the License. } // you may not use this file except in compliance with the License.
{ You may obtain a copy of the License at } // You may obtain a copy of the License at
{ } //
{ http://www.apache.org/licenses/LICENSE-2.0 } // http://www.apache.org/licenses/LICENSE-2.0
{ } //
{ Unless required by applicable law or agreed to in writing, software } // Unless required by applicable law or agreed to in writing, software
{ distributed under the License is distributed on an "AS IS" BASIS, } // distributed under the License is distributed on an "AS IS" BASIS,
{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
{ See the License for the specific language governing permissions and } // See the License for the specific language governing permissions and
{ limitations under the License. } // limitations under the License.
{ } //
{ This IDE expert is based off of the one included with the DUnitX } // This IDE expert is based off of the one included with the DUnitX
{ project. Original source by Robert Love. Adapted by Nick Hodges. } // project. Original source by Robert Love. Adapted by Nick Hodges.
{ } //
{ The DUnitX project is run by Vincent Parrett and can be found at: } // The DUnitX project is run by Vincent Parrett and can be found at:
{ } //
{ https://github.com/VSoftTechnologies/DUnitX } // https://github.com/VSoftTechnologies/DUnitX
{ *************************************************************************** } // ***************************************************************************
unit DMVC.Expert.CodeGen.Templates; unit DMVC.Expert.CodeGen.Templates;
@ -44,6 +44,7 @@ resourcestring
'uses' + sLineBreak + 'uses' + sLineBreak +
' System.SysUtils,' + sLineBreak + ' System.SysUtils,' + sLineBreak +
' MVCFramework.Logger,' + sLineBreak + ' MVCFramework.Logger,' + sLineBreak +
' MVCFramework.Commons,' + sLineBreak +
' Winapi.Windows,' + sLineBreak + ' Winapi.Windows,' + sLineBreak +
' Winapi.ShellAPI,' + sLineBreak + ' Winapi.ShellAPI,' + sLineBreak +
' ReqMulti, {enables files upload}' + sLineBreak + ' ReqMulti, {enables files upload}' + sLineBreak +
@ -60,7 +61,7 @@ resourcestring
' LHandle: THandle;' + sLineBreak + ' LHandle: THandle;' + sLineBreak +
' LServer: TIdHTTPWebBrokerBridge;' + sLineBreak + ' LServer: TIdHTTPWebBrokerBridge;' + sLineBreak +
'begin' + sLineBreak + 'begin' + sLineBreak +
' Writeln(''** DMVCFramework Server **'');' + sLineBreak + ' Writeln(''** DMVCFramework Server ** build '' + DMVCFRAMEWORK_VERSION);' + sLineBreak +
' Writeln(Format(''Starting HTTP Server on port %%d'', [APort]));' + sLineBreak + ' Writeln(Format(''Starting HTTP Server on port %%d'', [APort]));' + sLineBreak +
' LServer := TIdHTTPWebBrokerBridge.Create(nil);' + sLineBreak + ' LServer := TIdHTTPWebBrokerBridge.Create(nil);' + sLineBreak +
' try' + sLineBreak + ' try' + sLineBreak +
@ -95,6 +96,7 @@ resourcestring
sLineBreak + sLineBreak +
'begin' + sLineBreak + 'begin' + sLineBreak +
' ReportMemoryLeaksOnShutdown := True;' + sLineBreak + ' ReportMemoryLeaksOnShutdown := True;' + sLineBreak +
' IsMultiThread := True;' + sLineBreak +
' try' + sLineBreak + ' try' + sLineBreak +
' if WebRequestHandler <> nil then' + sLineBreak + ' if WebRequestHandler <> nil then' + sLineBreak +
' WebRequestHandler.WebModuleClass := WebModuleClass;' + sLineBreak + ' WebRequestHandler.WebModuleClass := WebModuleClass;' + sLineBreak +
@ -117,7 +119,7 @@ resourcestring
'interface' + sLineBreak + 'interface' + sLineBreak +
sLineBreak + sLineBreak +
'uses' + sLineBreak + 'uses' + sLineBreak +
' MVCFramework;' + sLineBreak + ' MVCFramework, MVCFramework.Commons;' + sLineBreak +
sLineBreak + sLineBreak +
'type' + sLineBreak + 'type' + sLineBreak +
sLineBreak + sLineBreak +
@ -128,7 +130,7 @@ resourcestring
'%4:s' + '%4:s' +
' end;' + sLineBreak + ' end;' + sLineBreak +
sLineBreak + sLineBreak +
'implementation' + sLineBreak + 'implementation' + sLineBreak + sLineBreak +
'uses' + sLineBreak + 'uses' + sLineBreak +
' MVCFramework.Logger;' + sLineBreak + ' MVCFramework.Logger;' + sLineBreak +
sLineBreak + sLineBreak +
@ -150,16 +152,15 @@ resourcestring
'procedure %0:s.Index;' + sLineBreak + 'procedure %0:s.Index;' + sLineBreak +
'begin' + sLineBreak + 'begin' + sLineBreak +
' //use Context property to access to the HTTP request and response ' + sLineBreak + ' //use Context property to access to the HTTP request and response ' + sLineBreak +
' Render(''Hello World'');' + sLineBreak + ' Render(''Hello DelphiMVCFramework World'');' + sLineBreak +
sLineBreak +
'end;' + sLineBreak + sLineBreak + 'end;' + sLineBreak + sLineBreak +
'procedure %0:s.GetSpecializedHello(const FirstName: String);' + sLineBreak + 'procedure %0:s.GetSpecializedHello(const FirstName: String);' + sLineBreak +
'begin' + sLineBreak + 'begin' + sLineBreak +
' Render(''Hello '' + FirstName);' + sLineBreak + ' Render(''Hello '' + FirstName);' + sLineBreak +
sLineBreak +
'end;' + sLineBreak; 'end;' + sLineBreak;
sActionFiltersIntf = sActionFiltersIntf =
' protected' + sLineBreak +
' procedure OnBeforeAction(Context: TWebContext; const AActionName: string; var Handled: Boolean); override;' ' procedure OnBeforeAction(Context: TWebContext; const AActionName: string; var Handled: Boolean); override;'
+ sLineBreak + + sLineBreak +
' procedure OnAfterAction(Context: TWebContext; const AActionName: string); override;' + ' procedure OnAfterAction(Context: TWebContext; const AActionName: string); override;' +
@ -244,7 +245,8 @@ resourcestring
' Config[TMVCConfigKey.Messaging] := ''false'';' + sLineBreak + ' Config[TMVCConfigKey.Messaging] := ''false'';' + sLineBreak +
' //Enable Server Signature in response' + sLineBreak + ' //Enable Server Signature in response' + sLineBreak +
' Config[TMVCConfigKey.ExposeServerSignature] := ''true'';' + sLineBreak + ' Config[TMVCConfigKey.ExposeServerSignature] := ''true'';' + sLineBreak +
' // Define a default URL for requests that don''t map to a route or a file (useful for client side web app)' + sLineBreak + ' // Define a default URL for requests that don''t map to a route or a file (useful for client side web app)' +
sLineBreak +
' Config[TMVCConfigKey.FallbackResource] := ''index.html'';' + sLineBreak + ' Config[TMVCConfigKey.FallbackResource] := ''index.html'';' + sLineBreak +
' end);' + sLineBreak + ' end);' + sLineBreak +
' FMVC.AddController(%3:s);' + sLineBreak + ' FMVC.AddController(%3:s);' + sLineBreak +

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

View File

@ -2,7 +2,7 @@
{ } { }
{ Delphi MVC Framework } { Delphi MVC Framework }
{ } { }
{ Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team } { Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team }
{ } { }
{ https://github.com/danieleteti/delphimvcframework } { https://github.com/danieleteti/delphimvcframework }
{ } { }

@ -1 +1 @@
Subproject commit 31001af0cb56097b4a982d75c9089693a166863e Subproject commit 6724ff46bbfae41129e5c54bd4a7fc991de55129

@ -1 +1 @@
Subproject commit a4a9abd311152797dff16886d81b77678fd0b57c Subproject commit cd36a96d19c983f3b59d27e9d4d127feb9f297ac

@ -1 +1 @@
Subproject commit 7e84f870082d60c94c3ffe7a930c8be847f9fc2c Subproject commit 765512249408358eacf2e07dd39eec1b2a653ba3

View File

@ -2,7 +2,7 @@
// //
// Delphi MVC Framework // Delphi MVC Framework
// //
// Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team // Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team
// //
// https://github.com/danieleteti/delphimvcframework // https://github.com/danieleteti/delphimvcframework
// //

View File

@ -2,7 +2,7 @@
// //
// Delphi MVC Framework // Delphi MVC Framework
// //
// Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team // Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team
// //
// https://github.com/danieleteti/delphimvcframework // https://github.com/danieleteti/delphimvcframework
// //

View File

@ -2,7 +2,7 @@
// //
// Delphi MVC Framework // Delphi MVC Framework
// //
// Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team // Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team
// //
// https://github.com/danieleteti/delphimvcframework // https://github.com/danieleteti/delphimvcframework
// //

View File

@ -2,7 +2,7 @@
// //
// Delphi MVC Framework // Delphi MVC Framework
// //
// Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team // Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team
// //
// https://github.com/danieleteti/delphimvcframework // https://github.com/danieleteti/delphimvcframework
// //
@ -27,7 +27,7 @@ unit PrivateControllerU;
interface interface
uses uses
MVCFramework; MVCFramework, MVCFramework.Commons;
type type

View File

@ -2,7 +2,7 @@
// //
// Delphi MVC Framework // Delphi MVC Framework
// //
// Copyright (c) 2010-2016 Daniele Teti and the DMVCFramework Team // Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team
// //
// https://github.com/danieleteti/delphimvcframework // https://github.com/danieleteti/delphimvcframework
// //
@ -27,7 +27,7 @@ unit PublicControllerU;
interface interface
uses uses
MVCFramework; MVCFramework, MVCFramework.Commons;
type type

View File

@ -3,7 +3,7 @@ unit CustomersControllerU;
interface interface
uses uses
MVCFramework, CustomersTDGU; MVCFramework, MVCFramework.Commons, CustomersTDGU;
type type

View File

@ -68,7 +68,7 @@ procedure TWineCellarDataModule.ConnectionBeforeConnect(Sender: TObject);
begin begin
Connection.Params.Values['Database'] := Connection.Params.Values['Database'] :=
{ change this path to be compliant with your system } { change this path to be compliant with your system }
'C:\DEV\DMVCFramework\samples\winecellar\WINES.FDB'; 'D:\DEV\dmvcframework\samples\winecellarserver\WINES.FDB';
end; end;
function TWineCellarDataModule.FindWines(Search: string): TJSONArray; function TWineCellarDataModule.FindWines(Search: string): TJSONArray;

View File

@ -4,6 +4,7 @@ interface
uses uses
MVCFramework, MVCFramework,
MVCFramework.Commons,
MainDataModuleUnit; MainDataModuleUnit;
type type
@ -52,7 +53,7 @@ type
implementation implementation
uses uses
System.SysUtils, System.Classes, System.IOUtils, MVCFramework.Commons; System.SysUtils, System.Classes, System.IOUtils;
procedure TWineCellarApp.FindWines(ctx: TWebContext); procedure TWineCellarApp.FindWines(ctx: TWebContext);
begin begin

View File

@ -70,8 +70,8 @@ end;
procedure TArticle.CheckDelete; procedure TArticle.CheckDelete;
begin begin
inherited; inherited;
if Price > 0 then // if Price > 0 then
raise Exception.Create('Cannot delete an article with a price greater than 0 (yes, it is a silly check)'); // raise Exception.Create('Cannot delete an article with a price greater than 0 (yes, it is a silly check)');
end; end;
procedure TArticle.CheckInsert; procedure TArticle.CheckInsert;

View File

@ -2,7 +2,7 @@ unit Controllers.Articles;
interface interface
uses mvcframework, Controllers.Base; uses mvcframework, mvcframework.Commons, Controllers.Base;
type type
@ -13,8 +13,8 @@ type
[MVCDoc('Returns the list of articles')] [MVCDoc('Returns the list of articles')]
[MVCPath] [MVCPath]
[MVCHTTPMethod([httpGET])] [MVCHTTPMethod([httpGET])]
procedure GetArticles; procedure GetArticles;
[MVCDoc('Returns the article with the specified id')] [MVCDoc('Returns the article with the specified id')]
[MVCPath('/($id)')] [MVCPath('/($id)')]
[MVCHTTPMethod([httpGET])] [MVCHTTPMethod([httpGET])]
@ -40,7 +40,7 @@ implementation
{ TArticlesController } { TArticlesController }
uses Services, BusinessObjects, Commons, mvcframework.Commons; uses Services, BusinessObjects, Commons;
procedure TArticlesController.CreateArticle(Context: TWebContext); procedure TArticlesController.CreateArticle(Context: TWebContext);
var var

View File

@ -3,7 +3,7 @@ unit Controllers.Base;
interface interface
uses uses
MVCFramework, Services, MainDM; MVCFramework, MVCFramework.Commons, Services, MainDM;
type type
TBaseController = class abstract(TMVCController) TBaseController = class abstract(TMVCController)

View File

@ -22,7 +22,7 @@ implementation
{ %CLASSGROUP 'Vcl.Controls.TControl' } { %CLASSGROUP 'Vcl.Controls.TControl' }
uses Controllers.Articles; uses Controllers.Articles, MVCFramework.Middleware.CORS;
{$R *.dfm} {$R *.dfm}
@ -41,6 +41,7 @@ procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin begin
FEngine := TMVCEngine.Create(self); FEngine := TMVCEngine.Create(self);
FEngine.AddController(TArticlesController); FEngine.AddController(TArticlesController);
FEngine.AddMiddleware(TCORSMiddleware.Create);
end; end;
end. end.

View File

@ -157,7 +157,7 @@
<iPad_SpotLight80>$(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png</iPad_SpotLight80> <iPad_SpotLight80>$(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png</iPad_SpotLight80>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''"> <PropertyGroup Condition="'$(Base_Win32)'!=''">
<DCC_UnitSearchPath>..\..\lib\iocpdelphiframework\Base;..\..\lib\delphistompclient;..\..\lib\luadelphibinding;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> <DCC_UnitSearchPath>..\..\lib\iocpdelphiframework\Base;..\..\lib\luadelphibinding;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<DCC_ExeOutput>bin</DCC_ExeOutput> <DCC_ExeOutput>bin</DCC_ExeOutput>
<VerInfo_Locale>1033</VerInfo_Locale> <VerInfo_Locale>1033</VerInfo_Locale>
<DCC_UsePackage>FireDACSqliteDriver;FireDACDSDriver;DBXSqliteDriver;SampleListViewMultiDetailAppearancePackage;FireDACPgDriver;fmx;IndySystem;TeeDB;tethering;ITDevCon2012AdapterPackage;inetdbbde;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapServer;DataSnapCommon;DataSnapProviderClient;DBXSybaseASEDriver;DbxCommonDriver;vclimg;dbxcds;DatasnapConnectorsFreePascal;MetropolisUILiveTile;vcldb;vcldsnap;fmxFireDAC;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;vclribbon;dsnap;IndyIPServer;fmxase;vcl;IndyCore;DBXMSSQLDriver;IndyIPCommon;CloudService;FmxTeeUI;FireDACIBDriver;CodeSiteExpressPkg;DataSnapFireDAC;FireDACDBXDriver;soapserver;inetdbxpress;dsnapxml;FireDACInfxDriver;FireDACDb2Driver;adortl;CustomAdaptersMDPackage;FireDACASADriver;bindcompfmx;vcldbx;FireDACODBCDriver;RESTBackendComponents;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;inetdb;SampleListViewRatingsAppearancePackage;Tee;DBXOdbcDriver;vclFireDAC;xmlrtl;DataSnapNativeClient;svnui;ibxpress;IndyProtocols;DBXMySQLDriver;FireDACCommonDriver;bindcompdbx;bindengine;vclactnband;soaprtl;FMXTee;TeeUI;bindcompvcl;vclie;FireDACADSDriver;vcltouch;VclSmp;FireDACMSSQLDriver;FireDAC;DBXInformixDriver;Intraweb;VCLRESTComponents;DataSnapConnectors;DataSnapServerMidas;dsnapcon;DBXFirebirdDriver;SampleGenerator1Package;inet;fmxobj;FireDACMySQLDriver;soapmidas;vclx;svn;DBXSybaseASADriver;FireDACOracleDriver;fmxdae;RESTComponents;bdertl;FireDACMSAccDriver;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage)</DCC_UsePackage> <DCC_UsePackage>FireDACSqliteDriver;FireDACDSDriver;DBXSqliteDriver;SampleListViewMultiDetailAppearancePackage;FireDACPgDriver;fmx;IndySystem;TeeDB;tethering;ITDevCon2012AdapterPackage;inetdbbde;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapServer;DataSnapCommon;DataSnapProviderClient;DBXSybaseASEDriver;DbxCommonDriver;vclimg;dbxcds;DatasnapConnectorsFreePascal;MetropolisUILiveTile;vcldb;vcldsnap;fmxFireDAC;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;vclribbon;dsnap;IndyIPServer;fmxase;vcl;IndyCore;DBXMSSQLDriver;IndyIPCommon;CloudService;FmxTeeUI;FireDACIBDriver;CodeSiteExpressPkg;DataSnapFireDAC;FireDACDBXDriver;soapserver;inetdbxpress;dsnapxml;FireDACInfxDriver;FireDACDb2Driver;adortl;CustomAdaptersMDPackage;FireDACASADriver;bindcompfmx;vcldbx;FireDACODBCDriver;RESTBackendComponents;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;inetdb;SampleListViewRatingsAppearancePackage;Tee;DBXOdbcDriver;vclFireDAC;xmlrtl;DataSnapNativeClient;svnui;ibxpress;IndyProtocols;DBXMySQLDriver;FireDACCommonDriver;bindcompdbx;bindengine;vclactnband;soaprtl;FMXTee;TeeUI;bindcompvcl;vclie;FireDACADSDriver;vcltouch;VclSmp;FireDACMSSQLDriver;FireDAC;DBXInformixDriver;Intraweb;VCLRESTComponents;DataSnapConnectors;DataSnapServerMidas;dsnapcon;DBXFirebirdDriver;SampleGenerator1Package;inet;fmxobj;FireDACMySQLDriver;soapmidas;vclx;svn;DBXSybaseASADriver;FireDACOracleDriver;fmxdae;RESTComponents;bdertl;FireDACMSAccDriver;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage)</DCC_UsePackage>
@ -258,7 +258,16 @@
</Excluded_Packages> </Excluded_Packages>
</Delphi.Personality> </Delphi.Personality>
<Deployment Version="3"> <Deployment Version="3">
<DeployClass Name="ProjectiOSDeviceResourceRules"/> <DeployClass Name="DependencyModule">
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
<Platform Name="OSX32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXResource"> <DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32"> <Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir> <RemoteDir>Contents\Resources</RemoteDir>
@ -598,16 +607,7 @@
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="DependencyModule"> <DeployClass Name="ProjectiOSDeviceResourceRules"/>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
<Platform Name="OSX32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
</DeployClass>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/> <ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/> <ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/> <ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>

View File

@ -1,9 +1,9 @@
object Form4: TForm4 object MainForm: TMainForm
Left = 0 Left = 0
Top = 0 Top = 0
Caption = 'Articles CRUD SAMPLE' Caption = 'Articles CRUD SAMPLE'
ClientHeight = 391 ClientHeight = 391
ClientWidth = 747 ClientWidth = 592
Color = clBtnFace Color = clBtnFace
Font.Charset = DEFAULT_CHARSET Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText Font.Color = clWindowText
@ -18,56 +18,50 @@ object Form4: TForm4
object Panel1: TPanel object Panel1: TPanel
Left = 0 Left = 0
Top = 0 Top = 0
Width = 747 Width = 592
Height = 43 Height = 39
Align = alTop Align = alTop
TabOrder = 0 TabOrder = 0
object btnGetListAsynch: TButton
AlignWithMargins = True
Left = 120
Top = 6
Width = 89
Height = 31
Margins.Left = 10
Margins.Top = 5
Margins.Right = 10
Margins.Bottom = 5
Align = alLeft
Caption = 'Get List Asynch'
TabOrder = 0
OnClick = btnGetListAsynchClick
end
object btnGetListSynch: TButton
AlignWithMargins = True
Left = 11
Top = 6
Width = 89
Height = 31
Margins.Left = 10
Margins.Top = 5
Margins.Right = 10
Margins.Bottom = 5
Align = alLeft
Caption = 'Get List'
TabOrder = 1
OnClick = Button1Click
end
object DBNavigator1: TDBNavigator object DBNavigator1: TDBNavigator
Left = 222 AlignWithMargins = True
Top = 8 Left = 301
Width = 240 Top = 4
Height = 25 Width = 287
DataSource = DataSource1 Height = 31
DataSource = dsrcArticles
Align = alRight
TabOrder = 0
end
object btnOpen: TButton
AlignWithMargins = True
Left = 4
Top = 4
Width = 75
Height = 31
Align = alLeft
Caption = 'Open'
TabOrder = 1
OnClick = btnOpenClick
end
object btnClose: TButton
AlignWithMargins = True
Left = 85
Top = 4
Width = 75
Height = 31
Align = alLeft
Caption = 'Close'
TabOrder = 2 TabOrder = 2
OnClick = btnCloseClick
end end
end end
object DBGrid1: TDBGrid object DBGrid1: TDBGrid
Left = 0 Left = 0
Top = 43 Top = 39
Width = 747 Width = 592
Height = 348 Height = 352
Align = alClient Align = alClient
DataSource = DataSource1 DataSource = dsrcArticles
TabOrder = 1 TabOrder = 1
TitleFont.Charset = DEFAULT_CHARSET TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText TitleFont.Color = clWindowText
@ -78,29 +72,37 @@ object Form4: TForm4
item item
Expanded = False Expanded = False
FieldName = 'id' FieldName = 'id'
Title.Caption = '#ID'
Visible = True Visible = True
end end
item item
Expanded = False Expanded = False
FieldName = 'code' FieldName = 'code'
Title.Caption = 'Code'
Visible = True Visible = True
end end
item item
Expanded = False Expanded = False
FieldName = 'description' FieldName = 'description'
Title.Caption = 'Description'
Width = 265
Visible = True Visible = True
end end
item item
Expanded = False Expanded = False
FieldName = 'price' FieldName = 'price'
Title.Caption = 'Price'
Visible = True Visible = True
end> end>
end end
object FDMemTable1: TFDMemTable object dsArticles: TFDMemTable
BeforePost = FDMemTable1BeforePost AfterOpen = dsArticlesAfterOpen
BeforeDelete = FDMemTable1BeforeDelete BeforePost = dsArticlesBeforePost
BeforeDelete = dsArticlesBeforeDelete
BeforeRefresh = dsArticlesBeforeRefresh
FieldDefs = <> FieldDefs = <>
IndexDefs = <> IndexDefs = <>
BeforeRowRequest = dsArticlesBeforeRowRequest
FetchOptions.AssignedValues = [evMode] FetchOptions.AssignedValues = [evMode]
FetchOptions.Mode = fmAll FetchOptions.Mode = fmAll
ResourceOptions.AssignedValues = [rvSilentMode] ResourceOptions.AssignedValues = [rvSilentMode]
@ -110,23 +112,23 @@ object Form4: TForm4
StoreDefs = True StoreDefs = True
Left = 136 Left = 136
Top = 120 Top = 120
object FDMemTable1id: TIntegerField object dsArticlesid: TIntegerField
FieldName = 'id' FieldName = 'id'
end end
object FDMemTable1code: TStringField object dsArticlescode: TStringField
FieldName = 'code' FieldName = 'code'
end end
object FDMemTable1description: TStringField object dsArticlesdescription: TStringField
FieldName = 'description' FieldName = 'description'
Size = 50 Size = 50
end end
object FDMemTable1price: TCurrencyField object dsArticlesprice: TCurrencyField
FieldName = 'price' FieldName = 'price'
end end
end end
object DataSource1: TDataSource object dsrcArticles: TDataSource
DataSet = FDMemTable1 DataSet = dsArticles
Left = 312 Left = 136
Top = 192 Top = 184
end end
end end

View File

@ -11,137 +11,141 @@ uses
Vcl.DBCtrls; Vcl.DBCtrls;
type type
TForm4 = class(TForm) TMainForm = class(TForm)
Panel1: TPanel; Panel1: TPanel;
DBGrid1: TDBGrid; DBGrid1: TDBGrid;
FDMemTable1: TFDMemTable; dsArticles: TFDMemTable;
FDMemTable1id: TIntegerField; dsArticlesid: TIntegerField;
FDMemTable1code: TStringField; dsArticlescode: TStringField;
FDMemTable1description: TStringField; dsArticlesdescription: TStringField;
FDMemTable1price: TCurrencyField; dsArticlesprice: TCurrencyField;
DataSource1: TDataSource; dsrcArticles: TDataSource;
btnGetListAsynch: TButton;
btnGetListSynch: TButton;
DBNavigator1: TDBNavigator; DBNavigator1: TDBNavigator;
procedure Button1Click(Sender: TObject); btnOpen: TButton;
procedure btnGetListAsynchClick(Sender: TObject); btnClose: TButton;
procedure FormCreate(Sender: TObject); procedure FormCreate(Sender: TObject);
procedure FDMemTable1BeforePost(DataSet: TDataSet); procedure dsArticlesBeforePost(DataSet: TDataSet);
procedure FDMemTable1BeforeDelete(DataSet: TDataSet); procedure dsArticlesBeforeDelete(DataSet: TDataSet);
procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure dsArticlesBeforeRefresh(DataSet: TDataSet);
procedure dsArticlesAfterOpen(DataSet: TDataSet);
procedure btnOpenClick(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
procedure dsArticlesBeforeRowRequest(DataSet: TFDDataSet);
private private
CltAsynch: TRESTClient;
FLoading: Boolean; FLoading: Boolean;
Clt: TRESTClient; Clt: TRESTClient;
{ Private declarations } { Private declarations }
procedure ShowError(const AResponse: IRESTResponse); procedure ShowError(const AResponse: IRESTResponse);
public
procedure RefreshAsynch;
end; end;
var var
Form4: TForm4; MainForm: TMainForm;
implementation implementation
uses uses
ObjectsMappers; ObjectsMappers, System.UITypes;
{$R *.dfm} {$R *.dfm}
procedure TForm4.btnGetListAsynchClick(Sender: TObject); procedure TMainForm.btnCloseClick(Sender: TObject);
begin begin
// this an asychronous request... just like you could do in jQuery dsArticles.Close;
CltAsynch.Asynch(
procedure(Res: IRESTResponse)
begin
FDMemTable1.Close;
FDMemTable1.Open;
FLoading := true;
FDMemTable1.AppendFromJSONArrayString(Res.BodyAsString);
FLoading := false;
end,
nil, nil, true)
.doGET('/articles', []);
end; end;
procedure TForm4.Button1Click(Sender: TObject); procedure TMainForm.btnOpenClick(Sender: TObject);
begin begin
RefreshAsynch; dsArticles.Open;
end; end;
procedure TForm4.FDMemTable1BeforeDelete(DataSet: TDataSet); procedure TMainForm.dsArticlesAfterOpen(DataSet: TDataSet);
var var
Res: IRESTResponse; Res: IRESTResponse;
begin begin
if FDMemTable1.State = dsBrowse then // this a simple sychronous request...
Res := Clt.DataSetDelete('/articles', FDMemTable1id.AsString); Res := Clt.doGET('/articles', []);
if not(Res.ResponseCode in [200, 201]) then DataSet.DisableControls;
try
FLoading := true;
dsArticles.AppendFromJSONArrayString(Res.BodyAsString);
FLoading := false;
dsArticles.First;
finally
DataSet.EnableControls;
end;
end;
procedure TMainForm.dsArticlesBeforeDelete(DataSet: TDataSet);
var
Res: IRESTResponse;
begin
if dsArticles.State = dsBrowse then
Res := Clt.DataSetDelete('/articles', dsArticlesid.AsString);
if not(Res.ResponseCode in [200]) then
begin begin
ShowError(Res); ShowError(Res);
Abort; Abort;
end end;
else
Refresh;
end; end;
procedure TForm4.FDMemTable1BeforePost(DataSet: TDataSet); procedure TMainForm.dsArticlesBeforePost(DataSet: TDataSet);
var var
Res: IRESTResponse; Res: IRESTResponse;
begin begin
if not FLoading then if not FLoading then
begin begin
if FDMemTable1.State = dsInsert then if dsArticles.State = dsInsert then
Res := Clt.DataSetInsert('/articles', FDMemTable1) Res := Clt.DataSetInsert('/articles', dsArticles)
else else
Res := Clt.DataSetUpdate('/articles', FDMemTable1, FDMemTable1id.AsString); Res := Clt.DataSetUpdate('/articles', dsArticles, dsArticlesid.AsString);
if not(Res.ResponseCode in [200, 201]) then if not(Res.ResponseCode in [200, 201]) then
begin begin
ShowError(Res); ShowError(Res);
Abort; Abort;
end end
else else
RefreshAsynch; DataSet.Refresh;
end; end;
end; end;
procedure TForm4.FormClose(Sender: TObject; var Action: TCloseAction); procedure TMainForm.dsArticlesBeforeRefresh(DataSet: TDataSet);
//var
// Res: IRESTResponse;
begin
DataSet.Close;
DataSet.Open;
//
// Res := Clt.doGET('/articles', [DataSet.FieldByName('id').AsString]);
// FLoading := true;
// dsArticles.Edit;
// dsArticles.LoadFromJSONObjectString(Res.BodyAsString);
// dsArticles.Post;
// FLoading := false;
end;
procedure TMainForm.dsArticlesBeforeRowRequest(DataSet: TFDDataSet);
begin
ShowMessage('RowRequest');
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin begin
CltAsynch.Free;
Clt.Free; Clt.Free;
end; end;
procedure TForm4.FormCreate(Sender: TObject); procedure TMainForm.FormCreate(Sender: TObject);
begin begin
Clt := TRESTClient.Create('localhost', 8080); Clt := TRESTClient.Create('localhost', 8080);
// just for demo, here's the asych version. It is the same :-)
// check the GETLISTASYNCH button
CltAsynch := TRESTClient.Create('localhost', 8080);
end; end;
procedure TForm4.RefreshAsynch; procedure TMainForm.ShowError(const AResponse: IRESTResponse);
var
Res: IRESTResponse;
lRNo: Integer;
begin begin
// this a simple sychronous request... MessageDlg(
Res := Clt.doGET('/articles', []);
lRNo := FDMemTable1.RecNo;
FDMemTable1.Close;
FDMemTable1.Open;
FLoading := true;
FDMemTable1.AppendFromJSONArrayString(Res.BodyAsString);
FDMemTable1.RecNo := lRNo;
FLoading := false;
end;
procedure TForm4.ShowError(const AResponse: IRESTResponse);
begin
ShowMessage(
AResponse.ResponseCode.ToString + ': ' + AResponse.ResponseText + sLineBreak + AResponse.ResponseCode.ToString + ': ' + AResponse.ResponseText + sLineBreak +
AResponse.BodyAsJsonObject.Get('message').JsonValue.Value); AResponse.BodyAsJsonObject.Get('message').JsonValue.Value,
mtError, [mbOK], 0);
end; end;
end. end.

View File

@ -3,10 +3,10 @@
<ProjectGuid>{AF04BD45-3137-4757-B1AC-147D4136E52C}</ProjectGuid> <ProjectGuid>{AF04BD45-3137-4757-B1AC-147D4136E52C}</ProjectGuid>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Projects Include="..\articles_crud\articles_crud.dproj"> <Projects Include="articles_crud_vcl_client.dproj">
<Dependencies/> <Dependencies/>
</Projects> </Projects>
<Projects Include="articles_crud_vcl_client.dproj"> <Projects Include="..\articles_crud_server\articles_crud.dproj">
<Dependencies/> <Dependencies/>
</Projects> </Projects>
</ItemGroup> </ItemGroup>
@ -17,15 +17,6 @@
<Default.Personality/> <Default.Personality/>
</BorlandProject> </BorlandProject>
</ProjectExtensions> </ProjectExtensions>
<Target Name="articles_crud">
<MSBuild Projects="..\articles_crud\articles_crud.dproj"/>
</Target>
<Target Name="articles_crud:Clean">
<MSBuild Projects="..\articles_crud\articles_crud.dproj" Targets="Clean"/>
</Target>
<Target Name="articles_crud:Make">
<MSBuild Projects="..\articles_crud\articles_crud.dproj" Targets="Make"/>
</Target>
<Target Name="articles_crud_vcl_client"> <Target Name="articles_crud_vcl_client">
<MSBuild Projects="articles_crud_vcl_client.dproj"/> <MSBuild Projects="articles_crud_vcl_client.dproj"/>
</Target> </Target>
@ -35,14 +26,23 @@
<Target Name="articles_crud_vcl_client:Make"> <Target Name="articles_crud_vcl_client:Make">
<MSBuild Projects="articles_crud_vcl_client.dproj" Targets="Make"/> <MSBuild Projects="articles_crud_vcl_client.dproj" Targets="Make"/>
</Target> </Target>
<Target Name="articles_crud">
<MSBuild Projects="..\articles_crud_server\articles_crud.dproj"/>
</Target>
<Target Name="articles_crud:Clean">
<MSBuild Projects="..\articles_crud_server\articles_crud.dproj" Targets="Clean"/>
</Target>
<Target Name="articles_crud:Make">
<MSBuild Projects="..\articles_crud_server\articles_crud.dproj" Targets="Make"/>
</Target>
<Target Name="Build"> <Target Name="Build">
<CallTarget Targets="articles_crud;articles_crud_vcl_client"/> <CallTarget Targets="articles_crud_vcl_client;articles_crud"/>
</Target> </Target>
<Target Name="Clean"> <Target Name="Clean">
<CallTarget Targets="articles_crud:Clean;articles_crud_vcl_client:Clean"/> <CallTarget Targets="articles_crud_vcl_client:Clean;articles_crud:Clean"/>
</Target> </Target>
<Target Name="Make"> <Target Name="Make">
<CallTarget Targets="articles_crud:Make;articles_crud_vcl_client:Make"/> <CallTarget Targets="articles_crud_vcl_client:Make;articles_crud:Make"/>
</Target> </Target>
<Import Project="$(BDS)\Bin\CodeGear.Group.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Group.Targets')"/> <Import Project="$(BDS)\Bin\CodeGear.Group.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Group.Targets')"/>
</Project> </Project>

View File

@ -2,13 +2,13 @@ program articles_crud_vcl_client;
uses uses
Vcl.Forms, Vcl.Forms,
MainFormU in 'MainFormU.pas' {Form4}; MainFormU in 'MainFormU.pas' {MainForm};
{$R *.res} {$R *.res}
begin begin
Application.Initialize; Application.Initialize;
Application.MainFormOnTaskbar := True; Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm4, Form4); Application.CreateForm(TMainForm, MainForm);
Application.Run; Application.Run;
end. end.

View File

@ -97,7 +97,7 @@
<MainSource>MainSource</MainSource> <MainSource>MainSource</MainSource>
</DelphiCompile> </DelphiCompile>
<DCCReference Include="MainFormU.pas"> <DCCReference Include="MainFormU.pas">
<Form>Form4</Form> <Form>MainForm</Form>
<FormType>dfm</FormType> <FormType>dfm</FormType>
</DCCReference> </DCCReference>
<BuildConfiguration Include="Release"> <BuildConfiguration Include="Release">
@ -128,27 +128,12 @@
</Excluded_Packages> </Excluded_Packages>
</Delphi.Personality> </Delphi.Personality>
<Deployment Version="3"> <Deployment Version="3">
<DeployClass Name="DependencyModule"> <DeployClass Name="ProjectiOSDeviceResourceRules">
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
<Platform Name="iOSDevice64"> <Platform Name="iOSDevice64">
<Operation>1</Operation> <Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform> </Platform>
<Platform Name="iOSDevice32"> <Platform Name="iOSDevice32">
<Operation>1</Operation> <Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimulator">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="ProjectOSXResource"> <DeployClass Name="ProjectOSXResource">
@ -528,12 +513,27 @@
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="ProjectiOSDeviceResourceRules"> <DeployClass Name="DependencyModule">
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
<Platform Name="iOSDevice64"> <Platform Name="iOSDevice64">
<Operation>1</Operation> <Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform> </Platform>
<Platform Name="iOSDevice32"> <Platform Name="iOSDevice32">
<Operation>1</Operation> <Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimulator">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform> </Platform>
</DeployClass> </DeployClass>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/> <ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>

View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -0,0 +1,40 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
#System Files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,31 @@
# RouterSample
This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.24.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
## Deploying to Github Pages
Run `ng github-pages:deploy` to deploy to Github Pages.
## Further help
To get more help on the `angular-cli` use `ng help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -0,0 +1,60 @@
{
"project": {
"version": "1.0.0-beta.24",
"name": "router-sample"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"test": "test.ts",
"tsconfig": "tsconfig.json",
"prefix": "app",
"mobile": false,
"styles": [
"styles.css",
"../node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [],
"environments": {
"source": "environments/environment.ts",
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"addons": [],
"packages": [],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"prefixInterfaces": false,
"inline": {
"style": false,
"template": false
},
"spec": {
"class": false,
"component": true,
"directive": true,
"module": false,
"pipe": true,
"service": true
}
}
}

View File

@ -0,0 +1,14 @@
import { RouterSamplePage } from './app.po';
describe('router-sample App', function() {
let page: RouterSamplePage;
beforeEach(() => {
page = new RouterSamplePage();
});
it('should display message saying app works', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('app works!');
});
});

View File

@ -0,0 +1,11 @@
import { browser, element, by } from 'protractor';
export class RouterSamplePage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@ -0,0 +1,16 @@
{
"compileOnSave": false,
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../dist/out-tsc-e2e",
"sourceMap": true,
"target": "es5",
"typeRoots": [
"../node_modules/@types"
]
}
}

View File

@ -0,0 +1,43 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', 'angular-cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-remap-istanbul'),
require('angular-cli/plugins/karma')
],
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./src/test.ts': ['angular-cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
remapIstanbulReporter: {
reports: {
html: 'coverage',
lcovonly: './coverage/coverage.lcov'
}
},
angularCli: {
config: './angular-cli.json',
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'karma-remap-istanbul']
: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

View File

@ -0,0 +1,49 @@
{
"name": "router-sample",
"version": "0.0.0",
"license": "MIT",
"angular-cli": {},
"scripts": {
"ng": "ng",
"start": "ng serve",
"lint": "tslint \"src/**/*.ts\"",
"test": "ng test",
"pree2e": "webdriver-manager update --standalone false --gecko false",
"e2e": "protractor"
},
"private": true,
"dependencies": {
"@angular/common": "^2.3.1",
"@angular/compiler": "^2.3.1",
"@angular/core": "^2.3.1",
"@angular/forms": "^2.3.1",
"@angular/http": "^2.3.1",
"@angular/platform-browser": "^2.3.1",
"@angular/platform-browser-dynamic": "^2.3.1",
"@angular/router": "^3.3.1",
"bootstrap": "^3.3.7",
"core-js": "^2.4.1",
"ng2-bootstrap": "^1.2.1",
"rxjs": "^5.0.1",
"ts-helpers": "^1.1.1",
"zone.js": "^0.7.6"
},
"devDependencies": {
"@angular/compiler-cli": "^2.3.1",
"@types/jasmine": "2.5.38",
"@types/node": "^6.0.42",
"angular-cli": "1.0.0-beta.24",
"codelyzer": "~2.0.0-beta.1",
"jasmine-core": "2.5.2",
"jasmine-spec-reporter": "2.5.0",
"karma": "1.2.0",
"karma-chrome-launcher": "^2.0.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-remap-istanbul": "^0.2.1",
"protractor": "~4.0.13",
"ts-node": "1.2.1",
"tslint": "^4.0.2",
"typescript": "~2.0.3"
}
}

View File

@ -0,0 +1,32 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
/*global jasmine */
var SpecReporter = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
useAllAngular2AppRoots: true,
beforeLaunch: function() {
require('ts-node').register({
project: 'e2e'
});
},
onPrepare: function() {
jasmine.getEnv().addReporter(new SpecReporter());
}
};

View File

@ -0,0 +1,22 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome against localhost, with sourcemaps",
"type": "chrome",
"request": "launch",
"url": "http://localhost:4200",
"sourceMaps": true,
"webRoot": "${workspaceRoot}"
},
{
"name": "Attach to Chrome, with sourcemaps",
"type": "chrome",
"request": "attach",
"port": 9222,
"sourceMaps": true,
"webRoot": "${workspaceRoot}"
}
]
}

View File

@ -0,0 +1,44 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ArticlesListComponent } from './articles-list/articles-list.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { ArticleEditComponent } from './article-edit/article-edit.component';
import { HomeComponent } from './home/home.component';
import { ShoppingCartComponent } from './shopping-cart/shopping-cart.component';
import { ProductListComponent } from './product-list/product-list.component';
import { PrivateArea1Component } from './private-area1/private-area1.component';
import { PrivateArea2Component } from './private-area2/private-area2.component';
import { AuthGuard } from './guards/auth-guard';
import { LoginComponent } from './login/login.component';
// The router selects the route with a first match wins strategy.
// Wildcard routes are the least specific routes in the route configuration.
// Be sure it is the last route in the configuration.
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'articles-list', component: ArticlesListComponent },
{ path: 'article-edit/:id', component: ArticleEditComponent },
// { path: 'shopping', component: ProductListComponent /*data: { title: 'Heroes List'}*/ },
{
path: '',
redirectTo: '/home',
pathMatch: 'full'
},
{
path: 'admin', canActivate: [AuthGuard],
children: [
{ path: 'private1', component: PrivateArea1Component },
{ path: 'private2', component: PrivateArea2Component }
]
},
{ path: 'login', component: LoginComponent },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }

View File

@ -0,0 +1,3 @@
.active-route {
font-weight: bold;
}

View File

@ -0,0 +1,32 @@
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<h1 class="text-center">
DelphiMVCFramework
<!--<span style="margin-left: 5px; font-size: 60%" class="pull-right"><app-login-logout></app-login-logout></span>-->
<!--<span class="pull-right"><app-shopping-cart></app-shopping-cart></span>-->
</h1>
</div>
</div>
<div class="row">
<div class="col-md-2">
<ul class="nav nav-pills nav-stacked">
<li role="presentation"><a [routerLink]="['/home']" routerLinkActive="active-route">Home</a></li>
<li role="presentation"><a [routerLink]="['/articles-list']" routerLinkActive="active-route">Articles</a></li>
<!--<li role="presentation"><a [routerLink]="['/shopping']" routerLinkActive="active-route">Place an Order</a></li>-->
</ul>
<hr>
<div *ngIf="isLogged">
<hr>
<ul class="nav nav-pills nav-stacked">
<li role="presentation"><a [routerLink]="['/admin','private1']" routerLinkActive="active-route">Private Page1</a></li>
<li role="presentation"><a [routerLink]="['/admin','private2']" routerLinkActive="active-route">Private Page2</a></li>
</ul>
</div>
</div>
<div class="col-md-10">
<router-outlet></router-outlet>
</div>
</div>
</div>

View File

@ -0,0 +1,38 @@
/* tslint:disable:no-unused-variable */
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
});
TestBed.compileComponents();
});
it('should create the app', async(() => {
let fixture = TestBed.createComponent(AppComponent);
let app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app works!'`, async(() => {
let fixture = TestBed.createComponent(AppComponent);
let app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app works!');
}));
it('should render title in a h1 tag', async(() => {
let fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('app works!');
}));
});

View File

@ -0,0 +1,17 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from './services/auth.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
isLogged: boolean;
constructor(private authService: AuthService) { }
ngOnInit() {
this.authService.getSubject().subscribe((username) => {
this.isLogged = this.authService.isLogged();
});
}
}

View File

@ -0,0 +1,45 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { ArticlesListComponent } from './articles-list/articles-list.component';
import { ArticlesService } from './services/articles.service';
import { ArticleEditComponent } from './article-edit/article-edit.component';
import { HomeComponent } from './home/home.component';
import { ProductListComponent } from './product-list/product-list.component';
import { ShoppingCartComponent } from './shopping-cart/shopping-cart.component';
import { ShoppingCartService } from './services/shopping-cart.service';
import { PrivateArea1Component } from './private-area1/private-area1.component';
import { PrivateArea2Component } from './private-area2/private-area2.component';
import { LoginComponent } from './login/login.component';
import { AuthGuard } from './guards/auth-guard';
import { AuthService } from './services/auth.service';
import { LoginLogoutComponent } from './login-logout/login-logout.component';
@NgModule({
declarations: [
AppComponent,
PageNotFoundComponent,
ArticlesListComponent,
ArticleEditComponent,
HomeComponent,
ProductListComponent,
ShoppingCartComponent,
PrivateArea1Component,
PrivateArea2Component,
LoginComponent,
LoginLogoutComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
AppRoutingModule
],
providers: [ArticlesService, ShoppingCartService, AuthGuard, AuthService],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,23 @@
<div class="row">
<div class="col-md-12">
<form>
<div class="form-group">
<label for="codice ">Codice</label>
<input [(ngModel)]="article.code" class="form-control" id="codice" name="codice" />
</div>
<div class="form-group">
<label for="descrizione">Descrizione</label>
<input [(ngModel)]="article.description" class="form-control" id="descrizione" name="descrizione" />
</div>
<div class="form-group">
<label for="prezzo">Prezzo</label>
<input [(ngModel)]="article.price" class="form-control" id="prezzo" name="prezzo" />
</div>
<div>
<button type="button" class="btn btn-success" (click)="doSaveArticle()">Save Article</button>
<button type="button" class="btn btn-normal" (click)="doCancel()">Cancel</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ArticleEditComponent } from './article-edit.component';
describe('ArticleEditComponent', () => {
let component: ArticleEditComponent;
let fixture: ComponentFixture<ArticleEditComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ArticleEditComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ArticleEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,44 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { ArticlesService } from '../services/articles.service';
import { Article } from '../models/articolo';
@Component({
selector: 'app-article-edit',
templateUrl: './article-edit.component.html',
styleUrls: ['./article-edit.component.css']
})
export class ArticleEditComponent implements OnInit {
private insertMode: boolean;
public article: Article = new Article();
constructor(private route: ActivatedRoute, private router: Router, private articlesService: ArticlesService) { }
ngOnInit() {
this.route.params.subscribe((data: Params) => {
console.log(data['id']);
this.insertMode = +data['id'] === -1;
if (!this.insertMode) {
this.articlesService.getArticolo(+data['id']).subscribe((articolo) => {
this.article = articolo;
});
}
});
}
doSaveArticle() {
if (this.insertMode) {
this.articlesService.addArticolo(this.article)
.do(articolo => console.log(articolo))
.subscribe((x) => this.router.navigate(['/articles-list']));
} else {
this.articlesService.saveArticle(this.article)
.do(articolo => console.log(articolo))
.subscribe((x) => this.router.navigate(['/articles-list']));
}
}
doCancel() {
this.router.navigate(['/articles-list']);
}
}

View File

@ -0,0 +1,30 @@
<div class="row">
<div class="col-md-12">
<button type="button" class="pull-right btn btn-large btn-success" (click)="doNewArticle()">New Article</button>
<table class="table table-condensed animated fadeIn">
<thead>
<tr>
<th>Code</th>
<th>Description</th>
<th class="text-right">Price</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr *ngFor=" let articolo of articoli, let i=index ">
<td>{{articolo.code}}</td>
<td>{{articolo.description }}</td>
<td class="text-right">{{articolo.price | currency:'EUR':true:'1.2-2'}}</td>
<td class="text-right">
<a href="#" [routerLink]="['/article-edit', articolo.id]" class="btn btn-default"><span class="glyphicon glyphicon-pencil "></span></a>
<button class="btn btn-default" (click)="doRemoveArticolo(i)"><span class="glyphicon glyphicon-remove "></span></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- class="row " -->
<div class="error " *ngIf="errorMessage ">{{errorMessage}}</div>

View File

@ -0,0 +1,41 @@
import { Component, OnInit } from '@angular/core';
import { Article } from '../models/articolo';
import { ArticlesService } from '../services/articles.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-articles-list',
templateUrl: 'articles-list.component.html',
styles: ['.error {color:red;}'],
providers: [ArticlesService]
})
export class ArticlesListComponent implements OnInit {
errorMessage: string;
articoli: Article[];
constructor(private articlesService: ArticlesService, private router: Router) { }
ngOnInit() {
this.getArticoli();
}
getArticoli() {
this.articlesService.getArticoli()
.subscribe(
articoli => this.articoli = articoli,
error => this.errorMessage = <any>error);
}
doNewArticle() {
// use the Router service to imperatively navigate to another route
this.router.navigate(['/article-edit', -1]);
}
doRemoveArticolo(index) {
if (confirm('Are you sure?')) {
this.articlesService.removeArticolo(this.articoli[index])
.subscribe(resp => {
this.getArticoli();
});
}
}
}

View File

@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) { }
canActivate() {
console.log('AuthGuard#canActivate called');
return this.checkLogin();
}
checkLogin(): boolean {
if (this.authService.isLogged()) {
return true;
}
// Navigate to the login page
this.router.navigate(['/login']);
return false;
}
}

View File

@ -0,0 +1,5 @@
<div class="jumbotron">
<h1>DMVCFramework</h1>
<p>This demo uses DMVCFramework, typescript and Angular2</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
</div>

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1 @@
<button (click)="doLoginLogout()" [ngClass]="{btn: true, 'btn-info': action == 'login', 'btn-danger': action == 'logout'}">{{action}}</button>

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { LoginLogoutComponent } from './login-logout.component';
describe('LoginLogoutComponent', () => {
let component: LoginLogoutComponent;
let fixture: ComponentFixture<LoginLogoutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginLogoutComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginLogoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,32 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-login-logout',
templateUrl: './login-logout.component.html',
styleUrls: ['./login-logout.component.css']
})
export class LoginLogoutComponent implements OnInit {
action: string = '';
constructor(private authService: AuthService, private router: Router) { }
ngOnInit() {
this.authService.getSubject().subscribe((username) => {
if (this.authService.isLogged()) {
this.action = 'logout';
} else {
this.action = 'login';
}
});
}
doLoginLogout() {
if (this.authService.isLogged()) {
this.authService.logout();
this.router.navigate(['/home']);
} else {
this.router.navigate(['/login']);
}
}
}

View File

@ -0,0 +1,142 @@
/*
* Specific styles of signin component
*/
/*
* General styles
*/
.card-container.card {
max-width: 350px;
padding: 40px 40px;
}
.btn {
font-weight: 700;
height: 36px;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: default;
}
/*
* Card component
*/
.card {
background-color: #F7F7F7;
/* just in case there no content*/
padding: 20px 25px 30px;
margin: 0 auto 25px;
margin-top: 50px;
/* shadows and rounded borders */
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}
.profile-img-card {
width: 96px;
height: 96px;
margin: 0 auto 10px;
display: block;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
}
/*
* Form styles
*/
.profile-name-card {
font-size: 16px;
font-weight: bold;
text-align: center;
margin: 10px 0 0;
min-height: 1em;
}
.reauth-email {
display: block;
color: #404040;
line-height: 2;
margin-bottom: 10px;
font-size: 14px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.form-signin #inputEmail,
.form-signin #inputPassword {
direction: ltr;
height: 44px;
font-size: 16px;
}
.form-signin input[type=email],
.form-signin input[type=password],
.form-signin input[type=text],
.form-signin button {
width: 100%;
display: block;
margin-bottom: 10px;
z-index: 1;
position: relative;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.form-signin .form-control:focus {
border-color: rgb(104, 145, 162);
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgb(104, 145, 162);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgb(104, 145, 162);
}
.btn.btn-signin {
/*background-color: #4d90fe; */
background-color: rgb(104, 145, 162);
/* background-color: linear-gradient(rgb(104, 145, 162), rgb(12, 97, 33));*/
padding: 0px;
font-weight: 700;
font-size: 14px;
height: 36px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
border: none;
-o-transition: all 0.218s;
-moz-transition: all 0.218s;
-webkit-transition: all 0.218s;
transition: all 0.218s;
}
.btn.btn-signin:hover,
.btn.btn-signin:active,
.btn.btn-signin:focus {
background-color: rgb(12, 97, 33);
}
.forgot-password {
color: rgb(104, 145, 162);
}
.forgot-password:hover,
.forgot-password:active,
.forgot-password:focus {
color: rgb(12, 97, 33);
}

View File

@ -0,0 +1,17 @@
<div class="container">
<div class="card card-container">
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
<img id="profile-img" class="profile-img-card" src="//ssl.gstatic.com/accounts/ui/avatar_2x.png" />
<p id="profile-name" class="profile-name-card"></p>
<form class="form-signin" (ngSubmit)="doLogin()">
<span id="reauth-email" class="reauth-email"></span>
<input [(ngModel)]="user.username" type="text" name="username" id="inputEmail" class="form-control" placeholder="Username" required autofocus>
<input [(ngModel)]="user.password" type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
<button class="btn btn-lg btn-primary btn-block btn-signin" type="submit">Sign in</button>
<div>{{message}}</div>
</form>
<!-- /form -->
</div>
<!-- /card-container -->
</div>
<!-- /container -->

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,27 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
message: string = '';
user: { username: string, password: string } = { username: '', password: '' };
constructor(private authService: AuthService, private router: Router) { }
ngOnInit() {
}
doLogin() {
this.message = '';
if (this.authService.login(this.user.username, this.user.password)) {
this.router.navigate(['/admin', 'private1']);
} else {
this.message = 'Invalid login';
}
}
}

View File

@ -0,0 +1,6 @@
export class Article {
id: number = 0;
code: string;
description: string;
price: number;
}

View File

@ -0,0 +1 @@
<div class="alert alert-danger" role="alert">Page not found</div>

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { PageNotFoundComponent } from './page-not-found.component';
describe('PageNotFoundComponent', () => {
let component: PageNotFoundComponent;
let fixture: ComponentFixture<PageNotFoundComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PageNotFoundComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PageNotFoundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-page-not-found',
templateUrl: './page-not-found.component.html',
styleUrls: ['./page-not-found.component.css']
})
export class PageNotFoundComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,3 @@
<p>
private-area1 works!
</p>

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { PrivateArea1Component } from './private-area1.component';
describe('PrivateArea1Component', () => {
let component: PrivateArea1Component;
let fixture: ComponentFixture<PrivateArea1Component>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PrivateArea1Component ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PrivateArea1Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-private-area1',
templateUrl: './private-area1.component.html',
styleUrls: ['./private-area1.component.css']
})
export class PrivateArea1Component implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,3 @@
<p>
private-area2 works!
</p>

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { PrivateArea2Component } from './private-area2.component';
describe('PrivateArea2Component', () => {
let component: PrivateArea2Component;
let fixture: ComponentFixture<PrivateArea2Component>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PrivateArea2Component ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PrivateArea2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-private-area2',
templateUrl: './private-area2.component.html',
styleUrls: ['./private-area2.component.css']
})
export class PrivateArea2Component implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,25 @@
<div class="row">
<div class="col-md-12">
<table class="table table-condensed animated fadeIn">
<thead>
<tr>
<th>Code</th>
<th>Description</th>
<th class="text-right">Price</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr *ngFor=" let articolo of articles, let i=index ">
<td>{{articolo.code}}</td>
<td>{{articolo.description }}</td>
<td class="text-right">{{articolo.price | currency:'EUR':true:'1.2-2'}}</td>
<td class="text-right">
<button class="btn btn-default" (click)="doAddProduct(i)"><span class="glyphicon glyphicon-plus"></span></button>
<button class="btn btn-default" (click)="doRemoveProduct(i)"><span class="glyphicon glyphicon-minus"></span></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ProductListComponent } from './product-list.component';
describe('ProductListComponent', () => {
let component: ProductListComponent;
let fixture: ComponentFixture<ProductListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ProductListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProductListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,40 @@
import { Component, OnInit } from '@angular/core';
import { ArticlesService } from '../services/articles.service';
import { Router } from '@angular/router';
import { Article } from '../models/articolo';
import { ShoppingCartService } from '../services/shopping-cart.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
articles: Array<Article>;
constructor(
private articlesService: ArticlesService,
private router: Router,
private cartService: ShoppingCartService) { }
ngOnInit() {
this.getArticoli();
}
getArticoli() {
this.articlesService.getArticoli()
.subscribe(
articoli => this.articles = articoli);
}
doAddProduct(index: number) {
let article: Article = this.articles[index];
this.cartService.addProduct(article.id, article.description, article.price);
}
doRemoveProduct(index: number) {
let article: Article = this.articles[index];
this.cartService.removeProduct(article.id);
}
}

View File

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Article } from '../models/articolo';
import { Observable } from 'rxjs';
@Injectable()
export class ArticlesService {
private _articoliUrl = 'http://localhost:8080/articles'; // URL to web api
constructor(private http: Http) { }
public getArticolo(id: number): Observable<Article> {
return this.http
.get(this._articoliUrl + '/' + id.toString())
.map((res: Response) => <Article>res.json())
.catch((err: any) => Observable.throw('Cannot find article'));
}
public getArticoli() {
return this.http.get(this._articoliUrl)
.map(res => <Article[]>res.json())
.do(data => console.log(data)) // eyeball results in the console
.catch(this.handleError);
}
private handleError(error: Response) {
// in a real world app, we may send the error to some remote logging infrastructure
// instead of just logging it to the console
console.error(error.text);
return Observable.throw(error.text || 'Server error');
// return Observable.throw('Server error');
}
public addArticolo(articolo: Article): Observable<Article> {
articolo.price = parseFloat(articolo.price.toString());
let body = JSON.stringify(articolo);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(this._articoliUrl, body, options)
.map(res => <Article>res.json())
.catch(this.handleError);
}
public saveArticle(articolo: Article): Observable<Article> {
articolo.price = parseFloat(articolo.price.toString());
let body = JSON.stringify(articolo);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.put(
this._articoliUrl + '/' + articolo.id.toString(), body, options)
/*.map(res => <Articolo>res.json())*/
.catch(this.handleError);
}
public removeArticolo(articolo: Article) {
return this.http.delete(this._articoliUrl + '/' + articolo.id);
}
}

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class AuthService {
private userSubject: BehaviorSubject<string>;
getSubject(): BehaviorSubject<string> {
return this.userSubject;
}
getLoggedUser() {
return localStorage['username'];
}
constructor() {
let username = '';
if (this.isLogged()) {
username = this.getLoggedUser();
}
this.userSubject = new BehaviorSubject<string>(username);
this.setLoggedUser(username);
}
setLoggedUser(username) {
localStorage['username'] = username;
this.userSubject.next(username);
}
isLogged() {
return !!localStorage['username'];
}
logout() {
this.setLoggedUser('');
localStorage.clear();
}
login(username: string, password: string) {
if (username === password) {
this.setLoggedUser(username);
return true;
}
return false;
}
}

View File

@ -0,0 +1,56 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
export interface Product {
id: number;
name: string;
quantity: number;
price: number;
}
@Injectable()
export class ShoppingCartService {
private items: Array<Product> = [];
private subject: BehaviorSubject<Array<Product>>;
getSubject(): BehaviorSubject<Array<Product>> {
return this.subject;
}
constructor() {
this.subject = new BehaviorSubject<Array<Product>>([]);
}
addProduct(id: number, name: string, price: number) {
let found = false;
this.items.map((item: Product, index: number, items: Array<Product>) => {
if (+item.id === +id) {
item.quantity++;
found = true;
return false;
}
});
if (!found) {
this.items.push({ id: id, name: name, quantity: 1, price: price });
}
this.subject.next(this.items);
}
removeProduct(id: number) {
let found = false;
this.items.map((item: Product, index: number, items: Array<Product>) => {
if (+item.id === +id) {
item.quantity--;
}
if (item.quantity === 0) {
items.splice(index, 1);
}
});
// if (!found) {
// this.items.push({ id: id, name: name, quantity: 1 });
// }
this.subject.next(this.items);
}
}

View File

@ -0,0 +1,5 @@
<span class="glyphicon glyphicon-shopping-cart" aria-hidden="true">
<span *ngIf="count > 0">
{{count}} <span style="font-size: 60%">{{total | currency:"EUR"}}</span>
</span>
</span>

View File

@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ShoppingCartComponent } from './shopping-cart.component';
describe('ShoppingCartComponent', () => {
let component: ShoppingCartComponent;
let fixture: ComponentFixture<ShoppingCartComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ShoppingCartComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ShoppingCartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,31 @@
import { Component, OnInit } from '@angular/core';
import { ShoppingCartService, Product } from '../services/shopping-cart.service';
@Component({
selector: 'app-shopping-cart',
templateUrl: './shopping-cart.component.html',
styleUrls: ['./shopping-cart.component.css']
})
export class ShoppingCartComponent implements OnInit {
count: number = 0;
total: number = 0;
constructor(private cartService: ShoppingCartService) {
cartService.getSubject().subscribe((value: Array<Product>) => {
console.log(value);
let c = 0;
let total = 0;
value.forEach((item: Product, index: number) => {
c += item.quantity;
console.log(item);
total += +item.quantity * +item.price;
});
this.count = c;
this.total = total;
});
}
ngOnInit() {
}
}

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,8 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `angular-cli.json`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>RouterSample</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More