mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-16 00:05:53 +01:00
Merge branch 'master' of https://github.com/danieleteti/delphimvcframework into HEAD
This commit is contained in:
commit
18e4412f73
28
README.md
28
README.md
@ -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)
|
||||
|
||||
@ -55,6 +56,31 @@ These are the most notable:
|
||||
* Mapper (convert JSON in Object and back, ObjectList in JSONArray and back, DataSets in JSONArray or ObjectList and back)
|
||||
* 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.
|
||||
@ -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;
|
||||
|
||||
|
24
build.py
24
build.py
@ -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
BIN
docs/periodic_table.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
@ -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 }
|
||||
{ }
|
||||
|
@ -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 }
|
||||
{ }
|
||||
|
@ -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 }
|
||||
{ }
|
||||
|
@ -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 }
|
||||
{ }
|
||||
|
@ -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 }
|
||||
{ }
|
||||
|
@ -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,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,9 +130,9 @@ resourcestring
|
||||
'%4:s' +
|
||||
' end;' + sLineBreak +
|
||||
sLineBreak +
|
||||
'implementation' + sLineBreak +
|
||||
'implementation' + sLineBreak + sLineBreak +
|
||||
'uses' + sLineBreak +
|
||||
' MVCFramework.Logger;' + sLineBreak +
|
||||
' MVCFramework.Logger;' + sLineBreak +
|
||||
sLineBreak +
|
||||
'%3:s' + sLineBreak +
|
||||
'%5:s' + 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 +
|
||||
|
@ -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 }
|
||||
{ }
|
||||
|
@ -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 }
|
||||
{ }
|
||||
|
@ -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
|
@ -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
|
||||
//
|
||||
|
@ -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
|
||||
//
|
||||
|
@ -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
|
||||
//
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -3,7 +3,7 @@ unit CustomersControllerU;
|
||||
interface
|
||||
|
||||
uses
|
||||
MVCFramework, CustomersTDGU;
|
||||
MVCFramework, MVCFramework.Commons, CustomersTDGU;
|
||||
|
||||
type
|
||||
|
||||
|
Binary file not shown.
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -3,7 +3,7 @@ unit Controllers.Base;
|
||||
interface
|
||||
|
||||
uses
|
||||
MVCFramework, Services, MainDM;
|
||||
MVCFramework, MVCFramework.Commons, Services, MainDM;
|
||||
|
||||
type
|
||||
TBaseController = class abstract(TMVCController)
|
||||
|
@ -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.
|
||||
|
@ -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"/>
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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)"/>
|
||||
|
13
samples/articles_crud_web_angular/.editorconfig
Normal file
13
samples/articles_crud_web_angular/.editorconfig
Normal 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
|
40
samples/articles_crud_web_angular/.gitignore
vendored
Normal file
40
samples/articles_crud_web_angular/.gitignore
vendored
Normal 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
|
31
samples/articles_crud_web_angular/README.md
Normal file
31
samples/articles_crud_web_angular/README.md
Normal 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).
|
60
samples/articles_crud_web_angular/angular-cli.json
Normal file
60
samples/articles_crud_web_angular/angular-cli.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
14
samples/articles_crud_web_angular/e2e/app.e2e-spec.ts
Normal file
14
samples/articles_crud_web_angular/e2e/app.e2e-spec.ts
Normal 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!');
|
||||
});
|
||||
});
|
11
samples/articles_crud_web_angular/e2e/app.po.ts
Normal file
11
samples/articles_crud_web_angular/e2e/app.po.ts
Normal 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();
|
||||
}
|
||||
}
|
16
samples/articles_crud_web_angular/e2e/tsconfig.json
Normal file
16
samples/articles_crud_web_angular/e2e/tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
43
samples/articles_crud_web_angular/karma.conf.js
Normal file
43
samples/articles_crud_web_angular/karma.conf.js
Normal 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
|
||||
});
|
||||
};
|
49
samples/articles_crud_web_angular/package.json
Normal file
49
samples/articles_crud_web_angular/package.json
Normal 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"
|
||||
}
|
||||
}
|
32
samples/articles_crud_web_angular/protractor.conf.js
Normal file
32
samples/articles_crud_web_angular/protractor.conf.js
Normal 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());
|
||||
}
|
||||
};
|
22
samples/articles_crud_web_angular/src/app/.vscode/launch.json
vendored
Normal file
22
samples/articles_crud_web_angular/src/app/.vscode/launch.json
vendored
Normal 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}"
|
||||
}
|
||||
]
|
||||
}
|
@ -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 { }
|
@ -0,0 +1,3 @@
|
||||
.active-route {
|
||||
font-weight: bold;
|
||||
}
|
32
samples/articles_crud_web_angular/src/app/app.component.html
Normal file
32
samples/articles_crud_web_angular/src/app/app.component.html
Normal 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>
|
@ -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!');
|
||||
}));
|
||||
});
|
17
samples/articles_crud_web_angular/src/app/app.component.ts
Normal file
17
samples/articles_crud_web_angular/src/app/app.component.ts
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
45
samples/articles_crud_web_angular/src/app/app.module.ts
Normal file
45
samples/articles_crud_web_angular/src/app/app.module.ts
Normal 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 { }
|
@ -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>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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']);
|
||||
}
|
||||
}
|
@ -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> </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>
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
<button (click)="doLoginLogout()" [ngClass]="{btn: true, 'btn-info': action == 'login', 'btn-danger': action == 'logout'}">{{action}}</button>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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 -->
|
@ -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();
|
||||
});
|
||||
});
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
export class Article {
|
||||
id: number = 0;
|
||||
code: string;
|
||||
description: string;
|
||||
price: number;
|
||||
}
|
@ -0,0 +1 @@
|
||||
<div class="alert alert-danger" role="alert">Page not found</div>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<p>
|
||||
private-area1 works!
|
||||
</p>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<p>
|
||||
private-area2 works!
|
||||
</p>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -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> </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>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
@ -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
|
||||
};
|
BIN
samples/articles_crud_web_angular/src/favicon.ico
Normal file
BIN
samples/articles_crud_web_angular/src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
14
samples/articles_crud_web_angular/src/index.html
Normal file
14
samples/articles_crud_web_angular/src/index.html
Normal 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
Loading…
Reference in New Issue
Block a user