资源说明:Selector-based templates with Hiccup
Tinsel ====== Selector-based HTML templates using Hiccup. Introduction ------------ Tinsel is pretty much just Hiccup with a few extra pieces of machinery. Like Hiccup, Tinsel does as much work as possible at macro-expansion time, so it tries to be as efficient as possible at runtime. It's also very much inspired by Enlive and Pure, in that there is no special language for specifying where to receive content in the templates; the template defines where to put things based on the properties of the nodes in the HTML tree. I prefer to write HTML in Hiccup, but Tinsel can also read in HTML and parse it into Hiccup forms transparently to you using [hickory](http://github.com/davidsantiago/hickory). The main idea is to take an HTML template, with absolutely no special template-oriented markup or scripts in it, and then specify where in the tree of HTML nodes to make replacements and additions in order to render the template. This logic is specified in two types of functions, selectors and transformers. The actual mechanics are a bit more involved, but basically, a selector is a function called on every node in the HTML tree, returning true if and only if that node should have the corresponding transformer applied. A transformer is another function that takes a node and returns a new node that should be inserted in its place before final rendering. The main new construct is the `deftemplate` macro. It takes the following arguments: 1. A name 2. A sequence of hiccup forms defining the markup 3. An argument list (for your use in the transformers) 4. As many selector/transformer pairs as you like (they will be run one after the other in the order given) As the simplest possible example, consider (ns tinsel-test-drive (:use tinsel.core)) (deftemplate simple-template [[:h1 "Just a Simple Template"]] []) This generates a function called simple-template, which can be called with no argument. When you call it, it returns the string user> (simple-template) "Just a Simple Template
" To make it actually do something interesting, let's make a page that addresses the user by name, assuming that the web server knows the user's name when it services the request. (deftemplate welcome-template [[:h1#user-welcome]] [user-name] (id= :user-welcome) (set-content (str "Welcome " user-name "!"))) Which outputs user> (welcome-template "Don Draper") "Welcome Don Draper!
" Here we made the template take a single argument, `user-name`, which is a string containing the user's name. We also used the `id=` selector to select any node with id "user-welcome". Then we gave the `set-content` transformer some code to generate the string to set the content to. Note that the user-name argument is visible to the transformer. Note also that set-content left the tag and attributes unchanged. Templates can take any number of selector/transformer pairs, generating as many changes to the document tree as you like. As a slightly longer example, look at the template below: (deftemplate medium-template [[:html [:head [:title "Literal String"]] [:body [:div.example] [:ul.times-table]]]] [text] (select (or-ancestor (tag= :head)) (tag= :title)) (set-content "Times Table for 9") (has-class? :example) (set-content text) (has-class? :times-table) (set-content (for [n (range 1 13)] [:li n " * 9 = " (* n 9)]))) Which outputs user> (medium-template "9 x 9 = 81") "Times Table for 9 9 x 9 = 81
- 1 * 9 = 9
- 2 * 9 = 18
- 3 * 9 = 27
- 4 * 9 = 36
- 5 * 9 = 45
- 6 * 9 = 54
- 7 * 9 = 63
- 8 * 9 = 72
- 9 * 9 = 81
- 10 * 9 = 90
- 11 * 9 = 99
- 12 * 9 = 108
The new title
" By keeping the argument unevaluated, this guarantees that they will be evaluated in the context of the function that `deftemplate` builds, which has the argument list provided to `deftemplate`. In the example above, a string was passed in, but the user could also pass in code: user> (retitle-template (str "The " (+ 1 1) "nd title")) "The 2nd title
" So ultimately transformers can also be tricky to write. I am working to make sure that Tinsel has a good number of transformers that will hopefully span just about any use cases I can find, but again, if you need to write your own, you can go ahead and do so. Currently available transformers include * `set-content` - Replaces the node's content with the results of the argument. * `append-content` - Adds the results of the argument after the node's current content. * `prepend-content` - Adds the results of the argument before the node's current content. * `set-attrs` - Adds the map argument to the node's attributes, overwriting any that are already present. ####Transformer Combinators#### Just as there are selector combinators to create more complicated selectors from simpler ones, it is also possible to create transformer combinators, functions that take one or more other transformers and return a new transformer based on the arguments. Currently available transformer combinators are * `accumulate` - Takes any number of transformers as argument and returns a transformer that performs all of the transformations in order on its argument node, using the output of the first transformation as the input to the second, etc. Performance ----------- In my testing, Tinsel renders templates exactly as fast as the equivalent Hiccup code, which is itself just a tad slower than raw string concatenation. The results below are from [viewbenchmarks](http://github.com/davidsantiago/viewbenchmarks) hiccup "Elapsed time: 7.126 msecs" "Elapsed time: 13.655 msecs" "Elapsed time: 5.012 msecs" hiccup (type-hint) "Elapsed time: 8.084 msecs" "Elapsed time: 6.157 msecs" "Elapsed time: 3.573 msecs" str "Elapsed time: 5.61 msecs" "Elapsed time: 4.928 msecs" "Elapsed time: 2.902 msecs" tinsel "Elapsed time: 5.358 msecs" "Elapsed time: 4.577 msecs" "Elapsed time: 3.094 msecs" tinsel (type-hint) "Elapsed time: 5.062 msecs" "Elapsed time: 3.646 msecs" "Elapsed time: 2.989 msecs" As you can see, Tinsel still allows type-hinting just like Hiccup (really, it is passing on the type-hinted forms to Hiccup). However, it is important to remember that templates can only go as fast as the code you ask them to evaluate at run-time. Obtaining it ------------ You can add [tinsel "0.4.0"] to your project.clj, or whatever is equivalent in the build tool you use. Bugs and Missing Features ------------------------- I use this myself, and have run out of things I feel it is missing. But there's always room to add more selectors and transformers, as we come across the need. My particular usage patterns have not made me aware of any known bugs, but they are of course in there, and I'd like to fix them if you find them, so please let me know. License ------- Eclipse Public License
本源码包内暂不包含可直接显示的源代码文件,请下载源码包。