Marionette templateHelper is escaping html chars - backbone.js

I have a View that's model includes a small array. I want to output each array item wrapped in it's own <span>, but when using the template helper in the template, I am getting the html chars escaped and it's outputted as <span>
Is there a way to prevent that from happening?
templateHelpers: function () {
return {
tagsHelper: function(){
var t = "";
this.tags.forEach(function(tag)
{
t+="<span>"+tag+"</span>";
});
return t;
},
}
},
and
<script type="text/template" id="font-list-item">
<td class="alias"><%- name %></td>
<td class="tags"><%- tagsHelper() %></td>
</script>

Marionette uses underscore.js#templates behind the scenes to render it's templates. This template-system supports two ways of printing variables:
<%- variable %> will output the variable escaped
<%= variable %> will output the variable unescaped
In your first example, you're using the escaped one (<%- ... %>) which will output characters like:
< as <
> as >
& as &
etc
In order to be able to use the templateHelper in your example, you should render the tagsHelper as <td class="tags"><%= tagsHelper() %></td> (since it contains HTML which shouldn't be escaped).
However, in some situations you should be aware of possible XSS attacks while rendering variables unescaped. Therefore, it's better make sure the variables themselves are escaped. Your example could be rewritten as:
templateHelpers: function () {
return {
tagsHelper: function(){
var t = "";
this.tags.forEach(function(tag) {
// note the _.escape(..) below
t += "<span>" + _.escape(tag) + "</span>";
});
return t;
}
}
}
Howeverrr, I'd suggest to go for your second solution, with the html-markup inside your template, since it makes more sense to type html insde a template :-) Note the tag itself should be escaped though (so use the <%- ... %> syntax)

I was able to get it to work with a different approach, using an in-template logic block, but I would still really like to know how to fix the original problem, in case I find myself needing to use templateHelpers in the future.
<script type="text/template" id="font-list-item">
<td class="alias"><%- name %></td>
<td class="tags">
<% tags.forEach(function(tag){ %>
<span><%= tag %></span>
<% }); %>
</td>
</script>

Related

Replace all ↵ for a new line in angular loop

I have a loop currently in my application located here:
<tr ng-repeat="audit in audctrl.audits | orderBy:'-created_at'">
<td>
{{audit.objects}}
</td>
</tr>
The object's code shows a lot of text, but with a lot of the ↵ symbol.
How would I go about making these replaced?
I have tried
{{audit.object.replace(/\u21B5/g,'<br/>')}}
What would be the best method to make this work?
I would suggest you to write an angular filter. For example:
myApp.filter('multiline', function () {
return function(text) {
return text.replace(/\n/g, '<br>');
}
});
and then call it with pipe:
<tr ng-repeat="audit in audctrl.audits | orderBy:'-created_at'">
<td>
{{audit.objects | multiline}}
</td>
</tr>
#Edit
I'm not sure about your new line character code but you can easy change it. Angular filters are good because they are reusable in other parts of application.
#Edit2
To display html tags in Angular binding u have to use $sce service and ng-bind-html="audit.objects | multiline" instead of {{audit.objects | multiline}}
http://jsfiddle.net/c1qwg776/
Write this in your controller at initialisation:
for(var i=0;i<audctrl.length;i++){
for(var j=0;j<audctrl[i].objects.length;j++){
if(audctrl[i].objects.charCodeAt(j)===10){
var temp=audctrl[i].objects.substring(0,j);
temp+="<br/>";
audctrl[i].objects= temp+audctrl[i].objects.substring(j+5,audctrl[i].objects.length-1);
}
}
}

Grails GSP Loop through an index and do somthing with selected lines

In an Index-gsp, I want to be able to select an arbitrary number of lines and then by clicking a link send all those lines to a controller for processing e.g. creating new objects of a different kind.
I've no idea how selection can be done or how to collect these selected lines in a GSP. Maybe I should use a checkbox on each line if that's possible?
It's a list of products which is displayed using a modified index.gsp.
Each product-line has a checkbox in front.
What I want is to make a list of the products that are checked an then transmit this list to a controller.
a part of this index.gsp:
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
<li><g:link class="create" action="createOffer"><g:message code="default.new.label" args="[entityName]" params="toOffer" /></g:link></li>
</ul>
</div>
<div id="list-prodBuffer" class="content scaffold-list" role="main">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<table>
<thead>
<tr>
<td> Välj</td>
<td> ID</td>
</tr>
</thead>
<tbody>
<g:each in="${prodBufferList}" status="i" var="prodBuffer">
<tr class="${ (i % 2) == 0 ? 'even': 'odd'}">
<td><g:checkBox name="toOffer" value="${prodBuffer.id}" checked="false" /></td>
<td>${prodBuffer.id}</td>
So this not an ordinary form, just a list where I want to use a link to transmit it to the controller.
I'm a beginner and have no idea how to do it.
You can collect all necessary data from page using javascript, and then send all data to your controller for processing.
There are a lot of ways to do it.
For example send via JQuery:
<script>
//some code
var items = [1,2,3];
//some code
$('#add-location').click(function () {
$.ajax({
type: "POST",
url: "${g.createLink(controller:'myController', action: 'myControllerMethod')}",
data: {items: items},
success: function (data) {
console.log(data)
}
});
});
</script>
I will answer this but have to slow down since it feels like i am beginning to write your project:
In gsp you will need to have a hidden field followed by a check box amongst data you are trying to capture, checkbox should contain all the data elements required to build your output.
<g:hiddenField name="userSelection" value=""/>
<g:checkBox name="myCheckBox" id='myCheckBox' value="${instance.id}"
data-field1="${instance.field1}" data-field1="${instance.field1}"
checked="${instance.userSelected?.contains(instance.id)?true:false}" />
In the java script segment of the page you will need to add the following
This will then auto select selection and add to javascript array
// Customized collection of elements used by both selection and search form
$.fn.serializeObject = function() {
if ($("[name='myCheckBox']:checked").size()>0) {
var data=[]
$("[name='myCheckBox']:checked").each(function() {
var field1=$(this).data('field1');
var field2=$(this).data('field2');
data.push({id: this.value, field1:field1, field2:field2 });
});
return data
}
};
Most importantly will your data sit across many different gsp listing pages if so you will need to hack pagination:
//Modify pagination now to capture
$(".pagination a").click(function() {
var currentUrl=$(this).attr('href');
var parsedUrl=$(this).attr('href', currentUrl.replace(/\&userSelection=.*&/, '&').replace(/\&userSelection=\&/, '&'));
var newUrl=parsedUrl.attr('href') + '&userSelection=' + encodeURIComponent($('#userSelection').val());
window.location.href=newUrl
return false;
});
Then in the controller parse the JSON form field and make it into what you want when posted
def u=[]
def m=[:]
if (params.userSelection) {
def item=JSON.parse(params.userSelection)
item?.each {JSONObject i->
// When field1 is null in JSON set it as null properly
if (JSONObject.NULL.equals(i.field1)) {
i.field1=null
}
if (resultsGroup) {
if (!resultsGroup.contains(i.id as Long)) {
u << i
}
} else {
u << i
}
}
m.userSelected=item?.collect{it.id as Long}
m.results=u
}
return m

Strip characters from ng-repeat filter?

I'm writing an Angular app that will read a magnetic stripe card from a USB device. When I swipe a test card, I get a string back containing the card number. For example, ;12345?, where 12345 is the card number.
The data my app uses doesn't include these "control characters", so I'd like to strip them out of the search string if the string starts with a ; and ends with a ?.
When I write a custom filter:
angular.module('app.filters', [])
.filter('stripcardcontrolcharacters', function() {
return function(text) {
if(text.substring(0, 1) === ";" && text.substring(text.length - 1) === "?") {
return text.substring(1, text.length - 1);
}
};
});
It fails because I'm ng-repeating over an array, and not the string that I've searched for.
How would I get what string I'm filtering for and strip the characters from it?
EDIT: Current suggestion is to use a filter to modify the array to ADD in the control characters so filter: can find it. I might go with that for now, but I'm still curious to know if you can write such a filter
You're passing the entire array to your filter via
ng-repeat="user in users | stripcardcontrolcharacters ...
If that's how you want it to work, you would need to treat it as an array, for example
return function(textArray) {
var invalidChars = /\D/g; // just an example
return textArray.map(text => {
console.log(text);
return text.replace(invalidChars, '');
});
}
You are probably applying the filter to the array, not the string itself.
Look at this example:
angular.module('test', [])
.controller('testController', function($scope){
$scope.names = ['John Doe', 'Jane Doe'];
})
// The test filter
.filter('strip', function(){
return function(str) {
return str.substring(1, str.length - 1);
};
});
And here is how to use it:
<body ng-app="test" ng-controller="testController">
<p ng-repeat="name in names">
{{name | strip }}
</p>
</body>
Note that I'm applying the filter where I use the value, not in the ng-repeat statement.
And here is the working plunker

angular filter name as variable

I'm designing universal table that reads data and columns from ajax.
In columns description is also filter name which angular should use for a specific column.
But in HTML templates I can't use variables for filter names:/
Is there a solution for that? Or should I code javascript loop with data source?
Here is code example:
<tr ng-repeat="item in data">
<td ng-repeat="col in cols">
{{item[col.source]}}
<span ng-if="col.ngFilter">
{{col.ngFilter}} // ex. "state" filter
{{item[col.source]|col.ngFilter}} //is not working - is looking for "col.ngFilter" not "state" filter.
</span>
</td>
</tr>
You cannot do it in your HTML. First, you need to apply the filter in your controller.
function MyCtrl($scope, $filter) {
$scope.applyFilter = function(model, filter) {
return $filter(filter)(model);
};
}
Then, in your HTML:
Instead of
{{item[col.source]|col.ngFilter}}
use
{{applyFilter(item[col.source], col.ngFilter)}}
For anyone looking to do something like
{{applyFliter(item[col.source], col.ngFilter)}}
where ngFilter might contains some colon separated parameters such as
currency:"USD$":0
I ended up writing this little helper
function applyFilter (model, filter){
if(filter){
var pieces = filter.split(':');
var filterName = pieces[0];
var params = [model];
if(pieces.length>1){
params = params.concat(pieces.slice(1));
}
return $filter(filterName).apply(this,params);
}else{
return model;
}
}

How to ng-repeat into html table with multiple levels of json?

I have an object of social media stats. I'm trying to ng-repeat them into a table. Here's my plunker.
HTML:
<table>
<tr ng-repeat="(metric, metricData) in data">
<td>{{metric}}</td>
<td>{{metricData}}</td>
</tr>
</table>
Controller object:
$scope.data = { buzz:0,
Delicious:121,
Facebook:
{
like_count: "6266",
share_count: "20746"
},
GooglePlusOne:429,
LinkedIn:820,
Twitter:4074
};
I run into a problem when I get to the Facebook results. Within the <td> that entire object gets displayed (as it should be with how I have my code setup). But what I'd rather have happen is to repeat through that object and display the key and value in the cell.
I tried doing something looking to see if metricData is an object and doing some sort of ng-repeat on that. But I wasn't having luck with that. Any idea on how I can display the inner object (keys & value) within the cells?
You can define a scope function returning the type of metricData :
$scope.typeOf = function(input) {
return typeof input;
}
And then you can display it according to its type :
<tr ng-repeat="(metric, metricData) in data">
<td>{{metric}}</td>
<td ng-switch on="typeOf(metricData)">
<div ng-switch-when="object">
<div ng-repeat="(key, value) in metricData">
<span>{{key}}</span>
<span>{{value}}</span>
</div>
</div>
<span ng-switch-default>{{metricData}}</span>
</td>
</tr>
You can see it in this Plunker
Sounds like you'll need a specific directive that wires up children to be recursive, take a look at this example: Recursion in Angular directives
What you'd check on is if what you need to repeat is an object and not a value, then add the new element compile it, and start the process over again.
I'm assuming you want each of those values to have their own line but you don't explain exactly how you want it to work. I think the matter would best be handled by passing a clean version of what you want to the ng-repeat directive. I'm assuming you want two rows for facebook in your sample. You could create a filter to flatten the metrics so there are properties "Facebook_like_count" and "Facebook_share_count" (PLUNKER):
app.filter('flatten', function() {
function flattenTo(source, dest, predicate) {
predicate = predicate || '';
angular.forEach(source, function(value, key) {
if (typeof(value) == 'object') {
flattenTo(value, dest, predicate + key + '_');
} else {
dest[predicate + key] = value;
}
});
}
return function(input) {
var obj = {};
flattenTo(input, obj, '');
return obj;
}
});
Then your repeat can use the filter:
<tr ng-repeat="(metric, metricData) in data|flatten">

Resources