Safe Haskell | None |
---|---|
Language | Haskell98 |
Integrate an AngularJS application written in Javascript into the static subsite.
AngularJS is a javascript web framework for enhancing HTML to provide dynamic web applications. This module integrates an AngularJS application written in pure javascript into a Yesod application. (As of January 2014, there is some experiemental development work on Fay bindings to AngularJS, but unfortunately it is not currently usable.)
An AngularJS application consists of several pieces:
- Javascript code consisting of controllers, directives, and services attached to Angular
modules. The goal of this javascript is to produce a domain specific language extending HTML.
embedNgModule
uses the static subsite to serve this javascript code, serving a minimized and compressed file during production and serving individual files during development. - Directive Templates. In Angular, directives should be the only components which manipulate the
DOM. This can happen in the directive javascript code or through a directive HTML template.
embedNgModule
supports templates written in Hamlet, converting the Hamlet to HTML at compile time before embedding the HTML into the generated javascript. - The View. In Angular, the view is written in the DSL extending HTML. Normal Yesod
Handlers and Widgets work great for the view (so nothing needed from this module). Note that your
Yesod Widgets will not have any julius or attached javascript code, the javascript is entirely
managed by
embedNgModule
. - Testing. Angular makes testing (both unit and end-to-end) easy. For unit and mid-level
testing, the normal Angular test runner karma is the best.
hamletTestTemplate
assists with integrating Hamlet directive templates into karma. For end2end testing, the Test.WebDriver.Commands.Angular module in the webdriver-angular package works well.
There is an example in the source code which shows an application, unit testing with karma, and end2end testing with webdriver.
- embedNgModule :: String -> Location -> FilePath -> (ByteString -> IO ByteString) -> Generator
- embedNgModules :: Location -> FilePath -> (ByteString -> IO ByteString) -> Generator
- embedNgModuleWithoutTemplates :: String -> Location -> FilePath -> (ByteString -> IO ByteString) -> Generator
- embedNgModulesWithoutTemplates :: Location -> FilePath -> (ByteString -> IO ByteString) -> Generator
- directiveTemplates :: FilePath -> ExpQ
- directiveTemplatesWithSettings :: HamletSettings -> FilePath -> ExpQ
- directiveWidget :: Text -> WidgetT site IO () -> WidgetT site IO ()
- hamletTestTemplate :: FilePath -> ExpQ
File Layout
This Haskell module assists with the management of Angular modules. Each Angular module
cooresponds to a directory where each controller/directive/service/factory/etc. is located in
separate files within this directory. For example, you might have files angular/myctrl.js
,
angular/somedirective.js
, angular/myservice.js
, and so on. Also, for each directive that
does not use an inline template, you should have the corresponding directive template in a file
with the same name as the javascript file but with a hamlet
extension (e.g.
angular/somedirective.hamlet
). For larger applications, you can organize the Angular code into
multiple Angular modules by using one directory per module.
Javascript
All files with a .js
extension in the given directory will be loaded. Each javascript file
should be written to assume that a variable named module
is already defined and holds the
angular module. For example,
module.controller("MyController", function($scope) { $scope.hello = "Hello, World!"; });
When compiling for production, these .js
files will be concatenated, combined with the code
defining the module
variable, DI annotated, minified, compressed, and then embedded into the
executable to be served from the given location. That is, the following javascript will be
automatically created:
(function(module) { module.controller("MyController", ["$scope", function($scope) { $scope.hello = "Hello, world!"; }]); <contents of all the other .js files> <optionally contents of the directive templates (see below)> })(angular.module("module-name", <contents of module-deps.json>));
Note the module-deps.json
file: if this file exists within the directory, it
will be parsed and used as the module dependencies (so therefore the contents should be a
list of strings). If this file does not exist, an empty list of dependencies is used.
Note also the inline dependency injection annotation of the $scope
parameter. When compiling
for production, before running the minimizer, the javascript is parsed and DI parameters are
annotated. The arguments to the top-level call to module.somefunction
will be annotated.
Also, if the call is module.directive
, the body of the factory function will be scanned for a
return of an object literal (must be an object literal, not a variable). The object literal is
scanned for a controller
property, and if found that controller property is also DI annotated
(the example program shows this in action).
Directives
If a directive uses an external hamlet template, the directive hamlet and directive javascript
should be in two files with the same name but .js
and .hamlet
extensions. To embed the
directive template so that the javascript is able to find it, the template ID must be loaded. To
do so, the javascript is parsed to find the value of templateUrl
. The .js
file must have a
top-level call to module.directive
. Some statement directly inside the factory function must
be a return
of an object literal. This object literal must have a templateUrl
property with
a single string literal as the value. This string literal is used as the ID when embedding the
template. For example, if my-dialog.js
contains
module.directive("my-dialog", function() { return { restrict: "E", transclude: true, templateUrl: "mydialog-template", }; });
then something like the following will be automatically created and then inserted into the generated file:
module.run(["$templateCache", function($templateCache) { $templateCache.put("mydialog-template", <rendered my-dialog.hamlet>); }]);
Generators
:: String | Angular module name |
-> Location | location within the static subsite for the resulting javascript file |
-> FilePath | directory on disk (relative to build directory/directory containing the .cabal file) which contains the contents of the module. |
-> (ByteString -> IO ByteString) | minimizer such as |
-> Generator |
Embed the javascript and directive templates from a single directory into the static subsite as a single Angular module file. For this to work, the directive templates cannot use any hamlet variable interpolation.
During development, each .js
file is served separately (with some code to define the module
variable) and at the location where the combined file would appear during production, a small
script which just loads all the .js
files from the directory is served. This makes debugging
much easier.
This generator produces one variable definition of type Route EmbeddedStatic
which is named by
passing the location through pathToName
.
:: Location | directory location within the static subsite where the modules should appear |
-> FilePath | directory on disk (relative to build directory/directory containing the .cabal file) containing angular modules. |
-> (ByteString -> IO ByteString) | minimizer such as |
-> Generator |
Embed multiple angular modules into the static subsite.
All subdirectories within the given directory are assumed to be angular modules, and each
subdirectory is embedded by calling embedNgModule
on it. The subdirectory name is used
for the module name. The location for the module will be the location given to this generator
combined with the subdirectory name and then .js
.
Custom Directive Template Processing
embedNgModuleWithoutTemplates Source
:: String | Angular module name |
-> Location | location within the static subsite for the resulting javascript file |
-> FilePath | directory on disk (relative to build directory/directory containing the .cabal file) which contains the contents of the module. |
-> (ByteString -> IO ByteString) | minimizer such as |
-> Generator |
Same as embedNgModule
but the directive templates are not included. Use this if your
directive templates require variable/type safe route interpolation. Your directive templates
should then instead be inserted into a WidgetT site IO ()
using directiveTemplates
and then
embedded into the final page.
embedNgModulesWithoutTemplates Source
:: Location | directory location within the static subsite where the modules should appear |
-> FilePath | directory on disk (relative to build directory/directory containing the .cabal file) containing angular modules. |
-> (ByteString -> IO ByteString) | minimizer such as |
-> Generator |
Embed multiple angular modules without templates into the static subsite.
All subdirectories within the given directory are assumed to be angular modules, and each
subdirectory is embedded by calling embedNgModuleWithoutTemplates
on it.
directiveTemplates :: FilePath -> ExpQ Source
Create a
which contains all the directive templates written in Hamlet
from the passed in directory. This is only needed if you use
WidgetT
site IO ()embedNgModuleWithoutTemplates
because your directive templates use variable/url
interpolation. The template will be inside a <script type="text/ng-template"
id="someid">
, where the ID is found by parsing the javascript code for templateUrl
. This
widget must be inside the tag which has the ng-app
attribute.
directiveTemplatesWithSettings :: HamletSettings -> FilePath -> ExpQ Source
Same as directiveTemplates
but allows you to specify the hamlet settings.
Wrap a widget in a <script type="text/ng-template" id="someid">
block.
Testing
hamletTestTemplate :: FilePath -> ExpQ Source
Convert a hamlet file to javascript for unit testing.
When unit testing the Angular code, the javascript is executed directly (without any processing
by embedNgModule
). But for the directives to work, the Hamlet templates must still be
converted to javascript which inserts the template into the Angular $templateCache
. This
TH splice takes a path to a hamlet file and produces a ByteString
which contains this
javascript. Before unit testing the javascript code, this TH splice must be run on every
directive hamlet template.
If you use karma, the
karma-ng-hamlet2js-preprocessor
does this automatically by using runghc
to run a small Haskell script which calls
hamletTestTemplate
. The example application uses this karma preprocessor.