redstone-dart / redstone

A metadata driven microframework for Dart.
http://redstone-dart.github.io/redstone
MIT License
342 stars 42 forks source link

View Plugin #116

Closed cgarciae closed 9 years ago

cgarciae commented 9 years ago

I've created a plugin to render an html view based on mustache and redstone_mapper. The annotation tells the route of the HTML and the route returns an object encodable by redstone mapper. The plugin gets the value returned by route, encodes it into a map and renders with the view with mustache using that map.

I currently use it like this

@app.Route ('/email', responseType: "text/html", methods: const[app.POST])
@View ('bin/email/index.html')
Email email (@Decode() Email _email) => _email;

Here I receive an object of the custom Email class through JSON on route /email and return the bin/email/index.html HTML render with _emails information.

Here is my implementation of the ViewPluggin

import 'package:redstone/server.dart' as app;
import 'package:redstone_mapper/mapper.dart';
import 'package:path/path.dart' as path;
import 'dart:io';
import 'package:mustache/mustache.dart';

class View {
  final String route;
  const View(this.route);
}

void ViewPluggin(app.Manager manager) {
  manager.addResponseProcessor(View, (metadata, handlerName, value, injector) async {
    var view = metadata as View;
    var current = path.current;

    var html = await new File ('$current/${view.route}').readAsString();
    var template = new Template(html, lenient: true);

    return template.renderString(encode(value)); 
  });
}

I'd like to expand this to an API that kind of looks like this

@app.Group (...)
@ViewGroup ('path/to/group/folder')
class MyController
{
  @app.Route (...)
  @View ('someRoute.html')
  someRoute (...)
  {
    ...
    return encodableObject;
  }
}

I don't know if this is easy or possible. I might have to user reflections on the current instance of the group. No idea how.

cgarciae commented 9 years ago

I got this example working with groups

@app.Group ('/email')
@ViewGroup ('bin/email')
class TestEmail
{
  @app.DefaultRoute (responseType: "text/html", methods: const[app.POST])
  @View ('index.html')
  email (@Decode() Email _email) => _email;
}

Implementation (not sure its really safe, I am (ab)using request.attributes to pass the base path from the ViewGroup.

import 'package:redstone/server.dart' as app;
import 'package:redstone_mapper/mapper.dart';
import 'package:path/path.dart' as path;
import 'dart:io';
import 'package:mustache/mustache.dart';

class View {
  final String route;
  const View(this.route);
}

class ViewGroup {
  final String route;
  const ViewGroup(this.route);
}

void ViewPluggin(app.Manager manager) {

  manager.addRouteWrapper(ViewGroup, (metadata, pathSegments, injector, request, route) async {

    var controller = metadata as ViewGroup;
    request.attributes['__controllerRoute'] = controller.route;

    return route (pathSegments, injector, request);

  }, includeGroups: true);

  manager.addResponseProcessor(View, (metadata, handlerName, value, injector) async {

    var view = metadata as View;
    var current = path.current;
    var controllerRoute = app.request.attributes['__controllerRoute'];

    var fullRoute = controllerRoute != null ?
      '$controllerRoute/${view.route}' :
      view.route;

    var html = await new File ('$current/$fullRoute').readAsString();
    var template = new Template(html, lenient: true);

    return template.renderString(encode(value));
  });
}
azenla commented 9 years ago

I like the idea. I'll review your code soon and then we can make this official :)