Backbone.js template: Render HTML from model? - html

I have an API that return HTML serialized in JSON accessed through a REST API. When I try to render the HTML from the API response, the HTML code is display as such:
<p>Hello this is a response!</p>, instead of as: Hello this is a response!
Is there any way to work around this?
Also, what are the potential security issues with doing this and actually render the HTML?
Best regards and help is much appreciated. :)
EDIT: Here are my models and my template. Sufficient to say, I'm new to Backbone.js, and this is mostly based on the Todos example.
Views:
app.DataView = Backbone.View.extend({
tagName: "li",
template: _.template($("#data-template").html()),
initialize: function() {
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
app.AppView = Backbone.View.extend({
el: "#htmldataapp",
initialize: function() {
app.datamodels.on('reset', this.addAll, this);
app.datamodels.fetch();
this.render();
},
addOne: function(datamodel) {
var view = new app.DataView({model: datamodel});
$('#data-list').append(view.render().el);
},
addAll: function() {
this.$('#data-list').html('');
app.datamodels.each(this.addOne, this);
}
});
Template:
<script type="text/template" id="data-template">
<%= data %>
</script>

You state that your api sends you HTML already?
So could it be that the data you get back is like <p>Hello this is a response!</p>, thus including the HTML tags?
If you would have underscore render this string in a template, it's no surprise you actually get the HTML tags to display.
Look at the generated source of the page, is the string Hello this is a response! wrapped in double <p> elements?
I would suggest changing your API in such a way that it returns data only (i.e. the string without the HTML), and have your HTML rendering be done by the underscore template engine.
Hope it helps!
EDIT:
I think you were using the underscore template function to often, causing the data to be rendered as a string.
Please see this fiddle for a sample setup how I think you should setup your app

Related

How can I display dynamic HTML having Vue variables inside?

I am trying to make the page content dynamic. I am using ck-editor in which i added html content and used the same vue variables inside it which i declared in the vue file where i want to show ck-editor data. I found a similar post vuejs - “editing” html inside variable
which works fine if i write the html inside a variable. But in my case, i am saving data in database. It is saving properly with html tags, without converting the tags. When i get data using axios it returns it in form of string. And i used vue variable to display that html.
Here is my code for better understanding:
<div v-html="htmlText"></div>
new Vue({
el: '#app',
created() {
this.getSalesContent();
},
data: {
salesContent: '',
pageName: 'Sales',
salesNumber: '987-586-4511'
},
computed: {
htmlText() {
return `${this.salesContent}`;
//return this.salesContent;
}
},
methods: {
getSalesContent(){
axios.get('api/Sales').then(({ data }) => { // getting data from DB
this.salesContent = data.sales; //data.sales have this.pageName and this.salesNumber variables
});
}
}
});
Here is the example of data saved in db:
<p style="font-weight:bold"><span style="color:red">{{pageName}}</span>,</p>
<p style="font-weight:bold"><span style="color:red">${this.pageName} ${this.pageName}</span></p>
<p style="font-weight:bold">Contact Sales at ${this.salesNumber} {{salesNumber}}</span></p>
I used variables in all possible ways. But on the page they are printing in it the same way i saved it. Here is the output:
screenshot
Can anyone help me make it working.
Thanks in Advance.
According to the docs this does not seem possible:
https://v2.vuejs.org/v2/guide/syntax.html#Raw-HTML
Particularly:
The contents of the span will be replaced with the value of the
rawHtml property, interpreted as plain HTML - data bindings are
ignored.
You could as suggested in that answer just use a computed based on what you get from the server.
IMHO since the salesContent is fetched from db, it's a plain String. Thus nor vuejs or vanilla javascript will replace the inline variables with their values. (It may be possible by using eval, but it's totally out of question...) You should manually do that with String replace function. Like the following:
<p style="font-weight:bold"><span style="color:red">{{pageName}}</span>,</p>
<p style="font-weight:bold">Contact Sales at {{salesNumber}}</span></p>
methods: {
getSalesContent(){
axios.get('api/Sales').then(({ data }) => { // getting data from DB
let salesContent = data.sales; //data.sales have this.pageName and this.salesNumber variables
salesContent = salesContent.replace(/{{pageName}}/g, this.pageName)
salesContent = salesContent.replace(/{{salesNumber}}/g, this.salesNumber)
this.salesContent = salesContent
});
}
}

Include directive before html render

I tried wrapping my head around the following problem:
I have a html string which I render using ng-bind-html
I managed to change a given placeholder (in the html string) with a directive (or more). For example I have: [placeholder]test[/placeholder] and replaced with <my-directive></my-directive> for a certain functionality.
This approach is needed to make some content dynamic.
When rendering the html string I notice that the directive is missing, I understand, but is there a way to render it and make the directive functionally?
P.S:
Tried rendering it as a normal string but the html is escaped
Tried using $sce.trustAsHtml()
I cannot apply $compile(element.contents())(scope); since the directive is not triggered
I have managed to do achieve this by doing the following:
Add the directive in the html:
<my-directive update-data-trigger="someObject.content" data="someObject"></<my-directive>
The directive:
app.directive("myDirective", function ($compile) {
return {
replace:true,
restrict: "E",
scope: {
//Data holds the html in the content attribute
data: '=',
updateDataTrigger: '='
},
link: function ($scope, element) {
//Add a watcher to refresh data because the loaded data passed is async
$scope.$watch('updateDataTrigger', function(){
//Check if data passed has been loaded with our desired object
if($scope.updateDataTrigger != null) {
//Do some content manipulation here
//Append directives to the content as well
//render as html
element.html(data.content);
$compile(element.contents())($scope);
}
});
}
}
});

With ng-bind-html-unsafe removed, how do I inject HTML?

I'm trying to use $sanitize provider and the ng-bind-htm-unsafe directive to allow my controller to inject HTML into a DIV.
However, I can't get it to work.
<div ng-bind-html-unsafe="{{preview_data.preview.embed.html}}"></div>
I discovered that it is because it was removed from AngularJS (thanks).
But without ng-bind-html-unsafe, I get this error:
http://errors.angularjs.org/undefined/$sce/unsafe
Instead of declaring a function in your scope, as suggested by Alex, you can convert it to a simple filter :
angular.module('myApp')
.filter('to_trusted', ['$sce', function($sce){
return function(text) {
return $sce.trustAsHtml(text);
};
}]);
Then you can use it like this :
<div ng-bind-html="preview_data.preview.embed.html | to_trusted"></div>
And here is a working example : http://jsfiddle.net/leeroy/6j4Lg/1/
You indicated that you're using Angular 1.2.0... as one of the other comments indicated, ng-bind-html-unsafe has been deprecated.
Instead, you'll want to do something like this:
<div ng-bind-html="preview_data.preview.embed.htmlSafe"></div>
In your controller, inject the $sce service, and mark the HTML as "trusted":
myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {
// ...
$scope.preview_data.preview.embed.htmlSafe =
$sce.trustAsHtml(preview_data.preview.embed.html);
}
Note that you'll want to be using 1.2.0-rc3 or newer. (They fixed a bug in rc3 that prevented "watchers" from working properly on trusted HTML.)
You need to make sure that sanitize.js is loaded. For example, load it from https://ajax.googleapis.com/ajax/libs/angularjs/[LAST_VERSION]/angular-sanitize.min.js
you need to include ngSanitize module on your app
eg: var app = angular.module('myApp', ['ngSanitize']);
you just need to bind with ng-bind-html the original html content. No need to do anything else in your controller. The parsing and conversion is automatically done by the ngBindHtml directive. (Read the How does it work section on this: $sce). So, in your case <div ng-bind-html="preview_data.preview.embed.html"></div> would do the work.
For me, the simplest and most flexible solution is:
<div ng-bind-html="to_trusted(preview_data.preview.embed.html)"></div>
And add function to your controller:
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
}
Don't forget add $sce to your controller's initialization.
The best solution to this in my opinion is this:
Create a custom filter which can be in a common.module.js file for example - used through out your app:
var app = angular.module('common.module', []);
// html filter (render text as html)
app.filter('html', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml(text);
};
}])
Usage:
<span ng-bind-html="yourDataValue | html"></span>
Now - I don't see why the directive ng-bind-html does not trustAsHtml as part of its function - seems a bit daft to me that it doesn't
Anyway - that's the way I do it - 67% of the time, it works ever time.
You can create your own simple unsafe html binding, of course if you use user input it could be a security risk.
App.directive('simpleHtml', function() {
return function(scope, element, attr) {
scope.$watch(attr.simpleHtml, function (value) {
element.html(scope.$eval(attr.simpleHtml));
})
};
})
You do not need to use {{ }} inside of ng-bind-html-unsafe:
<div ng-bind-html-unsafe="preview_data.preview.embed.html"></div>
Here's an example: http://plnkr.co/edit/R7JmGIo4xcJoBc1v4iki?p=preview
The {{ }} operator is essentially just a shorthand for ng-bind, so what you were trying amounts to a binding inside a binding, which doesn't work.
I've had a similar problem. Still couldn't get content from my markdown files hosted on github.
After setting up a whitelist (with added github domain) to the $sceDelegateProvider in app.js it worked like a charm.
Description: Using a whitelist instead of wrapping as trusted if you load content from a different urls.
Docs: $sceDelegateProvider and ngInclude (for fetching, compiling and including external HTML fragment)
Strict Contextual Escaping can be disabled entirely, allowing you to inject html using ng-html-bind. This is an unsafe option, but helpful when testing.
Example from the AngularJS documentation on $sce:
angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
// Completely disable SCE. For demonstration purposes only!
// Do not use in new projects.
$sceProvider.enabled(false);
});
Attaching the above config section to your app will allow you inject html into ng-html-bind, but as the doc remarks:
SCE gives you a lot of security benefits for little coding overhead.
It will be much harder to take an SCE disabled application and either
secure it on your own or enable SCE at a later stage. It might make
sense to disable SCE for cases where you have a lot of existing code
that was written before SCE was introduced and you're migrating them a
module at a time.
You can use filter like this
angular.module('app').filter('trustAs', ['$sce',
function($sce) {
return function (input, type) {
if (typeof input === "string") {
return $sce.trustAs(type || 'html', input);
}
console.log("trustAs filter. Error. input isn't a string");
return "";
};
}
]);
usage
<div ng-bind-html="myData | trustAs"></div>
it can be used for other resource types, for example source link for iframes and other types declared here

Getting JSON in to Underscore Template in Backbone

Just getting started with Backbone and still making sense of the ins and outs.
I'm trying to simply display some JSON using Underscore and Backbone. I'm able to make it to work just using Underscore and $.getJSON, but when I try to wire it up with Backbone I get a variety of errors depending upon what I try.
I've also been able to get Backbone to work by hardcoding values in to the model, but I'm running in to a wall when I try to bring it all together. Any help is appreciated.
Here is my Underscore template:
<script type="text/html" id='trailTemplate'>
<% _.each(trails,function(trail){ %>
<%= trail.trailname %><br />
<% }); %>
</script>
And here is my Backbone code:
var Trail = Backbone.Model.extend({
urlRoot: "trails.json"
});
var trail = new Trail({});
var TrailView = Backbone.View.extend({
el: '.page',
template: _.template($("#trailTemplate").html(), {trails:trail.fetch()}),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var trailView = new TrailView({
model: trail
});
trailView.render();
And in case you need it, here is trails.json
[
{
"trailhead": "Bear Lake",
"trailname": "Bear Lake",
"distance": ".5",
"gain": "20",
"level": "easy"
},
{
"trailhead": "Bear Lake",
"trailname": "Nymph Lake",
"distance": ".5",
"gain": "225",
"level": "fairly easy"
}
]
Your trails.json file contains an array with 2 objects, which both represent a single 'Trail'. So you should have a collection 'Trails' instead of a single model
var Trails = Backbone.Collection.extend({
url: '/trails.json'
});
var trails = new Trails();
The underscore template function can be used in 2 ways:
_.template(templateString) - compiles the templateString into function that can be evaluated when necessary
_.template(templateString, data) - compiles and immediately evaluates the template with the given data
Now the way you are using is number 2 (the way you declare the template) combined with number 1 (how you use it inside render). Let's examine the template declaration:
template: _.template($("#trailTemplate").html(), {trails:trail.fetch()})
This is all good up until the point you try to give it the data -attribute. First of all you don't need to give the data at this point, you just want to create the template function that can be evaluated when the View renders. Second, the stuff you are trying to pass as data is not at all what you think it is.
trail.fetch() doesn't return the the fetch results, it returns the ajax handle for the ajax call that is made with fetch. Thankfully Backbone is made so you don't have to think about all this painful ajax stuff, but instead you can trust the events that Backbone emits. So whip out the Backbone Catalog o' Events and check out reset
"reset" (collection, options) — when the collection's entire contents have been replaced.
This is the event you collection will emit, after fetch (also sync, i think). Before this event is emitted, your collection will be empty, so there is no point in doing anything with it before hearing this reset event. So let's bring it all together now:
var TrailView = Backbone.View.extend({
el: '.page',
template: _.template($("#trailTemplate").html()), // no data attribute here
initialize: function() {
this.collection.on('reset', this.render); // render after collection is reset
this.collection.fetch(); // fetch the collection from the trails.json file
}
render: function(){
// evaluate the template here
this.$el.html(this.template(this.collection.toJSON()));
return this;
}
});
var trailView = new TrailView({
collection: trails
});
// trailView.render(); <- No need for this, because it will render itself
Hope this helps!

parse dojoAttachEvent in content returned from xhr in a custom widget?

Instead of the typical custom widget that mixes in _Templated for it's markup, I want a widget that just mixes in _Widget then does an xhrGet to fetch the markup. In addition, and this is the part that I need help with, I want dojo to parse the xhr result text for dojoAttachPoint and dojoAttachEvent in the context of the widget, hookup up nodes and events.
The core of what I've tried for the widget is below:
dojo.provide("com.example.widget.DynamicAttachedTemplate");
dojo.require("dijit._Widget");
dojo.require("dojo.html");
dojo.declare("com.example.widget.DynamicAttachedTemplate", [dijit._Widget], {
postCreate: function() {
dojo.xhrGet({
url: "/product/ajax/editTemplate",
content: { id: 1 },
handleAs: "text",
preventCache: true,
load: dojo.hitch(this, function(markup) {
// this only parses markup for dojoType it seems :(
dojo.html.set(this.srcNodeRef, markup, { parseContent: true });
},
error: function(error) {
alert(error);
}
});
},
submitHandler: function(event) {
dojo.stopEvent(event);
// handle validation, form submit, etc.
}
});
And the page that uses it could look something like this:
...
<div id="productEditContainer">
loading...
</div>
...
<script type="text/javascript">
dojo.addOnLoad(function() {
dojo.require("com.example.widget.DynamicAttachedTemplate");
new com.example.widget.DynamicAttachedTemplate({}, dojo.byId("productEditContainer"));
});
</script>
And let's say the markup returned from /product/ajax/editTemplate?id=1 was something like:
<form dojoAttachEvent="onsubmit: submitHandler">
...
</form>
In the end I want the dojoAttachEvent="onsubmit: submitHandler" in the xhr returned template markup to result in an event connect from the form submit to the widget submit handler method. I'm open to better approaches as well but the widget markup needs to be generated on the server and I'd really like to leverage dojoAttach* instead of using DOM IDs and manually hooking things up via dojo.byId in the widget setup code.
consider using inline template: http://www.sitepen.com/blog/2008/06/24/creating-dojo-widgets-with-inline-templates/.
However it requires _Templated.