I'm trying to dynamically / programatically insert code into the DOM and have aurelia handle the inserted code like any other module.
The tricky part is that the inserted html has a <compose view-model='A'></compose> element which should itself load a module "A".
I have a gistRun here
app.js
loadmod(container, viewmodel){
debugger;
var childID = container + "child";
var content = `<compose view-model='${viewmodel}' id='${childID}'></compose>`;
$("#" + container).append("<div>" + content + "</div>");
let el = $("#" + childID)[0];
let view = this.templatingEngine.enhance({ element: el, bindingContext: {}, overrideContext: {}});
view.bind();
view.attached();
}
loadm1(e){
this.loadmod("m1holder", "m1");
}
loadm2(e){
this.loadmod("m2holder", "m2");
}
loadm2again(e){
this.loadmod("m2holderagain", "m2");
}
<template>
<div>Stuff here</div>
<button click.delegate="loadm1($event)">Load M1</button>
<button click.delegate="loadm2($event)">Load M2</button>
<button click.delegate="loadm2again($event)">Load Another M2</button>
<div id="m1holder"></div>
<div id="m2holder"></div>
<div id="m2holderagain"></div>
</template>
The issue is you are attempting to enhance an element directly. You need to enhance a parent element which contains nodes to be enhanced. The Templating Engine looks for a parentNode when it does the enhancement logic.
See this updated Gist.run example for how it should work.
Related
I have several pages on a website that use the same header for each page. I was wondering if there was some way to simply reference a file with the html for the header sort of like in this pseudo code:
<!-- Main Page -->
<body>
<html_import_element src = "myheadertemplate.html">
<body>
Then in a separate file:
<!-- my header template html -->
<div>
<h1>This is my header</h1>
<div id = "navbar">
<div class = "Tab">Home</div>
<div class = "Tab">Contact</div>
</div>
</div>
This way I could write the header html once and just import it in each of my pages where I need it by writing one simple tag. Is this possible? Can I do this with XML?
You could do it in this fashion below.
<head>
<link rel="import" href="myheadertemplate.html">
</head>
where you could have your myheadertemplate.html
<div>
<h1>This is my header</h1>
<div id = "navbar">
<div class = "Tab">Home</div>
<div class = "Tab">Contact</div>
</div>
</div>
You can then use it with JS below
var content = document.querySelector('link[rel="import"]').import;
So, after a long time I actually found a way to do this using AJAX. HTML Imports are a great solution, but the support across browsers is severely lacking as of 04/2017, so I came up with a better solution. Here's my source code:
function HTMLImporter() {}
HTMLImporter.import = function (url) {
var error, http_request, load, script;
script =
document.currentScript || document.scripts[document.scripts.length - 1];
load = function (event) {
var attribute, index, index1, new_script, old_script, scripts, wrapper;
wrapper = document.createElement("div");
wrapper.innerHTML = this.responseText;
scripts = wrapper.getElementsByTagName("SCRIPT");
for (index = scripts.length - 1; index > -1; --index) {
old_script = scripts[index];
new_script = document.createElement("script");
new_script.innerHTML = old_script.innerHTML;
for (index1 = old_script.attributes.length - 1; index1 > -1; --index1) {
attribute = old_script.attributes[index1];
new_script.setAttribute(attribute.name, attribute.value);
}
old_script.parentNode.replaceChild(new_script, old_script);
}
while (wrapper.firstChild) {
script.parentNode.insertBefore(
wrapper.removeChild(wrapper.firstChild),
script
);
}
script.parentNode.removeChild(script);
this.removeEventListener("error", error);
this.removeEventListener("load", load);
};
error = function (event) {
this.removeEventListener("error", error);
this.removeEventListener("load", load);
alert("there was an error!");
};
http_request = new XMLHttpRequest();
http_request.addEventListener("error", error);
http_request.addEventListener("load", load);
http_request.open("GET", url);
http_request.send();
};
Now when I want to import HTML into another document, all I have to do is add a script tag like this:
<script>HTMLImporter.import("my-template.html");</script>
My function will actually replace the script tag used to call the import with the contents of my-template.html and it will execute any scripts found in the template. No special format is required for the template, just write the HTML you want to appear in your code.
As far as I know it's not possible. You can load the header as a webpage in a iframe element though. In the past webpages were built with frame elements to load seperate parts of a webpage, this is not recommended and support in current browsers is due to legacy.
In most cases this is done with server side languages like php with as example include("header.php");.
I have been trying to print after modifying html element but element has not been changed. (Angular2)
This source code is simplified one.
<div *ngIf="displayType === 'screen'">
<div>This is Screen</div>
</div>
<div *ngIf="displayType === 'print'">
<div>This is Print</div>
</div>
And when click a button the following event.
displayType: string = 'screen'; // default
OnPrint() {
this.displayType = 'print';
let tmp = document.createElement('div');
let el = this.elementRef.nativeElement.cloneNode(true);
tmp.appendChild(el);
let content = tmp.innerHTML;
let frame1 = document.createElement('iframe');
document.body.appendChild(frame1);
let frameDoc = frame1.contentWindow;
frameDoc.document.open();
frameDoc.document.write('<html><body>'+content+'</body></html>');
frameDoc.document.close();
setTimeout(function () {
window.frames["frame1"].focus();
window.frames["frame1"].print();
document.body.removeChild(frame1);
}, 500);
}
But contents are elements before modifying.
How can I refresh elements?
To refresh the elements you need to trigger the change detection.
Here is a very good post about change detection in Angular 2: http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
I've created a plunker here to illustrate it:
http://plnkr.co/edit/0ZwkaQ776mKpLYZJogYx?p=preview
<button (click)="OnPrint()">OnPrint</button>
Also you should not modify the DOM elements directly, you should create a component and use a structural directive to display it (here the component has a selector "my-print"):
<my-print *ngIf="isPrint" [iframeSrc]="..."></my-print>
I have a project with Polymer + Reveal.js
I have a view with polymer that gets all the Slides/Sections.
<template is="dom-repeat" items="[[my.slides]]" as="slide">
<section>
<h1>slide.title</h1>
<h2>slide.content</h2>
</section>
</template>
When I try to start Reveal.js, I have the issue related to:
(index):21136 Uncaught TypeError: Cannot read property
'querySelectorAll' of undefined
I think is because Reveal.js cannot select a Webcomponent generated by Polymer, because Reveal.js needs to have all slides content wrote on the html file by separate.
Then my question is: How to use Polymer Webcomponents with Reveal,js?
Alan: Yes, you are right.
Now I am creating DOM elements directly with JS avoiding Polymer shadowDOM elements.
Then I created a function called createSlides where - based in a JSON response - I appending slides (sections) within slides div.
Fist I create a Polymer template similar to:
<template>
<div class="reveal">
<div id="slides" class="slides">
<section>
This section will be removed
</section>
</div>
</div>
</template>
Next I removed the unused slide and appended some slides. Finally start the Reveal presentation
ready()
{
this.removeInitialSlide();
this.addSomeSlides();
this.startRevealPresentation();
}
clearInitialSlides()
{
var slidesComp = this.$.slides;
while (slidesComp.hasChildNodes()) {
slidesComp.removeChild(slidesComp.lastChild);
}
}
addSomeSlides()
{
var slide1 = document.createElement("section");
var image = document.createElement("img");
image.src = "some/path/to/image.jpg";
slide1.appendChild(image);
this.$.slides.appendChild(slide1);
var slide2 = document.createElement("section");
slide2.innerHTML = "<h1>Test content</h1>"
this.$.slides.appendChild(slide2);
}
Working fine for now.
I think you most likely can't use reveal.js in a web component created with Polymer right now and here's why.
If you look at reveal.js's code it looks for dom elements with the reveal and slides classes on the main document like this:
dom.wrapper = document.querySelector( '.reveal' );
dom.slides = document.querySelector( '.reveal .slides' );
The problem with that is that Polymer elements have their own local dom which is a different dom tree which can't be accessed using methods like document.querySelector which means reveal.js can't access to them.
I have a large chunk of HTML in an ng-repeat that for certain elements has a container element and for others it does not. I'm currently achieving this with two ng-ifs:
<strike ng-if="elem.flag">
… <!-- several lines of directives handling other branching cases -->
</strike>
<div ng-if="!elem.flag">
… <!-- those same several lines copied-and-pasted -->
</div>
While this works, it means I have to remember copy-and-paste any edits, which is not only inelegant but also prone to bugs. Ideally, I could DRY this up with something like the following (inspired by ng-class syntax):
<ng-element="{'strike':flag, 'div':(!flag)}">
… <!-- lots of code just once! -->
</ng-element>
Is there any way to achieve a similarly non-repetitive solution for this case?
You can make such directive yourself.
You can use ng-include to include the same content into both elements.
Assuming the effect you desire is to have the text within your tag be striked through based on the condition of the elem.flag:
You could simply use the ng-class as follows
angular.module('ngClassExample', [])
.controller('elemController', Controller1);
function Controller1() {
vm = this;
vm.flag = true;
vm.clickItem = clickItem
function clickItem() {
// Toggle the flag
vm.flag = !vm.flag;
};
}
.strikethrough{
text-decoration: line-through
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='ngClassExample' ng-controller="elemController as elem">
<div ng-class="{strikethrough: elem.flag}" ng-click="elem.clickItem()">
element content should be sticked through: {{elem.flag}}
</div>
</div>
You can do it with a directive
module.directive('myFlag', function() {
var tmpl1 = '<strike>...</strike>';
var tmpl2 = '<div>...</div>';
return {
scope: {
myFlag: '='
},
link: function(scope, element) {
element.html(''); // empty element
if (scope.myFlag) {
element.append(tmpl1);
} else {
element.append(tmpl2);
}
}
};
});
And you just use it like:
<div ng-repeat="item in list" my-flag="item.flag"></div>
You could create a directive which will transclude the content based on condition. For tranclusion you could use ng-transclude drirective, in directive template. Also you need to set transclude: true.
HTML
<my-directive ng-attr-element="{{elem.flag ? 'strike': 'div'}}">
<div> Common content</div>
</my-directive>
Directive
app.directive('myDirective', function($parse, $interpolate) {
return {
transclude: true,
replace: false, //will replace the directive element with directive template
template: function(element, attrs) {
//this seems hacky statement
var result = $interpolate(attrs.element)(element.parent().scope);
var html = '<'+ result + ' ng-transclude></'+result+'>';
return html;
}
}
})
Demo Plunkr
You can also use ng-transclude :
Create your directive :
<container-directive strike="flag">
<!-- your html here-->
</container-directive>
Then in your directive do something like :
<strike ng-if="strike">
<ng-transclude></ng-transclude>
</strike>
<div ng-if="!strike">
<ng-transclude></ng-transclude>
</div>
I have one big div with id="elements" and I load from JSON file new elements objects and I need that for every element create new div inside elements ( elements div should contain lot off smaller divs, for every element one small div ). How to place this small divs inside this big div one behind another ? How to add this small divs a class style ?
In Dojo (since you have the dojo tag):
var div_elements = dojo.byId("elements");
dojo.forEach(json_data.items, function(item) {
dojo.create("div", { "class":"whatever " + item.classNames }, div_elements);
});
Of course, you can put anything as the class for your div. I just provided an example. In the second argument to dojo.create, you pass in a hash containing all the properties you want that div to have.
Create a new DOM element like so:
var childDiv = document.createElement('div');
Then add to the outer div like so:
var insertedElement = div.insertBefore(childDiv, null);
You would then keep creating childDivs as you iterate over your JSON data, and inserting them into the div Node as above.
I think you need something like this:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js" ></script>
<script type="text/javascript">
$(document).ready(function(){
json_data = 'Hey';
$('#elements').append('<div class="in_elements">' + json_data + '</div>');
});
</script>
<div id="elements">
</div>
Test it
There a simple jQuery functions for that:
var box= $("#elements");
// create elements
for (var i=0; i<items.length; i++) {
var t = $("<div class=\"element\" id=\"item_"+i+"\">"+items[i]['text']+"</div>");
box.append(t);
}
That's what you where looking for?