-Paket -TutorialFiles

Removed Paket files (could not get reliable builds; we may revisit once
.NET core has stablizied); removed extraneous tutorial files; currently
blocked getting Auth0 Lock to load from within Aurelia app
This commit is contained in:
Daniel J. Summers 2017-05-31 22:22:08 -05:00
parent b0b20df36d
commit 55cf47af18
22 changed files with 125 additions and 1583 deletions

9
.gitignore vendored
View File

@ -252,8 +252,9 @@ paket-files/
.idea/ .idea/
*.sln.iml *.sln.iml
# Elm temporary files # Compiled files / application
src/elm-stuff **/vendor-bundle.js
# Compiled application **/app-bundle.js*
src/wwwroot/app.js src/api/wwwroot/index.html
src/api/appsettings.json
build/ build/

View File

@ -1,108 +0,0 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Mark that this target file has been loaded. -->
<IsPaketRestoreTargetsFileLoaded>true</IsPaketRestoreTargetsFileLoaded>
<PaketToolsPath>$(MSBuildThisFileDirectory)</PaketToolsPath>
<MonoPath Condition="'$(MonoPath)' == '' And Exists('/Library/Frameworks/Mono.framework/Commands/mono')">/Library/Frameworks/Mono.framework/Commands/mono</MonoPath>
<MonoPath Condition="'$(MonoPath)' == ''">mono</MonoPath>
<!-- Paket command -->
<PaketExePath Condition=" '$(PaketExePath)' == '' AND Exists('$(PaketRootPath)paket.exe')">$(PaketRootPath)paket.exe</PaketExePath>
<PaketExePath Condition=" '$(PaketExePath)' == '' ">$(PaketToolsPath)paket.exe</PaketExePath>
<PaketCommand Condition=" '$(OS)' == 'Windows_NT'">"$(PaketExePath)"</PaketCommand>
<PaketCommand Condition=" '$(OS)' != 'Windows_NT' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"</PaketCommand>
</PropertyGroup>
<Target Name="PaketRestore" BeforeTargets="_GenerateProjectRestoreGraphPerFramework;_GenerateRestoreGraphWalkPerFramework;CollectPackageReferences" >
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)" ' />
<PropertyGroup>
<PaketReferencesFilePath>$(MSBuildProjectDirectory)/obj/$(MSBuildProjectFile).references</PaketReferencesFilePath>
</PropertyGroup>
<ReadLinesFromFile File="$(PaketReferencesFilePath)" >
<Output TaskParameter="Lines" ItemName="PaketReferencesFileLines"/>
</ReadLinesFromFile>
<ItemGroup Condition=" '@(PaketReferencesFileLines)' != '' " >
<PaketReferencesFileLinesInfo Include="@(PaketReferencesFileLines)" >
<PackageName>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])</PackageName>
<PackageVersion>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])</PackageVersion>
</PaketReferencesFileLinesInfo>
<PackageReference Include="%(PaketReferencesFileLinesInfo.PackageName)">
<Version>%(PaketReferencesFileLinesInfo.PackageVersion)</Version>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<RestoreConfigFile>$(MSBuildProjectDirectory)/obj/$(MSBuildProjectFile).NuGet.Config</RestoreConfigFile>
</PropertyGroup>
</Target>
<Target Name="PaketDisableDirectPack" AfterTargets="_IntermediatePack" BeforeTargets="GenerateNuspec" >
<PropertyGroup>
<ContinuePackingAfterGeneratingNuspec>false</ContinuePackingAfterGeneratingNuspec>
</PropertyGroup>
</Target>
<Target Name="PaketOverrideNuspec" AfterTargets="GenerateNuspec" >
<PropertyGroup>
<PaketReferencesFilePath>$(MSBuildProjectDirectory)/obj/$(MSBuildProjectFile).references</PaketReferencesFilePath>
<ContinuePackingAfterGeneratingNuspec>true</ContinuePackingAfterGeneratingNuspec>
</PropertyGroup>
<ItemGroup>
<_NuspecFiles Include="$(BaseIntermediateOutputPath)*.nuspec"/>
</ItemGroup>
<Exec Command='$(PaketCommand) fix-nuspec file "@(_NuspecFiles)" references-file "$(PaketReferencesFilePath)" ' />
<ConvertToAbsolutePath Condition="@(_NuspecFiles) != ''" Paths="@(_NuspecFiles)">
<Output TaskParameter="AbsolutePaths" PropertyName="NuspecFileAbsolutePath" />
</ConvertToAbsolutePath>
<!-- Call Pack -->
<PackTask PackItem="$(PackProjectInputFile)"
PackageFiles="@(_PackageFiles)"
PackageFilesToExclude="@(_PackageFilesToExclude)"
PackageVersion="$(PackageVersion)"
PackageId="$(PackageId)"
Title="$(Title)"
Authors="$(Authors)"
Description="$(Description)"
Copyright="$(Copyright)"
RequireLicenseAcceptance="$(PackageRequireLicenseAcceptance)"
LicenseUrl="$(PackageLicenseUrl)"
ProjectUrl="$(PackageProjectUrl)"
IconUrl="$(PackageIconUrl)"
ReleaseNotes="$(PackageReleaseNotes)"
Tags="$(PackageTags)"
TargetPathsToAssemblies="@(_TargetPathsToAssemblies->'%(FinalOutputPath)')"
TargetPathsToSymbols="@(_TargetPathsToSymbols)"
TargetFrameworks="@(_TargetFrameworks)"
AssemblyName="$(AssemblyName)"
PackageOutputPath="$(PackageOutputAbsolutePath)"
IncludeSymbols="$(IncludeSymbols)"
IncludeSource="$(IncludeSource)"
PackageTypes="$(PackageType)"
IsTool="$(IsTool)"
RepositoryUrl="$(RepositoryUrl)"
RepositoryType="$(RepositoryType)"
SourceFiles="@(_SourceFiles->Distinct())"
NoPackageAnalysis="$(NoPackageAnalysis)"
MinClientVersion="$(MinClientVersion)"
Serviceable="$(Serviceable)"
AssemblyReferences="@(_References)"
ContinuePackingAfterGeneratingNuspec="$(ContinuePackingAfterGeneratingNuspec)"
NuspecOutputPath="$(BaseIntermediateOutputPath)"
IncludeBuildOutput="$(IncludeBuildOutput)"
BuildOutputFolder="$(BuildOutputTargetFolder)"
ContentTargetFolders="$(ContentTargetFolders)"
RestoreOutputPath="$(RestoreOutputAbsolutePath)"
NuspecFile="$(NuspecFileAbsolutePath)"
NuspecBasePath="$(NuspecBasePath)"
NuspecProperties="$(NuspecProperties)"/>
</Target>
</Project>

Binary file not shown.

View File

@ -1,8 +1,4 @@
@echo off @echo off
cls cls
.paket\paket.exe restore
if errorlevel 1 (
exit /b %errorlevel%
)
"packages\FAKE\tools\Fake.exe" build.fsx %1 "packages\FAKE\tools\Fake.exe" build.fsx %1
pause pause

View File

@ -38,12 +38,27 @@ Target "CopyApp" (fun _ ->
) )
Target "BuildApi" (fun _ -> Target "BuildApi" (fun _ ->
!! "src/api/*.fsproj" let result =
ExecProcessAndReturnMessages (fun info ->
info.UseShellExecute <- false
info.FileName <- "dotnet"
info.Arguments <- "build"
info.WorkingDirectory <- "src" @@ "api") (TimeSpan.FromMinutes 2.)
Log "AppBuild-Output: " result.Messages
match result.ExitCode with
| 0 -> ()
| _ -> failwith "API build failed"
(*!! "src/api/*.fsproj"
|> MSBuildRelease buildDir "Build" |> MSBuildRelease buildDir "Build"
|> Log "ApiBuild-Output: " |> Log "ApiBuild-Output: " *)
) )
Target "Run" (fun _ -> Target "Run" (fun _ ->
ExecProcess (fun info ->
info.FileName <- "dotnet"
info.Arguments <- """publish -o ..\..\build"""
info.WorkingDirectory <- "src" @@ "api") TimeSpan.MaxValue
|> ignore
ExecProcess (fun info -> ExecProcess (fun info ->
info.FileName <- "dotnet" info.FileName <- "dotnet"
info.Arguments <- "myPrayerJournal.dll" info.Arguments <- "myPrayerJournal.dll"

View File

@ -1,11 +0,0 @@
source https://api.nuget.org/v3/index.json
nuget Auth0.AuthenticationApi
nuget FAKE
nuget FSharp.Core
nuget FSharp.NET.Sdk
nuget jose-jwt
nuget Microsoft.EntityFrameworkCore.Tools
nuget Newtonsoft.Json
nuget Npgsql.EntityFrameworkCore.PostgreSQL
nuget Suave
nuget Suave.Experimental

1065
paket.lock

File diff suppressed because it is too large Load Diff

View File

@ -78,8 +78,7 @@ let schemeHostPort (req : HttpRequest) =
/// Authorization functions /// Authorization functions
module Auth = module Auth =
open Views (*
let exchangeCodeForToken code = context (fun ctx -> let exchangeCodeForToken code = context (fun ctx ->
async { async {
let client = AuthenticationApiClient (Uri (sprintf "https://%s" cfg.Auth0.Domain)) let client = AuthenticationApiClient (Uri (sprintf "https://%s" cfg.Auth0.Domain))
@ -120,7 +119,7 @@ module Auth =
match ctx |> HttpContext.state with match ctx |> HttpContext.state with
| Some state -> state.set "auth-key" null | Some state -> state.set "auth-key" null
| _ -> succeed | _ -> succeed
>=> FOUND (sprintf "%s/" (schemeHostPort ctx.request))) >=> FOUND (sprintf "%s/" (schemeHostPort ctx.request))) *)
let cw (x : string) = Console.WriteLine x let cw (x : string) = Console.WriteLine x
@ -152,7 +151,7 @@ module Auth =
| _ -> Writers.setUserData "user" None) | _ -> Writers.setUserData "user" None)
/// Create a user context for the currently assigned user /// Create a user context for the currently assigned user
let userCtx ctx = { Id = ctx.userState.["user"] :?> string option } //let userCtx ctx = { Id = ctx.userState.["user"] :?> string option }
/// Read an item from the user state, downcast to the expected type /// Read an item from the user state, downcast to the expected type
let read ctx key : 'value = let read ctx key : 'value =
@ -164,17 +163,17 @@ let dataCtx () =
/// Return an HTML page /// Return an HTML page
let html ctx content = let html ctx content =
Views.page (Auth.userCtx ctx) content ""//Views.page (Auth.userCtx ctx) content
/// Home page /// Home page
let viewHome = warbler (fun ctx -> OK (Views.home |> html ctx)) let viewHome = warbler (fun ctx -> OK ("" (*Views.home*) |> html ctx))
/// Journal page /// Journal page
let viewJournal = let viewJournal =
context (fun ctx -> context (fun ctx ->
use dataCtx = dataCtx () use dataCtx = dataCtx ()
let reqs = Data.Requests.allForUser (defaultArg (read ctx "user") "") dataCtx let reqs = Data.Requests.allForUser (defaultArg (read ctx "user") "") dataCtx
OK (Views.journal reqs |> html ctx)) OK ("" (*Views.journal reqs*) |> html ctx))
let idx = let idx =
context (fun ctx -> context (fun ctx ->
@ -187,8 +186,8 @@ let app =
>=> choose [ >=> choose [
path Route.home >=> Files.browseFileHome "index.html" path Route.home >=> Files.browseFileHome "index.html"
path Route.journal >=> viewJournal path Route.journal >=> viewJournal
path Route.User.logOn >=> Auth.handleSignIn //path Route.User.logOn >=> Auth.handleSignIn
path Route.User.logOff >=> Auth.handleSignOut //path Route.User.logOff >=> Auth.handleSignOut
Writers.setHeader "Cache-Control" "no-cache" >=> Files.browseHome Writers.setHeader "Cache-Control" "no-cache" >=> Files.browseHome
NOT_FOUND "Page not found." NOT_FOUND "Page not found."
] ]
@ -214,14 +213,7 @@ let main argv =
// Establish the data environment // Establish the data environment
//liftDep getConn (Data.establishEnvironment >> Async.RunSynchronously) //liftDep getConn (Data.establishEnvironment >> Async.RunSynchronously)
//|> run deps //|> run deps
let writeKey key = File.WriteAllText ("key.txt", key)
Crypto.generateKey Crypto.KeySize
|> Convert.ToBase64String
|> writeKey
ensureDatabase () ensureDatabase ()
startWebServer suaveCfg app startWebServer suaveCfg app
0 0
(*
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2Rqcy1jb25zdWx0aW5nLmF1dGgwLmNvbS8iLCJzdWIiOiJ3aW5kb3dzbGl2ZXw3OTMyNGZhMTM4MzZlZGNiIiwiYXVkIjoiT2YyczBSUUNRM210M2R3SWtPQlk1aDg1SjlzWGJGMm4iLCJleHAiOjE0OTI5MDc1OTAsImlhdCI6MTQ5Mjg3MTU5MH0.61JPm3Hz7XW-iaSq8Esv1cajQPbK0o9L5xz-RHIYq9g
*)

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="FSharp.NET.Sdk;Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<VersionPrefix>0.8.1</VersionPrefix> <VersionPrefix>0.8.1</VersionPrefix>
@ -17,7 +17,6 @@
<Compile Include="Migrations/20170104023341_InitialDb.fs" /> <Compile Include="Migrations/20170104023341_InitialDb.fs" />
<Compile Include="Migrations/DataContextModelSnapshot.fs" /> <Compile Include="Migrations/DataContextModelSnapshot.fs" />
<Compile Include="Route.fs" /> <Compile Include="Route.fs" />
<Compile Include="Views.fs" />
<Compile Include="App.fs" /> <Compile Include="App.fs" />
<None Update="appsettings.json"> <None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -27,9 +26,31 @@
</None> </None>
</ItemGroup> </ItemGroup>
<Import Project="..\..\packages\FSharp.NET.Sdk\build\FSharp.NET.Core.Sdk.targets" /> <!-- Import Project="..\..\.paket\Paket.Restore.targets" / -->
<Import Project="..\..\.paket\Paket.Restore.targets" /> <ItemGroup>
<PackageReference Include="Auth0.AuthenticationApi">
<Version>4.1.0</Version>
</PackageReference>
<PackageReference Include="FSharp.Core">
<Version>4.1.17</Version>
</PackageReference>
<PackageReference Include="FSharp.NET.Sdk">
<Version>1.0.5</Version>
</PackageReference>
<PackageReference Include="jose-jwt">
<Version>2.3.0</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>10.0.2</Version>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Suave">
<Version>2.1.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -1,113 +0,0 @@
module MyPrayerJournal.Views
//open Suave.Html
open Suave.Xml
type UserContext = { Id: string option }
[<AutoOpen>]
module Tags =
/// Generate a meta tag
let meta attr = tag "meta" attr empty
/// Generate a link to a stylesheet
let stylesheet url = linkAttr [ "rel", "stylesheet"; "href", url ]
let aAttr attr x = tag "a" attr (flatten x)
let a = aAttr []
let buttonAttr attr x = tag "button" attr (flatten x)
let button = buttonAttr []
let footerAttr attr x = tag "footer" attr (flatten x)
let footer = footerAttr []
let ulAttr attr x = tag "ul" attr (flatten x)
let ul = ulAttr []
/// Used to prevent a self-closing tag where we need no text
let noText = text ""
let navLinkAttr attr url linkText = aAttr (("href", url) :: attr) [ text linkText ]
let navLink = navLinkAttr []
let jsLink func linkText = navLinkAttr [ "onclick", func ] "javascript:void(0)" linkText
/// Create a link to a JavaScript file
let js src = scriptAttr [ "src", src ] [ noText ]
[<AutoOpen>]
module PageComponents =
let prependDoctype document = sprintf "<!DOCTYPE html>\n%s" document
let render = xmlToString >> prependDoctype
let navigation userCtx =
[
match userCtx.Id with
| Some _ ->
yield navLink Route.journal "Journal"
yield navLink Route.User.logOff "Log Off"
| _ -> yield jsLink "mpj.signIn()" "Log On"
]
|> List.map (fun x -> tag "li" [] x)
let pageHeader userCtx =
divAttr [ "class", "navbar navbar-inverse navbar-fixed-top" ] [
divAttr [ "class", "container" ] [
divAttr [ "class", "navbar-header" ] [
buttonAttr [ "class", "navbar-toggle"; "data-toggle", "collapse"; "data-target", ".navbar-collapse" ] [
spanAttr [ "class", "sr-only" ] (text "Toggle navigation")
spanAttr [ "class", "icon-bar" ] noText
spanAttr [ "class", "icon-bar" ] noText
spanAttr [ "class", "icon-bar" ] noText
]
navLinkAttr [ "class", "navbar-brand" ] "/" "myPrayerJournal"
]
divAttr [ "class", "navbar-collapse collapse" ] [
ulAttr [ "class", "nav navbar-nav navbar-right" ] (navigation userCtx)
]
]
]
let pageFooter =
footerAttr [ "class", "mpj-footer" ] [
pAttr [ "class", "text-right" ] [
text "myPrayerJournal v0.8.1"
]
]
let row = divAttr [ "class", "row" ]
let fullRow xml =
row [ divAttr [ "class", "col-xs-12" ] xml ]
/// Display a page
let page userCtx content =
html [
head [
meta [ "charset", "UTF-8" ]
meta [ "name", "viewport"; "content", "width=device-width, initial-scale=1" ]
title "myPrayerJournal"
stylesheet "https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css"
stylesheet "/content/styles.css"
stylesheet "https://fonts.googleapis.com/icon?family=Material+Icons"
]
body [
pageHeader userCtx
divAttr [ "class", "container body-content" ] [
content
pageFooter
]
js "https://cdn.auth0.com/js/lock/10.14/lock.min.js"
js "/js/mpj.js"
]
]
|> render
let home =
fullRow [
p [ text "&nbsp;"]
p [ text "myPrayerJournal is a place where individuals can record their prayer requests, record that they prayed for them, update them as God moves in the situation, and record a final answer received on that request. It will also allow individuals to review their answered prayers." ]
p [ text "This site is currently in very limited alpha, as it is being developed with a core group of test users. If this is something you are interested in using, check back around mid-February 2017 to check on the development progress." ]
]
let journal (reqs : Request list) =
fullRow [
p [ text "journal goes here" ]
]

View File

@ -1,6 +0,0 @@
Auth0.AuthenticationApi
jose-jwt
Newtonsoft.Json
Npgsql.EntityFrameworkCore.PostgreSQL
Suave
Suave.Experimental

View File

@ -100,6 +100,7 @@
"aurelia-dependency-injection", "aurelia-dependency-injection",
"aurelia-event-aggregator", "aurelia-event-aggregator",
"aurelia-framework", "aurelia-framework",
"aurelia-fetch-client",
"aurelia-history", "aurelia-history",
"aurelia-history-browser", "aurelia-history-browser",
"aurelia-loader", "aurelia-loader",
@ -151,6 +152,11 @@
"resources": [ "resources": [
"nprogress.css" "nprogress.css"
] ]
},
{
"name": "auth0-lock",
"path": "../node_modules/auth0-lock",
"main": "lib/index"
} }
] ]
} }

View File

@ -10,6 +10,8 @@
"dependencies": { "dependencies": {
"aurelia-animator-css": "^1.0.1", "aurelia-animator-css": "^1.0.1",
"aurelia-bootstrapper": "^2.1.0", "aurelia-bootstrapper": "^2.1.0",
"aurelia-fetch-client": "^1.1.2",
"auth0-lock": "^10.16.0",
"bluebird": "^3.4.1", "bluebird": "^3.4.1",
"bootstrap": "^3.3.7", "bootstrap": "^3.3.7",
"jquery": "^2.2.4", "jquery": "^2.2.4",

File diff suppressed because one or more lines are too long

View File

@ -15,6 +15,8 @@
</div> </div>
<div class="navbar-collapse collapse"> <div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li if.bind="!isAuthenticated()" click.delegate="logon()">Log On</li>
<li if.bind="isAuthenticated()" click.delegate="logoff()">Log Off</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -1,16 +1,66 @@
import {Router, RouterConfiguration} from "aurelia-router" import {Router, RouterConfiguration} from "aurelia-router"
import {EventAggregator} from "aurelia-event-aggregator" import {EventAggregator} from "aurelia-event-aggregator"
import {inject} from "aurelia-framework" import {inject} from "aurelia-framework"
import {HttpClient} from "aurelia-fetch-client"
import {PageTitle} from "./messages" import {PageTitle} from "./messages"
import {WebAPI} from "./web-api" import {Auth0Lock} from "auth0-lock"
@inject(WebAPI, EventAggregator) @inject(EventAggregator, HttpClient)
export class App { export class App {
router: Router; router: Router;
pageTitle: string; pageTitle: string;
constructor(public api: WebAPI, private ea: EventAggregator) { lock = new Auth0Lock('Of2s0RQCQ3mt3dwIkOBY5h85J9sXbF2n', 'djs-consulting.auth0.com', {
oidcConformant: true,
autoclose: true,
auth: {
redirectUrl: "http://localhost:8080/user/log-on",
responseType: 'token id_token',
audience: `https://djs-consulting.auth0.com/userinfo`,
params: {
scope: 'openid'
}
}
})
private setSession(authResult): void {
// Set the time that the access token will expire at
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime())
localStorage.setItem('access_token', authResult.accessToken)
localStorage.setItem('id_token', authResult.idToken)
localStorage.setItem('expires_at', expiresAt)
}
public logoff(): void {
// Remove tokens and expiry time from localStorage
localStorage.removeItem('access_token')
localStorage.removeItem('id_token')
localStorage.removeItem('expires_at')
// Go back to the home route
this.router.navigateToRoute("")
}
public isAuthenticated(): boolean {
// Check whether the current time is past the
// access token's expiry time
const expiresAt = JSON.parse(localStorage.getItem('expires_at'))
return new Date().getTime() < expiresAt
}
constructor(private ea: EventAggregator, private http: HttpClient) {
this.ea.subscribe(PageTitle, msg => this.pageTitle = msg.title) this.ea.subscribe(PageTitle, msg => this.pageTitle = msg.title)
var self = this
this.lock.on('authenticated', (authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult)
this.router.navigateToRoute("")
}
});
this.lock.on('authorization_error', (err) => {
this.router.navigateToRoute("")
console.log(err)
alert(`Error: ${err.error}. Check the console for further details.`)
});
} }
configureRouter(config: RouterConfiguration, router: Router){ configureRouter(config: RouterConfiguration, router: Router){
@ -18,10 +68,13 @@ export class App {
config.options.pushState = true config.options.pushState = true
config.options.root = "/" config.options.root = "/"
config.map([ config.map([
{ route: "", moduleId: "home", name: "home", title: "Welcome" }, { route: "", moduleId: "home", name: "home", title: "Welcome" }
{ route: 'contacts/:id', moduleId: 'contact-detail', name:'contacts' }
]) ])
this.router = router this.router = router
} }
public logon() {
this.lock.show()
}
} }

View File

@ -1,42 +0,0 @@
<template>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Profile</h3>
</div>
<div class="panel-body">
<form role="form" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">First Name</label>
<div class="col-sm-10">
<input type="text" placeholder="first name" class="form-control" value.bind="contact.firstName">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Last Name</label>
<div class="col-sm-10">
<input type="text" placeholder="last name" class="form-control" value.bind="contact.lastName">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<input type="text" placeholder="email" class="form-control" value.bind="contact.email">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Phone Number</label>
<div class="col-sm-10">
<input type="text" placeholder="phone number" class="form-control" value.bind="contact.phoneNumber">
</div>
</div>
</form>
</div>
</div>
<div class="button-bar">
<button class="btn btn-success" click.delegate="save()" disabled.bind="!canSave">Save</button>
</div>
</template>

View File

@ -1,58 +0,0 @@
import {inject} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
import {WebAPI} from './web-api';
import {ContactUpdated,ContactViewed} from './messages';
import {areEqual} from './utility';
interface Contact {
firstName: string;
lastName: string;
email: string;
}
@inject(WebAPI, EventAggregator)
export class ContactDetail {
routeConfig;
contact: Contact;
originalContact: Contact;
constructor(private api: WebAPI, private ea: EventAggregator) { }
activate(params, routeConfig) {
this.routeConfig = routeConfig;
return this.api.getContactDetails(params.id).then(contact => {
this.contact = <Contact>contact;
this.routeConfig.navModel.setTitle(this.contact.firstName);
this.originalContact = JSON.parse(JSON.stringify(this.contact));
this.ea.publish(new ContactViewed(this.contact));
});
}
get canSave() {
return this.contact.firstName && this.contact.lastName && !this.api.isRequesting;
}
save() {
this.api.saveContact(this.contact).then(contact => {
this.contact = <Contact>contact;
this.routeConfig.navModel.setTitle(this.contact.firstName);
this.originalContact = JSON.parse(JSON.stringify(this.contact));
this.ea.publish(new ContactUpdated(this.contact));
});
}
canDeactivate() {
if(!areEqual(this.originalContact, this.contact)){
let result = confirm('You have unsaved changes. Are you sure you wish to leave?');
if(!result) {
this.ea.publish(new ContactViewed(this.contact));
}
return result;
}
return true;
}
}

View File

@ -1,12 +0,0 @@
<template>
<div class="contact-list">
<ul class="list-group">
<li repeat.for="contact of contacts" class="list-group-item ${contact.id === $parent.selectedId ? 'active' : ''}">
<a route-href="route: contacts; params.bind: {id:contact.id}" click.delegate="$parent.select(contact)">
<h4 class="list-group-item-heading">${contact.firstName} ${contact.lastName}</h4>
<p class="list-group-item-text">${contact.email}</p>
</a>
</li>
</ul>
</div>
</template>

View File

@ -1,28 +0,0 @@
import {EventAggregator} from 'aurelia-event-aggregator';
import {WebAPI} from './web-api';
import {ContactUpdated, ContactViewed} from './messages';
import {inject} from 'aurelia-framework';
@inject(WebAPI, EventAggregator)
export class ContactList {
contacts;
selectedId = 0;
constructor(private api: WebAPI, ea: EventAggregator) {
ea.subscribe(ContactViewed, msg => this.select(msg.contact));
ea.subscribe(ContactUpdated, msg => {
let id = msg.contact.id;
let found = this.contacts.find(x => x.id == id);
Object.assign(found, msg.contact);
});
}
created() {
this.api.getContactList().then(contacts => this.contacts = contacts);
}
select(contact) {
this.selectedId = contact.id;
return true;
}
}

View File

@ -1,11 +1,3 @@
export class ContactUpdated {
constructor(public contact) { }
}
export class ContactViewed {
constructor(public contact) { }
}
export class PageTitle { export class PageTitle {
constructor(public title: string) { } constructor(public title: string) { }
} }

View File

@ -1,96 +0,0 @@
let latency = 200;
let id = 0;
function getId(){
return ++id;
}
let contacts = [
{
id:getId(),
firstName:'John',
lastName:'Tolkien',
email:'tolkien@inklings.com',
phoneNumber:'867-5309'
},
{
id:getId(),
firstName:'Clive',
lastName:'Lewis',
email:'lewis@inklings.com',
phoneNumber:'867-5309'
},
{
id:getId(),
firstName:'Owen',
lastName:'Barfield',
email:'barfield@inklings.com',
phoneNumber:'867-5309'
},
{
id:getId(),
firstName:'Charles',
lastName:'Williams',
email:'williams@inklings.com',
phoneNumber:'867-5309'
},
{
id:getId(),
firstName:'Roger',
lastName:'Green',
email:'green@inklings.com',
phoneNumber:'867-5309'
}
];
export class WebAPI {
isRequesting = false;
getContactList(){
this.isRequesting = true;
return new Promise(resolve => {
setTimeout(() => {
let results = contacts.map(x => { return {
id:x.id,
firstName:x.firstName,
lastName:x.lastName,
email:x.email
}});
resolve(results);
this.isRequesting = false;
}, latency);
});
}
getContactDetails(id){
this.isRequesting = true;
return new Promise(resolve => {
setTimeout(() => {
let found = contacts.filter(x => x.id == id)[0];
resolve(JSON.parse(JSON.stringify(found)));
this.isRequesting = false;
}, latency);
});
}
saveContact(contact){
this.isRequesting = true;
return new Promise(resolve => {
setTimeout(() => {
let instance = JSON.parse(JSON.stringify(contact));
let found = contacts.filter(x => x.id == contact.id)[0];
if(found){
let index = contacts.indexOf(found);
contacts[index] = instance;
}else{
instance.id = getId();
contacts.push(instance);
}
this.isRequesting = false;
resolve(instance);
}, latency);
});
}
}