├── yz_djs_demo_app ├── generated_js │ ├── generated_js_templates.js │ └── helper_functions_for_js_templates.js ├── __init__.py ├── templates │ ├── test_tpl1.html │ └── yz_djs_demo.html ├── urls.py ├── generate_js_tpl.py └── DEMO_SETUP.txt ├── customtags ├── __init__.py ├── AssignJsNode.py └── assign.py ├── defaulttags ├── __init__.py ├── ConstantIncludeJsNode.py ├── IfJsNode.py └── ForJsNode.py ├── customfilters ├── __init__.py ├── hash.py └── HashJsFilter.py ├── defaultfilters ├── __init__.py ├── DefaultJsFilter.js ├── DefaultJsFilter.py └── AddJsFilter.py ├── LICENSE ├── unit_tests.py ├── README └── __init__.py /yz_djs_demo_app/generated_js/generated_js_templates.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yz_djs_demo_app/__init__.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) -------------------------------------------------------------------------------- /customtags/__init__.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | import os 6 | import glob 7 | __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*JsNode.py")] -------------------------------------------------------------------------------- /defaulttags/__init__.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | import os 6 | import glob 7 | __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*JsNode.py")] -------------------------------------------------------------------------------- /customfilters/__init__.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | import os 6 | import glob 7 | __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*JsFilter.py")] -------------------------------------------------------------------------------- /defaultfilters/__init__.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | import os 6 | import glob 7 | __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*JsFilter.py")] -------------------------------------------------------------------------------- /defaultfilters/DefaultJsFilter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * YZ JavaScript Django Template Compiler 3 | * Copyright (c) 2010 Weiss I Nicht 4 | * (sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 5 | **/ 6 | function yzdjs_default(value, fallbackDefault) { 7 | if (value) { 8 | return value 9 | } else { 10 | return fallbackDefault 11 | } 12 | } -------------------------------------------------------------------------------- /customfilters/hash.py: -------------------------------------------------------------------------------- 1 | #Access dictionary values with variable keys 2 | #Code from: http://push.cx/2007/django-template-tag-for-dictionary-access and http://stackoverflow.com/questions/35948/django-templates-and-variable-attributes 3 | from django.template import Library 4 | register = Library() 5 | 6 | @register.filter 7 | def hash(h,key): 8 | if key in h: 9 | return h[key] 10 | else: 11 | return None -------------------------------------------------------------------------------- /yz_djs_demo_app/templates/test_tpl1.html: -------------------------------------------------------------------------------- 1 |
2 | {% if comment.comment_title %} 3 |

{{ comment.comment_title }}

4 | {% else %} 5 |

Apparently this comment has no title

6 | {% endif %} 7 |

8 | Author: {{comment.author|default:"Anonymous Coward"}} 9 |

10 |

{{ comment.comment_body }}

11 |

12 | This comment was created on the 13 | {% if server_or_client_side == 'server' %} 14 | serverside 15 | {% else %} 16 | client side 17 | {% endif %} 18 |

19 |
-------------------------------------------------------------------------------- /yz_djs_demo_app/generated_js/helper_functions_for_js_templates.js: -------------------------------------------------------------------------------- 1 | //DO NOT EDIT! Your changes may be wiped as this is an auto generated JS file via yz_js_django_tpl 2 | 3 | //This file contains function definitions that will be used in the javascript template files. 4 | //Most of javascript functions defined here are for filter functionality 5 | 6 | /** 7 | * YZ JavaScript Django Template Compiler 8 | * Copyright (c) 2010 Weiss I Nicht 9 | * (sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 10 | **/ 11 | function yzdjs_default(value, fallbackDefault) { 12 | if (value) { 13 | return value 14 | } else { 15 | return fallbackDefault 16 | } 17 | } -------------------------------------------------------------------------------- /defaultfilters/DefaultJsFilter.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | from yz_js_django_tpl import BaseJsFilter, JsProcessorRegistry 5 | 6 | class DefaultJsFilter(BaseJsFilter): 7 | """ 8 | Converts the "default" filter in django templates to javascript expression 9 | i.e. {{exampleVar|default:"default value"}} 10 | Examples: 11 | >>> from yz_js_django_tpl import TemplateJsNode,JsTplSettings 12 | >>> JsTplSettings.CONFIG['VERSAGER_MODE'] = False 13 | >>> ############### 14 | >>> #test django "default" filter 15 | >>> ############### 16 | >>> js_tpl = TemplateJsNode('Default value text: {{testVar|default:"default val"}}') 17 | >>> js_tpl.render() 18 | u'function(testVar){return "Default value text: "+yzdjs_default(testVar,"default val")}' 19 | """ 20 | expected_filter_funcname = 'default' 21 | js_func_name = 'yzdjs_default' 22 | file_path = __file__ 23 | 24 | JsProcessorRegistry.register_js_filter(DefaultJsFilter) -------------------------------------------------------------------------------- /defaultfilters/AddJsFilter.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | from yz_js_django_tpl import BaseJsFilter, JsProcessorRegistry 5 | 6 | class AddJsFilter(BaseJsFilter): 7 | """ 8 | Converts the "add" filter in django templates to native javascript expression, 9 | i.e. {{exampleVar|add:"2"}} 10 | Examples: 11 | >>> from yz_js_django_tpl import TemplateJsNode,JsTplSettings 12 | >>> JsTplSettings.CONFIG['VERSAGER_MODE'] = False 13 | >>> ############### 14 | >>> #test django "add" filter 15 | >>> ############### 16 | >>> js_tpl = TemplateJsNode('1 + 1 = {{ 1|add:"1" }}') 17 | >>> js_tpl.render() 18 | u'function(){return "1 + 1 = "+(1+1)}' 19 | >>> js_tpl = TemplateJsNode('{{ 1|add:"1" }}') 20 | >>> js_tpl.render() 21 | u'function(){return (1+1)}' 22 | """ 23 | expected_filter_funcname = 'add' 24 | 25 | def render(self): 26 | return '(%s)' % '+'.join(self.js_func_params) 27 | 28 | JsProcessorRegistry.register_js_filter(AddJsFilter) -------------------------------------------------------------------------------- /customfilters/HashJsFilter.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | from yz_js_django_tpl import BaseJsFilter, JsProcessorRegistry 5 | 6 | class HashJsFilter(BaseJsFilter): 7 | """ 8 | Converts the "hash" filter in django templates to native javascript hash look up, 9 | i.e. {{exampleVar|hash:varB}} 10 | Examples: 11 | >>> from yz_js_django_tpl import TemplateJsNode,JsTplSettings 12 | >>> from yz_js_django_tpl.customfilters import * 13 | >>> JsTplSettings.CONFIG['VERSAGER_MODE'] = False 14 | >>> ############### 15 | >>> #test django "hash" filter 16 | >>> ############### 17 | >>> js_tpl = TemplateJsNode('{% load hash %}Dict var1 with hash varB{{ varA|hash:varB }}') 18 | >>> js_tpl.render() 19 | u'function(varA,varB){return "Dict var1 with hash varB"+varA[varB]}' 20 | """ 21 | expected_filter_funcname = 'hash' 22 | 23 | def render(self): 24 | return '%s[%s]' % (self.expr, self.arg) 25 | 26 | JsProcessorRegistry.register_js_filter(HashJsFilter) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 Weiss I Nicht (sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 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. -------------------------------------------------------------------------------- /yz_djs_demo_app/urls.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | from django.conf.urls.defaults import * 6 | import os 7 | CURRENT_PATH = os.path.dirname(__file__) 8 | 9 | urlpatterns = patterns('django.views', 10 | (r'^$', 'generic.simple.direct_to_template', {'template': 'yz_djs_demo.html', 'extra_context' : { 11 | 'comments' : [ 12 | { 13 | 'comment_title': 'I really digg this tool', 14 | 'author' : 'Wisste Nicht', 15 | 'comment_body' : 'Now I just which somebody else could feel the same way' 16 | }, 17 | { 18 | 'comment_title': 'Save bandwidth and server load by off loading template construction to client side', 19 | 'author' : 'Bu Zhidao', 20 | 'comment_body' : 'One template, client and server sides, one parser to unit them all' 21 | } 22 | ], 23 | 'server_or_client_side' : 'server' 24 | }}), 25 | (r'^js/(?P.*)$', 'static.serve',{'document_root': CURRENT_PATH + '/generated_js'}), 26 | ) -------------------------------------------------------------------------------- /yz_djs_demo_app/generate_js_tpl.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | #Boiler plate Django environment setup 6 | import os 7 | import sys 8 | 9 | CURRENT_PATH = os.path.dirname(__file__) 10 | DJANGO_DIRECTORY_PATH = os.path.join(CURRENT_PATH, '../../../') 11 | sys.path.append(DJANGO_DIRECTORY_PATH) 12 | 13 | import manage 14 | import settings 15 | 16 | #Load files necessary for our specific task 17 | from yz_js_django_tpl import generate_js_tpl_file, JsTplSettings 18 | from yz_js_django_tpl.customtags import * 19 | from yz_js_django_tpl.customfilters import * 20 | import os 21 | 22 | template_generator_configs = { 23 | 'demo_config' : { 24 | 'VERSAGER_MODE': False, 25 | #definition for filters that are used 26 | 'js_dependencies_location' : CURRENT_PATH + '/generated_js/helper_functions_for_js_templates.js', 27 | 'generated_js_file_location': CURRENT_PATH + '/generated_js/generated_js_templates.js', 28 | 'tpls': { 29 | 'test_tpl1.html' : { 30 | 'tpl_func_name': 'yz_djstpl_test_tpl1', 31 | 'var_list': ['comment','server_or_client_side'] 32 | } 33 | } 34 | } 35 | } 36 | 37 | JsTplSettings.init_config(template_generator_configs['demo_config']) 38 | generate_js_tpl_file() 39 | -------------------------------------------------------------------------------- /yz_djs_demo_app/DEMO_SETUP.txt: -------------------------------------------------------------------------------- 1 | YZ JavaScript Django Template Compiler 2 | Copyright (c) 2010 Weiss I Nicht 3 | (sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | To setup the demo: 6 | 7 | 1. Make sure the yz_js_django_tpl package is in your project path, it should suffice to put it in your base project folder i.e. DJANGO_PROJECT_FOLDER/yz_js_django_tpl 8 | 9 | 2. Move the yz_djs_demo_app folder to the project path as well, i.e. DJANGO_PROJECT_FOLDER/yz_djs_demo_app 10 | 11 | 4. Open up yz_djs_demo_app/generate_js_tpl.py and make sure DJANGO_DIRECTORY_PATH points to the base of your django project directory. 12 | 13 | 5. From the commandline navigate to the demo folder and type: 14 | python generate_js_tpl.py 15 | That should autogenerate the django templates from yz_djs_demo_templates into javascript functions 16 | 17 | 6. In your project DJANGO_PROJECT_FOLDER/settings.py file, add 'yz_djs_demo_app' into the list of installed apps 18 | 19 | 7. In your project DJANGO_PROJECT_FOLDER/url.py file at the project into the url path by adding (r'^yz_djs_demo/', include('yz_djs_demo_app.urls')) into the url patterns list 20 | 21 | This should be enough to get everything up and running. If you goto your /yz_djs_demo of your project domain, you will be able to see it, otherwise you can always checkout 22 | http://yz-demos.appspot.com/yz_djs_demo/ to see what you should be seeing. -------------------------------------------------------------------------------- /unit_tests.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | #Boiler plate Django environment setup 6 | import os 7 | import sys 8 | import glob 9 | 10 | CURRENT_PATH = os.path.dirname(__file__) 11 | sys.path.append(os.path.join(CURRENT_PATH, '../..')) 12 | 13 | import manage 14 | import settings 15 | import doctest 16 | import unittest 17 | #Load files necessary for our specific task 18 | 19 | import yz_js_django_tpl.defaulttags 20 | import yz_js_django_tpl.customtags 21 | import yz_js_django_tpl.customfilters 22 | suite = unittest.TestSuite() 23 | #add default tags to test suite 24 | default_tag_modules_to_test = yz_js_django_tpl.defaulttags.__all__[:] 25 | for mod in default_tag_modules_to_test: 26 | suite.addTest(doctest.DocTestSuite('yz_js_django_tpl.defaulttags.' + mod)) 27 | #add default filters to test suite 28 | default_tag_modules_to_test = yz_js_django_tpl.defaultfilters.__all__[:] 29 | for mod in default_tag_modules_to_test: 30 | suite.addTest(doctest.DocTestSuite('yz_js_django_tpl.defaultfilters.' + mod)) 31 | 32 | #add custom tags to test suite 33 | default_tag_modules_to_test = yz_js_django_tpl.customtags.__all__[:] 34 | for mod in default_tag_modules_to_test: 35 | suite.addTest(doctest.DocTestSuite('yz_js_django_tpl.customtags.' + mod)) 36 | #add custom tags to test suite 37 | default_tag_modules_to_test = yz_js_django_tpl.customfilters.__all__[:] 38 | for mod in default_tag_modules_to_test: 39 | suite.addTest(doctest.DocTestSuite('yz_js_django_tpl.customfilters.' + mod)) 40 | 41 | runner = unittest.TextTestRunner() 42 | runner.run(suite) 43 | # doctest.testmod(yz_js_django_tpl.defaulttags) -------------------------------------------------------------------------------- /defaulttags/ConstantIncludeJsNode.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | from yz_js_django_tpl import BaseJsNode, JsProcessorRegistry, JsTplSettings 6 | 7 | class ConstantIncludeJsNode(BaseJsNode): 8 | """ 9 | Converts {% include %} tag in django into javascript expression 10 | 11 | Examples: 12 | >>> from yz_js_django_tpl import TemplateJsNode, JsTplSettings 13 | >>> from django.template import Template 14 | >>> ############### 15 | >>> #This object is pretty hard to test using doc test as we need an entire environment to setup 16 | >>> #hence for the time being we won't test it :( 17 | >>> ############### 18 | """ 19 | expected_node_classname = 'ConstantIncludeNode' 20 | def _init_vars(self): 21 | tpl_path = self.django_node.template.name 22 | if tpl_path not in JsTplSettings.CONFIG['tpls']: 23 | raise NameError("Unabled to find template file %s in JsTplSettings.CONFIG[\'tpls\']" % tpl_path) 24 | tpl_info = JsTplSettings.CONFIG['tpls'][tpl_path] 25 | tpl_var_list = None 26 | self.tpl_func_name = tpl_info['tpl_func_name'] 27 | if 'var_list' in tpl_info: 28 | tpl_var_list = tpl_info['var_list'] 29 | self.tpl_var_list = tpl_var_list 30 | #add parameters of the include file into the global context of the calling file 31 | if tpl_var_list: 32 | [self.context.register_var(var_name, 'global') for var_name in tpl_var_list] 33 | self.update_parent_context() 34 | def generate_js_statement(self): 35 | return 'return %s' % self.generate_js_statement_as_closure() 36 | def generate_js_statement_as_closure(self): 37 | return self._wrap_expr_in_js_func(self.tpl_func_name, self.tpl_var_list) 38 | JsProcessorRegistry.register_js_node(ConstantIncludeJsNode) -------------------------------------------------------------------------------- /customtags/AssignJsNode.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | from yz_js_django_tpl import BaseJsNode, JsProcessorRegistry 5 | 6 | class AssignJsNode(BaseJsNode): 7 | """ 8 | Converts assign tag in django template into javascript expression 9 | Unit tests: 10 | >>> from yz_js_django_tpl import TemplateJsNode, JsTplSettings 11 | >>> from yz_js_django_tpl.customtags import * 12 | >>> JsTplSettings.CONFIG['VERSAGER_MODE'] = False 13 | >>> ############### 14 | >>> #test django AssignNode object behaves as expected 15 | >>> ############### 16 | >>> from django.template import Template 17 | >>> django_tpl = Template('{%load assign%} example here: {% assign other_str "another"|capfirst %}') 18 | >>> assign_node = django_tpl.nodelist[2] 19 | >>> assign_node.name 20 | u'other_str' 21 | >>> assign_node.value.var 22 | u'another' 23 | >>> assign_node.value.filters[0][0].__name__ 24 | 'capfirst' 25 | >>> ############### 26 | >>> #test assign js node works 27 | >>> ############### 28 | >>> js_tpl = TemplateJsNode('{%load assign%} assigning here: {% assign other_str varA|add:"5" %} displaying here: {{ other_str }}') 29 | >>> js_tpl.render() 30 | u'function(varA){var other_str;return " assigning here: "+function(){other_str=(varA+5);return ""}()+" displaying here: "+other_str}' 31 | """ 32 | expected_node_classname = 'AssignNode' 33 | def _init_vars(self): 34 | self.new_var_name = self.context.register_var(self.django_node.name, scope="global", var_type="implicit") 35 | self.new_var_value_expr = self._extract_filter_expression(self.django_node.value) 36 | def generate_js_statement(self): 37 | #e.g. varA=123 38 | #e.g. function(){varA=123;return null}() 39 | return self.new_var_name + '=' + self.new_var_value_expr + ';return ""' 40 | JsProcessorRegistry.register_js_node(AssignJsNode) -------------------------------------------------------------------------------- /customtags/assign.py: -------------------------------------------------------------------------------- 1 | #Useful template plugin for assigning new variables. Code taken from http://djangosnippets.org/snippets/539/ 2 | from django import template 3 | register = template.Library() 4 | class AssignNode(template.Node): 5 | """ 6 | Sometimes you want to create a temporal variable to store something or do anything you want with it, problem is that django template system doesn't provide this kind of feature. This template tag is just for that. 7 | 8 | Syntax:: 9 | 10 | {% assign [name] [value] %} 11 | 12 | This will create a context variable named [name] with a value of [value] [name] can be any identifier and [value] can be anything. If [value] is a callable, it will be called first and the returning result will be assigned to [name]. [value] can even be filtered. 13 | 14 | Example:: 15 | 16 | {% assign count 0 %} {% assign str "an string" %} {% assign number 10 %} {% assign list entry.get_related %} {% assign other_str "another"|capfirst %} 17 | 18 | {% ifequal count 0 %} ... {% endifequal %} 19 | 20 | {% ifequal str "an string" %} ... {% endifequal %} 21 | 22 | {% if list %} {% for item in list %} ... {% endfor %} {% endif %} 23 | 24 | Author: 25 | jmrbcu 26 | Posted: 27 | January 9, 2008 28 | """ 29 | def __init__(self, name, value): 30 | self.name = name 31 | self.value = value 32 | 33 | def render(self, context): 34 | context[self.name] = self.value.resolve(context, True) 35 | return '' 36 | 37 | def do_assign(parser, token): 38 | """ 39 | Assign an expression to a variable in the current context. 40 | 41 | Syntax:: 42 | {% assign [name] [value] %} 43 | Example:: 44 | {% assign list entry.get_related %} 45 | 46 | """ 47 | bits = token.contents.split() 48 | if len(bits) != 3: 49 | raise template.TemplateSyntaxError("'%s' tag takes two arguments" % bits[0]) 50 | value = parser.compile_filter(bits[2]) 51 | return AssignNode(bits[1], value) 52 | 53 | register.tag('assign', do_assign) -------------------------------------------------------------------------------- /yz_djs_demo_app/templates/yz_djs_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yz JavaScript Django Template Compiler Demo 6 | 7 | 8 | 9 | 43 | 44 | 45 | 46 |

This is a demo of yz_js_django_tpl!

47 |

48 | Yz Javascript Django Template compiler converts django templates into readily executable javascript functions that can render html on the client side via Javascript. The template is already parsed and built into the javascript functions so it is blazing fast and uses minimal browser resources. Write a template once and have it execute on both the client as well as server side. Reduce your server traffic by sending back pure data and create the html via javascript! 49 |

50 |

51 | The first two comments in this demo have been generated server side. Using the comment submission form on the bottom, you can append additional comments with the javascript. The code for rendering the comments on the client side is an auto-generated javascript function created using the same django template that rendered the comments on the server side. 52 |

53 |

54 | Please checkout this project at: http://github.com/comolongo/Yz-Javascript-Django-Template-Compiler on GitHub. Currently only the basic django template tags and filters are supported, e.g. if tag, for tag, default filter, etc. Your contribution will be greatly appreciated! 55 |

56 |

Here are some made up comments about this demo

57 |
58 | {% for comment in comments %} 59 | {% include 'test_tpl1.html' %} 60 | {% empty %} 61 |

I guess no one wants to make any comments

62 | {% endfor %} 63 |
64 |
65 |

Comment submission

66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 |
74 |
75 | 76 | 77 |
78 | 79 |
80 | 92 | 93 | -------------------------------------------------------------------------------- /defaulttags/IfJsNode.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | from yz_js_django_tpl import BaseJsNode, JsProcessorRegistry 5 | 6 | class IfJsNode(BaseJsNode): 7 | """ 8 | Converts if tag in django template into javascript expression 9 | Unit tests: 10 | >>> from yz_js_django_tpl import TemplateJsNode, JsTplSettings 11 | >>> JsTplSettings.CONFIG['VERSAGER_MODE'] = False 12 | >>> ############### 13 | >>> #test django IfNode object behaves as expected 14 | >>> ############### 15 | >>> from django.template import Template 16 | >>> django_tpl = Template('{% if cond > 5 %}true cond string{% else %}false cond string{% endif %}') 17 | >>> if_node = django_tpl.nodelist[0] 18 | >>> print if_node.var.first.text 19 | cond 20 | >>> print if_node.var.second.text 21 | 5 22 | >>> print if_node.var.id 23 | > 24 | >>> print if_node.nodelist_true 25 | [] 26 | >>> print if_node.nodelist_false 27 | [] 28 | >>> ############### 29 | >>> #test if variable cond is larger than 5 30 | >>> ############### 31 | >>> js_tpl = TemplateJsNode('{% if cond > 5 %}true cond string{% else %}false cond string{% endif %}') 32 | >>> js_tpl.render() 33 | u'function(cond){if(cond>5){return "true cond string"}else{return "false cond string"}}' 34 | >>> ############### 35 | >>> #test if variable cond is true or false 36 | >>> ############### 37 | >>> js_tpl = TemplateJsNode('{% if cond %}true cond string{% else %}false cond string{% endif %}') 38 | >>> js_tpl.render() 39 | u'function(cond){if(cond){return "true cond string"}else{return "false cond string"}}' 40 | >>> ############### 41 | >>> #test if condition when comparing against string literal 42 | >>> ############### 43 | >>> js_tpl = TemplateJsNode('{% if cond == "apple" %}true cond string{% else %}false cond string{% endif %}') 44 | >>> js_tpl.render() 45 | u'function(cond){if(cond=="apple"){return "true cond string"}else{return "false cond string"}}' 46 | >>> ############### 47 | >>> #test if condition with multiple subnodes 48 | >>> ############### 49 | >>> js_tpl = TemplateJsNode('{% if cond %}true cond {{ var1 }} string{% else %}false cond string{% endif %}') 50 | >>> js_tpl.render() 51 | u'function(cond,var1){if(cond){return "true cond "+var1+" string"}else{return "false cond string"}}' 52 | >>> ############### 53 | >>> #test if condition with multiple subnodes in Versager mode 54 | >>> ############### 55 | >>> JsTplSettings.CONFIG['VERSAGER_MODE'] = True 56 | >>> js_tpl = TemplateJsNode('{% if cond %}true cond {{ var1 }} string{% else %}false cond string{% endif %}') 57 | >>> js_tpl.render() 58 | u'function(cond,var1){if(cond){return ["true cond ",var1," string"].join("")}else{return "false cond string"}}' 59 | """ 60 | expected_node_classname = 'IfNode' 61 | def _init_vars(self): 62 | if_condition_var = self.django_node.var 63 | if if_condition_var.id == 'literal': 64 | self.if_condition = self._extract_filter_expression(if_condition_var.value) 65 | else: 66 | self.if_condition = self._extract_filter_expression(if_condition_var.first.value) + \ 67 | if_condition_var.id + self._extract_filter_expression(if_condition_var.second.value) 68 | def _init_sub_nodes(self): 69 | django_if_node = self.django_node 70 | self.if_block = self.scan_section(django_if_node.nodelist_true) 71 | self.else_block = None 72 | if django_if_node.nodelist_false: 73 | self.else_block = self.scan_section(django_if_node.nodelist_false) 74 | def generate_js_statement(self): 75 | rendered_if_block = 'if(' + self.if_condition + '){' + self._nodes_to_js_str(self.if_block) + '}' 76 | rendered_else_block = '' 77 | if self.else_block: 78 | rendered_else_block = 'else{' + self._nodes_to_js_str(self.else_block) + '}' 79 | if_statement = rendered_if_block + rendered_else_block 80 | return if_statement 81 | JsProcessorRegistry.register_js_node(IfJsNode) -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | YZ JavaScript Django Template Compiler 2 | Copyright (c) 2010 Weiss I Nicht 3 | (sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | This library compiles standard django templates into javascript functions. It allows developers to create templates and markup one time and be able to display it on both the server and client side without any additional work. Unlike many other javascript template systems that parse and compile the templates at run time, ours templates are already compiled and readily executable when they are loaded. Hence on the client browser does not need to do any work, and can generate the HTML extremely quickly. 6 | 7 | For a quick demo, please take a look at a simple demo: http://yz-demos.appspot.com/yz_djs_demo 8 | 9 | How to use: 10 | 11 | yz_js_django_tpl.TemplateJsNode() is the main workhorse through which all the templates are parsed. It can take either a string of the template or a path to the template file in order to generate the javascript expression. 12 | 13 | Parsing django template as string 14 | To generate a javascript function via a template string (in the Django shell console via "python manage.py shell" command): 15 | 16 | >>> from yz_js_django_tpl import TemplateJsNode() 17 | >>> js_tpl = TemplateJsNode('{% if cond %}true cond string{% else %}false cond string{% endif %}') 18 | >>> js_tpl.render() 19 | u'function(cond){if(cond){return "true cond string"}else{return "false cond string"}}' 20 | 21 | Parsing django template with template file path 22 | To generate a javascript function via the template path: 23 | 24 | >>> from yz_js_django_tpl import TemplateJsNode() 25 | >>> TemplateJsNode(django_template_path = 'djang_tpl.html', var_list = [var1, var2, var3]) 26 | >>> js_tpl.render() 27 | u'function(cond){if(cond){return "true cond string"}else{return "false cond string"}}' 28 | 29 | The var_list is optional, the template compiler can figure out what variables you need, however, it is useful if you want to explicitly set the order of the parameters of the generated javascript function 30 | 31 | Parsing django templates in batch as a script 32 | For the general use case, you may want to setup a script that auto parses a batch of templates and saves them to an autogenerated javascript file. To do that, take a look at generate_js_tpl.py in the yz_djs_demo_app folder. Basically you define the template files to read from, the location of the output files, and a few other variables. Lets take a look: 33 | 34 | >>> from yz_js_django_tpl import generate_js_tpl_file, JsTplSettings 35 | >>> template_generator_configs = { 36 | >>> 'VERSAGER_MODE': False, 37 | >>> 'js_dependencies_location' : 'js_dependencies.js', 38 | >>> 'generated_js_file_location': 'generated_tpl.js', 39 | >>> 'tpls': { 40 | >>> 'test_tpl1.html' : { 41 | >>> 'tpl_func_name': 'yz_djstpl_demo1', 42 | >>> 'var_list': ['server_or_client_side', 'feel_about_demo', 'age', 'addition_to_one', 'comments'] 43 | >>> }, 44 | >>> 'test_tpl2.html' : { 45 | >>> 'tpl_func_name': 'yz_djstpl_demo2', 46 | >>> 'var_list': ['comment'] 47 | >>> } 48 | >>> } 49 | >>> } 50 | >>> JsTplSettings.init_config(template_generator_configs) 51 | >>> generate_js_tpl_file() 52 | 53 | The above script reads from two template files: test_tpl1.html and test_tpl2.html and converts them into javascript functions. The tpl_func_name is the name of the corresponding javascript function. The generated_js_file_location is the path of the generated javascript file that contains the javascript templates. The js_dependencies_location is the path of the generated file that contains dependencies. For example, some django template tags and filters require additional helper functions in order to work, so after the compiler parses all of the templates in template_generator_configs['tpls'], it knows which dependencies it needs and generates the file accordingly. This way you won't be including more files than you need. If you don't want the generator to auto generate the dependencies, just set the js_dependencies_location to None 54 | Take a look in the yz_djs_demo_app/generated_js folder for what the generated JS files will look like 55 | 56 | Versager Mode: 57 | For older browsers such as IE6, string concatenation is an expensive operation, hence instead of adding strings and closures together (e.g. 'a cat ' + eating_action + 'the bat') to form a rendered string, Versager mode will use array.join() to create a parsed string i.e. ['a cat',eating_action,'the bat'].join(). If your audience enjoys using computers from pre y2k-era, you can set Versager mode to true 58 | 59 | 60 | Development Status: 61 | Currently only a small subset of all the available Django template tags and filters have been implemented, take a look at defaulttags/ and defaultfilters/ to see what is there. As my needs for more filters and tags grow, I'll be implementing more parsers. Let me know if you think there is one I really should look into porting over, or better yet, create the parsers yourself and contribute to this project! 62 | 63 | Nuts and Bolts - Creating tag and filter parsers: 64 | To create a parser for a specific tag or filter, we need to first understand how the parser system works. The parser system works by relying on django.template.Template() to first read and parse the django templates. The parsed Template object has a nodelist property which is a list of the nodes that make up the template. What is a node? The template file can be thought of as a collection of tags, variables, and continuos blocks of strings, all of which are nodes. To get the gist of it, run the following code the the console: 65 | 66 | >>> from django.template import Template 67 | >>> django_tpl = Template('some string {{some_var}} more string {% if cond > 5 %}true cond string{% else %}false cond string{% endif %}') 68 | >>> django_tpl.__dict__ 69 | {'nodelist': [, , , ], 'name': ''} 70 | 71 | Each tag, text block, and variable is handled by a specific type of Node. Each node has all of the information we need to render its representative section of the template. The yz_js_django_tpl.TemplateJsNode parser will read through this list and use yz_js_django_tpl.JsProcessorRegistry to identify each corresponding JsNode and create an instance. Each node may also have lists of subnodes, so it will need to instantiate those as well. Once all of the nodes and subnodes have been instantiated, they are recursively rendered to create the final javascript expression. 72 | 73 | If we look at the If node: 74 | >>> if_node = django_tpl.nodelist[3] 75 | 76 | >>> if_node.__dict__ 77 | {'var': (> (literal ) (literal )), 'source': (, (37, 54)), 'nodelist_false': [], 'nodelist_true': []} 78 | 79 | We see that it has the var, nodelist_false, and nodelist_true properties. We can use the var property to create the conditional part of the if statement i.e. if (cond > 5) 80 | The nodelist_true and nodelist_false can be parsed to form the body of the if statement. Take a look at defaulttags/IfJsNode.py for details. 81 | 82 | From the top of the class, we have: 83 | expected_node_classname = 'IfNode' 84 | This identifies the corresponding django Node class. So when an IfNode is encountered in the django nodelist, the yz_js_django_tpl.JsProcessorRegistry will know to call the IfJsNode class. The JsNode is registered at the end of the file: 85 | JsProcessorRegistry.register_js_node(IfJsNode) 86 | 87 | Writing a tag: 88 | By convention, the JsNode first calls the _init_vars() method. That is where all of the variables should be initiated. In the IfJsNode, this is where the useful properties from IfNode are extracted. If we look at the 'var' property of the IfNode: 89 | 90 | >>> if_node.var.__dict__ 91 | {'second': (literal ), 'first': (literal )} 92 | 93 | We see that it has two sub properties of type FilterExpression, which need to be handled via self._extract_filter_expression in the JsNode. FilterExpression is section in a template tag represented by a template variable and filter e.g. {{ var1|default:"0" }} . The _extract_filter_expression() registers the variable into the JsNodeContext() and uses yz_js_django_tpl.JsProcessorRegistry to figure out which filters to call. The returned result from _extract_filter_expression is a javascript expression representing the variable and any filters that are applied to it, e.g. {{ var1|add:"2"|default:"def" }} renders to default(add(var1,2), "def") 94 | 95 | After the properties have been extracted and the template variables have been registered into context, we call the _init_sub_nodes() . In this method, we see if there are any subnodes, and if so, we instantiate them. Generally subnodes are stored in a list, so we can use the self.self.scan_section() method to instantiate them. Upon instantiation of a node, we pass the a reference of the current node and the JsNodeContext to the child node. This way the child node will know which variables come from the parent context. After the subnodes have been instantiated _init_sub_nodes, the child nodes update the parent context on which variables it has created and which variables it has encountered and needs the parent node to provide. 96 | 97 | The render method assembles the initiated nodes as well as the extracted properties from the Node to create the actual javascript expression. The self._nodes_to_js_str() renders a list of JsNodes into string. It also checks the settings to make sure the strings are concatenated using the desired method. It also checks for any room for optimization. For example, if the subnode only has a single element and that element is a ForNode, IfNode, or any other node that generally requires an anonymous function closure when being concatenated (i.e. function(inner_if){if(inner_if){return 'inner text'}}()), _nodes_to_js_str() will be smart enough to render the node without the closure as it is unnecessary when there is no concatenation involved. In other words, instead of something like this 98 | function(outer_if, inner_if){if(outer_if){ return function(inner_if){if(inner_if){ return 'inner if string'}}}}, the _nodes_to_js_str will compile this: 99 | function(outer_if, inner_if){if(outer_if){ if(inner_if){ return 'inner if string'}}} 100 | 101 | Writing a filter: 102 | As briefly mentioned in the previous section on writing tags, filter tags are located in the FilterExpression object: 103 | 104 | >>> from django.template import Template 105 | >>> django_tpl = Template('{{var1|add:"2"}}') 106 | >>> django_tpl.nodelist[0] 107 | 108 | >>> django_tpl.nodelist[0].filter_expression.__dict__ 109 | {'var': , 'token': u'var1|add:"2"', 'filters': [(, [(False, u'2')])]} 110 | 111 | Compared to nodes, filters are much simpler. They are simply a function that takes two parameters; the variable value and an optional argument. The BaseJsFilter off of which all filters inherit, provides 3 variables: self.expr, self.arg, and self.js_func_params . self.expr is the name of the template variable i.e. var1 in the previous example. self.arg may or may not be set to a value, and represents the argument i.e. "2" in the previous example. The self.js_func_params is a list of the two aforementioned variables that makes it easy to call self._wrap_expr_in_js_func() to generate the javascript function call to the appropriate function. The js_func_name defines the name of the javascript function to call. Since most filters will simply render a javascript function call to the designated filter function using the format: jsFilterName(var_value, argument), the BaseJsFilter class does this by default. In most cases, only the js_func_name is required when creating a new filter class. The javascript function definition that will be doing most of the work should be located in the same folder and have the same filename as the filter except with a .js extension. If you look at defaultfilters/DefaultJsFilter.py, the corresponding js dependency is called DefaultJsFilter.js 112 | 113 | 114 | Test the environment: 115 | This library was built on Django 1.2 using Python 2.5.5 on a Mac OSX snow lepoard, and uses Django's templating system to parse the template files. It is not known how well it'll work on earlier builds of django. 116 | 117 | Fortunately, there is a unit test suite that can be run to make sure you won't get any huge surprises: 118 | 1. Place the yz_js_django_tpl package into your django 119 | 2. From commandline, goto package directory and type: 120 | >>> python unittest.py 121 | 3. With any luck the tests will all pass, otherwise let me know at KeineAhnung@atliva.com and I'll try to see if I have an idea of whats going on. 122 | 123 | -------------------------------------------------------------------------------- /defaulttags/ForJsNode.py: -------------------------------------------------------------------------------- 1 | #YZ JavaScript Django Template Compiler 2 | #Copyright (c) 2010 Weiss I Nicht 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | from yz_js_django_tpl import BaseJsNode, JsProcessorRegistry, JsTplSettings 6 | 7 | class ForJsNode(BaseJsNode): 8 | """ 9 | converts django ForNode to javascript expression 10 | This objects listens to which forloop helper variables (i.e. forloop.counter, forloop.first, etc) are 11 | being called in the child subnodes and instantiates them only necessary. So if a child node 12 | uses forloop.first, then we instantiate that variable here, otherwise, we don't, and there by save 13 | a few characters of code and a few cycles of cpu. 14 | The forloop.counter0 is basically the loop iterator, hence it is just renamed to the 15 | variable name of the for iterator. 16 | 17 | Examples: 18 | >>> from yz_js_django_tpl import TemplateJsNode, JsTplSettings 19 | >>> ############### 20 | >>> #test django ForNode object behaves as expected 21 | >>> ############### 22 | >>> from django.template import Template 23 | >>> django_tpl = Template('{% for arr_ele in arr %}

for loop text

{% endfor %}') 24 | >>> for_node = django_tpl.nodelist[0] 25 | >>> print for_node.loopvars 26 | [u'arr_ele'] 27 | >>> print for_node.sequence 28 | arr 29 | >>> print for_node.nodelist_loop 30 | [for loop text'>] 31 | >>> print for_node.is_reversed 32 | False 33 | >>> print for_node.nodelist_empty 34 | [] 35 | >>> ############### 36 | >>> #test basic for loop 37 | >>> ############### 38 | >>> js_tpl = TemplateJsNode('{% for arr_ele in arr %}

for loop text

{% endfor %}') 39 | >>> js_tpl.render() 40 | u'function(arr){for(var n0=0,arr_ele,n2=arr.length,n1="";n0for loop text"} return n1}' 41 | >>> ############### 42 | >>> #test for empty array 43 | >>> ############### 44 | >>> js_tpl = TemplateJsNode('{% for arr_ele in arr %}

for loop text

{% empty %}Lo siento empty array{% endfor %}') 45 | >>> js_tpl.render() 46 | u'function(arr){var n2=arr.length;if(n2){for(var n0=0,arr_ele,n1="";n0for loop text"} return n1}else{return "Lo siento empty array"}}' 47 | >>> ############### 48 | >>> #test for empty array in Versager Mode 49 | >>> ############### 50 | >>> JsTplSettings.CONFIG['VERSAGER_MODE'] = True 51 | >>> js_tpl = TemplateJsNode('{% for arr_ele in arr %}

for loop text

{% empty %}Lo siento empty array{% endfor %}') 52 | >>> js_tpl.render() 53 | u'function(arr){var n2=arr.length;if(n2){for(var n0=0,arr_ele,n1=[];n0for loop text"} return n1.join("")}else{return "Lo siento empty array"}}' 54 | >>> ############### 55 | >>> #test for loop iterator variables i.e. forloop.counter, forloop.counter0 56 | >>> ############### 57 | >>> JsTplSettings.CONFIG['VERSAGER_MODE'] = False 58 | >>> js_tpl = TemplateJsNode('{% for arr_ele in arr %}iterator value: {{forloop.counter0}} iterator plus one: {{forloop.counter}}{% endfor %}') 59 | >>> js_tpl.render() 60 | u'function(arr){for(var n0=0,arr_ele,n4,n2=arr.length,n1="";n0>> ############### 62 | >>> #test for loop reverse iterator variables i.e. forloop.revcounter, forloop.revcounter0 63 | >>> ############### 64 | >>> js_tpl = TemplateJsNode('{% for arr_ele in arr %}forloop.revcounter0 : {{forloop.revcounter0}} forloop.revcounter: {{forloop.revcounter}}{% endfor %}') 65 | >>> js_tpl.render() 66 | u'function(arr){for(var n0=0,arr_ele,n5,n6,n2=arr.length,n1="";n0>> ############### 68 | >>> #test for loop iterator variables forloop.first, forloop.last 69 | >>> ############### 70 | >>> js_tpl = TemplateJsNode('{% for arr_ele in arr %}forloop.first : {{forloop.first}} forloop.last: {{forloop.last}}{% endfor %}') 71 | >>> js_tpl.render() 72 | u'function(arr){for(var n0=0,arr_ele,n8,n7,n2=arr.length,n3=n2-1,n1="";n0>> ############### 74 | >>> #test for loop parent iterator variables 75 | >>> ############### 76 | >>> js_tpl = TemplateJsNode('{% for arr_ele in arr %}{% for ele2 in arr2 %}inner loop iterator: {{ forloop.counter }} parent loop iterator: {{ forloop.parentloop.counter }}{% endfor %}{% endfor %}') 77 | >>> js_tpl.render() 78 | u'function(arr,arr2){for(var n0=0,arr_ele,n4,n2=arr.length,n1="";n0 3 | #(sha-1: 90f01291285340bf03c2d41952c4b21b3d338907) 4 | 5 | from django.template import Template 6 | import django.template 7 | from django.template.loader import get_template 8 | from django.utils.html import strip_spaces_between_tags 9 | import os 10 | import re 11 | remove_whitespaces = re.compile(r'\s+') 12 | 13 | class JsTplSettings(object): 14 | filters_in_use = {} 15 | CONFIG = { 16 | 'VERSAGER_MODE' : False, #i.e. loser mode i.e. IE6 17 | 'generated_js_file_location': None, 18 | 'js_dependencies_location' : None, 19 | 'tpls' : {} 20 | } 21 | @classmethod 22 | def init_config(cls, config_dict): 23 | filters_in_use = {} 24 | cls.CONFIG = config_dict.copy() 25 | def generate_js_tpl_file(): 26 | """ 27 | generates a javascript file with django templates as javascript functions 28 | file_location - path of where the generated file will be put 29 | tpl_info_list - dictionary of each django template to be converted to javascript. Look into the 30 | demo folder to see the format of the tpl_info_list 31 | """ 32 | file_location = JsTplSettings.CONFIG['generated_js_file_location'] 33 | js_tpl_file_contents = [] 34 | tpl_info_list = JsTplSettings.CONFIG['tpls'] 35 | print "opening " + file_location + " to generate javascript template file" 36 | js_tpl_file = open(file_location,"w+") 37 | js_tpl_file_contents = ["//Auto generated JS file for via yz_js_django_tpl\n"] 38 | for tpl_path in tpl_info_list: 39 | tpl_info = tpl_info_list[tpl_path] 40 | tpl_var_list = None 41 | if 'var_list' in tpl_info: 42 | tpl_var_list = tpl_info['var_list'] 43 | tpl_func_name = tpl_info['tpl_func_name'] 44 | tpl_node = TemplateJsNode(django_template_path = tpl_path, var_list = tpl_var_list) 45 | js_tpl_file_contents.append('var ' + tpl_func_name + '=' + tpl_node.render()) 46 | if len(js_tpl_file_contents) > 50: 47 | print "writing javascript expression batch to file" 48 | #ensures that we write to disk and clear the memory after every so many files so we don't 49 | #run out of ram 50 | js_tpl_file.writelines("\n".join(js_tpl_file_contents)) 51 | js_tpl_file_contents = [] 52 | js_tpl_file.close() 53 | js_tpl_file = open(file_location,"a") 54 | js_tpl_file.write("\n".join(js_tpl_file_contents)) 55 | js_tpl_file.close() 56 | print "yz_js_django_tpl::generate_js_tpl_file successfully finished generating javascript template file" 57 | if JsTplSettings.CONFIG['js_dependencies_location']: 58 | filters_in_use = JsTplSettings.filters_in_use 59 | print "Checking to see if any javascript include files need to be added" 60 | if not filters_in_use: 61 | print "No function definitions need to be included" 62 | else: 63 | js_include_file = open(JsTplSettings.CONFIG['js_dependencies_location'],"w+") 64 | file_contents = ["//DO NOT EDIT! Your changes may be wiped as this is an auto generated JS file via yz_js_django_tpl\n"] 65 | file_contents.append("//This file contains function definitions that will be used in the javascript template files.") 66 | file_contents.append("//Most of javascript functions defined here are for filter functionality") 67 | js_include_file.write("\n".join(file_contents) + "\n") 68 | js_include_file.close() 69 | js_include_file = open(JsTplSettings.CONFIG['js_dependencies_location'],"a+") 70 | for filter_classname in filters_in_use: 71 | filter_info = filters_in_use[filter_classname] 72 | js_file_path = filter_info['js_file_path'] 73 | if not js_file_path: 74 | continue 75 | js_func_file = open(js_file_path, 'r') 76 | js_include_file.write("\n" + js_func_file.read()) 77 | js_func_file.close() 78 | js_include_file.close() 79 | 80 | class JsProcessorRegistry(object): 81 | """ 82 | Keeps track of the js nodes and filters available and decides on which one to use 83 | given a specified django node or filter 84 | """ 85 | django_to_js_nodes = {} 86 | django_to_js_filters = {} 87 | 88 | @classmethod 89 | def register_js_node(cls, js_node_class): 90 | django_node_classnames = js_node_class.expected_node_classname 91 | if django_node_classnames.__class__.__name__ == 'str': 92 | django_node_classnames = [django_node_classnames] 93 | for django_node_classname in django_node_classnames: 94 | cls.django_to_js_nodes[django_node_classname] = js_node_class 95 | return js_node_class 96 | 97 | @classmethod 98 | def register_js_filter(cls, js_filter_class): 99 | django_filter_funcname = js_filter_class.expected_filter_funcname 100 | if js_filter_class.file_path and js_filter_class.js_func_name: 101 | js_file_path = os.path.dirname(js_filter_class.file_path) + '/' + js_filter_class.__name__ + '.js' 102 | js_filter_class.js_file_path = js_file_path 103 | cls.django_to_js_filters[django_filter_funcname] = js_filter_class 104 | return js_filter_class 105 | 106 | @classmethod 107 | def get_js_node(cls, django_node_classname): 108 | if django_node_classname in cls.django_to_js_nodes: 109 | return cls.django_to_js_nodes[django_node_classname] 110 | else : 111 | None 112 | 113 | @classmethod 114 | def get_js_filter(cls, django_filter_funcname): 115 | if django_filter_funcname in cls.django_to_js_filters: 116 | js_filter_class = cls.django_to_js_filters[django_filter_funcname] 117 | js_filter_classname = js_filter_class.__class__.__name__ 118 | if js_filter_classname not in JsTplSettings.filters_in_use: 119 | JsTplSettings.filters_in_use[js_filter_classname] = { 120 | 'js_file_path': js_filter_class.js_file_path 121 | } 122 | return js_filter_class 123 | else : 124 | None 125 | 126 | 127 | class JsNodeContext(): 128 | """ 129 | Keeps track of the variables accessable to a specific js node or filter 130 | Whenever a node or filter parses a variable into javascript, it either registers it with the context 131 | or gets it from the context. Either way, all variables that are rendered into javascript go through 132 | this method. 133 | """ 134 | _var_generator_cntr = 0 135 | _global_cross_context_var_list = {} 136 | def __init__(self): 137 | self.global_vars = {} #vars global to the current context scope 138 | self.local_vars = {} #vars local to the current context scope 139 | self.vars_used_in_parent = {} #keeps track of all the variables used in a parent node 140 | self.vars_in_use = {} #keeps track of all the variables used in the current node 141 | self.vars_used_in_children = {} #keeps track of all variables used in child nodes 142 | self.rename_vars = {} #dictionary to rename variable in register_var() 143 | 144 | @classmethod 145 | def reset_global_context(cls): 146 | cls._var_generator_cntr = 0 147 | cls._global_cross_context_var_list = {} 148 | def register_var(self, base_var_name, scope, from_parent_or_child = None, full_var_name = None, var_type='input'): 149 | """ 150 | Registers a variable so it can be rendered into javascript. This ensures that variables global to a given 151 | closure context in javascript are pass correctly from its parent js node and that each closure is only passed 152 | the variables that it needs. It also allows for a node to know which variables have been called in its 153 | parents and child nodes, which could be useful for nodes such as the ForJsNode, which depending on whether or not 154 | certain variables are called (e.g. forloop.counter), it will choose whether or not to define certain variables 155 | 156 | var_type - can be either input or implicit . input variables will we passed in via its calling functions via the 157 | input parameters, implicit variables will not be passed via the input parameters but will be assumed to be part 158 | of the global scope 159 | """ 160 | if scope == 'local': 161 | var_list = self.local_vars 162 | elif scope == 'global': 163 | var_list = self.global_vars 164 | else: 165 | raise NameError('scope not defined') 166 | 167 | #full variable name example a.b 168 | #base variable name example a 169 | if not full_var_name: 170 | full_var_name = base_var_name 171 | if full_var_name in self.rename_vars: 172 | full_var_name = self.rename_vars[full_var_name] 173 | base_var_name = full_var_name 174 | else: 175 | self.__class__._global_cross_context_var_list[base_var_name] = var_type 176 | var_list[base_var_name] = var_type 177 | if from_parent_or_child == 'parent': 178 | var_usage_list = self.vars_used_in_parent 179 | elif from_parent_or_child == 'child': 180 | var_usage_list = self.vars_used_in_children 181 | else: 182 | var_usage_list = self.vars_in_use 183 | var_usage_list[full_var_name] = var_type 184 | return full_var_name 185 | 186 | def merge_js_vars(self, new_js_vars, scope): 187 | for var_name, var_type in new_js_vars.items(): 188 | if var_name not in self.local_vars and \ 189 | var_name not in self.global_vars: 190 | self.register_var(var_name, scope, var_type=var_type) 191 | 192 | def get_js_var_type(self, js_var_name): 193 | if js_var_name in self.local_vars: 194 | return self.local_vars[js_var_name] 195 | elif js_var_name in self.global_vars: 196 | return self.global_vars[js_var_name] 197 | else: 198 | raise NameError('variable does not exist') 199 | 200 | def get_vars_of_type(self, var_type): 201 | var_names = [] 202 | for js_var_name in self.global_vars: 203 | if self.global_vars[js_var_name] == var_type: 204 | var_names.append(js_var_name) 205 | 206 | return var_names 207 | 208 | def create_new_var(self, scope, var_type='input'): 209 | """creates and registers a new variable name to use in javascript""" 210 | is_taken = True 211 | cntr = self.__class__._var_generator_cntr 212 | if (scope != 'local') and (scope != 'global'): 213 | raise NameError('scope not defined') 214 | 215 | while (is_taken == True): 216 | new_var_name = 'n' + str(cntr) 217 | if new_var_name in self.__class__._global_cross_context_var_list: 218 | is_taken = True 219 | else: 220 | self.register_var(new_var_name, scope, var_type) 221 | is_taken = False 222 | cntr += 1 223 | return new_var_name 224 | 225 | class BaseJsTpl(object): 226 | """ 227 | Base object for converting django template nodes/filters into javascript representation 228 | """ 229 | def __init__(self, parent_js_node): 230 | self.context = JsNodeContext() 231 | self.parent_js_node = parent_js_node 232 | if parent_js_node: 233 | self.context.rename_vars.update(parent_js_node.context.rename_vars) 234 | def register_var_obj(self, var_obj): 235 | """Given a django Variable object, returns the variable name and registers it into context""" 236 | #if our variable was a property, e.g. a.b, base varname would be 'a' 237 | full_var_name = var_obj.var 238 | if var_obj.lookups:#if var_obj represents a literal i.e. 'apple', 5, etc., then we don't do anything 239 | base_varname = var_obj.lookups[0] 240 | full_var_name = self.context.register_var(base_varname, scope="global", full_var_name = full_var_name) 241 | return full_var_name 242 | def _wrap_expr_in_js_anon_func(self, js_expr, execute_now = True, var_list = None, show_params=False): 243 | """ 244 | given a javascript expression, wraps it into a closure, e.g. function(if_cond){if(if_cond){return a}}(if_cond,a) 245 | """ 246 | input_vars_list = self.context.get_vars_of_type('input') 247 | if var_list: 248 | unaccounted_vars = set(input_vars_list).symmetric_difference(set(var_list)) 249 | if len(unaccounted_vars): 250 | msg = 'Input variables and given variables do not match. \n \ 251 | input vars: %s \n \ 252 | given vars: %s' % (','.join(input_vars_list), ','.join(var_list)) 253 | 254 | raise NameError(msg) 255 | input_vars_list = var_list 256 | func_params = '' 257 | if show_params: 258 | func_params = ','.join(input_vars_list) 259 | if execute_now: 260 | func_execution = '(' + func_params + ')' 261 | else: 262 | func_execution = '' 263 | return 'function(' + func_params + '){' + js_expr + '}' + func_execution 264 | def _wrap_expr_in_js_func(self, func_name, func_params): 265 | """calls a javascript function, e.g. yzdjs_default(val, defaultVal)""" 266 | return func_name + '(' + ','.join(func_params) + ')' 267 | def _list_to_js_str(self, python_list): 268 | """converts a python list into a javascript string""" 269 | if JsTplSettings.CONFIG['VERSAGER_MODE']: 270 | #old browsers like IE6 don't like string concatenation, hence we do join 271 | return '[' + ','.join(map(str, python_list)) + '].join("")' 272 | else: 273 | #newer browsers are optimized to do string concatenation and it is actually faster 274 | #than creating an array and doing join 275 | return '+'.join(map(str, python_list)) 276 | def update_parent_context(self, parent_same_closure = False): 277 | """ 278 | Makes sure that the variables global to the child are also being accounted for by 279 | the parent node. If the parent node did not define the variable locally, then it will 280 | be assumed to be global to the parent node 281 | """ 282 | if self.parent_js_node: 283 | parent_context = self.parent_js_node.context 284 | parent_context.merge_js_vars(self.context.global_vars, 'global') 285 | parent_context.vars_used_in_children.update(self.context.vars_in_use) 286 | parent_context.vars_used_in_children.update(self.context.vars_used_in_children) 287 | if parent_same_closure: 288 | parent_context.merge_js_vars(self.context.local_vars, 'local') 289 | 290 | def generate_js_statement(self): 291 | return None 292 | def generate_js_statement_as_closure(self): 293 | return None 294 | def render(self, as_closure = False): 295 | if (as_closure) : 296 | js_stmt = self.generate_js_statement_as_closure() 297 | else: 298 | js_stmt = self.generate_js_statement() 299 | self.update_parent_context() 300 | return js_stmt 301 | 302 | class BaseJsFilter(BaseJsTpl): 303 | """ 304 | Base filter for converting django filters into javascript expressions 305 | """ 306 | file_path = None 307 | js_file_path = None 308 | js_func_name = None #name of javascript function to call in order to process the given filter value and arguments 309 | def __init__(self, parent_js_node, expr, arg_info): 310 | super(BaseJsFilter, self).__init__(parent_js_node) 311 | self.parent_js_node 312 | self.expr = expr 313 | self.js_func_params = [expr] 314 | arg = None 315 | if (len(arg_info) == 2): 316 | arg_is_var, arg = arg_info 317 | if arg_is_var: #checks if argument is a django Variable object, if so register it 318 | arg = self.register_var_obj(arg) 319 | self.update_parent_context(); 320 | else: 321 | #check if arg is a number 322 | try: 323 | float(arg) 324 | except ValueError: 325 | arg = '"' + arg + '"' 326 | self.arg = arg 327 | if arg: 328 | self.js_func_params.append(arg) 329 | def generate_js_statement(self): 330 | """ 331 | renders the javascript expression for processing the filter 332 | just like the django filters, the javascript filters should follow the conventions of filtername(var, argument) 333 | """ 334 | return self._wrap_expr_in_js_func(self.__class__.js_func_name, self.js_func_params) 335 | def generate_js_statement_as_closure(self): 336 | return self.generate_js_statement() 337 | 338 | class BaseJsNode(BaseJsTpl): 339 | def __init__(self, django_node = None, parent_js_node = None): 340 | super(BaseJsNode, self).__init__(parent_js_node) 341 | self.django_node = django_node 342 | self._init_vars() 343 | self._init_sub_nodes() 344 | if parent_js_node: 345 | self.update_parent_context() 346 | def _init_vars(self): 347 | """ 348 | Registers and declares local and global variables used by current node 349 | """ 350 | return None 351 | def _init_sub_nodes(self): 352 | """ 353 | use scan_section to initialize any subnodes of the current node 354 | """ 355 | return None 356 | def scan_section(self, django_tpl_nodelist): 357 | """ 358 | loops through list of django nodes - usually child nodes - and converts them into list of initialized js nodes 359 | """ 360 | js_nodes = [] 361 | for django_tpl_node in django_tpl_nodelist: 362 | js_node = JsProcessorRegistry.get_js_node(django_tpl_node.__class__.__name__) 363 | if js_node: 364 | js_nodes.append(js_node(django_tpl_node, self)) 365 | return js_nodes 366 | 367 | def _extract_filter_expression(self, django_filter_expression): 368 | """ 369 | given a django FilterExpression object, register the containing variables and 370 | initialize any of the filters defined by the FilterExpression 371 | e.g. {{varName|add:"5"|default:10}} -> yzdjs_default(yzdjs_add(varName, 5), 10) 372 | """ 373 | var_obj = django_filter_expression.var 374 | filters = django_filter_expression.filters 375 | if isinstance(var_obj, django.template.Variable): 376 | var_expr = self.register_var_obj(var_obj) 377 | else: 378 | var_expr = '"%s"' % var_obj 379 | if filters: 380 | for django_filter, filter_args in filters: 381 | js_filter_class = JsProcessorRegistry.get_js_filter(django_filter.__name__) 382 | if js_filter_class: 383 | js_filter = js_filter_class(self, var_expr, filter_args[0]) 384 | var_expr = js_filter.render() 385 | return var_expr 386 | 387 | def _nodes_to_js_str(self, nodes_list, as_closure = False): 388 | """converts a list of nodes, generally child nodes to a javascript expression""" 389 | #optimization to check if there is only one node, if so then we could potentially forgo the declaration of a 390 | #closure and improve performance a little bit 391 | #e.g. instead of function(cond, another_cond){if(cond){return [function(another_cond){if(another_cond){return 'test string'}}(another_cond)].join("")}}(cond, another_cond) 392 | #we could save a join() and anonymous function call as well as a few characters by doing: 393 | #function(cond, another_cond){if(cond){if(another_cond){return 'test string'}}(cond, another_cond) 394 | if len(nodes_list) == 1: 395 | if as_closure: 396 | rendered_nodes = nodes_list[0].render(as_closure = True) 397 | else: 398 | rendered_nodes = nodes_list[0].render() 399 | else: 400 | if as_closure: 401 | rendered_nodes = self._list_to_js_str(nodes_list) 402 | else: 403 | rendered_nodes = 'return ' + self._list_to_js_str(nodes_list) 404 | #updates the parent context so it knows of what variables its child is using 405 | self.update_parent_context(parent_same_closure = not as_closure) 406 | return rendered_nodes 407 | def __str__(self): 408 | return self.render(as_closure = True) 409 | def generate_js_statement_as_closure(self): 410 | return self._wrap_expr_in_js_anon_func(js_expr = self.generate_js_statement()) 411 | 412 | class TemplateJsNode(BaseJsNode): 413 | """Main node that represents a document and contains all subnodes""" 414 | def __init__(self, django_template_string = None, django_template_path = None, var_list = None): 415 | self.var_list = var_list 416 | if django_template_string: 417 | django_tpl_node = Template(django_template_string) 418 | elif django_template_path: 419 | django_tpl_node = get_template(django_template_path) 420 | else: 421 | raise NameError('neither django_template_string nor django_template_path is defined, at least one needs to be set') 422 | JsNodeContext.reset_global_context() 423 | super(TemplateJsNode, self).__init__(None) 424 | self.js_nodes = self.scan_section(django_tpl_node.nodelist) 425 | 426 | def generate_js_statement(self): 427 | js_expr = '' 428 | implicit_vars = self.context.get_vars_of_type('implicit') 429 | if len(implicit_vars): 430 | js_expr += 'var ' + ','.join(implicit_vars) + ';' 431 | js_expr += self._nodes_to_js_str(self.js_nodes) 432 | rendered_content = self._wrap_expr_in_js_anon_func(js_expr = js_expr, execute_now = False, var_list = self.var_list, show_params = True) 433 | rendered_content = strip_spaces_between_tags(rendered_content) 434 | return remove_whitespaces.sub(' ', rendered_content.strip()) 435 | 436 | def generate_js_statement_as_closure(self): 437 | return self.render() 438 | 439 | class VariableJsNode(BaseJsNode): 440 | """ 441 | processes and renders a django variable tag i.e. {{ variable_name }} or 442 | {{ variable_name|filter1|filter2... }} 443 | 444 | Examples: 445 | >>> ############### 446 | >>> #test django VariableNode behaves as expected 447 | >>> ############### 448 | >>> from django.template import Template 449 | >>> django_tpl = Template('{{ var }}') 450 | >>> django_tpl.nodelist[0].filter_expression.__dict__ 451 | {'var': , 'token': u'var', 'filters': []} 452 | >>> django_tpl = Template('{{ var.subvar }}') 453 | >>> django_tpl.nodelist[0].filter_expression.__dict__ 454 | {'var': , 'token': u'var.subvar', 'filters': []} 455 | >>> django_tpl.nodelist[0].filter_expression.var.__dict__ 456 | {'var': u'var.subvar', 'literal': None, 'translate': False, 'lookups': (u'var', u'subvar')} 457 | >>> ############### 458 | >>> #test VariableJsNode 459 | >>> ############### 460 | >>> js_tpl = TemplateJsNode('{{ var }}') 461 | >>> js_tpl.render() 462 | u'function(var){return var}' 463 | >>> js_tpl = TemplateJsNode('{{ var.subvar }}') 464 | >>> js_tpl.render() 465 | u'function(var){return var.subvar}' 466 | """ 467 | expected_node_classname = ['VariableNode', 'DebugVariableNode'] 468 | def _init_vars(self): 469 | django_filter_expression = self.django_node.filter_expression 470 | self.var_expr = self._extract_filter_expression(django_filter_expression) 471 | self.update_parent_context() 472 | def generate_js_statement(self): 473 | return 'return ' + self.var_expr 474 | def generate_js_statement_as_closure(self): 475 | return self.var_expr 476 | JsProcessorRegistry.register_js_node(VariableJsNode) 477 | 478 | class TextJsNode(BaseJsNode): 479 | """ 480 | Converts django's TextNode to javascript expression. This node handles the strings between the tags and filters 481 | Examples: 482 | >>> ############### 483 | >>> #test django TextNode behaves as expected 484 | >>> ############### 485 | >>> from django.template import Template 486 | >>> django_tpl = Template('text string') 487 | >>> django_tpl.nodelist[0].__dict__ 488 | {'source': (, (0, 11)), 's': u'text string'} 489 | >>> django_tpl.nodelist[0].s 490 | u'text string' 491 | >>> ############### 492 | >>> #test TextJsNode 493 | >>> ############### 494 | >>> js_tpl = TemplateJsNode('text string') 495 | >>> js_tpl.render() 496 | u'function(){return "text string"}' 497 | """ 498 | expected_node_classname = 'TextNode' 499 | def _init_vars(self): 500 | self.text = self.django_node.s 501 | def generate_js_statement(self): 502 | return 'return ' + self.generate_js_statement_as_closure() 503 | def generate_js_statement_as_closure(self): 504 | return '"' + self.text.replace('"', '\\"') + '"' 505 | JsProcessorRegistry.register_js_node(TextJsNode) 506 | 507 | #loads all of the filters in the defaulttags and defaultfilters folders, this makes it easy to drop in new tags and filters 508 | #as they are being created 509 | from yz_js_django_tpl.defaulttags import * 510 | from yz_js_django_tpl.defaultfilters import * --------------------------------------------------------------------------------