So I have been using v-html tag to render the html in my vue pages.
But I encountered a string which was a proper html file and it contained text kind of like this:
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
....
<style>
</style>
</head>
<body style="....">
</body>
</html>
The problem is, I have the v-html on a div, but this code starts affecting the whole page and adds its styling to the whole page and not only to that specific div.
I tried adding "scope" to the style tags but it did not work. Maybe because there's also a style inline tag on body?
I need to find a way to make the html affect only on the div it is on, and not the whole page.
Your best bet would probably be to have a better control over the HTML added using v-html. I would suggest to parse it before and keep only the <body> tag. You could do it using a regex, but it would be easier using a dom parser lib. Example with DomParser:
const DomParser = require("dom-parser");
const parser = new DomParser();
export default {
// ...
computed: {
html() {
const rawHtml = "<html><body><div>test</div></body></html>"; // This data should come from your server
const dom = parser.parseFromString(rawHtml);
return dom.getElementsByTagName("body")[0].innerHTML;
}
}
}
Please note that it is an oversimplified solution as it does not handle the case where there is no <body> tag.
First, you should be very careful when using external HTML with v-html as it can make your site vulnerable to various sorts of attacks (see Vue docs).
Now if you trust the HTML source, other problem is how to embed it without affecting your own side. There is special element for this case, <iframe> - it is not without risk and you should definitely read a bit on how to make it safe but it should solve your problem because is "sandbox" external HMTL so it does not affect your site.
https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies
Related
As the title reads, I want to import an HTML-file as external CSS to a website.
Just hear me out: my problem is that I'm working with a very inconvenient CMS that doesn't let me upload CSS-files no matter what.
I'm able to write CSS inside the page directly via HTML-style-tag but that generates a lot of text on every site and also makes maintaining CSS tedious.
As I can't upload CSS-files, I thought maybe I can create a dummy-site inside the CMS with only CSS in it and then later import that site as CSS.
The idea was: when parsed, the HTML of the site (header, body, etc.) will just be skipped (as when CSS has i.e. type-errors) while any valid CSS found is going to be imported.
Now when I try importing this website with
<style type="text/css"> #import url(dummyCSSWebsiteURL); </style>
(as the CMS also doesn't grant me access to the header of a page),
I - of course - get the error:
"Resource interpreted as Stylesheet but transferred with MIME type text/html"
as I am obviously requesting an HTML-file and not CSS.
I also tried jQuery to simply include all the dummy-HTML into an element (that I would have just not displayed):
$("#cssDummy").load(dummyCSSWebsiteURL);
but I get 2 errors that are probably just showing what a horribly inefficient idea this is:
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience
A parser-blocking, cross site (i.e. different eTLD+1) script, ["..."], is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message.
maybe I am just disregarding (or not understanding) things on a conceptual level at all but I still wonder if there is a workaround for this problem?
EDIT: I found a workaround
Definitely don't recommend. Try using different server as pointed out in the comments if you can.
I used an XMLHttpRequest to get the external site's HTML, then used regEx to match the content of the div on the page that contains the css and - with added style-tags - inserted the matched css into a div on the page.
Code for external site that contains the CSS:
<div id="generalCode">.testBox{background-color: red; min-height: 200px;}</div>
Code on site that imports the external CSS:
<div class="testBox">
</div>
<div id="cssCodeOnPage">
</div>
<script>
// use onload if you want
getCssCode();
function getCssCode(){
// send request to page where div #generalCode contains css
var xhr = new XMLHttpRequest();
xhr.open('GET', dummyCSSWebsiteURL);
xhr.onload = function(){
// use regex to separate css from rest of html
var re = /<div id="generalCode">([\s\S]*?)<\/div>/;
var cssString = xhr.response.match(re)[1];
cssString = "<style>" + cssString +"</style>";
// insert css into div
var cssDivOnPage = document.getElementById('cssCodeOnPage');
cssDivOnPage.innerHTML = cssString;
}
xhr.send();
}
(sorry for this monstrosity of a question..)
I think the best option is to load that HTML page into an iframe, query the styles out of hte iframe, and then attach that to the current document. I created a live example on Glitch here.
Nothing but the <style> block will be copied from the HTML. The external HTML document does need to be an actual HTML document though (<html><head><style>....), otherwise the page won't be queryable to retrieve the CSS.
This is an example in plain JavaScript:
// Create an iframe
const iframe = document.createElement("iframe");
// Make it not visible
iframe.style.visibility = "hidden";
// This will run once the HTML page has loaded in the <iframe>
iframe.onload = () => {
// Find the <style> element in the HTML document
const style = iframe.contentDocument.querySelector("style");
// Take that <style> element and append it to the current document's <head>
document.querySelector("head").appendChild(style);
// Remove the <iframe> after we are done.
iframe.parentElement.removeChild(iframe);
};
// Setting a source starts the loading process
iframe.src = "css.html";
// The <iframe> doesn't actually load until it is appended to a document
document.querySelector("body").appendChild(iframe);
Its possible in a different way. However not through JS, but PHP include
For that however, your server needs to support PHP and need to use PHP instead of HTML as documents. Sounds more complicated now then it actually is. To use PHP the document has to becalled .php at the end instead of .html e.g. index.php.
The document itself can be written the very same way as you write HTML websites.
Now for the issue to use PHP include. you inlcude the CSS as head style:
index.php:
<html>
<head>
<?php
include('styles.css');
?>
</head>
<body>
content
</body>
</html>
the line <?php include('url'); ?> will load a file mentioned in the url server sided into the document. Therefor you only need to create another file with the name styles.css and write your css in there the normal way.
You Do
<link rel="stylesheet" href="css/style.css">
I am trying to find a way (if possible) to use javascript to add some attributes to an element at render time and before the DOM is fully loaded. I know, that sounds counterproductive, but let me give you some background:
I'm working in an extremely limited templating platform that gives me access to some page variables, but they need some minor string manipulation. I can't leverage any of the ASP preprocessing so it has to happen on the client-side.
Specifically, I am trying to add Schema.org Microdata markup to an element before Googlebot scans through the document contents.
Essentially I need to modify an attribute value from $5.99 to 5.99.
Here's my most recent attempt, which makes the DOM modifications correctly, but not before Google's rich snippet validator processes the page:
<div class="pitinfo"><div class="padleft padright"><%Product.BasePrice%></div></div>
<!-- at page bottom -->
<script type="text/javascript">
(function() {
var pricesting = "<%Product.BasePrice%>";
var price = pricesting.slice(1);
$('.pitinfo').attr('itemprop', 'price');
$('.pitinfo').attr('content', price);
})();
</script>
After load I get this <div class="pitinfo" itemprop="price" content="9.99">$9.99</div>, however the Rich Snippet Testing tool tells me price is not set.
I have already tried using ASP in my template code but the hosting provider does not allow it.
Is it possible to make the DOM modifications sometime in the middle of the document rendering flow?
It is possible to insert a <script> tag inside of the <body>. JavaScript inside of the tag is loaded before the HTML after it, so you would be able to edit the element's value before the rest of the HTML/JS is loaded.
For example:
<div>
<div id="element" value="$5.99"></div>
<script>
var element = document.getElementById("element")
element.value = 5.99;
</script>
</div>
You can check it out here
There is open source (client side) which I can use to extend HTML,
for example I need to add scripts to it or change some of the src values and add additional tags, etc.
I found the following: https://www.npmjs.com/package/gulp-html-extend
but I'm not sure if I can use it in the client (we don't use gulp in our project) By client I mean for example to use it in jsFiddle.
The input should be HTML content with some object/json with the new content and the output should be extended HTML.
If there is no open source , and I need to develop it myself, is there is some guide line I should follow from good design aspects?
UPDATE:
For example if I've the following HTML doc as JS input variable
THIS IS THE INPUT WHICH I GOT AS STRING
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="UTF-8">
<title>td</title>
<script id="test-ui-bootstrap"
src="resources/test-ui-core.js"
data-test-ui-libs="test.m"
data-test-ui-xx-bindingSyntax="complex"
data-test-ui-resourceroots='{"tdrun": "./"}'>
</script>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script>
test.ui.get().attachInit(function() {
});
</script>
</head>
<body class="testUiBody" id="content">
</body>
</html>
For example I need the following:
1.
I want to add additional script (e.g. with alert inside) after
<script id="test-ui-bootstrap" ....
if there is in the file script with id "test-ui-bootstrap"
I want to add immediately after this script another script e.g.
script with alert inside
2.
To add additional property inside the first script(with id id="test-ui-bootstrap") after the last script...
data-test-ui-libs="test.m"
To add
data-test-ui-libs123 ="test.bbb"
3.
If I want to modify the value of existing property e.g. change
src="resources/test-ui-core.js"
to
src="resources/aaaa/test-ui-core.js"
I got string with HTML and I need to create new string with the modified HTML I can I do it right with nice way?
UPDATE 2
THIS IS THE OUTPUT AFTER THE HTML WAS CHANGED
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="UTF-8">
<title>td</title>
<script id="test-ui-bootstrap"
src="resources/aaaa/test-ui-core.js"
data-test-ui-libs="test.m"
data-test-ui-libs123 ="test.bbb"
data-test-ui-xx-bindingSyntax="complex"
data-test-ui-resourceroots='{"tdrun": "./"}'>
</script>
<script>
alert("test)
</script>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script>
test.ui.get().attachInit(function() {
});
</script>
</head>
<body class="testUiBody" id="content">
</body>
</html>
You can create a sandboxed element outside of the DOM, then insert your HTML into it.
var sandbox = document.createElement('div');
sandbox.innerHTML = yourHTMLString;
The browser will parse your HTML, then you'll be able to traverse/modify it with the DOM APIs.
You can use it to find elements and add attributes.
var script = sandbox.querySelectorAll('#test-ui-bootstrap');
script.setAttribute('data-test-ui-libs', 'test.m');
script.setAttribute('src', 'resources/aaaa/test-ui-core.js');
Or insert new elements after existing ones.
var newScript = document.createElement('script');
newScript.innerText = 'your script contents';
script.parentNode.insertBefore(newScript, script.nextSibling);
As soon as you're ready to work with it as a string again, you can read it out as a property.
var html = sandbox.innerHTML;
Note. Different browsers handle the innerHTML mechanism differently and you might find that they strip the <body> and <head> tags when you insert your HTML into your sandbox.
If this is the case then you can workaround it with a hack.
var escapedTags = yourHTMLString
.replace(/body/ig, 'body$')
.replace(/head/ig, 'head$')
// now the browser won't recognize the tags
// and therefore won't strip them out.
sandbox.innerHTML = escapedTags;
// do some work
// ...
// don't forget to unescape them!
var unescapedTags = sandbox.innerHTML
.replace(/body\$/g, 'body')
.replace(/head\$/g, 'head');
This makes use of the fact that the browser won't understand what a <body$> or a <head$> tag is, so it just leaves in intact.
You can use:
DOMParser and XMLSerializer.
The most important thing is; this is not a sandbox. It only uses a parser & serializer; and therefore it will not execute the scripts within the input; until you inject the output into an actual DOM.
// HTML string to be modified
var strHTML = '<html>...</html>'; // your HTML
// We'll parse this string into DOM in memory.
var parser = new DOMParser(),
doc = parser.parseFromString(strHTML, 'text/html'),
// in this example, we'll get the script elements and change/set
// some attributes of the first and the content of the second
scripts = doc.getElementsByTagName('script');
scripts[0].setAttribute('data-test-ui-libs123', 'test.bbb');
scripts[0].setAttribute('src', 'resources/aaaa/test-ui-core.js');
scripts[1].innerHTML = 'alert("test")';
// now that we've modified the HTML, we can serialize it into string
var serializer = new XMLSerializer(),
outputHTML = serializer.serializeToString(doc);
Example Pen.
DOMParser and XMLSerializer on MDN.
Browser support: IE10+ and modern browsers.
jQuery.parseHTML()
The document.implementation.createHTMLDocument() API also does not execute scripts or fetch resources via HTTP (such as videos, images, etc). This is the approach used by jQuery.parseHTML() method. See source here.
From jQuery docs; security considerations:
Most jQuery APIs that accept HTML strings will run scripts that are included in the HTML. jQuery.parseHTML does not run scripts in the parsed HTML unless keepScripts is explicitly true. However, it is still possible in most environments to execute scripts indirectly, for example via the attribute. The caller should be aware of this and guard against it by cleaning or escaping any untrusted inputs from sources such as the URL or cookies. For future compatibility, callers should not depend on the ability to run any script content when keepScripts is unspecified or false.
INITIAL (Node.js)
I understand your question as follows: You want to parse a HTML string in a Node.js environment (you mentioned Gulp), extend it and get the resulting string back.
First, you need to parse the string into a structure, on which you can make queries. There are several libraries available to achieve this. Cheerio.js was recommended and explained in a StackOverflow answer. Other solutions are also explaind there. The library provides you then an interface to the DOM of your HTML code. In the example of Cheerio.js, you can access the DOM similarly as in jQuery. The official example of their GitHub page is depicted below. In a similar manner, you can do your logic by selecting the elements and add your content (modify it, etc.). By calling the $.html() function, you get the modified structure back.
var cheerio = require('cheerio'),
$ = cheerio.load('<h2 class="title">Hello world</h2>');
$('h2.title').text('Hello there!');
$('h2').addClass('welcome');
$.html();
// => returns '<h2 class="title welcome">Hello there!</h2>'
If you want to use this logic in a Gulp build process, you need to wrap it into a Gulp plugin with Cheerio.js as a dependency. On this official GitHub readme file of Gulp it is explained in detail how you can create a Gulp plugin.
EDIT (Browser)
According to your edited question, I'll add this section about editing the HTML in the browser.
It is very convenient to use jQuery to modify a DOM in the browser. You can also modify a virtual DOM with jQuery. To do that, you just need to create the element but not append it to the real DOM. Unfortunately, the browser acts special when it comes to the following tags: <html>, <body>, <head> and <!DOCTYPE html>. As a workaround, you can just edit those tags with a regular expression and rename them to something like <body_temp> and so on. You need to have a good regular expression to only match tags and not content like class="testUiBody" which does also contain the word body. The special behavior is decribed here in detail.
The following code makes all the desired changes in the HTML. You can test it in an updated JSFiddle. Just click the Submit button and you can see the changes. The upper textarea acts as HTML input and the lower one as HTML output.
var html = "<!DOCTYPE html><html><head><meta.....";
// replace html, head and body tag with html_temp, head_temp and body_temp
html = html.replace(/<!DOCTYPE HTML>/i, '<doctype></doctype>');
html = html.replace(/(<\/?(?:html)|<\/?(?:head)|<\/?(?:body))/ig, '$1_temp');
// wrap the dom into a <container>: the html() function returns only the contents of an element
html = "<container>"+html+"</container>";
// parse the HTML
var element = $(html);
// do your calculations on the parsed html
$("<script>alert(\"test\");<\/script>").insertAfter(element.find('#test-ui-bootstrap'));
element.find("#test-ui-bootstrap").attr('data-test-ui-libs123', "test.bbb");
element.find("#test-ui-bootstrap").attr('src', 'resources/aaaa/test-ui-core.js');
// reset the initial changes (_temp)
var extended_html = element.html();
extended_html = extended_html.replace(/<doctype><\/doctype>/, '<!DOCTYPE HTML>');
extended_html = extended_html.replace(/(<\/?html)_temp/ig, '$1');
extended_html = extended_html.replace(/(<\/?head)_temp/ig, '$1');
extended_html = extended_html.replace(/(<\/?body)_temp/ig, '$1');
// replace all " inside data-something=""
while(extended_html.match(/(<.*?\sdata.*?=".*?)(")(.*?".*?>)/g)) {
extended_html = extended_html.replace(/(<.*?\sdata.*?=".*?)(")(.*?".*?>)/g, "$1'$3");
}
// => extended_html contains now your edited HTML
Is there any easy way to take in a block of CSS from the user from an textarea and add this styling to the styling for a specific div?
See I'm creating a simple code preview tool like codePen, so far I have two textarea inputs, one for Html and one for CSS, as the user types in the Html input this updates the preview pane, this works, now I want to do it for CSS.
CSS textarea could contain a few blocks like:
h1 {
font-size:23px;
}
.myClass {
//Somestyle
}
Now I want this CSS to be contained in the
<div id="preview"></div>
So it doesnt effect the rest of the page, so a manual example would be
$('preview h1').css('font-size','23px');
Anyway to automate this?
Do it like this. Hope it works.
Add a style block for dynamic styling.
<style id="dynamicCss">
</style>
on the apply button click handler, set the style
$('#btnApplyStyle').click(function(){
$('#dynamicCss').html('').html($('#txtaCustomCss').val());
});
See the Fiddle here.
Please use developer tools to see the new style tag added to head section.
This script simply adds rule to the document. If you don't want that behavior, you can use this plugin in combination with my logic to set scope for rule. You will need to place the style tag inside of the container and add a scoped attribute to style for it to work. Please see the documentation.
If you want to use the iframe approach instead, you'll first need an HTML document to host inside of the iframe. The iframe document should be loaded for the same origin (protocol + domain) as the host document (cross-document cross-domain stuff is tricky otherwise). With your application, this is probably not an issue.
Host page:
<iframe id="preview" src="preview.html"></iframe>
To make things easier on yourself, this iframe document could load a script with convenience functions for injecting the HTML and CSS from the host.
preview.html:
<html>
<head>
<script src="preview.js"></script>
<style type="text/css" id="page-css"></style>
</head>
<body></body>
</html>
preview.js:
function setHTML(html) {
document.querySelector('body').innerHTML = html;
}
function setCSS(css) {
var stylesheet = document.querySelector('#page-css');
// Empty the stylesheet
while (stylesheet.firstChild) {
stylesheet.removeChild(stylesheet.firstChild);
}
// Inject new CSS
stylesheet.appendChild(document.createTextNode(css));
}
Now, from the host page, you can call these functions whenever your text inputs change:
document.querySelector('#preview').contentWindow.setCSS(someCSS);
This plugin may come in handy: https://github.com/websanova/wJSNova/downloads .
Edited
Insert the text of the rules in one of the existing cssStyleSheets you have.
It will be something like
window.document.styleSheets[0].insertRule("a{color:red;}",window.document.styleSheets[0].cssRules.length)
The first parameter is the rule to insert and the second is the index.
Fiddle
The only problem here is that this will affect all the DOM on the page maybe looking for a way to add the #preview before each css rule to get something like
#preview h1{}
We've got a little tool that I built where you can edit a jQuery template in one field and JSON data in another and then hit a button to see the results immediately within the browser.
I really need to expand this though so the designer can edit a full CSS stylesheet within another field and when we render the template, it will have the CSS applied to it. The idea being that once we've got good results we can take the contents of these three fields, put them in files and use them in our project.
I found the jQuery.cssRule plugin but it looks like it's basically abandoned (all the links go nowhere and there's been no development in three years). Is there something better or is it the only game in town?
Note: We're looking for something where someone types traditional CSS stylesheet data in here and that is used immediately for rendering within the page and that can be edited and changed at will with the old rules going away and new ones used in their stead. I'm not looking for something where the designer has to learn jQuery syntax and enter in individual .css("attribute", "value") type calls to jQuery.
Sure, just append a style tag to the head:
$("head").append("<style>p { color: blue; }</style>");
See it in action here.
You can replace the text in a dynamically added style tag using something like this:
$("head").append("<style id='dynamicStylesheet'></style>");
$("#dynamicStylesheet").text(newStyleTextGoesHere);
See this in action here.
The cleanest way to achieve this is by sandboxing your user-generated content into an <iframe>. This way, changes to the CSS won't affect the editor. (For example, input { display:none; } can't break your page.)
Just render out your HTML (including the CSS in the document's <head>, and write it into the <iframe>.
Example:
<iframe id="preview" src="about:blank">
var i = $('#preview')[0];
var doc = i.contentWindow || i.contentDocument;
if (doc.document) doc = doc.document;
doc.open('text/html',true);
doc.write('<!DOCTYPE html><html>...</html>');
doc.close();
If the user should be able to edit a whole stylesheet, not only single style attributes, then you can store the entered stylesheet in a temporary file and load it into your html document using
$('head').append('<link rel="stylesheet" href="temp.css" type="text/css" />');
sounds like you want to write an interpreter for the css? if it is entered by hand in text, then using it later would be as simple as copy and pasting it into a css file.
so if you have a textarea on your page to type in css and want to apply those rules when you press the button, you could use something like this (only pseudocode, needs work):
//for each css id in the text area
$.each($('textarea[name=cssTextArea]').html().split('#'), function({
//now get each property
$.each($(this).split(';'), function(){
$(elem).css({property:value});
});
});
then you could write something to go through each element that your designer typed in, and get the current css rules for it (including those that you applied using some code like the snippet above) and create a css string from that which could then be output or saved in a db. It's a pain and much faffing around with substrings but unfortunately I don't know of a faster or more efficient way.
Hope this atleast gives you some ideas