In what contexts is interpolation legal in Angular, and why? - html

I know that Angular's string interpolation normally operates on expressions inside Handlebars-style {{ double curly braces }}, and by observation I know that I can use it in contexts like
text outside HTML tags: <span>{{ 'string literal expression ' }}</span>
attribute values inside HTML tags: link
and not to generate attributes themselves, i.e.
<a {{ 'href="/link/to/elsewhere"' }}>link</a>
does not get interpolated.
What I'm curious about is why: what are the rules on where interpolation does and doesn't happen, where this is documented, and what the design considerations or constraints are that led to this.
I guess this is because the document is parsed as HTML by the browser before Angular sees it, so the structure is dictated by HTML and the {{ stuff }} has to appear in places that are well-formed according to HTML even before interpolation happens. But I'd appreciate knowing the whole story.

What are the rules on where interpolation does and doesn't happen ?
Angular.js uses $compile service to compile a piece of DOM. The docs says:
The compilation is a process of walking the DOM tree and matching DOM elements to directives.
In the source code of compile.js there is a function collectDirectives, I trimmed it to show only the relevant code:
function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
var nodeType = node.nodeType;
// ....
switch(nodeType) {
case 1: /* Element */
// ....
// iterate over the attributes
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
// ....
addAttrInterpolateDirective(node, directives, value, nName);
// ....
}
// ....
break;
case 3: /* Text Node */
addTextInterpolateDirective(directives, node.nodeValue);
break;
case 8: /* Comment */
// ....
break;
}
directives.sort(byPriority);
return directives;
}
As you can see, $compile search for interpolated content only inside attributes and text nodes when it iterates a piece of DOM.
Those functions, addTextInterpolateDirective and addAttrInterpolateDirective "translate" the interpolated expression into directives that $watch interpolated expressions and update the DOM element.
Where this is documented?
The compilation phase is documented here: http://docs.angularjs.org/guide/compiler
It's getting better every day but still some in-depth stuff are not clear until you read the source code itself. I guess some things are just too compilcated to explain without showing the code.
What the design considerations or constraints are that led to this?
I guess there are two reasons:
Angular operates on DOM nodes rather than strings, If angular needed to interpolate attributes or elements then It should operate on html strings which is probably bad for performance.
There is no major use case for such things.
If you still want to interpolate everything, do that kind of magic inside a directive:
An example:
app.directive('myAnchor',function(){
return {
restrict: "E",
transclude: true,
link: function(scope,element,attrs,ctrl,$transclude) {
attrs.$observe('interpolate', function(val){
var e = angular.element("<a " + val + "></a>");
$transclude(scope,function(clone){
e.append(clone);
});
element.replaceWith(e);
});
}
};
});
Be sure to read this:
What is the difference between the $parse, $interpolate and $compile services?

Related

How can I render a tr with soy templates?

I've got this soy template
{template .myRowTemplate}
<tr><td>Hello</td></tr>
{/template}
and I want to do something like
var myTable = goog.dom.createElement("table");
goog.dom.appendChild(myTable, goog.soy.renderAsFragment(mytemplates.myRowTemplate));
goog.dom.appendChild(myTable, goog.soy.renderAsFragment(mytemplates.myRowTemplate));
But that causes
Uncaught goog.asserts.AssertionError
Assertion failed: This template starts with a <tr>,
which cannot be a child of a <div>, as required by soy internals.
Consider using goog.soy.renderElement instead.
Template output: <tr><td>Hello</td></tr>
What's the best way to do this?
Why it fails
Right, the documentation of renderAsFragment is a bit confusing; it reads:
Renders a Soy template into a single node or a document fragment. If the rendered HTML string represents a single node, then that node is returned
However, the (simplified) implementation of renderAsFragment is:
var output = template(opt_templateData);
var html = goog.soy.ensureTemplateOutputHtml_(output);
goog.soy.assertFirstTagValid_(html); // This is your failure
var safeHtml = output.toSafeHtml();
return dom.safeHtmlToNode(safeHtml);
So why do the closure author assert that the first tag is not <tr>?
That's because, internally, safeHtmlToNode places safeHtml in a temporary div, before deciding if it should return the div wrappper (general case) or the only child (if the rendered HTML represents only one Node). Once again simplified, the code of safeHtmlToNode is:
var tempDiv = goog.dom.createElement_(doc, goog.dom.TagName.DIV);
goog.dom.safe.setInnerHtml(tempDiv, html);
if (tempDiv.childNodes.length == 1) {
return tempDiv.removeChild(tempDiv.firstChild);
} else {
var fragment = doc.createDocumentFragment();
while (tempDiv.firstChild) {
fragment.appendChild(tempDiv.firstChild);
}
return fragment;
}
renderAsElement won't work either
And I'm unsure what you are asking for fragments, but unfortunately goog.soy.renderAsElement() will behave the same because it also uses a temporary div to render the DOM.
renderElement cannot loop
The error message suggests goog.soy.renderElement, but that will only work if your table has one row, since it replaces content, and doesn't append children nodes.
Recommended approach
So usually, we do the for loop in the template:
{template .myTable}
<table>
{foreach $person in $data.persons}
<tr><td>Hello {$person.name}</td></tr>
{/foreach}
</table>
{/template}
Of course, we can keep the simple template you have for one row and call it from the larger template.

How to build Regular Expression for more strict Anchor tag

I am trying to restrict some of the attributes that can go with anchor tag in HTML for Markdown editor. This is what I have right now:
/^(<a\shref="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&##\/%?=~_|!:,.;\(\)]+"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i
This allows an anchor tag with title and href attributes but nothing else. I also wanted to add target but whatever I tried didn't work.
The link must contain an href attribute.
It can contain title attribute but doens't have to.
It can contain target attribute but doesn't have to too.
How can I modify the Regex above which satisfies all the conditions above.
Usually regexes aren't the best tool for parsing some languages. On the other hand, if you just want to match an isolated anchor tag, then a parser could be a little too much, and regexes can do a decent job.
To match an anchor tag to your requirements, you can use the regex below. It uses backreferencing to keep track of at most one title and one target (duplicated attributes), also at least/most one href:
^(<a(?=[^>]*?(\s+href="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&##\/%?=~_|!:,.;\(\)]+")[^>]*>)(?=([^>]*?(\s+title="[^"<>]+"))?[^>]*>)(?=([^>]*?(\s+target="[^"<>]+"))?[^>]*>)(\2(\6\8?)?|\2\8\6?|\6\2\8?|\8\2\6?|\6\8\2|\8\6\2)\s*>[^<]*</a>)$
Check the demo here, along with dozens of test cases which you can proof test the solution.
Observe this regex is not so complicated (the "ugly" part is the URL, really), but it is very comprehensive and takes care of:
a mandatory href attribute
an optional title attribute
an optional target attribute
no other type of attribute is allowed (altought it would be easy to customize and add support)
any number of spaces between them
they can come in any order
there can be at most one href, one title and one target.
RegEx-en by themselves aren't really good at this kind of thing.
I'd do something like:
function validateAnchor(anchor){
var match,
name,
value,
test,
attrRE=/\s([a-z]+)(?:\s*=\s*"([^"]+))?"/gi, // matches one tag attribute
allowedAttrs={ // attributes must appear here to be considered legal
href:{
isValid:function(val){
return isValidURL(val);
}
},
title:{
isValid:function(val){
return true;
}
},
target:{
isValid:function(val){
return true;
}
}
},
result=true;
while(match=attrRE.exec(anchor)){
name=match[1].toLowerCase(); // lowerCase to match our allowedAttrs keys
value=match[2]||''; // optional
// must have a name
if(!name){
console.log('no name for this attr - should not happen!');
result=false;
break;
}
// and must exist in allowedAttrs
if(test=allowedAttrs[name]) {
console.log('unknown attr');
result=false;
break;
}
// if it has a value and there is am isValid function.
if(value && 'function'==typeof(attr.isValid)){
if(!attr.isValid(value)){ // which fails!
result=false;
break;
}
}
}
return result;
}
So, that given:
var anchor='<a href=\"...\" target = \"...\" foo >';
validateAnchor(anchor) will fail since 'foo' is a disallowed attribute (not defined in allowedAttrs).
The benefits of this approach are that you
don't need to modify your RE each time you need to accept a new attribute,
can have value-less attributes
I leave isValidURL() for you to define.
Let's make it easier:
/<a(?=.*href="((ht|f)tps?:\/)?\/.*")\s*((href|title|target)="[^"]*"\s*)*>[^<]*</a>/

JQuery selectors - using html snippets as "context" in filter and find

A quick question about using context with Jquery selectors:
I'm trying to grab the text from a div element that has id="time". Can a HTML snippet be used as context in the following:
// An AJAX request here returns a HTML snippet "response":
var myTime = $("#time", response).text();
The reason I'm doing this is that I want the time variable from within the html held in response, but don't want the overhead of loading all of the html into the DOM first. (it's a large amount of html).
From the comments what I understand is the response is <span id="time">blah blah</span> which means the element time is the root variable itself, that is why the child lookup is not working.
var response = '<span id="time">blah blah</span>';
var myTime = $(response).text(); // Or $(response).filter("#time").text();
alert(myTime)
Demo: Fiddle
This method uses filter() rather than find(), the difference being:
filter() – search through the passed element set
find() – search through all the child elements only.
Did you try it?
$("#time", "<div><span id=time></span></div>")[0].id //returns 'time'
From the jQuery source code:
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
so valid selectors should work in the context parameter. Personally, I prefer using find to begin with because it keeps all the selectors in the same order instead of $("second > third", "first");

Using controller-scoped data in a directive's jqlite-generated html

This question is similiar to them one asked in Mike's post Using ng-model within a directive.
I am writing a page which is small spreadsheet that displays calculated output based on user input fields. Using a directive, I'm making custom tags like this:
<wbcalc item="var1" title="Variable 1" type="input"></wbcalc>
<wbcalc item="var2" title="Variable 2" type="input"></wbcalc>
<wbcalc item="calc" title="Calculation" type="calc"></wbcalc>
The 'item' field references scoped data in my controller:
$scope.var1 = '5'; // pre-entered input
$scope.var2 = '10'; // pre-entered input
$scope.calc = function() {
return parseInt($scope.var1) + parseInt($scope.var2);
};
And the 'type' field is used in the directive's logic to know whether to treat the item as a string or a function.
Here's a fiddle for this: http://jsfiddle.net/gregsandell/PTkms/3/ I can get the output elements to work with the astonishing line of code:
html.append(angular.element("<span>")
.html(scope.$eval(attrs.item + "()"))
);
...and I'm using this to get my inputs connected to my scoped controller data (I got this from Mike's post:
var input = angular.element("<input>").attr("ng-model", attrs.item);
$compile(input)(scope);
html.append(input);
...while it does put the values in the fields, they aren't bound to the calculation, as you can see by changing inputs in my fiddle.
Is there a better and/or more intuitive way to link my controller-scoped data to the jqlite-generated html in my directive?
Take a look at this, I think you can simplify the process a fair bit.
http://jsfiddle.net/PTkms/4/
angular.module('calculator', []).directive('wbcalc', function($compile) {
return {
restrict: 'E',
template: '<div><div class="span2">{{title}}</div><input ng-model="item"></div>',
scope: {
title: '#',
item: '='
},
link: function(scope, element, attrs) {
// Don't need to do this.
}
}
});
function calcCtrl($scope) {
$scope.var1 = '5';
$scope.var2 = '10';
$scope.calc = function() {
// Yes, this is a very simple calculation which could
// have been handled in the html with {{0 + var1 + var2}}.
// But in the real app the calculations will be more
// complicated formulae that don't belong in the html.
return parseInt($scope.var1) + parseInt($scope.var2);
};
}
I know you said you like jQuery - but to make best use of Angular you need to think in an Angular way - use bindings, don't manipulate the DOM directly etc.
For this example, it would be helpful to read up on the isolated scope bindings used - '#' and '=', see:
http://docs.angularjs.org/guide/directive

JQuery weird syntax

I'm new to JQuery and trying to use it to dynamically build HTML based on results of a query for JSON objects. Anyways on the JQuery API site (http://api.jquery.com/jQuery.getJSON/) I found this example where I don't understand the syntax and I can't seem to find any explanation of why this syntax is legal or how to use it.
$.getJSON('ajax/test.json', function(data) {
var items = [];
$.each(data, function(key, val) {
items.push('<li id="' + key + '">' + val + '</li>');
});
// *** THIS IS THE PART THAT IS WEIRD ***
$('<ul/>', {
'class': 'my-new-list',
html: items.join('')
}).appendTo('body');
});
Can someone refer me to documentation that explains the syntax with the comment above it?
$('<ul/>')
Creates a new UL element not attached to the DOM
$('<ul/>', {'class':'my-new-list'})
Sets DOM variables for that element using a key value pair. So now you have a UL element with a class of my-new-list.
$('<ul/>', {'class':'my-new-list', 'html': items.join('')})
This is taking the array of LI elements created above joining the elements together in a string (with no delimiter in this case, .join('-') would have put a hyphen between each LI element) and assigning to the inner html of the UL.
$('<ul/>', {'class':'my-new-list', 'html': items.join('')}).appendTo('body');
Last but not least, it appends the newly created UL element with this child LI elements to the BODY element making it visible on the page.
$('<ul/>', {
'class': 'my-new-list',
html: items.join('')
}).appendTo('body');
This simply creates a new UL element with a class of 'my-new-list' and the contents of items. It will create a structure like:
<ul class='my-new-list'>
<li id="key1">val1</li><li id="key2">val2</li><li id="key3">val3</li>
</ul>
This is simply short hand for:
$('<ul></ul>').attr('class', 'my-new-list').attr('html', items.join('')).appendTo('body');
Documentation: http://api.jquery.com/jQuery/#jQuery2
Check out the documentation for jQuery ($). Specifically, look at the part dealing with the overload jQuery( html, props ):
html: A string defining a single, standalone, HTML element (e.g. or ).
props: An map of attributes, events, and methods to call on the newly-created element.
Further down there is more information:
As of jQuery 1.4, the second argument to jQuery() can accept a map
consisting of a superset of the properties that can be passed to the
.attr() method. Furthermore, any event type can be passed in, and the
following jQuery methods can be called: val, css, html, text, data,
width, height, or offset. The name "class" must be quoted in the map
since it is a JavaScript reserved word, and "className" cannot be used
since it is not the correct attribute name.
See the jQuery documentation here. Specifically the section entitled:
Creating New Elements
This section contains details on the use of the jQuery( html, props ) overload, added in version 1.4. This is the overload used in your example. It takes an html string argument which is used to create a new DOM element and then adds the attributes contained in the props object argument.
That means you're appending a <ul> element with the my-new-list class to the body. The <li> items you've constructed in the items array are then added to the <ul>, so you'll end up with something like:
<ul class="my-new-list">
<li id="key1">val1</li><li id="key2">val2</li>...
</ul>
$('<ul/>', {
'class': 'my-new-list',
html: items.join('')
}).appendTo('body');
In jQuery, besides searching for DOM elements, you can also create them.
$('<ul/>') // or $('<ul></ul>')
This makes a new <ul> elements and returns it as a jQuery object.
The object passed as the 2nd parameter sets its attributes. Thus making this:
<ul class="my-new-list"><li id="one">O Hai</li></ul>
This <ul> is then appended to the body.
The part you don't understand is a completely different usage of the $ function.
In most cases you use $ function as a selector which returns a set of jquery elements (dom nodes wrapped as jquery objects) which you can perform some kind of operation on.
In this case you are instead using the $ function to build DOM nodes (actually, jquery objects) and then those objects you can perform functions on, such as appendTo in the example shown.
It is pretty much equavalent to writing something like
var node = document.createElement("ul");
node.setAttribute("class", "my-new-list");
node.innerHTML = items.join('');
document.body.appendChild(node);