underscore.js 模板扩展

underscore模板插件定制

加入特性:

1、嵌套模板

2、支持渲染过滤器模式{{A | filterA:arg1..}}

3、缓存(考虑加入localstorage?)

4、调试

todo

性能优化

异步渲染

代码如下

<!-- lang: js -->
(function (_) {
    _.templateSettings = {
        evaluate   : /{%([\s\S]+?)%}/g,
        interpolate: /{{([\s\S]+?)}}/g,
        escape     : /{%-([\s\S]+?)%}/g,
        include    : /{%include([\s\S]+?)%}/g,
        isCache    : true,
        isDebug    : true,
    };
    // When customizing `templateSettings`, if you don't want to define an
    // interpolation, evaluation or escaping regex, we need one that is
    // guaranteed not to match.
    var noMatch = /(.)^/;

    // Certain characters need to be escaped so that they can be put into a
    // string literal.
    var escapes = {
        "'"     : "'",
        '\\'    : '\\',
        '\r'    : 'r',
        '\n'    : 'n',
        '\t'    : 't',
        '\u2028': 'u2028',
        '\u2029': 'u2029'
    };
    var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
    /**
     * 参考template
     * add:
     * filter,可以存在data、或者全局filters里面
     */
    //gloabal filter
    var filters = _.filters = {},
    //模板缓存类
    tplCache = {};

    _.addFilters = function (obj, fn) {
        if ( typeof  obj == 'object' ) {
            _.extend(filters, obj);
        }
        else if ( typeof obj == 'string' ) {
            _.extend(filters, {obj: fn});
        }
    }

    _.template2 = function (text, data, settings) {
        settings = _.defaults({}, settings, _.templateSettings);
        //模板外部引入include
        (function replaceInclude() {
            text = text.replace(settings.include, function (match, result) {
                var url = $.trim(result)
                var ret = syncGetTpl(url)
                return ret
            })
            //模板中还有模板
            if ( settings.include.test(text) ) {
                replaceInclude();
            }
        })()

        // Combine delimiters into one regular expression via alternation.
        var matcher = new RegExp([
            (settings.escape || noMatch).source,
            (settings.interpolate || noMatch).source,
            (settings.evaluate || noMatch).source
        ].join('|') + '|$', 'g');

        // Compile the template source, escaping string literals appropriately.
        var index = 0;
        var source = "__p+='";
        text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
            source += text.slice(index, offset)
                .replace(escaper, function (match) {
                    return '\\' + escapes[match];
                });

            if ( escape ) {
                source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
            }
            //解析属性表达式、、支持过滤器
            //example:value|fn2:a1,a2|fn3:b1,b2,b3
            if ( interpolate ) {
                source += "'+\n((__t=(" + getAssembleFnStr() + "))==null?'':__t)+\n'";
            }
            function getAssembleFnStr() {
                var filterChain = interpolate.split('|'), curFilterIdx = -1;
                var filterStack = [];

                return _(filterChain).reduce(function (val, filter) {
                    //filter有两种;一种function、另一种functionName
                    if ( filter.length ) {
                        curFilterIdx++;
                        var fname = filter.split(':'),
                            name = fname[0],
                            args = fname[1] || '',
                            callFnstr = new Function('x', 'return x');

                        //name is function Str
                        //                        var nameFn = new Function('return name')()
                        var exp = '';

                        if ( name.indexOf('function') > -1 ) {
                            exp = 'var filterFunc = name;\n' +
                            'return name.apply(_,_.flatten(['+val+', args.split(",")]))';
                        }
                        //debug模式
                        else if ( name == 'debug' ) {
                            var beforeFilter = filterChain[curFilterIdx],
                                beforeFilterName = beforeFilter.split(':')[0],
                                beforeFilterFn = beforeFilterName.indexOf('function') > -1 ? beforeFilterName : filters[beforeFilterName]

                            exp =
                            '\n(function(){\n' +
                                'var fv = '+val+';\n'+
                            (settings.isDebug ?
                                (
                                "console.debug(\"Filter:" + beforeFilter + "\");\n" +
                                "console.debug(\"%cOutput:\" +fv" + ",'color:red');\n" +
                                (curFilterIdx > 0 ? "console.debug(\"FilterFn:" + beforeFilterName + ":\"," + beforeFilterFn + ");\n" : "")
                                )
                                : ";" ) +
                            '\nreturn fv;' +
                            '\n})()\n';
                        }
                        else {
                            if ( filters[name] ) {
                                exp = '(' + filters[name] + ')' + '(' + val + (args.length ? (',' + args) : '') + ')'
                            }
                            else {
                                console.error('[' + name + ']filter:doesnt exist!')
                                exp = val;
                            }
                        }
                        return exp;
                    }
                    else {
                        return val;
                    }
                })
            }

            if ( evaluate ) {
                source += "';\n" + evaluate + "\n__p+='";
            }
            index = offset + match.length;
            return match;
        });
        source += "';\n";

        // If a variable is not specified, place data values in local scope.
        if ( !settings.variable ) source = 'with(obj||{}){\n'
        + source +
        '}\n';

        source = "var __t,__p='',__j=Array.prototype.join," +
        "print=function(){__p+=__j.call(arguments,'');};\n" +
        source + "return __p;\n";

        var render;
        try {
            render = new Function(settings.variable || 'obj', '_', source);
        } catch (e) {
            e.source = source;
            throw e;
        }

        if ( data && _.size(data) ) return render(data, _);
        var template = function (data) {
            return render.call(this, data, _);
        };

        // Provide the compiled function source as a convenience for precompilation.
        template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';

        return template;
    };

    _.tpl = function (htmlSelectorOrTplUrl, renderData, renderDomSeletor, renderedFn) {

        var htmlOriginal = $(htmlSelectorOrTplUrl).html(), t1 = _.now(),
            compiledFn = function (html) {
                var tpl = _.template2(html);
                var t2 = _.now()
                $(renderDomSeletor).html(tpl(renderData));
                var t3 = _.now()
                debug('耗时分析', tpl.source, '\n[ms]compile time', t2 - t1, 'render time:', t3 - t2, 'total', t3 - t1)
                renderedFn ? renderedFn.apply(this) : null;
            };

        if ( $.trim(htmlOriginal) == '' ) {
            //ajax请求获取模板
            $.get(htmlSelectorOrTplUrl, function (html) {
                compiledFn(html)
            })
        }
        else {
            compiledFn(htmlOriginal)
        }
    }

    function debug(group) {
        if ( _.templateSettings.isDebug ) {
            console.group(group);
            _([].slice.call(arguments, 1)).each(function (text) {
                console.log(text);
            })
            console.groupEnd();

        }
    }

    function syncGetTpl(htmlSelectorOrTplUrl) {
        //如果一个url作为selector进来会抛异常,
        //hack:
        var html = '';
        //cache
        if ( _.templateSettings.isCache && tplCache[htmlSelectorOrTplUrl] ) {
            return tplCache[htmlSelectorOrTplUrl];
        }
        try {
            if ( $(htmlSelectorOrTplUrl).length ) {
                html = $(htmlSelectorOrTplUrl).html();
            }
            else {
                aj();
            }
        } catch (e) {
            aj();
        }

        function aj() {
            $.ajax({
                url    : htmlSelectorOrTplUrl + '?' + _.now(),
                type   : 'get',
                async  : false,
                success: function (resp) {
                    html = resp
                }
            })
        }

        if ( _.templateSettings.isCache ) {
            tplCache[htmlSelectorOrTplUrl] = html;
        }

        return html;
    }
})(_)

你可能感兴趣的:(underscore.js 模板扩展)