1.Handlebars简单介绍:
Handlebars是JavaScript一个语义模板库,通过对view和data的分离来快速构建Web模板。它采用"Logic-less template"(无逻辑模版)的思路,在加载时被预编译,而不是到了客户端执行到代码时再去编译, 这样可以保证模板加载和运行的速度。Handlebars兼容Mustache,你可以在Handlebars中导入Mustache模板。
2.参考文章:
Handlebars官网:
Handlebars中文介绍( ):
Handlebars中文文档 - 块级helpers(译自官方版):
Handlebars.js 中文文档:
js模版引擎handlebars.js实用教程:
handlebars玩家指南:
com.github.jknack.handlebars.Helper:
3.块级Helpers使用技巧:
Handlebars内置了with,
each
,if,
unless,
Helpers。log这5种基本的
if标签只能判断true或false,不能执行一些复杂的运算逻辑。
这些基本的Helpers并不能满足我们所有的需求,因此需要自定义一些辅助Helpers。
① 基本的
Helpers—each使用示例:orderList.hbs
的
订单列表
{ {#each _DATA_.orderList}} 采购凭证号 公司 供应商编号 项目交货日期 产地 { {/each}} { {purproofno}} { {company}} { {supplierno}} { {projectdate}} { {proplace}}
② 自定义Helpers使用示例:handlebars.coffee
Handlebars.registerHelper 'jsonToStr', (json, options) -> JSON.stringify(json)Handlebars.registerHelper 'add', (a,b, options) -> a + bHandlebars.registerHelper "formatPrice", (price, type, options) -> return if not price? if type is 1 formatedPrice = (price / 100) roundedPrice = parseInt(price / 100) else formatedPrice = (price / 100).toFixed(2) roundedPrice = parseInt(price / 100).toFixed(2) if `formatePrice == roundedPrice` then roundedPrice else formatedPriceHandlebars.registerHelper "formatDate", (date, type, options) -> return unless date switch type when "gmt" then moment(date).format("EEE MMM dd HH:mm:ss Z yyyy") when "day" then moment(date).format("YYYY-MM-DD") when "minute" then moment(date).format("YYYY-MM-DD HH:mm") else moment(date).format("YYYY-MM-DD HH:mm:ss")Handlebars.registerHelper "lt", (a, b, options) -> if a < b options.fn(this) else options.inverse(this)Handlebars.registerHelper "gt", (a, b, options) -> if a > b options.fn(this) else options.inverse(this)Handlebars.registerHelper 'of', (a, b, options) -> values = if _.isArray b then b else b.split(",") if _.contains(values, a.toString()) or _.contains values, a options.fn(this) else options.inverse(this)Handlebars.registerHelper 'length', (a, options) -> length = a.lengthHandlebars.registerHelper "isArray", (a, options) -> if _.isArray a options.fn(this) else options.inverse(this)Handlebars.registerHelper "between", (a, b, c, options) -> if a >= b and a <= c options.fn(this) else options.inverse(this)Handlebars.registerHelper "multiple", (a, b, c, options) -> if c isnt 0 then a * b / c else 0Handlebars.registerHelper "contain", (a, b, options) -> return options.inverse @ if a is undefined or b is undefined array = if _.isArray a then a else a.toString().split(",") if _.contains a, b then options.fn @ else options.inverse @Handlebars.registerHelper "match", (a, b, options) -> return options.inverse @ if a is undefined or b is undefined if new RegExp(a).exec(b) is null then options.inverse @ else options.fn @Handlebars.registerHelper "isOdd", (a, b, options) -> if a % b == 1 options.fn(this) else options.inverse(this)
handlebars.coffee编译成handlebarsApp.js的结果:
Handlebars.registerHelper('jsonToStr', function(json, options) { return JSON.stringify(json); }); Handlebars.registerHelper('add', function(a, b, options) { return a + b; }); Handlebars.registerHelper("formatPrice", function(price, type, options) { var formatedPrice, roundedPrice; if (price == null) { return; } if (type === 1) { formatedPrice = price / 100; roundedPrice = parseInt(price / 100); } else { formatedPrice = (price / 100).toFixed(2); roundedPrice = parseInt(price / 100).toFixed(2); } if (formatedPrice == roundedPrice) { return roundedPrice; } else { return formatedPrice; } }); Handlebars.registerHelper("formatDate", function(date, type, options) { if (!date) { return; } switch (type) { case "gmt": return moment(date).format("EEE MMM dd HH:mm:ss Z yyyy"); case "day": return moment(date).format("YYYY-MM-DD"); case "minute": return moment(date).format("YYYY-MM-DD HH:mm"); default: return moment(date).format("YYYY-MM-DD HH:mm:ss"); } }); Handlebars.registerHelper("lt", function(a, b, options) { if (a < b) { return options.fn(this); } else { return options.inverse(this); } }); Handlebars.registerHelper("gt", function(a, b, options) { if (a > b) { return options.fn(this); } else { return options.inverse(this); } }); Handlebars.registerHelper('of', function(a, b, options) { var values; values = _.isArray(b) ? b : b.split(","); if (_.contains(values, a.toString()) || _.contains(values, a)) { return options.fn(this); } else { return options.inverse(this); } }); Handlebars.registerHelper('length', function(a, options) { var length; return length = a.length; }); Handlebars.registerHelper("isArray", function(a, options) { if (_.isArray(a)) { return options.fn(this); } else { return options.inverse(this); } }); Handlebars.registerHelper("between", function(a, b, c, options) { if (a >= b && a <= c) { return options.fn(this); } else { return options.inverse(this); } }); Handlebars.registerHelper("multiple", function(a, b, c, options) { if (c !== 0) { return a * b / c; } else { return 0; } }); Handlebars.registerHelper("contain", function(a, b, options) { var array; if (a === void 0 || b === void 0) { return options.inverse(this); } array = _.isArray(a) ? a : a.toString().split(","); if (_.contains(a, b)) { return options.fn(this); } else { return options.inverse(this); } }); Handlebars.registerHelper("match", function(a, b, options) { if (a === void 0 || b === void 0) { return options.inverse(this); } if (new RegExp(a).exec(b) === null) { return options.inverse(this); } else { return options.fn(this); } }); Handlebars.registerHelper("isOdd", function(a, b, options) { if (a % b === 1) { return options.fn(this); } else { return options.inverse(this); } });
③ Spring MVC框架中引入handlebars插件:
Maven配置:handlebars和handlebars-springmvc。
com.github.jknack handlebars 4.0.5 com.github.jknack handlebars-springmvc 4.0.5
在Spring MVC配置文件servlet-context.xml中添加handlebars视图解析器配置:
Controller中实现代码:
package com.ouc.handlebars.controller;import java.util.HashMap;import java.util.Map;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.servlet.ModelAndView;@Controllerpublic class HandlebarsController { @RequestMapping(value = "/testHandlebars", method = RequestMethod.GET) public ModelAndView helloWorld() { Mapmap = new HashMap (); map.put("helloWorld", "Hello World!"); return new ModelAndView("helloWorld", map); } }
前端helloWorld.html文件:
Hello World { {helloWorld}}
④ 自定义Helpers后台实现:OucHandlebarHelpers.java
package ouc.handlebars.web.helpers;import com.github.jknack.handlebars.Helper;import com.github.jknack.handlebars.Options;import com.google.common.base.MoreObjects;import com.google.common.base.Objects;import ouc.handlebars.common.utils.MapBuilder;import ouc.handlebars.common.utils.NumberUtils;import ouc.handlebars.common.utils.Splitters;import ouc.handlebars.pampas.engine.handlebars.HandlebarsEngine;import org.joda.time.DateTime;import org.joda.time.Days;import org.joda.time.Hours;import org.joda.time.Minutes;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.io.IOException;import java.text.DecimalFormat;import java.util.Collection;import java.util.Date;import java.util.List;import java.util.Map;@Componentpublic class OucHandlebarHelpers { @Autowired private HandlebarsEngine handlebarEngine; @PostConstruct public void init() { handlebarEngine.registerHelper("isEmpty", new Helper
⑤ HandlebarsEngine.java
package ouc.handlebars.pampas.engine.handlebars; import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.HandlebarsException; import com.github.jknack.handlebars.Helper; import com.github.jknack.handlebars.Template; import com.github.jknack.handlebars.io.TemplateLoader; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Maps; import ouc.handlebars.pampas.common.UserNotLoginException; import ouc.handlebars.pampas.common.UserUtil; import ouc.handlebars.pampas.engine.RenderConstants; import ouc.handlebars.pampas.engine.Setting; import ouc.handlebars.pampas.engine.config.ConfigManager; import ouc.handlebars.pampas.engine.config.model.Component; import ouc.handlebars.pampas.engine.mapping.Invoker; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.ServletContext; import java.io.FileNotFoundException; import java.util.Map; import java.util.concurrent.TimeUnit; /** * Author: Wu Ping */ @org.springframework.stereotype.Component @Slf4j public class HandlebarsEngine { private Handlebars handlebars; private Invoker invoker; private final LoadingCache> caches; private ConfigManager configManager; @Autowired public HandlebarsEngine(Invoker invoker, Setting setting, ConfigManager configManager, ServletContext servletContext) { this.invoker = invoker; TemplateLoader templateLoader = new GreatTemplateLoader(servletContext, "/views", ".hbs"); this.handlebars = new Handlebars(templateLoader); this.caches = initCache(!setting.isDevMode()); this.configManager = configManager; } private LoadingCache > initCache(boolean buildCache) { if (buildCache) { return CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader >() { @Override public Optional load(String path) throws Exception { Template t = null; try { t = handlebars.compile(path); } catch (Exception e) { log.error("failed to compile template(path={}), cause:{}",path, e.getMessage()); } return Optional.fromNullable(t); } }); } return null; } public void registerHelper(String name, Helper helper) { handlebars.registerHelper(name, helper); } public String execInline(String templateStr, Map params) { return execInline(templateStr, params, null); } public String execInline(String templateStr, Map params, String cacheKey) { try { if (params == null) { params = Maps.newHashMap(); } Template template; if (caches == null || cacheKey == null) { template = handlebars.compileInline(templateStr); } else { template = caches.getUnchecked("inline/" + cacheKey).orNull(); } if(template == null){ log.error("failed to exec handlebars' template:{}", templateStr); return ""; } return template.apply(params); } catch (Exception e) { log.error("exec handlebars' template failed: {},cause:{}", templateStr, Throwables.getStackTraceAsString(e)); return ""; } } @SuppressWarnings("unchecked") public String execPath(String path, Map params, boolean isComponent) throws FileNotFoundException { try { if (params == null) { params = Maps.newHashMap(); } Template template; if (isComponent) { if (caches == null) { template = handlebars.compile("component:" + path); } else { template = caches.getUnchecked("component:" + path).orNull(); } params.put(RenderConstants.COMPONENT_PATH, path); } else { if (caches == null) { template = handlebars.compile(path); } else { template = caches.getUnchecked(path).orNull(); } } params.put(RenderConstants.USER, UserUtil.getCurrentUser()); //user params.put(RenderConstants.HREF, configManager.getFrontConfig().getCurrentHrefs(Setting.getCurrentHost())); if(template == null){ log.error("failed to exec handlebars' template:path={}", path); return ""; } return template.apply(params); } catch (Exception e) { Throwables.propagateIfInstanceOf(e, FileNotFoundException.class); if (e instanceof HandlebarsException) { Throwables.propagateIfInstanceOf(e.getCause(), UserNotLoginException.class); } log.error("failed to execute handlebars' template(path={}),cause:{} ", path, Throwables.getStackTraceAsString(e)); } return ""; } public String execComponent(final Component component, final Map context) { if (!Strings.isNullOrEmpty(component.getService())) { Object object = null; try { object = invoker.invoke(component.getService(), context); } catch (UserNotLoginException e) { log.error("user doesn't login."); if (context.get(RenderConstants.DESIGN_MODE) == null) { throw e; // 非 DESIGN_MODE 时未登录需要抛出 } } catch (Exception e) { log.error("error when invoke component, component: {}", component, e); context.put(RenderConstants.ERROR, e.getMessage()); } context.put(RenderConstants.DATA, object); } try { return execPath(component.getPath(), context, true); } catch (Exception e) { log.error("failed to execute handlebars' template(path={}),cause:{} ", component.getPath(), Throwables.getStackTraceAsString(e)); } return ""; } }
整个块级Helpers的介绍基本就到这里了,前面介绍handlebars的文章都不错。
李白准备在黄鹤楼上题诗的时候看了崔颢写的诗,他认为崔颢的诗可以称为是千古绝唱,觉着自己写不出可以超越崔颢的作品,所以慨叹:“眼前有景道不得,崔颢题诗在上头”。