How do you use ViewCompiler to manually compile part of the DOM? - javascript

Say I have a simple view model (widget.js):
import {Behavior} from 'aurelia-framework';
export class Widget
{
static metadata() { return Behavior.customElement('widget') }
constructor()
{
this.title= "AwesomeWidget";
}
}
With the following view: (widget.html):
<template>
<div>${title}</div>
</template>
Now say I inject some markup like this into the DOM:
var markup = `<div><widget></widget></div>`;
var $markup = $(markup);
var $placeholder = $("#placeholder");
$placeholder.append($markup);
How can I now tell Aurelia to compile this newly added part of the DOM against a new instance of Widget? I know it involves ViewCompiler but need an example to help me along. I'd greatly appreciate any help. Thanks!

A few months ago the TemplatingEngine class got a newly accessible enhance API method. This shortcuts the need to manually use the viewCompiler and compile method which was originally the only easy approach. This blog post details how you can use the enhance API to Aureliaify dynamically added HTML in your pages.
This has the added benefit of not needing to clean up the compiled HTML or detach anything either.

Here's an example: https://gist.run/?id=762c00133d5d5be624f9
It uses Aurelia's view compiler to compile the html and create a view instance, bound to the supplied view-model.
view-factory.js
import {
inject,
ViewCompiler,
ViewResources,
Container,
ViewSlot,
createOverrideContext
} from 'aurelia-framework';
#inject(ViewCompiler, ViewResources)
export class ViewFactory {
constructor(viewCompiler, resources, container) {
this.viewCompiler = viewCompiler;
this.resources = resources;
this.container = container;
}
insert(containerElement, html, viewModel) {
let viewFactory = this.viewCompiler.compile(html);
let view = viewFactory.create(this.container);
let anchorIsContainer = true;
let viewSlot = new ViewSlot(containerElement, anchorIsContainer);
viewSlot.add(view);
view.bind(viewModel, createOverrideContext(viewModel));
return () => {
viewSlot.remove(view);
view.unbind();
};
}
}
Usage:
let view = this.viewFactory.insert(containerElement, viewHtml, viewModel);

Related

Create static content selectors for Cypress

We are moving from GEB/Spock to Cypress for front end automation. With GEB/Spock, the page objects had static content. This was used to create the selectors for the page objects.
class LoginPage extends Page {
//private static Logger log = LoggerFactory.getLogger(LoginPage.class);
static url = 'login/'
static at = { title == "Login to TalentBank"}
static content = {
backgroundImage { $("div [style*=\"background-image\"]") }
logo { $(".center-img img") }
emailHeader { $("#emailGroup:not([style*=\"none\"]) label") }
emailTextBox { $('#emailGroup:not([style*="none"]) #email') }
nextButton { $('#loginWithEmail') }
pwdHeader { $("#passwordGroup:not([style*=\"none\"]) label") }
pwdTextBox { $("#passwordGroup:not([style*=\"none\"]) #password") }
loginButton { $("#loginWithPassword") }
loginError(wait: true) { $("#loginError") }
In cypress, I'm unsure where to create and call these objects. Are the selectors supposed to be created as fixtures or as support files? I've read through the cypress documentation, but can't find what I am looking for.
Edit: 2/4
Under support, I tried creating a LoginPage.js file to contain the objects
// Login Page Objects
const emailTextBox = $('#emailGroup:not([style*="none"]) #email');
My test is under integration directory. I use the new constant in my test. There are no errors in my IDE, as the test appears to find the constant as it displays in the Ctrl+Space code completions.
describe('Successfull log in to the System', function() {
it('Standard User - Successful log in ', function() {
cy.get(emailTextBox).type('RodneyRuxin#mailinator.com')
When I run my test however, I get an error that says
ReferenceError: emailTextBox is not defined
.
Okay so these are different selectors to interact with:
I would recommend a completely different class and then do an import of them?
So for example a file called locators.js
which contains:
export const backgroundImage = () => cy.get("div [style*=\"background-image\"]");
then in your other file, you can import it like so:
import * as locators from "../locators/locators.js";
and call it like this (example):
locators.backgroundImage()
.should('be.visible')
.click();
Hope this helps!

Vue: Load static script

I feel like this should be a very trivial thing to accomplish, yet I've been struggling for too long now. I'm trying to load a static minified script in my vue project.
This is my project structure:
project
-- public
index.html
-- src
App.vue
main.js
-- static
p5.min.js
vanta.waves.min.js
I'm trying to follow the setup as instructed here:
<script src="three.r92.min.js"></script>
<script src="vanta.waves.min.js"></script>
<script>
VANTA.WAVES('#my-background')
</script>
Since I'm only using the script in one component, I would like to load it there instead of globally including it in the index.html file.
export default {
...
methods: {
animate() {
const p5Script = document.createElement('script')
p5Script.async = true;
p5Script.defer = true;
p5Script.id = 'p5'
p5Script.src = '#/static/p5.min.js';
document.head.appendChild(p5Script);
const vantaScript = document.createElement('script')
vantaScript.async = true;
vantaScript.defer = true;
vantaScript.id = 'vanta'
vantaScript.src = '#/static/vanta.topology.min.js'
document.head.appendChild(vantaScript);
vantaScript.onload = () => {
// window???
window.VANTA.TOPOLOGY({
el: '#vanta',
color: 0xced4b1,
backgroundColor: 0xe0ebeb
})
}
}
},
mounted() {
this.animate();
},
});
I'm trying to access the VANTA object on window after it loads, but there is none. It feels like my approach is just wrong to begin with, but I'm not able to find any documentation on how to include static scripts?
Check this repo for a vue vanta component with slots to use in specific components https://github.com/iwatakeshi/vue-vanta

Render Vue component tag added by external code

I'm trying to add simple Vue components to a legacy app. The goal is to be able use some newer technologies without rewriting everything.
Basically I want to be able to put <tags-input> element wherever I want and have Vue replace it with a component.
I have instantiated Vue and added some <tags-input> elements to the html. I use them as parts of table rows generated originally by ASP WebForms.
import Vue from 'vue'
import TagsInput from './ClientTags'
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
import '../style.css';
Vue.config.productionTip = false
window.VueApp = new Vue({
el: '#app',
components: {
TagsInput
}
})
Initially the <tags-input> elements render nicely. The problem is that external legacy JS code adds new rows to the table (WebForms performs some auto-magic pagination). After the new rows are added to the DOM, Vue doesn't render <tags-input> elements inside them.
So, my goal is:
Whenever a new <tags-input> element is added to the DOM (by external JS), it should be rendered by Vue and added to the window.VueApp.
I finally figured it out (thanks to Sphinx comment). I probably should not be using Vue app at all in my case. Instead I should manually create and mount my components like so:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
var MyComponent = Vue.component('my-component', {
// camelCase in JavaScript
props: ['someTitle'],
template: '<b>[XYZ {{ someTitle }}]</b> '
})
function dashToCamelCase( myStr ) {
return myStr.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
}
function getAttributes ( node ) {
var i,
attributeNodes = node.attributes,
length = attributeNodes.length,
attrs = {};
for ( i = 0; i < length; i++ ) attrs[dashToCamelCase(attributeNodes[i].name)] = attributeNodes[i].value;
return attrs;
}
function renderExisting(componentElementName, componentCreator){
$(componentElementName).each(function(){
var props = getAttributes(this)
var component = componentCreator(props)
component.$mount(this)
})
}
function renderNew(appElementId, componentElementName, componentCreator){
var obs = new MutationObserver(function(mutations, observer) {
$.each(mutations, function (i, mutation) {
var addedNodes = $(mutation.addedNodes);
var selector = componentElementName
var filteredEls = addedNodes.find(selector).addBack(selector);
filteredEls.each(function(){
var props = getAttributes(this)
var component = componentCreator(props)
component.$mount(this)
});
});
});
var canvasElement = $(appElementId)[0];
obs.observe(canvasElement, {childList: true, subtree: true});
}
function setUpRendering(appElementId, componentElementName, componentCreator){
renderExisting(componentElementName, componentCreator)
renderNew(appElementId, componentElementName, componentCreator)
}
$(function(){
setUpRendering('#myApp', 'my-component', (props) => new MyComponent({propsData: props}))
});
</script>
<script>
function addMyTag(){
$('#myApp').append( '<my-component some-title="' + (new Date()).getTime() + '"></my-component>' )
}
</script>
</head>
<body>
<button onclick='addMyTag()'>Add!</button>
<div id="myApp">
<my-component some-title="aaa"></my-component>
<my-component some-title="bbb"></my-component>
<my-component some-title="ccc"></my-component>
<div>
</body>
</html>
I will be happy to accept a better answer.

Using the Wiris editor within a Web Component

I have created a Web Component which hosts Wiris. However when the component is rendered the Wiris editor is (very) badly formed:
You can see the issue live here.
The code is as follows:
class WirisComponent extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
// Create a shadow root
var shadow = this.attachShadow( { mode: 'open' } );
// Create a div to host the Wiris editor
var div = document.createElement('div');
div.id = 'editorContainer';
var wirisDefaultConfig = {
'language': 'en'
};
var editor = com.wiris.jsEditor.JsEditor.newInstance(wirisDefaultConfig);
// Insert the Wiris instance into the div
editor.insertInto(div);
// Append it to the shadow route
shadow.appendChild(div);
}
}
// Define the new element
customElements.define('wiris-component', WirisComponent);
and the HTML mark-up is:
<wiris-component></wiris-component>
Note that I've tried this in Chrome which does have full support for web components.
Any idea what the problem is? Is the problem related to the styling issue found in this issue?
Don't use a Shadow DOM: the styles imported with your library are not working with it.
class WirisComponent extends HTMLElement {
connectedCallback() {
var wirisDefaultConfig = {
'language': 'en'
};
var editor = com.wiris.jsEditor.JsEditor.newInstance(wirisDefaultConfig);
editor.insertInto(this);
}
}
// Define the new element
customElements.define('wiris-component', WirisComponent);
<script src="https://www.wiris.net/demo/editor/editor"></script>
<wiris-component></wiris-component>

Extending HTML elements in Web components

From the custom elements page, I see that to extend an element you do:
var XFooButtonPrototype = Object.create(HTMLButtonElement.prototype);
XFooButtonPrototype.createdCallback = function() {
this.textContent = "I'm an x-foo button!";
};
var XFooButton = document.registerElement('x-foo-button', {
prototype: XFooButtonPrototype,
extends: 'button'
});
Then later in the guide it says that you can make an element by writing either:
<x-foo></x-foo>
Or:
<button is="x-foo-button"></button>
Questions:
Why is it important to specify extends: 'button' when the element is obviously_ inheriting from HTMLButtonElement (since it has HTMLButtonElement.prototype in its proto chain)
How is the link between button and x-foo-button established? Does x-foo-button become a possible option of button in terms of is="x-foo-button" thanks to that extends: 'button' ? What happens "internally", so to speak?
Why would you pick <button is="x-foo-button"></button> over <x-foo></x-foo>...?
[ADDENDUM]
Polymer saves us from this duplication:
MyInput = Polymer({
is: 'my-input',
extends: 'input',
created: function() {
this.style.border = '1px solid red';
}
});
If extends is there, Polymer will put the right prototype in the chain with Object.getPrototypeOf(document.createElement(tag));.
So, corollary question:
Why the duplication in the first place? If there is an extends, shouldn't the browser automatically do this?
You totally misunderstood how extending web components work.
Create simple elements
First of all, this is how you register a new element:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
To create an element you can do one of these:
<x-foo></x-foo>
var xFoo = new XFoo();
document.body.appendChild(xFoo);
var xFoo = document.createElement( 'x-foo')
document.body.appendChild(xFoo);
Create extended elements
This is how you extend an existing element:
var XFooButton = document.registerElement('x-foo-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
To create one you can do one of these:
<button is="x-foo-button"></button>
var xFooButton = new XFooButton();
document.body.appendChild(xFoo);
var xFooButton = document.createElement('button', 'x-foo-button');
document.body.appendChild(xFooButton);
Note that in case of extended custom elements, when registering them you have to specify both the prototype (set to HTMLButtonElement.prototype rather than HTMLElement.prototype), and the extended tag's name (extends: 'button').
Also, when you create an extended element using markup or createElement(), you need to also specify the basic element (button) and the extended one (x-foo-button),
(Note: I am aware I am answering myself)
I think its Importent to Say here:
WARNING DEPRECATED Browser API METHOD
Here in this Question a .registerElement is Used it got Replaced by .defineElement and the Api has changed
current way to define a element
class AppDrawer extends HTMLElement {
constructor() {
super()
this.innerHTML = '<h1>UH</h1>'
}
}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer-noname', class extends HTMLElement {
constructor() {
super()
this.innerHTML = '<h1>UH AH</h1>'
}
});
Example - defining a mobile drawer panel, < app - drawer >:
Example usage:
<app-drawer></app-drawer>
<app-drawer-noname></app-drawer-noname>
```

Categories