I am developing a reusable HTML component following this guide.
I have tested the guide with some sample code from bootstrap and it worked properly.
Now I am trying to use it for my navbar, which uses Thymeleaf syntax and I think it is giving conflicts due to the fact that both use single quotes.
Here is what I mean:
class Header extends HTMLElement {
connectedCallback() {
this.innerHTML = ` ***a single quote to open ***
<nav>
{*Header code goes here *}
</nav>
`; *** a single quote to close
}
}
Some code from my navbar, where in the srcset and src attribute single quotes are used:
<nav class="navbar navbar-expand-lg">
<div class="container">
<span class="navbar-brand">
<a href="/" class="navbar-brand">
<img
th:attr="srcset=|
#{${cloudinaryBaseUrl} + ${cloudinaryTransfCommon} + 'w_256/XXX.jpg'} 256w,
#{${cloudinaryBaseUrl} + ${cloudinaryTransfCommon} + 'w_1280//XXX.jpg'} 1280w|"
th:src="#{${cloudinaryBaseUrl} + ${cloudinaryTransfCommon} + 'w_auto' + '/XXX.jpg'}" class="img-fluid rounded-circle" width="70" alt="logo"
/>
</a>
</span>
If my intuition about the problem is correct, how can I overcome it?
===========================================================
UPDATE: I have tried isolating the problem, and found out that this line works:
th:src="#{https://theUrl}"
But this one doesn't:
th:src="#{${cloudinaryBaseUrl} + ${cloudinaryTransfCommon} + 'w_auto' + '/XXX.jpg'}"
So I believe the single quotes may be indeed the problem
There are a few different things happening here - but the bottom line is: The reason it is not working as expected is because of the way the code mixes Thymeleaf variables with JavaScript code.
Simple HTML Example
Consider this very simple piece of Thymeleaf:
<div th:text="${cloudinaryBaseUrl} + 'hello'"></div>
And assume that the value we pass to the Thymeleaf renderer for cloudinaryBaseUrl is /foo/bar/baz/.
If we place this in the HTML body, then the result will be the following HTML:
<div>/foo/bar/baz/hello</div>
So far, so good.
Adding JavaScript to the Mix
But in your case, you are embedding your HTML inside a JavaScript script - it's no longer a piece of HTML that will be rendered by Thymeleaf (on the server!). Now it is a string of text that will be processed by JavaScript (in the browser!), long after Thymeleaf has finished its work.
You have this:
<script>
class Header extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div th:text="${cloudinaryBaseUrl} + 'hello'"></div>
`;
}
};
customElements.define('main-header', Header);
</script>
The text inside the backticks is a JavaScript template literal - which just happens to also use ${...} for inserting values into text - but completely unrelated to Thymeleaf's ${...} syntax.
This will fail.
JavaScript will not be able to substitute any correct value into ${cloudinaryBaseUrl}. You may get something rendered - or you may get a JS error. Depends on the specific template literal being used.
Passing Thymeleaf Variables to JavaScript
If you want to pass Thymeleaf variables to JavaScript, you can do it like this:
<script th:inline="javascript">
var baseUrl = [[${cloudinaryBaseUrl}]];
class Header extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div>${baseUrl}hello</div>
`;
}
};
customElements.define('main-header', Header);
</script>
Note the th:inline="javascript" added to the script tag, and the use of [[...]].
By the time it reaches the browser, Thymeleaf has updated the JavaScript code to be this:
var baseUrl = '/foo/bar/baz/';
...
So, now the JS will generate this HTML:
<main-header>
<div>/foo/bar/baz/hello</div>
</main-header>
A Better Way - Use Thymeleaf Fragments
If you are doing this as an exercise, then this is how you can combine your JavaScript template fragments with Thymeleaf variables.
But Thymeleaf has support for fragments built into the Thymeleaf language. So, there is no real need for any of that JavaScript.
Take a look here: Template Layouts.
This does exactly the same thing as your JavaScript approach - but with full Thymeleaf support and no need to pass variables to JavaScript.
Related
I use expressjs and hbs(Handlebars) as the template engine.
One of the parameters that are passed to the template when loading the page contains HTML code.
When the page loads, instead of processing the parameter and displaying the elements, it is displayed as text.
How can I solve this?
//Server-side:
let parameter = "<h2 id="how-to-use">How To Use</h2>";
//HTML:
<div id="container">{{parameter}}</div>
//--------------------------------------
//result after page load
<h2 id="how-to-use">How To Use</h2>
//Instead of
How To Use
got it.
use the triple {{{ }}} brackets
Render the HTML of a component so that it could be used to open a new tab of the type about:blank (a blank new html page that nothing has to do with the application itself).
Why?
To avoid creating the HTML using a string variable as, for example:
var html = '<div>
<h3>My Template</h3>
' + myProperty + '
</div>';
I saw that you can render a component dynamically using ViewContainerRef.createComponent and ComponentFactoryResolver. The problem with this is that the component is rendered inside the view container, in the application. What I would like to do is generate the HTML of that component so that then I can use that HTML to put it wherever I want. (in this case, in the document property of the object given by the window.open() method)
Example:
#Component({
selector: 'app-component',
template: `
<div>
<h3>My Template</h3>
{{ myProperty }}
</div>
`,
styleUrls: ['./my.component.less']
})
export class MyComponent{
myProperty: string;
}
I expect to use it in this way:
//get new window tab
var newTab = window.open('about:blank', '_blank');
//get the HTML of the component
//use it to open the new tab
newTab.document.write(html);
It may help other people so I post here what I did and which problems I found. After looking to some solutions, this one worked for me. The problem was then I realize that Angular sanitizes your HTML removing all possible <script> tags. Unfortunately I had like 3 of them. In addition, if you don't want them to be sanitized, you have to use a service called DomSanitizerand use the method bypassSecurityTrustScript (doc) passing the script as a parameter. So the idea of don't 'stringify' the code was gone. Saying that, I used the original approach, where the HTML is stored in a variable then passed as a parameter to window.open
Very basic question about html.
Because the <body> is too long, I want to divide the file into multiple files. It is not about using iframe etc, but just want to include multiple text files to create <body> of the html file.
Thank you!
You can do it using jQuery:
<head>
<script src="jquery.js"></script>
<script>
$(function(){
$("#ContentToInclude").load("b.txt or b.html");
});
</script>
</head>
And load it in HTML:
<body>
<div id="ContentToInclude"></div>
</body>
Just change the extension to .php instead of .html. Then you can just put, for example, your whole head inside the file head.php( or head.inc).
The whole thing would look something like this then:
<!DOCTYPE html>
<?php
include 'head.php';
?>
<body>
<!-- stuff in here -->
</body>
<html>
You can obviously split your body up into seperate pieces like this:
<body>
<?php
include 'firstPart.php';
?>
<!-- some other stuff -->
</body>
You can easily break your code in multiple files, Then create one file with .php extension and include them all!
With only HTML it would not be possible you need to add some JavaScript to be able to do so.
Using a data attribute with the Fetch API and some async functions you could do it as follow:
HTML file:
<div data-src="./PATH/filename.html"></div>
This element will receive as HTML content the content of the file specified in its data-src attribute.
Now the JavaScript:
async function getFileContentAsText(file) {
const response = await fetch(file);
const fileContent = await response.text();
return fileContent;
}
async function insertContentsFromFiles() {
const tbl = document.querySelectorAll('[data-src]'); // get elements with the data attribute "data-src"
for (var i=0; i < tbl.length; i++) // loop over the elements contained in tbl
tbl[i].innerHTML = await getFileContentAsText(tbl[i].dataset.src);
}
// dont forget to call the function to insert the files content into the elements
insertContentsFromFiles();
When the insertContentsFromFiles() method will be called it will first retrieve all the elements that have the data attribute data-src then we loop over these elements using their data-src value with the getFileContentAsText() method to affect their innerHTML property as the content of the file specified in the data attribute.
As we are using querySelectorAll() to get the elements with the data-src attribute the above JavaScript code will work for an unlimited amount of elements as long as they have that data attribute.
Note: In its current state the above JavaScript code is not optimized for loading a big amount of files as it process the files to be loaded one by one. If you are interested in solving this issue you may want to use promise.all() and update the insertContentsFromFiles() method to parallelize the files loading by taking advantage of the asynchronous operations.
Warning: If you plan to use elements that are in the loaded files from JavaScript you will have to retrieve them after they have been loaded into the page otherwise they will have an undefined value. To do so you can dispatch an event when a file has been loaded so you can attach specific functionnalities to the page based on the triggered events.
I first initialize my app with ng-app="myApp" in the body tag and this works fine for all angularized-html that is loaded on first page load.
Later on I have some code that loads angularized-html in to the DOM.
In angular 1.08 I could just run angular.bootstrap($newLoadHTML, ["myApp"]) after the load and it would work; where $newLoadHTML is the newly added HTML grabbed with jQuery.
In angular 1.2 this does no longer work:(
Error: [ng:btstrpd] App Already Bootstrapped with this Element '' http://errors.angularjs.org/1.2.0-rc.2/ng/btstrpd?p0=%3Cdiv%20ng-controller%3D%22AfterCtrl%22%3E
I am getting this error which I understand, but I don't know how to solve it.
What I need to be able to do is load angularized-html and then make angular aware of it.
Here is a plunker to illustrate it: http://plnkr.co/edit/AHMkqEO4T6LxJvjuiMeT?p=preview
I will echo what others have mentioned: this kind of thing is generally a bad idea, but I also understand that you sometimes have to work with legacy code in ways you'd prefer not to. All that said, you can turn HTML loaded from outside Angular into Angular-bound views with the $compile service. Here's how you might rewrite your current example to make it work with $compile:
// We have to set up controllers ahead of time.
myApp.controller('AfterCtrl', function($scope) {
$scope.loaded = 'Is now loaded';
});
//loads html and afterwards creates a controller
$('button').on('click', function() {
$.get('ajax.html', function(data) {
// Get the $compile service from the app's injector
var injector = $('[ng-app]').injector();
var $compile = injector.get('$compile');
// Compile the HTML into a linking function...
var linkFn = $compile(data);
// ...and link it to the scope we're interested in.
// Here we'll use the $rootScope.
var $rootScope = injector.get('$rootScope');
var elem = linkFn($rootScope);
$('.content').append(elem);
// Now that the content has been compiled, linked,
// and added to the DOM, we must trigger a digest cycle
// on the scope we used in order to update bindings.
$rootScope.$digest();
}, 'html');
});
Here is an example: http://plnkr.co/edit/mfuyRJFfA2CjIQBW4ikB?p=preview
It simplifies things a bit if you can build your functionality as a directive instead of using raw jQuery--you can inject the $compile and $rootScope services into it, or even use the local scope inside the directive. Even better if you can use dynamic binding into an <ng-include> element instead.
Your approach doesn't seem right. You are usinging jQuery and Angular together in an inappropriate way that is likely to have conflicts.
Angular's built in template support is the best way to do this either using ng-include or you can use Angular's routing and along with ng-view. The documentation is here:
http://docs.angularjs.org/api/ng.directive:ngInclude
http://docs.angularjs.org/api/ngRoute.directive:ngView
The simplest possible thing would be to just set the ng-include to the url string:
<div ng-include="'ajax.html'"></div>
If you actually need it to load dynamically when you do something then this is a more complete solution for you:
http://plnkr.co/edit/a9DVEQArS4yzirEQAK8c?p=preview
HTML:
<div ng-controller="InitCtrl">
<p>{{ started }}</p>
<button ng-click="loadTemplate()">Load</button>
<div class="content" ng-include="template"></div>
</div>
Javascript:
var myApp = angular.module('myApp', [])
.controller('InitCtrl', function($scope)
{
$scope.started = 'App is started';
$scope.loadTemplate = function() {
console.log('loading');
$scope.template = "ajax.html";
}
}).controller('AfterCtrl', function($scope)
{
$scope.loaded = 'Is now loaded';
});
Loading an AngularJS controller dynamically
The answer to this question fixed my problem. Since I need to create the controllers after the content was added to the DOM. This fix requires me too register controllers after I have declared it. If someone has an easier solution pleace chip in.
One other gotcha that leads to this Bootstrapping error is the nginclude or ngview scenarios where your dynamic html includes script references to angular js.
My html below was causing this issue when it got injected into an existing Angular page. The reference to the angular.min.js caused Angular to rebootstrap:
<div id="fuelux-wizard" class="row-fluid" data-target="#step-container">
<ul class="wizard-steps">
<li data-target="#step1">
<span class="step">1</span>
<span class="title">Submit</span>
</li>
<li data-target="#step2">
<span class="step">2</span>
<span class="title">Approve</span>
</li>
<li data-target="#step3">
<span class="step">3</span>
<span class="title">Complete</span>
</li>
</ul>
</div>
<script src="/Scripts/Angular/angular.min.js"></script>
<script type="text/javascript">
angular.element('#requestMaster').scope().styleDisplayURL();
</script>
when I use Backbone toJSON method of the model like this:
this.$el.html(this.model.toJSON());
It doesn't render model into view root element ( more than one attribute ).
But when I get one property from the model, like this;
this.$el.html(this.model.get("city"));
It is rendered properly.
Also, when I use template in first case (toJSON) - it is rendered fine.
this.$el.html(this.template(this.model.toJSON());
Why is that ?
Thanks
this.$el.html(this.model.toJSON());
You're using the html method of jQuery, which expects a string (or a DOM element, or a jQuery element), to display a JSON object.
this.$el.html(this.template(this.model.toJSON());
Here you're using a template method which, I assume, is taking a JSON object to evaluate a template that will return you a string. The htmlmethod receives this string and displays it.
this.$el.html(JSON.stringify(this.model.toJSON()));
This would display the result of this.model.toJSON() (but won't do the same as using your template method).
So, basically this.template will be (in most of the cases) a compiled version of the html template which you have for the view.
It will have placeholders in it, and will take parameters with the same key as placeholders in the template. For example (Handlebars templates),
<section id="{{id}}">
<header>{{header_text}}</header>
</section>
Considering the above code as a template, when you compile and store it in this.template, it returns a function, which takes a json object as a parameter, so now this.template is a function.
You can call it like below,
var html_text = this.template({
id : "main_content",
header_text : "Hi Welcome !!"
});
this.$el.html(html_text);
After the execution, el's contents will be
<section id="main_content">
<header>Hi Welcome !!</header>
</section>
So when you do this.$el.html(this.template(this.model.toJSON());, it actually generates the required json parameter for the this.template method for you, hence works fine.
And as Loamhoof said, in this.$el.html(this.model.get("city")); you use the html method which will set the html content of the el based on the property value of the model.