1.0.0
This commit is contained in:
parent
a5ff5edcd8
commit
57f212eecc
7 changed files with 257 additions and 1 deletions
44
.gitignore
vendored
44
.gitignore
vendored
|
@ -10,3 +10,47 @@ pubspec.lock
|
|||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
|||
language: dart
|
83
README.md
83
README.md
|
@ -1,2 +1,83 @@
|
|||
# html_builder
|
||||
# html
|
||||
[![Pub](https://img.shields.io/pub/v/angel_html.svg)](https://pub.dartlang.org/packages/angel_html)
|
||||
[![build status](https://travis-ci.org/angel-dart/html.svg)](https://travis-ci.org/angel-dart/html)
|
||||
|
||||
A plug-in that allows you to return html_builder AST's from request handlers, and have them sent as HTML automatically.
|
||||
|
||||
[`package:html_builder`](https://github.com/thosakwe/html_builder) is a simple virtual DOM library
|
||||
(without diffing, you can find that
|
||||
[here](https://github.com/thosakwe/html_builder_vdom)), with a handy Dart DSL that makes it easy to build HTML
|
||||
AST's:
|
||||
|
||||
```dart
|
||||
import 'package:html_builder/elements.dart';
|
||||
|
||||
Node myDom = html(lang: 'en', c: [
|
||||
head(c: [
|
||||
meta(name: 'viewport', content: 'width=device-width, initial-scale=1'),
|
||||
title(c: [
|
||||
text('html_builder example page')
|
||||
]),
|
||||
]),
|
||||
body(c: [
|
||||
h1(c: [
|
||||
text('Hello world!'),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
```
|
||||
|
||||
This plug-in means that you can now `return` these AST's, and Angel will automatically send them to
|
||||
clients. Ultimately, the implication is that you can use `html_builder` as a substitute for a
|
||||
templating system within Dart. With [hot reloading](https://github.com/angel-dart/hot), you won't
|
||||
even need to reload your server (as it should be).
|
||||
|
||||
# Installation
|
||||
In your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel_html: ^1.0.0
|
||||
```
|
||||
|
||||
# Usage
|
||||
The `renderHtml` function does all the magic for you.
|
||||
|
||||
```dart
|
||||
configureServer(Angel app) async {
|
||||
// Wire it up!
|
||||
app.before.add(renderHtml());
|
||||
|
||||
// You can pass a custom StringRenderer if you need more control over the output.
|
||||
app.before.add(renderHtml(renderer: new StringRenderer(html5: false)));
|
||||
|
||||
app.get('/greet/:name', (RequestContext req) {
|
||||
return html(lang: 'en', c: [
|
||||
head(c: [
|
||||
meta(name: 'viewport', content: 'width=device-width, initial-scale=1'),
|
||||
title(c: [
|
||||
text('Greetings!')
|
||||
]),
|
||||
]),
|
||||
body(c: [
|
||||
h1(c: [
|
||||
text('Hello, ${req.params['id']}!'),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
By default, `renderHtml` will ignore the client's `Accept` header. However, if you pass
|
||||
`enforceAcceptHeader` as `true`, then a `406 Not Acceptable` error will be thrown if the
|
||||
client doesn't accept `*/*` or `text/html`.
|
||||
|
||||
```dart
|
||||
configureServer(Angel app) async {
|
||||
// Wire it up!
|
||||
app.before.add(renderHtml(enforceAcceptHeader: true));
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
35
lib/angel_html.dart
Normal file
35
lib/angel_html.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:html_builder/html_builder.dart';
|
||||
|
||||
/// Returns a [RequestMiddleware] that allows you to return `html_builder` [Node]s as responses.
|
||||
///
|
||||
/// You can provide a custom [renderer]. The default renders minified HTML5 pages.
|
||||
///
|
||||
/// Set [enforceAcceptHeader] to `true` to throw a `406 Not Acceptable` if the client doesn't accept HTML responses.
|
||||
RequestMiddleware renderHtml(
|
||||
{StringRenderer renderer, bool enforceAcceptHeader}) {
|
||||
renderer ??= new StringRenderer(pretty: false, html5: true);
|
||||
|
||||
return (RequestContext req, ResponseContext res) {
|
||||
var oldSerializer = res.serializer;
|
||||
|
||||
res.serializer = (data) {
|
||||
if (data is! Node)
|
||||
return oldSerializer(data);
|
||||
else {
|
||||
if (enforceAcceptHeader == true && !req.accepts('text/html'))
|
||||
throw new AngelHttpException.notAcceptable();
|
||||
|
||||
var content = renderer.render(data);
|
||||
res
|
||||
..headers['content-type'] = 'text/html'
|
||||
..write(content)
|
||||
..end();
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
return new Future<bool>.value(true);
|
||||
};
|
||||
}
|
14
pubspec.yaml
Normal file
14
pubspec.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: angel_html
|
||||
version: 1.0.0
|
||||
description: Easily render html_builder AST's as responses in Angel.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/html_builder
|
||||
environment:
|
||||
sdk: ">=1.19.0"
|
||||
dependencies:
|
||||
angel_framework: ^1.0.0
|
||||
html_builder: ^1.0.0
|
||||
dev_dependencies:
|
||||
angel_test: ^1.0.0
|
||||
html: ^0.13.2
|
||||
test: ^0.12.0
|
75
test/all_test.dart
Normal file
75
test/all_test.dart
Normal file
|
@ -0,0 +1,75 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_html/angel_html.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:html_builder/elements.dart';
|
||||
import 'package:html_builder/html_builder.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
Angel app;
|
||||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel();
|
||||
|
||||
app.before.add(renderHtml());
|
||||
|
||||
app.get('/html', () {
|
||||
return html(c: [
|
||||
head(c: [
|
||||
title(c: [text('ok')])
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
app
|
||||
.chain(renderHtml(
|
||||
enforceAcceptHeader: true,
|
||||
renderer: new StringRenderer(doctype: null, pretty: false)))
|
||||
.get('/strict', () {
|
||||
return div(c: [text('strict')]);
|
||||
});
|
||||
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
||||
tearDown(() => client.close());
|
||||
|
||||
test('sets content type and body', () async {
|
||||
var response = await client.get('/html');
|
||||
print('Response: ${response.body}');
|
||||
|
||||
expect(
|
||||
response,
|
||||
allOf(hasContentType('text/html'),
|
||||
hasBody('<!DOCTYPE html><html><head><title>ok</title></head></html>')));
|
||||
});
|
||||
|
||||
group('enforce accept header', () {
|
||||
test('sends if correct accept or wildcard', () async {
|
||||
var response = await client.get('/strict', headers: {'accept': '*/*'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response,
|
||||
allOf(hasContentType('text/html'), hasBody('<div>strict</div>')));
|
||||
|
||||
response = await client.get('/strict',
|
||||
headers: {'accept': 'text/html,application/json,text/xml'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response,
|
||||
allOf(hasContentType('text/html'), hasBody('<div>strict</div>')));
|
||||
});
|
||||
|
||||
test('throws if incorrect or no accept', () async {
|
||||
var response = await client.get('/strict');
|
||||
print('Response: ${response.body}');
|
||||
expect(response,
|
||||
isAngelHttpException(statusCode: 406, message: '406 Not Acceptable'));
|
||||
|
||||
response = await client
|
||||
.get('/strict', headers: {'accept': 'application/json,text/xml'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response,
|
||||
isAngelHttpException(statusCode: 406, message: '406 Not Acceptable'));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue