Initial KnockoutSpa template

This commit is contained in:
SteveSandersonMS
2016-03-08 12:16:22 +00:00
parent 7d7e974b5f
commit bbdbb449d5
45 changed files with 2609 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.css';
import './css/site.css';
import * as ko from 'knockout';
import appLayout from './components/app-layout/app-layout';
ko.components.register('app-layout', appLayout);
ko.applyBindings();
// Basic hot reloading support. Automatically reloads and restarts the Knockout app each time
// you modify source files. This will not preserve any application state other than the URL.
declare var module: any;
if (module.hot) {
module.hot.dispose(() => {
ko.cleanNode(document.body);
// TODO: Need a better API for this
Object.getOwnPropertyNames((<any>ko).components._allRegisteredComponents).forEach(componentName => {
ko.components.unregister(componentName);
});
});
module.hot.accept();
}

View File

@@ -0,0 +1,8 @@
<div class='container-fluid'>
<div class='row'>
<div class='col-sm-3'>
<nav-menu params='route: route'></nav-menu>
</div>
<div class='col-sm-9' data-bind='component: { name: route().page, params: route }'></div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
import * as ko from 'knockout';
import * as router from '../../router';
class AppLayoutViewModel {
public route = router.instance().currentRoute;
}
export default { viewModel: AppLayoutViewModel, template: require('./app-layout.html') };

View File

@@ -0,0 +1,7 @@
<h2>Counter</h2>
<p>This is a simple example of a Knockout component.</p>
<p>Current count: <strong data-bind='text: currentCount'></strong></p>
<button data-bind='click: incrementCounter'>Increment</button>

View File

@@ -0,0 +1,12 @@
import * as ko from 'knockout';
class CounterExampleViewModel {
public currentCount = ko.observable(0);
public incrementCounter() {
let prevCount = this.currentCount();
this.currentCount(prevCount + 1);
}
}
export default { viewModel: CounterExampleViewModel, template: require('./counter-example.html') };

View File

@@ -0,0 +1,24 @@
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
<p data-bind='ifnot: forecasts'><em>Loading...</em></p>
<table class='table' data-bind='if: forecasts'>
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody data-bind='foreach: forecasts'>
<tr>
<td data-bind='text: dateFormatted'></td>
<td data-bind='text: temperatureC'></td>
<td data-bind='text: temperatureF'></td>
<td data-bind='text: summary'></td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,22 @@
import * as ko from 'knockout';
interface WeatherForecast {
dateFormatted: string;
temperatureC: number;
temperatureF: number;
summary: string;
}
class FetchDataViewModel {
public forecasts = ko.observableArray<WeatherForecast>();
constructor() {
fetch('/api/SampleData/WeatherForecasts')
.then(response => response.json())
.then((data: WeatherForecast[]) => {
this.forecasts(data);
});
}
}
export default { viewModel: FetchDataViewModel, template: require('./fetch-data.html') };

View File

@@ -0,0 +1,15 @@
<h1>Hello, world!</h1>
<p>Welcome to your new single-page application, built with:</p>
<ul>
<li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>
<li><a href='http://knockoutjs.com/'>Knockout.js</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>
<li><a href='https://webpack.github.io/'>Webpack</a> for building and bundling client-side resources</li>
<li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>
</ul>
<p>To help you get started, we've also set up:</p>
<ul>
<li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>
<li><strong>Webpack dev middleware</strong>. In development mode, there's no need to run the <code>webpack</code> build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.</li>
<li><strong>Hot module replacement</strong>. In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, your Knockout app will be rebuilt and a new instance injected is into the page.</li>
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and the <code>webpack</code> build tool produces minified static CSS and JavaScript files.</li>
</ul>

View File

@@ -0,0 +1,6 @@
import * as ko from 'knockout';
class HomePageViewModel {
}
export default { viewModel: HomePageViewModel, template: require('./home-page.html') };

View File

@@ -0,0 +1,33 @@
<div class='main-nav'>
<div class='navbar navbar-inverse'>
<div class='navbar-header'>
<button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
<span class='sr-only'>Toggle navigation</span>
<span class='icon-bar'></span>
<span class='icon-bar'></span>
<span class='icon-bar'></span>
</button>
<a class='navbar-brand' href='#'>WebApplicationBasic</a>
</div>
<div class='clearfix'></div>
<div class='navbar-collapse collapse'>
<ul class='nav navbar-nav'>
<li>
<a class='navbar-brand' href='#' data-bind='css: { active: route().page === "home-page" }'>
<span class='glyphicon glyphicon-home'></span> Home
</a>
</li>
<li>
<a class='navbar-brand' href='#counter' data-bind='css: { active: route().page === "counter-example" }'>
<span class='glyphicon glyphicon-education'></span> Counter
</a>
</li>
<li>
<a class='navbar-brand' href='#fetch-data' data-bind='css: { active: route().page === "fetch-data" }'>
<span class='glyphicon glyphicon-th-list'></span> Fetch data
</a>
</li>
</ul>
</div>
</div>
</div>

View File

@@ -0,0 +1,19 @@
import * as ko from 'knockout';
import { Route } from '../../router';
interface NavMenuParams {
route: KnockoutObservable<Route>;
}
class NavMenuViewModel {
public route: KnockoutObservable<Route>;
constructor(params: NavMenuParams) {
// This viewmodel doesn't do anything except pass through the 'route' parameter to the view.
// You could remove this viewmodel entirely, and define 'nav-menu' as a template-only component.
// But in most apps, you'll want some viewmodel logic to determine what navigation options appear.
this.route = params.route;
}
}
export default { viewModel: NavMenuViewModel, template: require('./nav-menu.html') };

View File

@@ -0,0 +1,66 @@
.main-nav li .glyphicon {
margin-right: 10px;
}
/* Highlighting rules for nav menu items */
.main-nav li a.active,
.main-nav li a.active:hover,
.main-nav li a.active:focus {
background-color: #4189C7;
color: white;
}
/* Keep the nav menu independent of scrolling and on top of other items */
.main-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1;
}
@media (max-width: 767px) {
/* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */
body {
padding-top: 50px;
}
}
@media (min-width: 768px) {
/* On small screens, convert the nav menu to a vertical sidebar */
.main-nav {
height: 100%;
width: calc(25% - 20px);
}
.main-nav .navbar {
border-radius: 0px;
border-width: 0px;
height: 100%;
}
.main-nav .navbar-header {
float: none;
}
.main-nav .navbar-collapse {
border-top: 1px solid #444;
padding: 0px;
}
.main-nav .navbar ul {
float: none;
}
.main-nav .navbar li {
float: none;
font-size: 15px;
margin: 6px;
}
.main-nav .navbar li a {
padding: 10px 16px;
border-radius: 4px;
}
.main-nav .navbar a {
/* If a menu item's text is too long, truncate it */
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}

View File

@@ -0,0 +1,44 @@
import * as ko from 'knockout';
import * as crossroads from 'crossroads';
import * as hasher from 'hasher';
import { routes } from './routes';
// This module configures crossroads.js, a routing library. If you prefer, you
// can use any other routing library (or none at all) as Knockout is designed to
// compose cleanly with external libraries.
//
// You *don't* have to follow the pattern established here (each route entry
// specifies a 'page', which is a Knockout component) - there's nothing built into
// Knockout that requires or even knows about this technique. It's just one of
// many possible ways of setting up client-side routes.
export class Router {
public currentRoute = ko.observable<Route>({});
constructor(routes: Route[]) {
// Configure Crossroads route handlers
routes.forEach(route => {
crossroads.addRoute(route.url, (requestParams) => {
this.currentRoute(ko.utils.extend(requestParams, route.params));
});
});
// Activate Crossroads
crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;
hasher.initialized.add(hash => crossroads.parse(hash));
hasher.changed.add(hash => crossroads.parse(hash));
hasher.init();
}
}
export interface Route {
url?: string;
params?: any;
}
export function instance() {
// Ensure there's only one instance. This is needed to support hot module replacement.
const windowOrDefault: any = typeof window === 'undefined' ? {} : window;
windowOrDefault._router = windowOrDefault._router || new Router(routes);
return windowOrDefault._router;
}

View File

@@ -0,0 +1,18 @@
import * as ko from 'knockout';
import { Route } from './router';
import navMenu from './components/nav-menu/nav-menu';
import homePage from './components/home-page/home-page';
import counterExample from './components/counter-example/counter-example';
import fetchData from './components/fetch-data/fetch-data';
ko.components.register('nav-menu', navMenu);
ko.components.register('home-page', homePage);
ko.components.register('counter-example', counterExample);
ko.components.register('fetch-data', fetchData);
export const routes: Route[] = [
{ url: '', params: { page: 'home-page' } },
{ url: 'counter', params: { page: 'counter-example' } },
{ url: 'fetch-data', params: { page: 'fetch-data' } }
];