Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge back to main repo #1

Open
wants to merge 50 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
38b84f5
Added template resolver stub
Oct 28, 2011
e88fee7
Breaking everything :(
Oct 28, 2011
f2d8577
Fixing a few of the bugs, nested templates still not working. Sort is…
arobson Oct 30, 2011
97e5a0f
Updating progress
arobson Nov 7, 2011
57bf4d9
Updates to replicant
arobson Nov 29, 2011
3653cbf
Updates to replicant and better error prevention for edge cases
arobson Nov 30, 2011
6d28f9b
Merging with bug fixes from master and other bugs. Nested templating …
arobson Nov 30, 2011
ca21cd3
Remote templates working with infuser.
arobson Nov 30, 2011
b3d4e2f
Updates and bug fixes.
arobson Dec 2, 2011
a6ad2b0
working example
arobson Feb 22, 2012
4fb6563
consolidating fqn creation
arobson Feb 22, 2012
f8ff0ca
pre-overhaul
arobson Feb 23, 2012
18bb05e
nearing a working prototype
arobson Feb 23, 2012
a574a77
Flat test passes
arobson Feb 24, 2012
0bf73a6
simplest cases passing
arobson Feb 24, 2012
ad6c366
Fixed broken expectation
arobson Feb 24, 2012
d94bd07
In-place, external templating passing tests.
arobson Feb 24, 2012
f1fa892
External item templates work in collections
arobson Feb 24, 2012
72ffe6c
Pre cleanup
arobson Feb 24, 2012
69cf5f4
Working on clean up and adding new feature.
arobson Feb 24, 2012
25e92e6
Additional refactor to eliminate duplication in code.
arobson Feb 25, 2012
e83ec77
Fixed bugs in add and update features and added tests.
arobson Feb 25, 2012
51715de
Rewrite of Postal API, readme update and correction of how control id…
arobson Feb 26, 2012
d0eddba
Removing commented out code and most console.logs
arobson Feb 26, 2012
c1eb735
Pre perf branch
arobson Feb 27, 2012
0588492
Rewrite of template
arobson Feb 28, 2012
28602bb
Tests passing but stack problems exist in sample app.
arobson Feb 29, 2012
100ba87
Fixed stack issues (thanks @ifandelse)
arobson Feb 29, 2012
680bd57
Fixing iteration issue
arobson Mar 1, 2012
d1220bd
Rewriting the tests to run in firefox ... because it doesn't have out…
arobson Mar 1, 2012
a721757
Updating tests to make Firefox happier
arobson Mar 1, 2012
ff04735
Reverted element creation to DOM vs string concatenation.
arobson Mar 1, 2012
6296760
Cleaning up and removing ugly features.
arobson Mar 1, 2012
4bd269c
Fixed event problem
arobson Mar 1, 2012
4d72d37
Major read me overhaul
arobson Mar 1, 2012
471b007
Merging in perf branch
arobson Mar 1, 2012
4b5aa1f
Merge with perf branch
arobson Mar 2, 2012
6872400
fixed bug with callback order
arobson Mar 2, 2012
b932097
Fixing broken tests
arobson Mar 2, 2012
089ba3e
Improving IMG tag support.
arobson Mar 2, 2012
3b700dd
Better Anchor support and bug fixes for layout only tags.
arobson Mar 5, 2012
d5d2ab0
Fixed another stack bug
arobson Mar 5, 2012
0058159
Initial rewrite. Iteration busted.
arobson Mar 6, 2012
63563a8
breadcrumb ... rewriting all the fork join to use a single fork join …
arobson Mar 7, 2012
3817700
Ripped out event handling, refactored all classes to use prototypes a…
arobson Mar 7, 2012
4f632e0
Added better handling for models with missing properties and 'rootles…
arobson Mar 7, 2012
c08015a
Added better inference around __value__ properties and anchor tags. A…
arobson Mar 7, 2012
c904e18
Changing api 'apply' call to 'render'
arobson Mar 8, 2012
37fce2b
Updating readme
arobson Mar 8, 2012
c3cc22f
Fixing bug in anchor support and adding zipped output to the build.
arobson Mar 8, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
node_modules/
test/node_modules/
.idea/
pavlov/
index.html
./pavlov
index.html
76 changes: 76 additions & 0 deletions PostalAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Postal Topology


## Map
exchange cartographer
topic api
#### Message Properties
name template
operation "map"

## Apply
exchange cartographer
topic api
#### Message Properties
name template name
template template instance id
model object to base the render on
operation "apply"
### Responds With
exchange cartographer
topic render.{templateId}
#### Message Properties
template template instance id
markup the generated DOM
operation "render"

## Add
exchange cartographer
topic api
### Message
template the template instance id
id the fully qualified id of the list control to add to
model object to create the new item template from
operation "add"
### Responds With
exchange cartographer
topic render.{templateId}
#### Message Properties
template template instance id
parent the fully qualified id for the parent
markup the generated DOM for the new item
operation "add"

## Update
exchange cartographer
topic api
#### Message Properties
template the template instance id
id the fully qualified id of the control to update
model object to update the control template with
operation "update"
### RespondsWith
exchange cartographer
topic render.{templateId}
#### Message Properties
template template instance id
id the fully qualified id of the item
markup the generated DOM for the new item
operation "update"

## Append Resolver
exchange cartographer
topic api
#### Message Properties
order "append"
operation "resolver"
resolver callback in the form of function( name, onSuccess, onFailure )

## Prepend Resolver
exchange cartographer
topic api
#### Message Properties
order "prepend"
operation "resolver"
resolver callback in the form of function( name, onSuccess, onFailure )
template template instance id
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Cartographer
Cartographer is an HTML templating engine that works with plain HTML and simple JSON. It does this by inferring intention based on the structure of the markup and JSON. Cartographer will never place the generated markup on the page, it hands it back to the provided callback.

Though Cartographer's performance should be sufficient for most applications, several trade-offs were made in order to make the advanced features (not provided by the majority of other template engines) possible.

## Philosophy
I wanted to create a templating engine that would compliment more complex, asynchronous applications in the browser. Cartographer's APIs are built to provide responses to user provided callbacks. It has been designed to be complimentary with other OSS JS Libraries that encourage decoupled application design: (infuser, postal and machina specifically).

## Advanced Features
* Generate partial templates
* Nest templates
* Specify external templates in the markup or the model
* Generate new markup for new collection items
* Fully qualified namespacing for elements that map to the model
* A flexible way to define custom sources for templates
* Integration with Postal for messaging
* all API methods supported through Postal topography
* Configurable element identifier
* The default attribute used to identify the element for Cartographer is "data-id"
* Configurable template attribute
* The default attribute used to specify a replacement template is "data-template"

## Concepts
Cartographer creates named templates based on the name of the template source. If your source is an external file, the name of the template is the name of the file. If the source of the template is a DOM element then the data-template property would match the template name.

Once the template has been created, it can be used to create instances which must be uniquely identified (more on why later). The rendered instance is give fully qualified, hierarchical namespaces for all of the controls that had a data-id (mapped to a model property).

Partial renders can be done as an add or update. Adding is intended to support rendered a specific item template for a collection when new elements are added to the model that produced the original render. Updates allow you to regenerate the template, starting at any level, in order to get updated markup for the section of the template that should be reproduced.

## API

### Map - cartographer.map(templateName)
Creates a template from a named HTML source that will be used to generate markup from JSON.

### Apply - cartographer.render( templateName, templateId, model, onMarkup )
Renders the templateName and assigns the rendered instance templateId. The template instance id becomes the top of the fqn for all controls in the rendered instance. onMarkup is a function with 3 arguments: template id, markup and operation ( included for integration purposes ).

### Add - cartographer.add( templateId, containerControlFqn, model, onMarkup )
Adding creates a new set of markup for an existing template / model collection. The containerControlFqn is the fully qualified name of the containing list control. The other arguments are like the 'render' call.

### Update - cartographer.update( templateId, targetFqn, model, onMarkup )
Updating creates a new render at any level in the template hierarchy. The targetFqn is the level of the DOM template where the render should begin.

### Adding Template Resolvers - cartographer.resolver.appendSource|prependSource( resolverFunction )
Cartographer looks for the HTML using the template name. It searches its resolvers, in order, and by default has resolvers for in-page and infuser. If you glance at template-source.coffee under the spec folder, you'll see that it's trivial to create these and provide markup from any source you like.

You can add a resolver and control when it will be checked by either appending or prepending your resolver to the list.

The resolver has the signature function(name, success, fail). It uses the name argument to search for an HTML template based on the name and calls success with the HTML when it's found or fail when something tanks.

### Changing The Element Id Attribute

By default, Cartographer uses 'data-id' as the means to identify and correlate a DOM element to your model. This can be changed as follows:

cartographer.config.elementIdentifier

## Examples
Currently, there aren't a lot of great examples but the unit tests actually show all of the features.

## Dependencies

Cartographer has a number of dependencies which you must either include on your page OR have available via some AMD system (I've only tested it against Require at this point).

If you happen to use require, here are the path aliases it expects:

'jQuery', 'underscore', 'infuser', 'DOMBuilder'
17 changes: 14 additions & 3 deletions build.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@
"ext": "ext",
"lint": {},
"uglify": {},
"wrap":{
"prefix": "(function(context) {",
"suffix": "})(window);"
"gzip": {},
"hosts": {
"/": "./html",
"/play": "./play",
"/spec": "./spec"
},
"finalize": {
"header-file": "./header.txt"
},
"name": {
"amd.js": "cartographer.amd.js",
"browser.js": "cartographer.js",
"adapter.js": "postal.adapter.js",
"adapter.amd.js": "postal.adapter.amd.js"
}
}
188 changes: 188 additions & 0 deletions ext/infuser-0.2.0.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
define(['jquery', 'trafficcop'],function($,trafficcop) { // You might need to set up a config for require.js for the jquery/trafficcop dependencies to work, or replace it with an absolute path
/*
infuser.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.2.0
*/
var hashStorage = {
templates: {},

storeTemplate: function(templateId, templateContent) {
this.templates[templateId] = templateContent;
},

getTemplate: function(templateId) {
return this.templates[templateId];
},

purge: function() {
this.templates = {};
}
};
var scriptStorage = {
templateIds: [],
storeTemplate: function(templateId, templateContent) {
var node = document.getElementById(templateId);
if(node === null) {
this.templateIds.push(templateId);
node = document.createElement("script");
node.type = "text/html";
node.id = templateId;
document.body.appendChild(node);
}
node.text = templateContent;
},

getTemplate: function(templateId) {
return document.getElementById(templateId);
},

purge: function() {
for(var i = 0; i < this.templateIds.length; i++) {
document.body.removeChild(document.getElementById(this.templateIds[i]));
}
this.templateIds = [];
}
};
var errorHtml = "<div class='infuser-error'>The template <a href='{TEMPLATEURL}'>{TEMPLATEID}</a> could not be loaded. {STATUS}</div>",
returnErrorTemplate = function(status, templateId, templatePath) {
return errorHtml.replace('{STATUS}', status).replace('{TEMPLATEID}', templateId).replace('{TEMPLATEURL}', templatePath);
},
errors = [];
var helpers = {
getTemplatePath: function(templateOptions) {
var templateFile = templateOptions.templatePrefix + templateOptions.templateId + templateOptions.templateSuffix;
return templateOptions.templateUrl === undefined || templateOptions.templateUrl === "" ?
templateFile : templateOptions.templateUrl + "/" + templateFile;
},
templateGetSuccess: function(templateId, callback) {
return function(response) {
infuser.store.storeTemplate(templateId, response);
callback(infuser.store.getTemplate(templateId));
};
},
templateGetError: function(templateId, templatePath, callback) {
return function(exception) {
if($.inArray(templateId, errors) === -1) {
errors.push(templateId);
}
var templateHtml = returnErrorTemplate("HTTP Status code: " + exception.status, templateId, templatePath);
infuser.store.storeTemplate(templateId, templateHtml);
callback(infuser.store.getTemplate(templateId));
};
},
getAjaxOptions: function(templateOptions) {

}
},
infuserOptions = ['target','loadingTemplate','postRender','preRender','render','bindingInstruction','useLoadingTemplate','model','templateUrl','templateSuffix','templatePrefix',''];
var infuser = {
storageOptions: {
hash: hashStorage,
script: scriptStorage
},

store: hashStorage,

defaults: {
// Template name conventions
templateUrl: "",
templateSuffix: ".html",
templatePrefix: "",
// AJAX Options
ajax: {
"async": true,
"dataType": "html",
"type": "GET"
},
// infuse() specific options - NOT used for "get" or "getSync"
target: function(templateId) { return "#" + templateId }, // DEFAULT MAPPING
loadingTemplate: {
content: '<div class="infuser-loading">Loading...</div>',
transitionIn: function(target, content) {
var tgt = $(target);
tgt.hide();
tgt.html(content);
tgt.fadeIn();
},
transitionOut: function(target) {
$(target).html("");
}
},
postRender: function(targetElement) { }, // NO_OP effectively by default
preRender: function(targetElement, template) { }, // NO_OP effectively by default
render: function(target, template) {
var tgt = $(target);
if(tgt.children().length === 0) {
tgt.append($(template));
}
else {
tgt.children().replaceWith($(template));
}
},
bindingInstruction: function(template, model) { return template; }, // NO_OP effectively by default
useLoadingTemplate: true // true/false
},

get: function(options, callback) {
var templateOptions = $.extend({}, infuser.defaults, (typeof options === "object" ? options : { templateId: options })),
template;
templateOptions.ajax.url = helpers.getTemplatePath(templateOptions);
template = infuser.store.getTemplate(templateOptions.ajax.url);
if(!template || $.inArray(templateOptions.ajax.url, errors) !== -1) {
templateOptions.ajax.success = helpers.templateGetSuccess(templateOptions.ajax.url, callback);
templateOptions.ajax.error = helpers.templateGetError(templateOptions.templateId, templateOptions.ajax.url, callback);
$.trafficCop(templateOptions.ajax);
}
else {
callback(template);
}
},

getSync: function(options) {
var templateOptions = $.extend({}, infuser.defaults, (typeof options === "object" ? options : { templateId: options }), { ajax: { async: false } }),
template,
templateHtml;
templateOptions.ajax.url = helpers.getTemplatePath(templateOptions);
template = infuser.store.getTemplate(templateOptions.ajax.url);
if(!template || $.inArray(templateOptions.ajax.url, errors) !== -1) {
templateHtml = null;
templateOptions.ajax.success = function(response) { templateHtml = response; };
templateOptions.ajax.error = function(exception) {
if($.inArray(templateOptions.ajax.url) === -1) {
errors.push(templateOptions.ajax.url);
}
templateHtml = returnErrorTemplate("HTTP Status code: exception.status", templateOptions.templateId, templateOptions.ajax.url);
};
$.ajax(templateOptions.ajax);
if(templateHtml === null) {
templateHtml = returnErrorTemplate("An unknown error occurred.", templateOptions.templateId, templateOptions.ajax.url);
}
else {
infuser.store.storeTemplate(templateOptions.ajax.url, templateHtml);
template = infuser.store.getTemplate(templateOptions.ajax.url);
}
}
return template;
},

infuse: function(templateId, renderOptions) {
var templateOptions = $.extend({}, infuser.defaults, (typeof templateId === "object" ? templateId : renderOptions), (typeof templateId === "string" ? { templateId: templateId } : undefined )),
targetElement = typeof templateOptions.target === 'function' ? templateOptions.target(templateId) : templateOptions.target;
if(templateOptions.useLoadingTemplate) {
templateOptions.loadingTemplate.transitionIn(targetElement, templateOptions.loadingTemplate.content);
}
infuser.get(templateOptions, function(template) {
var _template = template;
templateOptions.preRender(targetElement, _template);
_template = templateOptions.bindingInstruction(_template, templateOptions.model);
if(templateOptions.useLoadingTemplate) {
templateOptions.loadingTemplate.transitionOut(targetElement);
}
templateOptions.render(targetElement, _template);
templateOptions.postRender(targetElement);
});
}
};
return infuser; });
Loading