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)
* Automatic documentation through /system/describeserver.info
* 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)
* 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)
* 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
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.
@ -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).
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
unit WebModuleUnit1;

View File

@ -8,7 +8,7 @@ init()
#################################################################################
def buildProject(project):
def buildProject(project, platform = 'Win32'):
print(Fore.YELLOW + "Building " + project)
p = project.replace('.dproj', '.cfg')
if os.path.isfile(p):
@ -16,7 +16,7 @@ def buildProject(project):
os.remove(p + '.unused')
os.rename(p, p + '.unused')
# 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):
@ -26,19 +26,14 @@ def summaryTable(builds):
print(Fore.YELLOW + "=" * 90)
good = bad = 0
for item in builds:
if item['status'] == 'ok':
#WConio.textcolor(WConio.LIGHTGREEN)
if item['status'].startswith('ok'):
good += 1
else:
#WConio.textcolor(WConio.RED)
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)
#WConio.textcolor(WConio.GREEN)
print(Fore.WHITE + "GOOD :".rjust(80) + str(good).rjust(10, '.'))
#WConio.textcolor(WConio.RED)
print(Fore.RED + "BAD :".rjust(80) + str(bad).rjust(10, '.'))
@ -49,6 +44,7 @@ def main(projects):
builds = []
for project in projects:
filename = '\\'.join(project.split('\\')[-3:])
list = {'project': filename}
if project.find('delphistompclient') > -1 or project.find('contribsamples') > -1:
list['status'] = 'skip'
continue
@ -59,6 +55,14 @@ def main(projects):
else:
list["status"] = "ko"
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)
# Store current attribute settings
@ -67,7 +71,7 @@ def main(projects):
def dmvc_copyright():
print(Style.BRIGHT + Fore.WHITE + "----------------------------------------------------------------------------------------")
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")
## 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 }
{ }
{ 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 }
{ }

View File

@ -2,7 +2,7 @@
{ }
{ 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 }
{ }

View File

@ -2,7 +2,7 @@
{ }
{ 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 }
{ }

View File

@ -2,7 +2,7 @@
{ }
{ 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 }
{ }

View File

@ -2,7 +2,7 @@
{ }
{ 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 }
{ }

View File

@ -2,7 +2,7 @@
{ }
{ 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 }
{ }

View File

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

View File

@ -2,7 +2,7 @@
{ }
{ 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 }
{ }

View File

@ -2,7 +2,7 @@
{ }
{ 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 }
{ }

View File

@ -2,7 +2,7 @@
{ }
{ 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 }
{ }

@ -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
//
// 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
//

View File

@ -2,7 +2,7 @@
//
// 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
//

View File

@ -2,7 +2,7 @@
//
// 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
//

View File

@ -2,7 +2,7 @@
//
// 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
//
@ -27,7 +27,7 @@ unit PrivateControllerU;
interface
uses
MVCFramework;
MVCFramework, MVCFramework.Commons;
type

View File

@ -2,7 +2,7 @@
//
// 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
//
@ -27,7 +27,7 @@ unit PublicControllerU;
interface
uses
MVCFramework;
MVCFramework, MVCFramework.Commons;
type

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -157,7 +157,7 @@
<iPad_SpotLight80>$(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png</iPad_SpotLight80>
</PropertyGroup>
<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>
<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>
@ -258,7 +258,16 @@
</Excluded_Packages>
</Delphi.Personality>
<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">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
@ -598,16 +607,7 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<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="ProjectiOSDeviceResourceRules"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>

View File

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

View File

@ -11,137 +11,141 @@ uses
Vcl.DBCtrls;
type
TForm4 = class(TForm)
TMainForm = class(TForm)
Panel1: TPanel;
DBGrid1: TDBGrid;
FDMemTable1: TFDMemTable;
FDMemTable1id: TIntegerField;
FDMemTable1code: TStringField;
FDMemTable1description: TStringField;
FDMemTable1price: TCurrencyField;
DataSource1: TDataSource;
btnGetListAsynch: TButton;
btnGetListSynch: TButton;
dsArticles: TFDMemTable;
dsArticlesid: TIntegerField;
dsArticlescode: TStringField;
dsArticlesdescription: TStringField;
dsArticlesprice: TCurrencyField;
dsrcArticles: TDataSource;
DBNavigator1: TDBNavigator;
procedure Button1Click(Sender: TObject);
procedure btnGetListAsynchClick(Sender: TObject);
btnOpen: TButton;
btnClose: TButton;
procedure FormCreate(Sender: TObject);
procedure FDMemTable1BeforePost(DataSet: TDataSet);
procedure FDMemTable1BeforeDelete(DataSet: TDataSet);
procedure dsArticlesBeforePost(DataSet: TDataSet);
procedure dsArticlesBeforeDelete(DataSet: TDataSet);
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
CltAsynch: TRESTClient;
FLoading: Boolean;
Clt: TRESTClient;
{ Private declarations }
procedure ShowError(const AResponse: IRESTResponse);
public
procedure RefreshAsynch;
end;
var
Form4: TForm4;
MainForm: TMainForm;
implementation
uses
ObjectsMappers;
ObjectsMappers, System.UITypes;
{$R *.dfm}
procedure TForm4.btnGetListAsynchClick(Sender: TObject);
procedure TMainForm.btnCloseClick(Sender: TObject);
begin
// this an asychronous request... just like you could do in jQuery
CltAsynch.Asynch(
procedure(Res: IRESTResponse)
begin
FDMemTable1.Close;
FDMemTable1.Open;
FLoading := true;
FDMemTable1.AppendFromJSONArrayString(Res.BodyAsString);
FLoading := false;
end,
nil, nil, true)
.doGET('/articles', []);
dsArticles.Close;
end;
procedure TForm4.Button1Click(Sender: TObject);
procedure TMainForm.btnOpenClick(Sender: TObject);
begin
RefreshAsynch;
dsArticles.Open;
end;
procedure TForm4.FDMemTable1BeforeDelete(DataSet: TDataSet);
procedure TMainForm.dsArticlesAfterOpen(DataSet: TDataSet);
var
Res: IRESTResponse;
begin
if FDMemTable1.State = dsBrowse then
Res := Clt.DataSetDelete('/articles', FDMemTable1id.AsString);
if not(Res.ResponseCode in [200, 201]) then
// this a simple sychronous request...
Res := Clt.doGET('/articles', []);
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
ShowError(Res);
Abort;
end
else
Refresh;
end;
end;
procedure TForm4.FDMemTable1BeforePost(DataSet: TDataSet);
procedure TMainForm.dsArticlesBeforePost(DataSet: TDataSet);
var
Res: IRESTResponse;
begin
if not FLoading then
begin
if FDMemTable1.State = dsInsert then
Res := Clt.DataSetInsert('/articles', FDMemTable1)
if dsArticles.State = dsInsert then
Res := Clt.DataSetInsert('/articles', dsArticles)
else
Res := Clt.DataSetUpdate('/articles', FDMemTable1, FDMemTable1id.AsString);
Res := Clt.DataSetUpdate('/articles', dsArticles, dsArticlesid.AsString);
if not(Res.ResponseCode in [200, 201]) then
begin
ShowError(Res);
Abort;
end
else
RefreshAsynch;
DataSet.Refresh;
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
CltAsynch.Free;
Clt.Free;
end;
procedure TForm4.FormCreate(Sender: TObject);
procedure TMainForm.FormCreate(Sender: TObject);
begin
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;
procedure TForm4.RefreshAsynch;
var
Res: IRESTResponse;
lRNo: Integer;
procedure TMainForm.ShowError(const AResponse: IRESTResponse);
begin
// this a simple sychronous request...
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(
MessageDlg(
AResponse.ResponseCode.ToString + ': ' + AResponse.ResponseText + sLineBreak +
AResponse.BodyAsJsonObject.Get('message').JsonValue.Value);
AResponse.BodyAsJsonObject.Get('message').JsonValue.Value,
mtError, [mbOK], 0);
end;
end.

View File

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

View File

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

View File

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