├── .gitignore ├── CONTRIBUTORS.md ├── MIT-LICENSE.txt ├── README.md ├── build.js ├── build.sh ├── example-build ├── build.txt ├── index.html ├── scripts │ ├── libs │ │ ├── handlebars.runtime.js │ │ └── require.js │ └── main.js └── templates │ └── message.html ├── example ├── index.html ├── scripts │ ├── libs │ │ ├── handlebars.js │ │ ├── handlebars.runtime.js │ │ ├── hbars.js │ │ ├── require.js │ │ └── text.js │ └── main.js └── templates │ └── message.html ├── hbars.js ├── libs └── r.js ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | .DS_Store 4 | .DS_Store? 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | Icon? 9 | ehthumbs.db 10 | Thumbs.db 11 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # requirejs-handlebars contributors 2 | 3 | You can check out the [contribution graphs on github](https://github.com/jfparadis/requirejs-handlebars/contributors). 4 | 5 | ``` 6 | $ git shortlog -s -n | cut -c8- 7 | Jean-Francois Paradis 8 | Chris Contolini 9 | Miller Medeiros 10 | ``` -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 J.F. Paradis and other contributors 2 | http://github.com/skaimauve/reword 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | requirejs-handlebars 2 | ==================== 3 | 4 | This is an AMD loader for [Handlebars semantic templates](http://handlebarsjs.com/) which can be used as a drop-in replacement to [SlexAxton/require-handlebars-plugin](http://github.com/SlexAxton/require-handlebars-plugin/blob/master/hbs.js) 5 | 6 | 7 | ## Overview 8 | 9 | - Uses an external HandlebarsJS engine (distributed by the Handlebars team). 10 | - Uses the official ``text`` loader plugin maintained by the RequireJS team. 11 | - You don't have to specify the template file extension (``.html is assumed``, but this is configurable). 12 | 13 | Notes: 14 | 15 | - The ``text`` and ``hbar`` plugins can be removed at build-time using ``r.js`` (with the ``stubModules`` setting). 16 | - The extension ``.html`` is assumed, and this makes loading templates similar to loading JavaScript files with RequireJS (all extensions are assumed). 17 | 18 | ## Changelog 19 | 20 | 0.0.1 Initial version 21 | 22 | 0.0.2 Various updates: 23 | - Add template path option to hbar.js (thanks drewrichards) 24 | - Updated require.js to 2.1.8 , and r.js to 2.1.8 25 | - Updated handlebar.js to 1.0.0 26 | 27 | ## Installation 28 | 29 | Download HandlebarsJS and RequireJS-text: 30 | 31 | - [HandlebarsJS](http://handlebarsjs.com/) 32 | - [RequireJS-text](http://requirejs.org/docs/download.html#text) 33 | 34 | Typically, you would place them in a ``scripts/libs`` folder then create a ``scripts/main.js`` file to alias them and to shim Handlebars: 35 | 36 | ``` 37 | require.config({ 38 | paths: { 39 | Handlebars: 'libs/handlebars', 40 | text: 'libs/text' 41 | hbars: 'libs/hbars' 42 | }, 43 | shim: { 44 | Handlebars: { 45 | exports: 'Handlebars' 46 | } 47 | } 48 | }); 49 | ``` 50 | 51 | ## Usage 52 | 53 | Specify the plugin using ``hbars!`` followed by the template file: 54 | 55 | ``` 56 | require(['backbone', 'hbars!template'], function (Backbone, template) { 57 | return Backbone.View.extend({ 58 | initialize: function(){ 59 | this.render(); 60 | }, 61 | render: function(){ 62 | this.$el.html(template({message: 'hello'})); 63 | }); 64 | }); 65 | ``` 66 | 67 | ## Customization 68 | 69 | You can specify the template file extension in your main.js: 70 | 71 | ``` 72 | require.config({ 73 | 74 | // some paths and shims 75 | 76 | hbars: { 77 | extension: '.hbs', // default = '.html' 78 | compileOptions: {} // options object which is passed to Handlebars compiler 79 | } 80 | }); 81 | ``` 82 | 83 | ## Optimization 84 | 85 | This plugin is compatible with [r.js](http://requirejs.org/docs/optimization.html). 86 | 87 | Optimization brings three benefits to a project: 88 | 89 | - The templates are bundled within your code and not dynamically loaded which reduces the number of HTTP requests. 90 | 91 | - The templates are pre-compiled before being bundled which reduces the work the client has to do. 92 | 93 | - Since templates are pre-compiled during build you can use the handlebars.runtime which is ~1KB after minification+gzip. 94 | 95 | If you don't use any extra tool to manage your libraries, need to manually replace the handlebars.runtime during compilation by adding this routine to your main.js. See the example for how it works: 96 | 97 | ``` 98 | onBuildWrite : function(moduleName, path, content){ 99 | 100 | // replace handlebars with the runtime version 101 | if (moduleName === 'Handlebars') { 102 | path = path.replace('handlebars.js','handlebars.runtime.js'); 103 | content = fs.readFileSync(path).toString(); 104 | content = content.replace(/(define\()(function)/, '$1"handlebars", $2'); 105 | } 106 | return content; 107 | } 108 | ``` 109 | 110 | The most important build options are: 111 | 112 | ```stubModules: ['text', 'hbars']``` 113 | 114 | The list of modules to stub out in the optimized file, i.e. the code is replaced with ``define('module',{});`` by ``r.js`` 115 | 116 | ```removeCombined: true``` 117 | 118 | Removes from the output folder the files combined into a build. 119 | 120 | ## Example 121 | 122 | Sample build options are in the root ``build.js``. Compile the example using: 123 | 124 | ``` 125 | $ ./build.sh 126 | ``` 127 | 128 | Copy the ``example`` and ``example-build`` folders to your web server (``text`` is not compatible with the ``file://`` protocol and opening ``index.hml`` directly from your browser will not work). 129 | 130 | ### Using a test server 131 | 132 | Alternatively, you can use Connect and NodeJS to spin a web server: 133 | 134 | - Install ``connect`` using ``npm`` and launch the server with NodeJS: 135 | 136 | ``` 137 | $ npm install -g connect 138 | $ npm link connect 139 | $ node server.js 140 | ``` 141 | 142 | Go to [http://localhost:9000/example](http://localhost:9000/example). Your browser should load: 143 | 144 | - index.html 145 | - require.js 146 | - main.js 147 | - hbars.js 148 | - handlebars.js 149 | - text.js 150 | - message.html 151 | 152 | Go to [http://localhost:9000/example-build](http://localhost:9000/example-build). Your browser should load: 153 | 154 | - index.html 155 | - require.js 156 | - main.js 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | // See https://github.com/jrburke/r.js/blob/master/build/example.build.js 2 | ({ 3 | // Assume your scripts are in a subdirectory under this path. 4 | appDir: 'example', 5 | 6 | // By default, all modules are located relative to this path. 7 | baseUrl: 'scripts', 8 | 9 | // Location of the runtime config be read for the build. 10 | mainConfigFile: 'example/scripts/main.js', 11 | 12 | //The directory path to save the output. 13 | dir: 'example-build', 14 | 15 | // If you do not want uglifyjs optimization. 16 | optimize: 'none', 17 | 18 | // Inlines any text! dependencies, to avoid separate requests. 19 | inlineText: true, 20 | 21 | // Modules to stub out in the optimized file. 22 | stubModules: ['text', 'hbars'], 23 | 24 | // Files combined into a build layer will be removed from the output folder. 25 | removeCombined: true, 26 | 27 | // This option will turn off the auto-preservation. 28 | preserveLicenseComments: false, 29 | 30 | //List the modules that will be optimized. 31 | modules: [ 32 | { 33 | name: "main" // main config file 34 | } 35 | ] 36 | }) 37 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf example-build 4 | node libs/r.js -o build.js -------------------------------------------------------------------------------- /example-build/build.txt: -------------------------------------------------------------------------------- 1 | 2 | scripts/main.js 3 | ---------------- 4 | scripts/libs/text.js 5 | scripts/libs/handlebars.js 6 | scripts/libs/hbars.js 7 | hbars!templates/message 8 | scripts/main.js 9 | -------------------------------------------------------------------------------- /example-build/index.html: -------------------------------------------------------------------------------- 1 | 2 | RequireJS Plugin for Handlebars semantic templates 3 | 4 | 5 | -------------------------------------------------------------------------------- /example-build/scripts/libs/handlebars.runtime.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2011 by Yehuda Katz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | */ 24 | 25 | // lib/handlebars/browser-prefix.js 26 | var Handlebars = {}; 27 | 28 | (function(Handlebars, undefined) { 29 | ; 30 | // lib/handlebars/base.js 31 | 32 | Handlebars.VERSION = "1.0.0"; 33 | Handlebars.COMPILER_REVISION = 4; 34 | 35 | Handlebars.REVISION_CHANGES = { 36 | 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 37 | 2: '== 1.0.0-rc.3', 38 | 3: '== 1.0.0-rc.4', 39 | 4: '>= 1.0.0' 40 | }; 41 | 42 | Handlebars.helpers = {}; 43 | Handlebars.partials = {}; 44 | 45 | var toString = Object.prototype.toString, 46 | functionType = '[object Function]', 47 | objectType = '[object Object]'; 48 | 49 | Handlebars.registerHelper = function(name, fn, inverse) { 50 | if (toString.call(name) === objectType) { 51 | if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } 52 | Handlebars.Utils.extend(this.helpers, name); 53 | } else { 54 | if (inverse) { fn.not = inverse; } 55 | this.helpers[name] = fn; 56 | } 57 | }; 58 | 59 | Handlebars.registerPartial = function(name, str) { 60 | if (toString.call(name) === objectType) { 61 | Handlebars.Utils.extend(this.partials, name); 62 | } else { 63 | this.partials[name] = str; 64 | } 65 | }; 66 | 67 | Handlebars.registerHelper('helperMissing', function(arg) { 68 | if(arguments.length === 2) { 69 | return undefined; 70 | } else { 71 | throw new Error("Missing helper: '" + arg + "'"); 72 | } 73 | }); 74 | 75 | Handlebars.registerHelper('blockHelperMissing', function(context, options) { 76 | var inverse = options.inverse || function() {}, fn = options.fn; 77 | 78 | var type = toString.call(context); 79 | 80 | if(type === functionType) { context = context.call(this); } 81 | 82 | if(context === true) { 83 | return fn(this); 84 | } else if(context === false || context == null) { 85 | return inverse(this); 86 | } else if(type === "[object Array]") { 87 | if(context.length > 0) { 88 | return Handlebars.helpers.each(context, options); 89 | } else { 90 | return inverse(this); 91 | } 92 | } else { 93 | return fn(context); 94 | } 95 | }); 96 | 97 | Handlebars.K = function() {}; 98 | 99 | Handlebars.createFrame = Object.create || function(object) { 100 | Handlebars.K.prototype = object; 101 | var obj = new Handlebars.K(); 102 | Handlebars.K.prototype = null; 103 | return obj; 104 | }; 105 | 106 | Handlebars.logger = { 107 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, 108 | 109 | methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, 110 | 111 | // can be overridden in the host environment 112 | log: function(level, obj) { 113 | if (Handlebars.logger.level <= level) { 114 | var method = Handlebars.logger.methodMap[level]; 115 | if (typeof console !== 'undefined' && console[method]) { 116 | console[method].call(console, obj); 117 | } 118 | } 119 | } 120 | }; 121 | 122 | Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; 123 | 124 | Handlebars.registerHelper('each', function(context, options) { 125 | var fn = options.fn, inverse = options.inverse; 126 | var i = 0, ret = "", data; 127 | 128 | var type = toString.call(context); 129 | if(type === functionType) { context = context.call(this); } 130 | 131 | if (options.data) { 132 | data = Handlebars.createFrame(options.data); 133 | } 134 | 135 | if(context && typeof context === 'object') { 136 | if(context instanceof Array){ 137 | for(var j = context.length; i": ">", 212 | '"': """, 213 | "'": "'", 214 | "`": "`" 215 | }; 216 | 217 | var badChars = /[&<>"'`]/g; 218 | var possible = /[&<>"'`]/; 219 | 220 | var escapeChar = function(chr) { 221 | return escape[chr] || "&"; 222 | }; 223 | 224 | Handlebars.Utils = { 225 | extend: function(obj, value) { 226 | for(var key in value) { 227 | if(value.hasOwnProperty(key)) { 228 | obj[key] = value[key]; 229 | } 230 | } 231 | }, 232 | 233 | escapeExpression: function(string) { 234 | // don't escape SafeStrings, since they're already safe 235 | if (string instanceof Handlebars.SafeString) { 236 | return string.toString(); 237 | } else if (string == null || string === false) { 238 | return ""; 239 | } 240 | 241 | // Force a string conversion as this will be done by the append regardless and 242 | // the regex test will do this transparently behind the scenes, causing issues if 243 | // an object's to string has escaped characters in it. 244 | string = string.toString(); 245 | 246 | if(!possible.test(string)) { return string; } 247 | return string.replace(badChars, escapeChar); 248 | }, 249 | 250 | isEmpty: function(value) { 251 | if (!value && value !== 0) { 252 | return true; 253 | } else if(toString.call(value) === "[object Array]" && value.length === 0) { 254 | return true; 255 | } else { 256 | return false; 257 | } 258 | } 259 | }; 260 | ; 261 | // lib/handlebars/runtime.js 262 | 263 | Handlebars.VM = { 264 | template: function(templateSpec) { 265 | // Just add water 266 | var container = { 267 | escapeExpression: Handlebars.Utils.escapeExpression, 268 | invokePartial: Handlebars.VM.invokePartial, 269 | programs: [], 270 | program: function(i, fn, data) { 271 | var programWrapper = this.programs[i]; 272 | if(data) { 273 | programWrapper = Handlebars.VM.program(i, fn, data); 274 | } else if (!programWrapper) { 275 | programWrapper = this.programs[i] = Handlebars.VM.program(i, fn); 276 | } 277 | return programWrapper; 278 | }, 279 | merge: function(param, common) { 280 | var ret = param || common; 281 | 282 | if (param && common) { 283 | ret = {}; 284 | Handlebars.Utils.extend(ret, common); 285 | Handlebars.Utils.extend(ret, param); 286 | } 287 | return ret; 288 | }, 289 | programWithDepth: Handlebars.VM.programWithDepth, 290 | noop: Handlebars.VM.noop, 291 | compilerInfo: null 292 | }; 293 | 294 | return function(context, options) { 295 | options = options || {}; 296 | var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); 297 | 298 | var compilerInfo = container.compilerInfo || [], 299 | compilerRevision = compilerInfo[0] || 1, 300 | currentRevision = Handlebars.COMPILER_REVISION; 301 | 302 | if (compilerRevision !== currentRevision) { 303 | if (compilerRevision < currentRevision) { 304 | var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], 305 | compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; 306 | throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ 307 | "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; 308 | } else { 309 | // Use the embedded version info since the runtime doesn't know about this revision yet 310 | throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ 311 | "Please update your runtime to a newer version ("+compilerInfo[1]+")."; 312 | } 313 | } 314 | 315 | return result; 316 | }; 317 | }, 318 | 319 | programWithDepth: function(i, fn, data /*, $depth */) { 320 | var args = Array.prototype.slice.call(arguments, 3); 321 | 322 | var program = function(context, options) { 323 | options = options || {}; 324 | 325 | return fn.apply(this, [context, options.data || data].concat(args)); 326 | }; 327 | program.program = i; 328 | program.depth = args.length; 329 | return program; 330 | }, 331 | program: function(i, fn, data) { 332 | var program = function(context, options) { 333 | options = options || {}; 334 | 335 | return fn(context, options.data || data); 336 | }; 337 | program.program = i; 338 | program.depth = 0; 339 | return program; 340 | }, 341 | noop: function() { return ""; }, 342 | invokePartial: function(partial, name, context, helpers, partials, data) { 343 | var options = { helpers: helpers, partials: partials, data: data }; 344 | 345 | if(partial === undefined) { 346 | throw new Handlebars.Exception("The partial " + name + " could not be found"); 347 | } else if(partial instanceof Function) { 348 | return partial(context, options); 349 | } else if (!Handlebars.compile) { 350 | throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); 351 | } else { 352 | partials[name] = Handlebars.compile(partial, {data: data !== undefined}); 353 | return partials[name](context, options); 354 | } 355 | } 356 | }; 357 | 358 | Handlebars.template = Handlebars.VM.template; 359 | ; 360 | // lib/handlebars/browser-suffix.js 361 | })(Handlebars); 362 | ; 363 | -------------------------------------------------------------------------------- /example-build/scripts/main.js: -------------------------------------------------------------------------------- 1 | 2 | define('text',{}); 3 | /* 4 | 5 | Copyright (C) 2011 by Yehuda Katz 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | */ 26 | 27 | // lib/handlebars/browser-prefix.js 28 | var Handlebars = {}; 29 | 30 | (function(Handlebars, undefined) { 31 | ; 32 | // lib/handlebars/base.js 33 | 34 | Handlebars.VERSION = "1.0.0"; 35 | Handlebars.COMPILER_REVISION = 4; 36 | 37 | Handlebars.REVISION_CHANGES = { 38 | 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 39 | 2: '== 1.0.0-rc.3', 40 | 3: '== 1.0.0-rc.4', 41 | 4: '>= 1.0.0' 42 | }; 43 | 44 | Handlebars.helpers = {}; 45 | Handlebars.partials = {}; 46 | 47 | var toString = Object.prototype.toString, 48 | functionType = '[object Function]', 49 | objectType = '[object Object]'; 50 | 51 | Handlebars.registerHelper = function(name, fn, inverse) { 52 | if (toString.call(name) === objectType) { 53 | if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } 54 | Handlebars.Utils.extend(this.helpers, name); 55 | } else { 56 | if (inverse) { fn.not = inverse; } 57 | this.helpers[name] = fn; 58 | } 59 | }; 60 | 61 | Handlebars.registerPartial = function(name, str) { 62 | if (toString.call(name) === objectType) { 63 | Handlebars.Utils.extend(this.partials, name); 64 | } else { 65 | this.partials[name] = str; 66 | } 67 | }; 68 | 69 | Handlebars.registerHelper('helperMissing', function(arg) { 70 | if(arguments.length === 2) { 71 | return undefined; 72 | } else { 73 | throw new Error("Missing helper: '" + arg + "'"); 74 | } 75 | }); 76 | 77 | Handlebars.registerHelper('blockHelperMissing', function(context, options) { 78 | var inverse = options.inverse || function() {}, fn = options.fn; 79 | 80 | var type = toString.call(context); 81 | 82 | if(type === functionType) { context = context.call(this); } 83 | 84 | if(context === true) { 85 | return fn(this); 86 | } else if(context === false || context == null) { 87 | return inverse(this); 88 | } else if(type === "[object Array]") { 89 | if(context.length > 0) { 90 | return Handlebars.helpers.each(context, options); 91 | } else { 92 | return inverse(this); 93 | } 94 | } else { 95 | return fn(context); 96 | } 97 | }); 98 | 99 | Handlebars.K = function() {}; 100 | 101 | Handlebars.createFrame = Object.create || function(object) { 102 | Handlebars.K.prototype = object; 103 | var obj = new Handlebars.K(); 104 | Handlebars.K.prototype = null; 105 | return obj; 106 | }; 107 | 108 | Handlebars.logger = { 109 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, 110 | 111 | methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, 112 | 113 | // can be overridden in the host environment 114 | log: function(level, obj) { 115 | if (Handlebars.logger.level <= level) { 116 | var method = Handlebars.logger.methodMap[level]; 117 | if (typeof console !== 'undefined' && console[method]) { 118 | console[method].call(console, obj); 119 | } 120 | } 121 | } 122 | }; 123 | 124 | Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; 125 | 126 | Handlebars.registerHelper('each', function(context, options) { 127 | var fn = options.fn, inverse = options.inverse; 128 | var i = 0, ret = "", data; 129 | 130 | var type = toString.call(context); 131 | if(type === functionType) { context = context.call(this); } 132 | 133 | if (options.data) { 134 | data = Handlebars.createFrame(options.data); 135 | } 136 | 137 | if(context && typeof context === 'object') { 138 | if(context instanceof Array){ 139 | for(var j = context.length; i": ">", 214 | '"': """, 215 | "'": "'", 216 | "`": "`" 217 | }; 218 | 219 | var badChars = /[&<>"'`]/g; 220 | var possible = /[&<>"'`]/; 221 | 222 | var escapeChar = function(chr) { 223 | return escape[chr] || "&"; 224 | }; 225 | 226 | Handlebars.Utils = { 227 | extend: function(obj, value) { 228 | for(var key in value) { 229 | if(value.hasOwnProperty(key)) { 230 | obj[key] = value[key]; 231 | } 232 | } 233 | }, 234 | 235 | escapeExpression: function(string) { 236 | // don't escape SafeStrings, since they're already safe 237 | if (string instanceof Handlebars.SafeString) { 238 | return string.toString(); 239 | } else if (string == null || string === false) { 240 | return ""; 241 | } 242 | 243 | // Force a string conversion as this will be done by the append regardless and 244 | // the regex test will do this transparently behind the scenes, causing issues if 245 | // an object's to string has escaped characters in it. 246 | string = string.toString(); 247 | 248 | if(!possible.test(string)) { return string; } 249 | return string.replace(badChars, escapeChar); 250 | }, 251 | 252 | isEmpty: function(value) { 253 | if (!value && value !== 0) { 254 | return true; 255 | } else if(toString.call(value) === "[object Array]" && value.length === 0) { 256 | return true; 257 | } else { 258 | return false; 259 | } 260 | } 261 | }; 262 | ; 263 | // lib/handlebars/runtime.js 264 | 265 | Handlebars.VM = { 266 | template: function(templateSpec) { 267 | // Just add water 268 | var container = { 269 | escapeExpression: Handlebars.Utils.escapeExpression, 270 | invokePartial: Handlebars.VM.invokePartial, 271 | programs: [], 272 | program: function(i, fn, data) { 273 | var programWrapper = this.programs[i]; 274 | if(data) { 275 | programWrapper = Handlebars.VM.program(i, fn, data); 276 | } else if (!programWrapper) { 277 | programWrapper = this.programs[i] = Handlebars.VM.program(i, fn); 278 | } 279 | return programWrapper; 280 | }, 281 | merge: function(param, common) { 282 | var ret = param || common; 283 | 284 | if (param && common) { 285 | ret = {}; 286 | Handlebars.Utils.extend(ret, common); 287 | Handlebars.Utils.extend(ret, param); 288 | } 289 | return ret; 290 | }, 291 | programWithDepth: Handlebars.VM.programWithDepth, 292 | noop: Handlebars.VM.noop, 293 | compilerInfo: null 294 | }; 295 | 296 | return function(context, options) { 297 | options = options || {}; 298 | var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); 299 | 300 | var compilerInfo = container.compilerInfo || [], 301 | compilerRevision = compilerInfo[0] || 1, 302 | currentRevision = Handlebars.COMPILER_REVISION; 303 | 304 | if (compilerRevision !== currentRevision) { 305 | if (compilerRevision < currentRevision) { 306 | var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], 307 | compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; 308 | throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ 309 | "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; 310 | } else { 311 | // Use the embedded version info since the runtime doesn't know about this revision yet 312 | throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ 313 | "Please update your runtime to a newer version ("+compilerInfo[1]+")."; 314 | } 315 | } 316 | 317 | return result; 318 | }; 319 | }, 320 | 321 | programWithDepth: function(i, fn, data /*, $depth */) { 322 | var args = Array.prototype.slice.call(arguments, 3); 323 | 324 | var program = function(context, options) { 325 | options = options || {}; 326 | 327 | return fn.apply(this, [context, options.data || data].concat(args)); 328 | }; 329 | program.program = i; 330 | program.depth = args.length; 331 | return program; 332 | }, 333 | program: function(i, fn, data) { 334 | var program = function(context, options) { 335 | options = options || {}; 336 | 337 | return fn(context, options.data || data); 338 | }; 339 | program.program = i; 340 | program.depth = 0; 341 | return program; 342 | }, 343 | noop: function() { return ""; }, 344 | invokePartial: function(partial, name, context, helpers, partials, data) { 345 | var options = { helpers: helpers, partials: partials, data: data }; 346 | 347 | if(partial === undefined) { 348 | throw new Handlebars.Exception("The partial " + name + " could not be found"); 349 | } else if(partial instanceof Function) { 350 | return partial(context, options); 351 | } else if (!Handlebars.compile) { 352 | throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); 353 | } else { 354 | partials[name] = Handlebars.compile(partial, {data: data !== undefined}); 355 | return partials[name](context, options); 356 | } 357 | } 358 | }; 359 | 360 | Handlebars.template = Handlebars.VM.template; 361 | ; 362 | // lib/handlebars/browser-suffix.js 363 | })(Handlebars); 364 | ; 365 | 366 | define("Handlebars", (function (global) { 367 | return function () { 368 | var ret, fn; 369 | return ret || global.Handlebars; 370 | }; 371 | }(this))); 372 | 373 | define('hbars',{load: function(id){throw new Error("Dynamic load not allowed: " + id);}}); 374 | define('hbars!templates/message', ['Handlebars'], function (Handlebars) { return Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { 375 | this.compilerInfo = [4,'>= 1.0.0']; 376 | helpers = this.merge(helpers, Handlebars.helpers); data = data || {}; 377 | var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression; 378 | 379 | 380 | buffer += "

\n Message\n

\n

\n "; 381 | if (stack1 = helpers.message) { stack1 = stack1.call(depth0, {hash:{},data:data}); } 382 | else { stack1 = depth0.message; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; } 383 | buffer += escapeExpression(stack1) 384 | + "\n

"; 385 | return buffer; 386 | }); }); 387 | 388 | require({ 389 | paths: { 390 | templates: '../templates', 391 | Handlebars: 'libs/handlebars', 392 | text: 'libs/text', 393 | hbars: 'libs/hbars' 394 | }, 395 | shim: { 396 | Handlebars: { 397 | exports: 'Handlebars' 398 | } 399 | }, 400 | 401 | onBuildWrite : function(moduleName, path, content){ 402 | 403 | // replace handlebars with the runtime version 404 | if (moduleName === 'Handlebars') { 405 | path = path.replace('handlebars.js','handlebars.runtime.js'); 406 | content = fs.readFileSync(path).toString(); 407 | content = content.replace(/(define\()(function)/, '$1"handlebars", $2'); 408 | } 409 | return content; 410 | } 411 | 412 | }, ['hbars!templates/message'], function (template) { 413 | 414 | 415 | console.log('template = ' + template); 416 | document.body.innerHTML += template({message: 'Hello World!'}); 417 | }); 418 | 419 | define("main", function(){}); 420 | -------------------------------------------------------------------------------- /example-build/templates/message.html: -------------------------------------------------------------------------------- 1 |

2 | Message 3 |

4 |

5 | {{ message }} 6 |

-------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | RequireJS Plugin for Handlebars semantic templates 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/scripts/libs/handlebars.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2011 by Yehuda Katz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | */ 24 | 25 | // lib/handlebars/browser-prefix.js 26 | var Handlebars = {}; 27 | 28 | (function(Handlebars, undefined) { 29 | ; 30 | // lib/handlebars/base.js 31 | 32 | Handlebars.VERSION = "1.0.0"; 33 | Handlebars.COMPILER_REVISION = 4; 34 | 35 | Handlebars.REVISION_CHANGES = { 36 | 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 37 | 2: '== 1.0.0-rc.3', 38 | 3: '== 1.0.0-rc.4', 39 | 4: '>= 1.0.0' 40 | }; 41 | 42 | Handlebars.helpers = {}; 43 | Handlebars.partials = {}; 44 | 45 | var toString = Object.prototype.toString, 46 | functionType = '[object Function]', 47 | objectType = '[object Object]'; 48 | 49 | Handlebars.registerHelper = function(name, fn, inverse) { 50 | if (toString.call(name) === objectType) { 51 | if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } 52 | Handlebars.Utils.extend(this.helpers, name); 53 | } else { 54 | if (inverse) { fn.not = inverse; } 55 | this.helpers[name] = fn; 56 | } 57 | }; 58 | 59 | Handlebars.registerPartial = function(name, str) { 60 | if (toString.call(name) === objectType) { 61 | Handlebars.Utils.extend(this.partials, name); 62 | } else { 63 | this.partials[name] = str; 64 | } 65 | }; 66 | 67 | Handlebars.registerHelper('helperMissing', function(arg) { 68 | if(arguments.length === 2) { 69 | return undefined; 70 | } else { 71 | throw new Error("Missing helper: '" + arg + "'"); 72 | } 73 | }); 74 | 75 | Handlebars.registerHelper('blockHelperMissing', function(context, options) { 76 | var inverse = options.inverse || function() {}, fn = options.fn; 77 | 78 | var type = toString.call(context); 79 | 80 | if(type === functionType) { context = context.call(this); } 81 | 82 | if(context === true) { 83 | return fn(this); 84 | } else if(context === false || context == null) { 85 | return inverse(this); 86 | } else if(type === "[object Array]") { 87 | if(context.length > 0) { 88 | return Handlebars.helpers.each(context, options); 89 | } else { 90 | return inverse(this); 91 | } 92 | } else { 93 | return fn(context); 94 | } 95 | }); 96 | 97 | Handlebars.K = function() {}; 98 | 99 | Handlebars.createFrame = Object.create || function(object) { 100 | Handlebars.K.prototype = object; 101 | var obj = new Handlebars.K(); 102 | Handlebars.K.prototype = null; 103 | return obj; 104 | }; 105 | 106 | Handlebars.logger = { 107 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, 108 | 109 | methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, 110 | 111 | // can be overridden in the host environment 112 | log: function(level, obj) { 113 | if (Handlebars.logger.level <= level) { 114 | var method = Handlebars.logger.methodMap[level]; 115 | if (typeof console !== 'undefined' && console[method]) { 116 | console[method].call(console, obj); 117 | } 118 | } 119 | } 120 | }; 121 | 122 | Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; 123 | 124 | Handlebars.registerHelper('each', function(context, options) { 125 | var fn = options.fn, inverse = options.inverse; 126 | var i = 0, ret = "", data; 127 | 128 | var type = toString.call(context); 129 | if(type === functionType) { context = context.call(this); } 130 | 131 | if (options.data) { 132 | data = Handlebars.createFrame(options.data); 133 | } 134 | 135 | if(context && typeof context === 'object') { 136 | if(context instanceof Array){ 137 | for(var j = context.length; i 2) { 351 | expected.push("'" + this.terminals_[p] + "'"); 352 | } 353 | if (this.lexer.showPosition) { 354 | errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; 355 | } else { 356 | errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); 357 | } 358 | this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); 359 | } 360 | } 361 | if (action[0] instanceof Array && action.length > 1) { 362 | throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); 363 | } 364 | switch (action[0]) { 365 | case 1: 366 | stack.push(symbol); 367 | vstack.push(this.lexer.yytext); 368 | lstack.push(this.lexer.yylloc); 369 | stack.push(action[1]); 370 | symbol = null; 371 | if (!preErrorSymbol) { 372 | yyleng = this.lexer.yyleng; 373 | yytext = this.lexer.yytext; 374 | yylineno = this.lexer.yylineno; 375 | yyloc = this.lexer.yylloc; 376 | if (recovering > 0) 377 | recovering--; 378 | } else { 379 | symbol = preErrorSymbol; 380 | preErrorSymbol = null; 381 | } 382 | break; 383 | case 2: 384 | len = this.productions_[action[1]][1]; 385 | yyval.$ = vstack[vstack.length - len]; 386 | yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; 387 | if (ranges) { 388 | yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; 389 | } 390 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); 391 | if (typeof r !== "undefined") { 392 | return r; 393 | } 394 | if (len) { 395 | stack = stack.slice(0, -1 * len * 2); 396 | vstack = vstack.slice(0, -1 * len); 397 | lstack = lstack.slice(0, -1 * len); 398 | } 399 | stack.push(this.productions_[action[1]][0]); 400 | vstack.push(yyval.$); 401 | lstack.push(yyval._$); 402 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; 403 | stack.push(newState); 404 | break; 405 | case 3: 406 | return true; 407 | } 408 | } 409 | return true; 410 | } 411 | }; 412 | /* Jison generated lexer */ 413 | var lexer = (function(){ 414 | var lexer = ({EOF:1, 415 | parseError:function parseError(str, hash) { 416 | if (this.yy.parser) { 417 | this.yy.parser.parseError(str, hash); 418 | } else { 419 | throw new Error(str); 420 | } 421 | }, 422 | setInput:function (input) { 423 | this._input = input; 424 | this._more = this._less = this.done = false; 425 | this.yylineno = this.yyleng = 0; 426 | this.yytext = this.matched = this.match = ''; 427 | this.conditionStack = ['INITIAL']; 428 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; 429 | if (this.options.ranges) this.yylloc.range = [0,0]; 430 | this.offset = 0; 431 | return this; 432 | }, 433 | input:function () { 434 | var ch = this._input[0]; 435 | this.yytext += ch; 436 | this.yyleng++; 437 | this.offset++; 438 | this.match += ch; 439 | this.matched += ch; 440 | var lines = ch.match(/(?:\r\n?|\n).*/g); 441 | if (lines) { 442 | this.yylineno++; 443 | this.yylloc.last_line++; 444 | } else { 445 | this.yylloc.last_column++; 446 | } 447 | if (this.options.ranges) this.yylloc.range[1]++; 448 | 449 | this._input = this._input.slice(1); 450 | return ch; 451 | }, 452 | unput:function (ch) { 453 | var len = ch.length; 454 | var lines = ch.split(/(?:\r\n?|\n)/g); 455 | 456 | this._input = ch + this._input; 457 | this.yytext = this.yytext.substr(0, this.yytext.length-len-1); 458 | //this.yyleng -= len; 459 | this.offset -= len; 460 | var oldLines = this.match.split(/(?:\r\n?|\n)/g); 461 | this.match = this.match.substr(0, this.match.length-1); 462 | this.matched = this.matched.substr(0, this.matched.length-1); 463 | 464 | if (lines.length-1) this.yylineno -= lines.length-1; 465 | var r = this.yylloc.range; 466 | 467 | this.yylloc = {first_line: this.yylloc.first_line, 468 | last_line: this.yylineno+1, 469 | first_column: this.yylloc.first_column, 470 | last_column: lines ? 471 | (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: 472 | this.yylloc.first_column - len 473 | }; 474 | 475 | if (this.options.ranges) { 476 | this.yylloc.range = [r[0], r[0] + this.yyleng - len]; 477 | } 478 | return this; 479 | }, 480 | more:function () { 481 | this._more = true; 482 | return this; 483 | }, 484 | less:function (n) { 485 | this.unput(this.match.slice(n)); 486 | }, 487 | pastInput:function () { 488 | var past = this.matched.substr(0, this.matched.length - this.match.length); 489 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); 490 | }, 491 | upcomingInput:function () { 492 | var next = this.match; 493 | if (next.length < 20) { 494 | next += this._input.substr(0, 20-next.length); 495 | } 496 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); 497 | }, 498 | showPosition:function () { 499 | var pre = this.pastInput(); 500 | var c = new Array(pre.length + 1).join("-"); 501 | return pre + this.upcomingInput() + "\n" + c+"^"; 502 | }, 503 | next:function () { 504 | if (this.done) { 505 | return this.EOF; 506 | } 507 | if (!this._input) this.done = true; 508 | 509 | var token, 510 | match, 511 | tempMatch, 512 | index, 513 | col, 514 | lines; 515 | if (!this._more) { 516 | this.yytext = ''; 517 | this.match = ''; 518 | } 519 | var rules = this._currentRules(); 520 | for (var i=0;i < rules.length; i++) { 521 | tempMatch = this._input.match(this.rules[rules[i]]); 522 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { 523 | match = tempMatch; 524 | index = i; 525 | if (!this.options.flex) break; 526 | } 527 | } 528 | if (match) { 529 | lines = match[0].match(/(?:\r\n?|\n).*/g); 530 | if (lines) this.yylineno += lines.length; 531 | this.yylloc = {first_line: this.yylloc.last_line, 532 | last_line: this.yylineno+1, 533 | first_column: this.yylloc.last_column, 534 | last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; 535 | this.yytext += match[0]; 536 | this.match += match[0]; 537 | this.matches = match; 538 | this.yyleng = this.yytext.length; 539 | if (this.options.ranges) { 540 | this.yylloc.range = [this.offset, this.offset += this.yyleng]; 541 | } 542 | this._more = false; 543 | this._input = this._input.slice(match[0].length); 544 | this.matched += match[0]; 545 | token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); 546 | if (this.done && this._input) this.done = false; 547 | if (token) return token; 548 | else return; 549 | } 550 | if (this._input === "") { 551 | return this.EOF; 552 | } else { 553 | return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), 554 | {text: "", token: null, line: this.yylineno}); 555 | } 556 | }, 557 | lex:function lex() { 558 | var r = this.next(); 559 | if (typeof r !== 'undefined') { 560 | return r; 561 | } else { 562 | return this.lex(); 563 | } 564 | }, 565 | begin:function begin(condition) { 566 | this.conditionStack.push(condition); 567 | }, 568 | popState:function popState() { 569 | return this.conditionStack.pop(); 570 | }, 571 | _currentRules:function _currentRules() { 572 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; 573 | }, 574 | topState:function () { 575 | return this.conditionStack[this.conditionStack.length-2]; 576 | }, 577 | pushState:function begin(condition) { 578 | this.begin(condition); 579 | }}); 580 | lexer.options = {}; 581 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { 582 | 583 | var YYSTATE=YY_START 584 | switch($avoiding_name_collisions) { 585 | case 0: yy_.yytext = "\\"; return 14; 586 | break; 587 | case 1: 588 | if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); 589 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); 590 | if(yy_.yytext) return 14; 591 | 592 | break; 593 | case 2: return 14; 594 | break; 595 | case 3: 596 | if(yy_.yytext.slice(-1) !== "\\") this.popState(); 597 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); 598 | return 14; 599 | 600 | break; 601 | case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; 602 | break; 603 | case 5: return 25; 604 | break; 605 | case 6: return 16; 606 | break; 607 | case 7: return 20; 608 | break; 609 | case 8: return 19; 610 | break; 611 | case 9: return 19; 612 | break; 613 | case 10: return 23; 614 | break; 615 | case 11: return 22; 616 | break; 617 | case 12: this.popState(); this.begin('com'); 618 | break; 619 | case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; 620 | break; 621 | case 14: return 22; 622 | break; 623 | case 15: return 37; 624 | break; 625 | case 16: return 36; 626 | break; 627 | case 17: return 36; 628 | break; 629 | case 18: return 40; 630 | break; 631 | case 19: /*ignore whitespace*/ 632 | break; 633 | case 20: this.popState(); return 24; 634 | break; 635 | case 21: this.popState(); return 18; 636 | break; 637 | case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31; 638 | break; 639 | case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31; 640 | break; 641 | case 24: return 38; 642 | break; 643 | case 25: return 33; 644 | break; 645 | case 26: return 33; 646 | break; 647 | case 27: return 32; 648 | break; 649 | case 28: return 36; 650 | break; 651 | case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36; 652 | break; 653 | case 30: return 'INVALID'; 654 | break; 655 | case 31: return 5; 656 | break; 657 | } 658 | }; 659 | lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; 660 | lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}}; 661 | return lexer;})() 662 | parser.lexer = lexer; 663 | function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; 664 | return new Parser; 665 | })();; 666 | // lib/handlebars/compiler/base.js 667 | 668 | Handlebars.Parser = handlebars; 669 | 670 | Handlebars.parse = function(input) { 671 | 672 | // Just return if an already-compile AST was passed in. 673 | if(input.constructor === Handlebars.AST.ProgramNode) { return input; } 674 | 675 | Handlebars.Parser.yy = Handlebars.AST; 676 | return Handlebars.Parser.parse(input); 677 | }; 678 | ; 679 | // lib/handlebars/compiler/ast.js 680 | Handlebars.AST = {}; 681 | 682 | Handlebars.AST.ProgramNode = function(statements, inverse) { 683 | this.type = "program"; 684 | this.statements = statements; 685 | if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } 686 | }; 687 | 688 | Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { 689 | this.type = "mustache"; 690 | this.escaped = !unescaped; 691 | this.hash = hash; 692 | 693 | var id = this.id = rawParams[0]; 694 | var params = this.params = rawParams.slice(1); 695 | 696 | // a mustache is an eligible helper if: 697 | // * its id is simple (a single part, not `this` or `..`) 698 | var eligibleHelper = this.eligibleHelper = id.isSimple; 699 | 700 | // a mustache is definitely a helper if: 701 | // * it is an eligible helper, and 702 | // * it has at least one parameter or hash segment 703 | this.isHelper = eligibleHelper && (params.length || hash); 704 | 705 | // if a mustache is an eligible helper but not a definite 706 | // helper, it is ambiguous, and will be resolved in a later 707 | // pass or at runtime. 708 | }; 709 | 710 | Handlebars.AST.PartialNode = function(partialName, context) { 711 | this.type = "partial"; 712 | this.partialName = partialName; 713 | this.context = context; 714 | }; 715 | 716 | Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { 717 | var verifyMatch = function(open, close) { 718 | if(open.original !== close.original) { 719 | throw new Handlebars.Exception(open.original + " doesn't match " + close.original); 720 | } 721 | }; 722 | 723 | verifyMatch(mustache.id, close); 724 | this.type = "block"; 725 | this.mustache = mustache; 726 | this.program = program; 727 | this.inverse = inverse; 728 | 729 | if (this.inverse && !this.program) { 730 | this.isInverse = true; 731 | } 732 | }; 733 | 734 | Handlebars.AST.ContentNode = function(string) { 735 | this.type = "content"; 736 | this.string = string; 737 | }; 738 | 739 | Handlebars.AST.HashNode = function(pairs) { 740 | this.type = "hash"; 741 | this.pairs = pairs; 742 | }; 743 | 744 | Handlebars.AST.IdNode = function(parts) { 745 | this.type = "ID"; 746 | 747 | var original = "", 748 | dig = [], 749 | depth = 0; 750 | 751 | for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + original); } 757 | else if (part === "..") { depth++; } 758 | else { this.isScoped = true; } 759 | } 760 | else { dig.push(part); } 761 | } 762 | 763 | this.original = original; 764 | this.parts = dig; 765 | this.string = dig.join('.'); 766 | this.depth = depth; 767 | 768 | // an ID is simple if it only has one part, and that part is not 769 | // `..` or `this`. 770 | this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; 771 | 772 | this.stringModeValue = this.string; 773 | }; 774 | 775 | Handlebars.AST.PartialNameNode = function(name) { 776 | this.type = "PARTIAL_NAME"; 777 | this.name = name.original; 778 | }; 779 | 780 | Handlebars.AST.DataNode = function(id) { 781 | this.type = "DATA"; 782 | this.id = id; 783 | }; 784 | 785 | Handlebars.AST.StringNode = function(string) { 786 | this.type = "STRING"; 787 | this.original = 788 | this.string = 789 | this.stringModeValue = string; 790 | }; 791 | 792 | Handlebars.AST.IntegerNode = function(integer) { 793 | this.type = "INTEGER"; 794 | this.original = 795 | this.integer = integer; 796 | this.stringModeValue = Number(integer); 797 | }; 798 | 799 | Handlebars.AST.BooleanNode = function(bool) { 800 | this.type = "BOOLEAN"; 801 | this.bool = bool; 802 | this.stringModeValue = bool === "true"; 803 | }; 804 | 805 | Handlebars.AST.CommentNode = function(comment) { 806 | this.type = "comment"; 807 | this.comment = comment; 808 | }; 809 | ; 810 | // lib/handlebars/utils.js 811 | 812 | var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; 813 | 814 | Handlebars.Exception = function(message) { 815 | var tmp = Error.prototype.constructor.apply(this, arguments); 816 | 817 | // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. 818 | for (var idx = 0; idx < errorProps.length; idx++) { 819 | this[errorProps[idx]] = tmp[errorProps[idx]]; 820 | } 821 | }; 822 | Handlebars.Exception.prototype = new Error(); 823 | 824 | // Build out our basic SafeString type 825 | Handlebars.SafeString = function(string) { 826 | this.string = string; 827 | }; 828 | Handlebars.SafeString.prototype.toString = function() { 829 | return this.string.toString(); 830 | }; 831 | 832 | var escape = { 833 | "&": "&", 834 | "<": "<", 835 | ">": ">", 836 | '"': """, 837 | "'": "'", 838 | "`": "`" 839 | }; 840 | 841 | var badChars = /[&<>"'`]/g; 842 | var possible = /[&<>"'`]/; 843 | 844 | var escapeChar = function(chr) { 845 | return escape[chr] || "&"; 846 | }; 847 | 848 | Handlebars.Utils = { 849 | extend: function(obj, value) { 850 | for(var key in value) { 851 | if(value.hasOwnProperty(key)) { 852 | obj[key] = value[key]; 853 | } 854 | } 855 | }, 856 | 857 | escapeExpression: function(string) { 858 | // don't escape SafeStrings, since they're already safe 859 | if (string instanceof Handlebars.SafeString) { 860 | return string.toString(); 861 | } else if (string == null || string === false) { 862 | return ""; 863 | } 864 | 865 | // Force a string conversion as this will be done by the append regardless and 866 | // the regex test will do this transparently behind the scenes, causing issues if 867 | // an object's to string has escaped characters in it. 868 | string = string.toString(); 869 | 870 | if(!possible.test(string)) { return string; } 871 | return string.replace(badChars, escapeChar); 872 | }, 873 | 874 | isEmpty: function(value) { 875 | if (!value && value !== 0) { 876 | return true; 877 | } else if(toString.call(value) === "[object Array]" && value.length === 0) { 878 | return true; 879 | } else { 880 | return false; 881 | } 882 | } 883 | }; 884 | ; 885 | // lib/handlebars/compiler/compiler.js 886 | 887 | /*jshint eqnull:true*/ 888 | var Compiler = Handlebars.Compiler = function() {}; 889 | var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; 890 | 891 | // the foundHelper register will disambiguate helper lookup from finding a 892 | // function in a context. This is necessary for mustache compatibility, which 893 | // requires that context functions in blocks are evaluated by blockHelperMissing, 894 | // and then proceed as if the resulting value was provided to blockHelperMissing. 895 | 896 | Compiler.prototype = { 897 | compiler: Compiler, 898 | 899 | disassemble: function() { 900 | var opcodes = this.opcodes, opcode, out = [], params, param; 901 | 902 | for (var i=0, l=opcodes.length; i 0) { 1414 | this.source[1] = this.source[1] + ", " + locals.join(", "); 1415 | } 1416 | 1417 | // Generate minimizer alias mappings 1418 | if (!this.isChild) { 1419 | for (var alias in this.context.aliases) { 1420 | if (this.context.aliases.hasOwnProperty(alias)) { 1421 | this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; 1422 | } 1423 | } 1424 | } 1425 | 1426 | if (this.source[1]) { 1427 | this.source[1] = "var " + this.source[1].substring(2) + ";"; 1428 | } 1429 | 1430 | // Merge children 1431 | if (!this.isChild) { 1432 | this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; 1433 | } 1434 | 1435 | if (!this.environment.isSimple) { 1436 | this.source.push("return buffer;"); 1437 | } 1438 | 1439 | var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; 1440 | 1441 | for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } 1975 | return this.topStackName(); 1976 | }, 1977 | topStackName: function() { 1978 | return "stack" + this.stackSlot; 1979 | }, 1980 | flushInline: function() { 1981 | var inlineStack = this.inlineStack; 1982 | if (inlineStack.length) { 1983 | this.inlineStack = []; 1984 | for (var i = 0, len = inlineStack.length; i < len; i++) { 1985 | var entry = inlineStack[i]; 1986 | if (entry instanceof Literal) { 1987 | this.compileStack.push(entry); 1988 | } else { 1989 | this.pushStack(entry); 1990 | } 1991 | } 1992 | } 1993 | }, 1994 | isInline: function() { 1995 | return this.inlineStack.length; 1996 | }, 1997 | 1998 | popStack: function(wrapped) { 1999 | var inline = this.isInline(), 2000 | item = (inline ? this.inlineStack : this.compileStack).pop(); 2001 | 2002 | if (!wrapped && (item instanceof Literal)) { 2003 | return item.value; 2004 | } else { 2005 | if (!inline) { 2006 | this.stackSlot--; 2007 | } 2008 | return item; 2009 | } 2010 | }, 2011 | 2012 | topStack: function(wrapped) { 2013 | var stack = (this.isInline() ? this.inlineStack : this.compileStack), 2014 | item = stack[stack.length - 1]; 2015 | 2016 | if (!wrapped && (item instanceof Literal)) { 2017 | return item.value; 2018 | } else { 2019 | return item; 2020 | } 2021 | }, 2022 | 2023 | quotedString: function(str) { 2024 | return '"' + str 2025 | .replace(/\\/g, '\\\\') 2026 | .replace(/"/g, '\\"') 2027 | .replace(/\n/g, '\\n') 2028 | .replace(/\r/g, '\\r') 2029 | .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 2030 | .replace(/\u2029/g, '\\u2029') + '"'; 2031 | }, 2032 | 2033 | setupHelper: function(paramSize, name, missingParams) { 2034 | var params = []; 2035 | this.setupParams(paramSize, params, missingParams); 2036 | var foundHelper = this.nameLookup('helpers', name, 'helper'); 2037 | 2038 | return { 2039 | params: params, 2040 | name: foundHelper, 2041 | callParams: ["depth0"].concat(params).join(", "), 2042 | helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") 2043 | }; 2044 | }, 2045 | 2046 | // the params and contexts arguments are passed in arrays 2047 | // to fill in 2048 | setupParams: function(paramSize, params, useRegister) { 2049 | var options = [], contexts = [], types = [], param, inverse, program; 2050 | 2051 | options.push("hash:" + this.popStack()); 2052 | 2053 | inverse = this.popStack(); 2054 | program = this.popStack(); 2055 | 2056 | // Avoid setting fn and inverse if neither are set. This allows 2057 | // helpers to do a check for `if (options.fn)` 2058 | if (program || inverse) { 2059 | if (!program) { 2060 | this.context.aliases.self = "this"; 2061 | program = "self.noop"; 2062 | } 2063 | 2064 | if (!inverse) { 2065 | this.context.aliases.self = "this"; 2066 | inverse = "self.noop"; 2067 | } 2068 | 2069 | options.push("inverse:" + inverse); 2070 | options.push("fn:" + program); 2071 | } 2072 | 2073 | for(var i=0; i= 1.0.0' 40 | }; 41 | 42 | Handlebars.helpers = {}; 43 | Handlebars.partials = {}; 44 | 45 | var toString = Object.prototype.toString, 46 | functionType = '[object Function]', 47 | objectType = '[object Object]'; 48 | 49 | Handlebars.registerHelper = function(name, fn, inverse) { 50 | if (toString.call(name) === objectType) { 51 | if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } 52 | Handlebars.Utils.extend(this.helpers, name); 53 | } else { 54 | if (inverse) { fn.not = inverse; } 55 | this.helpers[name] = fn; 56 | } 57 | }; 58 | 59 | Handlebars.registerPartial = function(name, str) { 60 | if (toString.call(name) === objectType) { 61 | Handlebars.Utils.extend(this.partials, name); 62 | } else { 63 | this.partials[name] = str; 64 | } 65 | }; 66 | 67 | Handlebars.registerHelper('helperMissing', function(arg) { 68 | if(arguments.length === 2) { 69 | return undefined; 70 | } else { 71 | throw new Error("Missing helper: '" + arg + "'"); 72 | } 73 | }); 74 | 75 | Handlebars.registerHelper('blockHelperMissing', function(context, options) { 76 | var inverse = options.inverse || function() {}, fn = options.fn; 77 | 78 | var type = toString.call(context); 79 | 80 | if(type === functionType) { context = context.call(this); } 81 | 82 | if(context === true) { 83 | return fn(this); 84 | } else if(context === false || context == null) { 85 | return inverse(this); 86 | } else if(type === "[object Array]") { 87 | if(context.length > 0) { 88 | return Handlebars.helpers.each(context, options); 89 | } else { 90 | return inverse(this); 91 | } 92 | } else { 93 | return fn(context); 94 | } 95 | }); 96 | 97 | Handlebars.K = function() {}; 98 | 99 | Handlebars.createFrame = Object.create || function(object) { 100 | Handlebars.K.prototype = object; 101 | var obj = new Handlebars.K(); 102 | Handlebars.K.prototype = null; 103 | return obj; 104 | }; 105 | 106 | Handlebars.logger = { 107 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, 108 | 109 | methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, 110 | 111 | // can be overridden in the host environment 112 | log: function(level, obj) { 113 | if (Handlebars.logger.level <= level) { 114 | var method = Handlebars.logger.methodMap[level]; 115 | if (typeof console !== 'undefined' && console[method]) { 116 | console[method].call(console, obj); 117 | } 118 | } 119 | } 120 | }; 121 | 122 | Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; 123 | 124 | Handlebars.registerHelper('each', function(context, options) { 125 | var fn = options.fn, inverse = options.inverse; 126 | var i = 0, ret = "", data; 127 | 128 | var type = toString.call(context); 129 | if(type === functionType) { context = context.call(this); } 130 | 131 | if (options.data) { 132 | data = Handlebars.createFrame(options.data); 133 | } 134 | 135 | if(context && typeof context === 'object') { 136 | if(context instanceof Array){ 137 | for(var j = context.length; i": ">", 212 | '"': """, 213 | "'": "'", 214 | "`": "`" 215 | }; 216 | 217 | var badChars = /[&<>"'`]/g; 218 | var possible = /[&<>"'`]/; 219 | 220 | var escapeChar = function(chr) { 221 | return escape[chr] || "&"; 222 | }; 223 | 224 | Handlebars.Utils = { 225 | extend: function(obj, value) { 226 | for(var key in value) { 227 | if(value.hasOwnProperty(key)) { 228 | obj[key] = value[key]; 229 | } 230 | } 231 | }, 232 | 233 | escapeExpression: function(string) { 234 | // don't escape SafeStrings, since they're already safe 235 | if (string instanceof Handlebars.SafeString) { 236 | return string.toString(); 237 | } else if (string == null || string === false) { 238 | return ""; 239 | } 240 | 241 | // Force a string conversion as this will be done by the append regardless and 242 | // the regex test will do this transparently behind the scenes, causing issues if 243 | // an object's to string has escaped characters in it. 244 | string = string.toString(); 245 | 246 | if(!possible.test(string)) { return string; } 247 | return string.replace(badChars, escapeChar); 248 | }, 249 | 250 | isEmpty: function(value) { 251 | if (!value && value !== 0) { 252 | return true; 253 | } else if(toString.call(value) === "[object Array]" && value.length === 0) { 254 | return true; 255 | } else { 256 | return false; 257 | } 258 | } 259 | }; 260 | ; 261 | // lib/handlebars/runtime.js 262 | 263 | Handlebars.VM = { 264 | template: function(templateSpec) { 265 | // Just add water 266 | var container = { 267 | escapeExpression: Handlebars.Utils.escapeExpression, 268 | invokePartial: Handlebars.VM.invokePartial, 269 | programs: [], 270 | program: function(i, fn, data) { 271 | var programWrapper = this.programs[i]; 272 | if(data) { 273 | programWrapper = Handlebars.VM.program(i, fn, data); 274 | } else if (!programWrapper) { 275 | programWrapper = this.programs[i] = Handlebars.VM.program(i, fn); 276 | } 277 | return programWrapper; 278 | }, 279 | merge: function(param, common) { 280 | var ret = param || common; 281 | 282 | if (param && common) { 283 | ret = {}; 284 | Handlebars.Utils.extend(ret, common); 285 | Handlebars.Utils.extend(ret, param); 286 | } 287 | return ret; 288 | }, 289 | programWithDepth: Handlebars.VM.programWithDepth, 290 | noop: Handlebars.VM.noop, 291 | compilerInfo: null 292 | }; 293 | 294 | return function(context, options) { 295 | options = options || {}; 296 | var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); 297 | 298 | var compilerInfo = container.compilerInfo || [], 299 | compilerRevision = compilerInfo[0] || 1, 300 | currentRevision = Handlebars.COMPILER_REVISION; 301 | 302 | if (compilerRevision !== currentRevision) { 303 | if (compilerRevision < currentRevision) { 304 | var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], 305 | compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; 306 | throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ 307 | "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; 308 | } else { 309 | // Use the embedded version info since the runtime doesn't know about this revision yet 310 | throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ 311 | "Please update your runtime to a newer version ("+compilerInfo[1]+")."; 312 | } 313 | } 314 | 315 | return result; 316 | }; 317 | }, 318 | 319 | programWithDepth: function(i, fn, data /*, $depth */) { 320 | var args = Array.prototype.slice.call(arguments, 3); 321 | 322 | var program = function(context, options) { 323 | options = options || {}; 324 | 325 | return fn.apply(this, [context, options.data || data].concat(args)); 326 | }; 327 | program.program = i; 328 | program.depth = args.length; 329 | return program; 330 | }, 331 | program: function(i, fn, data) { 332 | var program = function(context, options) { 333 | options = options || {}; 334 | 335 | return fn(context, options.data || data); 336 | }; 337 | program.program = i; 338 | program.depth = 0; 339 | return program; 340 | }, 341 | noop: function() { return ""; }, 342 | invokePartial: function(partial, name, context, helpers, partials, data) { 343 | var options = { helpers: helpers, partials: partials, data: data }; 344 | 345 | if(partial === undefined) { 346 | throw new Handlebars.Exception("The partial " + name + " could not be found"); 347 | } else if(partial instanceof Function) { 348 | return partial(context, options); 349 | } else if (!Handlebars.compile) { 350 | throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); 351 | } else { 352 | partials[name] = Handlebars.compile(partial, {data: data !== undefined}); 353 | return partials[name](context, options); 354 | } 355 | } 356 | }; 357 | 358 | Handlebars.template = Handlebars.VM.template; 359 | ; 360 | // lib/handlebars/browser-suffix.js 361 | })(Handlebars); 362 | ; 363 | -------------------------------------------------------------------------------- /example/scripts/libs/hbars.js: -------------------------------------------------------------------------------- 1 | // RequireJS Handlebars template plugin 2 | // http://github.com/jfparadis/requirejs-handlebars 3 | // 4 | // An alternative to http://github.com/SlexAxton/require-handlebars-plugin/blob/master/hbs.js 5 | // 6 | // Using Handlebars Semantic templates at http://handlebarsjs.com 7 | // Using and RequireJS text.js at http://requirejs.org/docs/api.html#text 8 | // @author JF Paradis 9 | // @version 0.0.2 10 | // 11 | // Released under the MIT license 12 | // 13 | // Usage: 14 | // require(['backbone', 'hbar!mytemplate'], function (Backbone, mytemplate) { 15 | // return Backbone.View.extend({ 16 | // initialize: function(){ 17 | // this.render(); 18 | // }, 19 | // render: function(){ 20 | // this.$el.html(mytemplate({message: 'hello'})); 21 | // }); 22 | // }); 23 | // 24 | // Configuration: (optional) 25 | // require.config({ 26 | // hbars: { 27 | // extension: '.hbar' // default = '.html' 28 | // } 29 | // }); 30 | 31 | /*jslint nomen: true */ 32 | /*global define: false */ 33 | 34 | define(['text', 'Handlebars'], function (text, Handlebars) { 35 | 'use strict'; 36 | 37 | var buildMap = {}, 38 | buildTemplateSource = "define('{pluginName}!{moduleName}', ['Handlebars'], function (Handlebars) { return Handlebars.template({content}); });\n"; 39 | 40 | return { 41 | version: '0.0.2', 42 | 43 | load: function (moduleName, parentRequire, onload, config) { 44 | if (buildMap[moduleName]) { 45 | onload(buildMap[moduleName]); 46 | 47 | } else { 48 | var ext = (config.hbars && config.hbars.extension) || '.html', 49 | path = (config.hbars && config.hbars.path) || '', 50 | compileOptions = (config.hbars && config.hbars.compileOptions) || {}; 51 | 52 | text.load(path + moduleName + ext, parentRequire, function (source) { 53 | if (config.isBuild) { 54 | // We store the precompiled template so we can use the 55 | // handlebars.runtime after build. 56 | buildMap[moduleName] = Handlebars.precompile(source, compileOptions); 57 | // Don't bother doing anything else during build. 58 | onload(); 59 | } else { 60 | // We store the compiled template for reuse 61 | buildMap[moduleName] = Handlebars.compile(source); 62 | onload(buildMap[moduleName]); 63 | } 64 | }, config); 65 | } 66 | }, 67 | 68 | write: function (pluginName, moduleName, write, config) { 69 | var content = buildMap[moduleName]; 70 | if (content) { 71 | write.asModule(pluginName + '!' + moduleName, 72 | buildTemplateSource 73 | .replace('{pluginName}', pluginName) 74 | .replace('{moduleName}', moduleName) 75 | .replace('{content}', content)); 76 | } 77 | } 78 | }; 79 | }); 80 | -------------------------------------------------------------------------------- /example/scripts/libs/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS text 2.0.10 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/requirejs/text for details 5 | */ 6 | /*jslint regexp: true */ 7 | /*global require, XMLHttpRequest, ActiveXObject, 8 | define, window, process, Packages, 9 | java, location, Components, FileUtils */ 10 | 11 | define(['module'], function (module) { 12 | 'use strict'; 13 | 14 | var text, fs, Cc, Ci, xpcIsWindows, 15 | progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], 16 | xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, 17 | bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, 18 | hasLocation = typeof location !== 'undefined' && location.href, 19 | defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), 20 | defaultHostName = hasLocation && location.hostname, 21 | defaultPort = hasLocation && (location.port || undefined), 22 | buildMap = {}, 23 | masterConfig = (module.config && module.config()) || {}; 24 | 25 | text = { 26 | version: '2.0.10', 27 | 28 | strip: function (content) { 29 | //Strips declarations so that external SVG and XML 30 | //documents can be added to a document without worry. Also, if the string 31 | //is an HTML document, only the part inside the body tag is returned. 32 | if (content) { 33 | content = content.replace(xmlRegExp, ""); 34 | var matches = content.match(bodyRegExp); 35 | if (matches) { 36 | content = matches[1]; 37 | } 38 | } else { 39 | content = ""; 40 | } 41 | return content; 42 | }, 43 | 44 | jsEscape: function (content) { 45 | return content.replace(/(['\\])/g, '\\$1') 46 | .replace(/[\f]/g, "\\f") 47 | .replace(/[\b]/g, "\\b") 48 | .replace(/[\n]/g, "\\n") 49 | .replace(/[\t]/g, "\\t") 50 | .replace(/[\r]/g, "\\r") 51 | .replace(/[\u2028]/g, "\\u2028") 52 | .replace(/[\u2029]/g, "\\u2029"); 53 | }, 54 | 55 | createXhr: masterConfig.createXhr || function () { 56 | //Would love to dump the ActiveX crap in here. Need IE 6 to die first. 57 | var xhr, i, progId; 58 | if (typeof XMLHttpRequest !== "undefined") { 59 | return new XMLHttpRequest(); 60 | } else if (typeof ActiveXObject !== "undefined") { 61 | for (i = 0; i < 3; i += 1) { 62 | progId = progIds[i]; 63 | try { 64 | xhr = new ActiveXObject(progId); 65 | } catch (e) {} 66 | 67 | if (xhr) { 68 | progIds = [progId]; // so faster next time 69 | break; 70 | } 71 | } 72 | } 73 | 74 | return xhr; 75 | }, 76 | 77 | /** 78 | * Parses a resource name into its component parts. Resource names 79 | * look like: module/name.ext!strip, where the !strip part is 80 | * optional. 81 | * @param {String} name the resource name 82 | * @returns {Object} with properties "moduleName", "ext" and "strip" 83 | * where strip is a boolean. 84 | */ 85 | parseName: function (name) { 86 | var modName, ext, temp, 87 | strip = false, 88 | index = name.indexOf("."), 89 | isRelative = name.indexOf('./') === 0 || 90 | name.indexOf('../') === 0; 91 | 92 | if (index !== -1 && (!isRelative || index > 1)) { 93 | modName = name.substring(0, index); 94 | ext = name.substring(index + 1, name.length); 95 | } else { 96 | modName = name; 97 | } 98 | 99 | temp = ext || modName; 100 | index = temp.indexOf("!"); 101 | if (index !== -1) { 102 | //Pull off the strip arg. 103 | strip = temp.substring(index + 1) === "strip"; 104 | temp = temp.substring(0, index); 105 | if (ext) { 106 | ext = temp; 107 | } else { 108 | modName = temp; 109 | } 110 | } 111 | 112 | return { 113 | moduleName: modName, 114 | ext: ext, 115 | strip: strip 116 | }; 117 | }, 118 | 119 | xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, 120 | 121 | /** 122 | * Is an URL on another domain. Only works for browser use, returns 123 | * false in non-browser environments. Only used to know if an 124 | * optimized .js version of a text resource should be loaded 125 | * instead. 126 | * @param {String} url 127 | * @returns Boolean 128 | */ 129 | useXhr: function (url, protocol, hostname, port) { 130 | var uProtocol, uHostName, uPort, 131 | match = text.xdRegExp.exec(url); 132 | if (!match) { 133 | return true; 134 | } 135 | uProtocol = match[2]; 136 | uHostName = match[3]; 137 | 138 | uHostName = uHostName.split(':'); 139 | uPort = uHostName[1]; 140 | uHostName = uHostName[0]; 141 | 142 | return (!uProtocol || uProtocol === protocol) && 143 | (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && 144 | ((!uPort && !uHostName) || uPort === port); 145 | }, 146 | 147 | finishLoad: function (name, strip, content, onLoad) { 148 | content = strip ? text.strip(content) : content; 149 | if (masterConfig.isBuild) { 150 | buildMap[name] = content; 151 | } 152 | onLoad(content); 153 | }, 154 | 155 | load: function (name, req, onLoad, config) { 156 | //Name has format: some.module.filext!strip 157 | //The strip part is optional. 158 | //if strip is present, then that means only get the string contents 159 | //inside a body tag in an HTML string. For XML/SVG content it means 160 | //removing the declarations so the content can be inserted 161 | //into the current doc without problems. 162 | 163 | // Do not bother with the work if a build and text will 164 | // not be inlined. 165 | if (config.isBuild && !config.inlineText) { 166 | onLoad(); 167 | return; 168 | } 169 | 170 | masterConfig.isBuild = config.isBuild; 171 | 172 | var parsed = text.parseName(name), 173 | nonStripName = parsed.moduleName + 174 | (parsed.ext ? '.' + parsed.ext : ''), 175 | url = req.toUrl(nonStripName), 176 | useXhr = (masterConfig.useXhr) || 177 | text.useXhr; 178 | 179 | // Do not load if it is an empty: url 180 | if (url.indexOf('empty:') === 0) { 181 | onLoad(); 182 | return; 183 | } 184 | 185 | //Load the text. Use XHR if possible and in a browser. 186 | if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { 187 | text.get(url, function (content) { 188 | text.finishLoad(name, parsed.strip, content, onLoad); 189 | }, function (err) { 190 | if (onLoad.error) { 191 | onLoad.error(err); 192 | } 193 | }); 194 | } else { 195 | //Need to fetch the resource across domains. Assume 196 | //the resource has been optimized into a JS module. Fetch 197 | //by the module name + extension, but do not include the 198 | //!strip part to avoid file system issues. 199 | req([nonStripName], function (content) { 200 | text.finishLoad(parsed.moduleName + '.' + parsed.ext, 201 | parsed.strip, content, onLoad); 202 | }); 203 | } 204 | }, 205 | 206 | write: function (pluginName, moduleName, write, config) { 207 | if (buildMap.hasOwnProperty(moduleName)) { 208 | var content = text.jsEscape(buildMap[moduleName]); 209 | write.asModule(pluginName + "!" + moduleName, 210 | "define(function () { return '" + 211 | content + 212 | "';});\n"); 213 | } 214 | }, 215 | 216 | writeFile: function (pluginName, moduleName, req, write, config) { 217 | var parsed = text.parseName(moduleName), 218 | extPart = parsed.ext ? '.' + parsed.ext : '', 219 | nonStripName = parsed.moduleName + extPart, 220 | //Use a '.js' file name so that it indicates it is a 221 | //script that can be loaded across domains. 222 | fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; 223 | 224 | //Leverage own load() method to load plugin value, but only 225 | //write out values that do not have the strip argument, 226 | //to avoid any potential issues with ! in file names. 227 | text.load(nonStripName, req, function (value) { 228 | //Use own write() method to construct full module value. 229 | //But need to create shell that translates writeFile's 230 | //write() to the right interface. 231 | var textWrite = function (contents) { 232 | return write(fileName, contents); 233 | }; 234 | textWrite.asModule = function (moduleName, contents) { 235 | return write.asModule(moduleName, fileName, contents); 236 | }; 237 | 238 | text.write(pluginName, nonStripName, textWrite, config); 239 | }, config); 240 | } 241 | }; 242 | 243 | if (masterConfig.env === 'node' || (!masterConfig.env && 244 | typeof process !== "undefined" && 245 | process.versions && 246 | !!process.versions.node && 247 | !process.versions['node-webkit'])) { 248 | //Using special require.nodeRequire, something added by r.js. 249 | fs = require.nodeRequire('fs'); 250 | 251 | text.get = function (url, callback, errback) { 252 | try { 253 | var file = fs.readFileSync(url, 'utf8'); 254 | //Remove BOM (Byte Mark Order) from utf8 files if it is there. 255 | if (file.indexOf('\uFEFF') === 0) { 256 | file = file.substring(1); 257 | } 258 | callback(file); 259 | } catch (e) { 260 | errback(e); 261 | } 262 | }; 263 | } else if (masterConfig.env === 'xhr' || (!masterConfig.env && 264 | text.createXhr())) { 265 | text.get = function (url, callback, errback, headers) { 266 | var xhr = text.createXhr(), header; 267 | xhr.open('GET', url, true); 268 | 269 | //Allow plugins direct access to xhr headers 270 | if (headers) { 271 | for (header in headers) { 272 | if (headers.hasOwnProperty(header)) { 273 | xhr.setRequestHeader(header.toLowerCase(), headers[header]); 274 | } 275 | } 276 | } 277 | 278 | //Allow overrides specified in config 279 | if (masterConfig.onXhr) { 280 | masterConfig.onXhr(xhr, url); 281 | } 282 | 283 | xhr.onreadystatechange = function (evt) { 284 | var status, err; 285 | //Do not explicitly handle errors, those should be 286 | //visible via console output in the browser. 287 | if (xhr.readyState === 4) { 288 | status = xhr.status; 289 | if (status > 399 && status < 600) { 290 | //An http 4xx or 5xx error. Signal an error. 291 | err = new Error(url + ' HTTP status: ' + status); 292 | err.xhr = xhr; 293 | errback(err); 294 | } else { 295 | callback(xhr.responseText); 296 | } 297 | 298 | if (masterConfig.onXhrComplete) { 299 | masterConfig.onXhrComplete(xhr, url); 300 | } 301 | } 302 | }; 303 | xhr.send(null); 304 | }; 305 | } else if (masterConfig.env === 'rhino' || (!masterConfig.env && 306 | typeof Packages !== 'undefined' && typeof java !== 'undefined')) { 307 | //Why Java, why is this so awkward? 308 | text.get = function (url, callback) { 309 | var stringBuffer, line, 310 | encoding = "utf-8", 311 | file = new java.io.File(url), 312 | lineSeparator = java.lang.System.getProperty("line.separator"), 313 | input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), 314 | content = ''; 315 | try { 316 | stringBuffer = new java.lang.StringBuffer(); 317 | line = input.readLine(); 318 | 319 | // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 320 | // http://www.unicode.org/faq/utf_bom.html 321 | 322 | // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: 323 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 324 | if (line && line.length() && line.charAt(0) === 0xfeff) { 325 | // Eat the BOM, since we've already found the encoding on this file, 326 | // and we plan to concatenating this buffer with others; the BOM should 327 | // only appear at the top of a file. 328 | line = line.substring(1); 329 | } 330 | 331 | if (line !== null) { 332 | stringBuffer.append(line); 333 | } 334 | 335 | while ((line = input.readLine()) !== null) { 336 | stringBuffer.append(lineSeparator); 337 | stringBuffer.append(line); 338 | } 339 | //Make sure we return a JavaScript string and not a Java string. 340 | content = String(stringBuffer.toString()); //String 341 | } finally { 342 | input.close(); 343 | } 344 | callback(content); 345 | }; 346 | } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env && 347 | typeof Components !== 'undefined' && Components.classes && 348 | Components.interfaces)) { 349 | //Avert your gaze! 350 | Cc = Components.classes, 351 | Ci = Components.interfaces; 352 | Components.utils['import']('resource://gre/modules/FileUtils.jsm'); 353 | xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc); 354 | 355 | text.get = function (url, callback) { 356 | var inStream, convertStream, fileObj, 357 | readData = {}; 358 | 359 | if (xpcIsWindows) { 360 | url = url.replace(/\//g, '\\'); 361 | } 362 | 363 | fileObj = new FileUtils.File(url); 364 | 365 | //XPCOM, you so crazy 366 | try { 367 | inStream = Cc['@mozilla.org/network/file-input-stream;1'] 368 | .createInstance(Ci.nsIFileInputStream); 369 | inStream.init(fileObj, 1, 0, false); 370 | 371 | convertStream = Cc['@mozilla.org/intl/converter-input-stream;1'] 372 | .createInstance(Ci.nsIConverterInputStream); 373 | convertStream.init(inStream, "utf-8", inStream.available(), 374 | Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); 375 | 376 | convertStream.readString(inStream.available(), readData); 377 | convertStream.close(); 378 | inStream.close(); 379 | callback(readData.value); 380 | } catch (e) { 381 | throw new Error((fileObj && fileObj.path || '') + ': ' + e); 382 | } 383 | }; 384 | } 385 | return text; 386 | }); 387 | -------------------------------------------------------------------------------- /example/scripts/main.js: -------------------------------------------------------------------------------- 1 | require({ 2 | paths: { 3 | templates: '../templates', 4 | Handlebars: 'libs/handlebars', 5 | text: 'libs/text', 6 | hbars: 'libs/hbars' 7 | }, 8 | shim: { 9 | Handlebars: { 10 | exports: 'Handlebars' 11 | } 12 | }, 13 | 14 | onBuildWrite : function(moduleName, path, content){ 15 | 16 | // replace handlebars with the runtime version 17 | if (moduleName === 'Handlebars') { 18 | path = path.replace('handlebars.js','handlebars.runtime.js'); 19 | content = fs.readFileSync(path).toString(); 20 | content = content.replace(/(define\()(function)/, '$1"handlebars", $2'); 21 | } 22 | return content; 23 | } 24 | 25 | }, ['hbars!templates/message'], function (template) { 26 | 'use strict'; 27 | 28 | console.log('template = ' + template); 29 | document.body.innerHTML += template({message: 'Hello World!'}); 30 | }); 31 | -------------------------------------------------------------------------------- /example/templates/message.html: -------------------------------------------------------------------------------- 1 |

2 | Message 3 |

4 |

5 | {{ message }} 6 |

-------------------------------------------------------------------------------- /hbars.js: -------------------------------------------------------------------------------- 1 | // RequireJS Handlebars template plugin 2 | // http://github.com/jfparadis/requirejs-handlebars 3 | // 4 | // An alternative to http://github.com/SlexAxton/require-handlebars-plugin/blob/master/hbs.js 5 | // 6 | // Using Handlebars Semantic templates at http://handlebarsjs.com 7 | // Using and RequireJS text.js at http://requirejs.org/docs/api.html#text 8 | // @author JF Paradis 9 | // @version 0.0.2 10 | // 11 | // Released under the MIT license 12 | // 13 | // Usage: 14 | // require(['backbone', 'hbar!mytemplate'], function (Backbone, mytemplate) { 15 | // return Backbone.View.extend({ 16 | // initialize: function(){ 17 | // this.render(); 18 | // }, 19 | // render: function(){ 20 | // this.$el.html(mytemplate({message: 'hello'})); 21 | // }); 22 | // }); 23 | // 24 | // Configuration: (optional) 25 | // require.config({ 26 | // hbars: { 27 | // extension: '.hbar' // default = '.html' 28 | // } 29 | // }); 30 | 31 | /*jslint nomen: true */ 32 | /*global define: false */ 33 | 34 | define(['text', 'Handlebars'], function (text, Handlebars) { 35 | 'use strict'; 36 | 37 | var buildMap = {}, 38 | buildTemplateSource = "define('{pluginName}!{moduleName}', ['Handlebars'], function (Handlebars) { return Handlebars.template({content}); });\n"; 39 | 40 | return { 41 | version: '0.0.2', 42 | 43 | load: function (moduleName, parentRequire, onload, config) { 44 | if (buildMap[moduleName]) { 45 | onload(buildMap[moduleName]); 46 | 47 | } else { 48 | var ext = (config.hbars && config.hbars.extension) || '.html', 49 | path = (config.hbars && config.hbars.path) || '', 50 | compileOptions = (config.hbars && config.hbars.compileOptions) || {}, 51 | textOnload = function (source) { 52 | if (config.isBuild) { 53 | // We store the precompiled template so we can use the 54 | // handlebars.runtime after build. 55 | buildMap[moduleName] = Handlebars.precompile(source, compileOptions); 56 | // Don't bother doing anything else during build. 57 | onload(); 58 | } else { 59 | // We store the compiled template for reuse 60 | buildMap[moduleName] = Handlebars.compile(source); 61 | onload(buildMap[moduleName]); 62 | } 63 | }; 64 | 65 | textOnload.error = onload.error; 66 | text.load(path + moduleName + ext, parentRequire, textOnload, config); 67 | } 68 | }, 69 | 70 | write: function (pluginName, moduleName, write, config) { 71 | var content = buildMap[moduleName]; 72 | if (content) { 73 | write.asModule(pluginName + '!' + moduleName, 74 | buildTemplateSource 75 | .replace('{pluginName}', pluginName) 76 | .replace('{moduleName}', moduleName) 77 | .replace('{content}', content)); 78 | } 79 | } 80 | }; 81 | }); 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "requirejs-handlebars", 3 | "description": "RequireJS Handlebars template plugin", 4 | "version": "0.0.2", 5 | "keywords": [ 6 | "requirejs", 7 | "handlebars", 8 | "template", 9 | "javascript" 10 | ], 11 | "maintainers": [ 12 | { 13 | "name": "Jean-Francois Paradis", 14 | "twitter": "@jfparadis", 15 | "web": "http://www.jeanfrancoisparadis.com" 16 | } 17 | ], 18 | "dependencies": { 19 | "http://github.com/jfparadis/requirejs-handlebars": "master" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/jfparadis/requirejs-handlebars/issues" 23 | }, 24 | "repositories": [ 25 | { 26 | "type": "git", 27 | "url": "git@github.com:jfparadis/requirejs-handlebars.git" 28 | } 29 | ], 30 | "licenses": [ 31 | { 32 | "name": "MIT", 33 | "url": "http://www.opensource.org/licenses/mit-license.php" 34 | } 35 | ], 36 | "homepage": "http://github.com/jfparadis/requirejs-handlebars" 37 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'); 2 | connect.createServer( 3 | connect.static(__dirname) 4 | ).listen(9000); 5 | --------------------------------------------------------------------------------