I have 2 vue projects. In one I am creating a simple component:
<template>
<button #click="importIframe">Click me</button>
</template>
<script>
export default {
name: 'App',
methods: {
importIframe() {
console.log(parent.document)
},
}
</script>
In the other project of vue, import with an iframe the project 01 with a script:
<script src="http://localhost:8081/myiframe.js"></script>
Project 01 is running in localhost:8081 and 02 in 8080. I have a my iframe script which is the one that creates the iframe and imports it into the body.
This is myiframe.js script:
ready(function () {
initIframe()
})
/**
* Auto execute javascript function
* #param callbackFunction
*/
function ready(callbackFunction) {
if (document.readyState != 'loading') callbackFunction()
else document.addEventListener('DOMContentLoaded', callbackFunction)
}
/**
* Initialize iframe
* #return {Promise<void>}
*/
function initIframe() {
// Set url
const baseUrl = 'http://localhost:8081'
// Create div tag
const divContainer = document.createElement('div')
divContainer.setAttribute('id', 'container-iframe')
// Create iframe tag
const iframe = document.createElement('iframe')
iframe.setAttribute('src', baseUrl)
divContainer.appendChild(iframe)
document.body.appendChild(divContainer)
}
What I want to achieve is that by clicking on the button. I should create a new div inside # container-iframe. If I select the iframe container it returns null. And if I select the parent of the iframe with parent.document. It gives me a security error, how can I add an element to the parent of the iframe?
This is a very common issue when you work with cross-domains.
Good news is that we have postMessage to send events between the iframe and the parent.
In the child (iframe button):
<script>
export default {
name: 'App',
methods: {
importIframe() {
parent.postMessage("clicked", "*");
},
}
</script>
Then listen in paranet:
function listenToChild() {
var eventMethod = window.addEventListener
? "addEventListener"
: "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent"
? "onmessage"
: "message";
eventer(messageEvent, function (e) {
// if (e.origin !== 'http://the-trusted-iframe-origin.com') return;
if (e.data === "clicked" || e.message === "clicked")
alert('Message from iframe just came!');
// create a new div inside # container-iframe.
console.log(e);
});
}
Then add it to page load (ready function in your case)
More about the postMessage
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
https://gist.github.com/cirocosta/9f730967347faf9efb0b
Related
Example Stencil.js web component:
import { Component, ComponentInterface, Event, EventEmitter, h, Host } from "#stencil/core";
#Component({
tag: 'foo-testwebcomponent'
})
export class TestWebComponent implements ComponentInterface {
#Event({
eventName: 'foo-click',
cancelable: true
}) fooClick: EventEmitter;
fooClickHandler() {
this.fooClick.emit();
}
render() {
return(
<Host>
<a href="#"
onClick={this.fooClickHandler.bind(this)}
>Testing</a>
</Host>
)
}
}
HTML:
<foo-testwebcomponent id="test"></foo-testwebcomponent>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('test')
.addEventListener('foo-click', event => {
event.preventDefault();
console.log(`Foo Test Web Component clicked!`);
});
});
</script>
Problem:
In the HTML implementation, the prevent default does not stop the link from working.
Question:
How can I allow the end-user of my web component to prevent default, and stop the link from working?
I know that I can add preventDefault() in the fooClickHandler() (see below), but that seems odd to me. I'd like to give the control to the end user of the web component.
#Event({
eventName: 'foo-click',
cancelable: true
}) fooClick: EventEmitter<MouseEvent>;
fooClickHandler(event: MouseEvent) {
event.preventDefault();
this.fooClick.emit();
}
There are two separate events:
The user-initiated click event
Your fooClick custom event
In your example you call preventDefault() on the custom event but you need to call it on the original click event to prevent the link from navigating.
I know of two ways to achieve this:
1: Track whether your custom event is canceled
You can check whether the user called preventDefault() on your custom event using the defaultPrevented property. The fooClick event handler can stay the same.
fooClickHandler(clickEvent: MouseEvent) {
const customEvent = this.fooClick.emit();
if (customEvent.defaultPrevented) {
clickEvent.preventDefault();
}
}
Check out this online demo.
2: Pass the click event
Pass the click event to the fooClick event handler so the user can cancel it.
fooClickHandler(clickEvent: MouseEvent) {
this.fooClick.emit({ originalEvent: clickEvent });
}
And in the handler:
element.addEventListener('foo-click', event => {
event.detail.originalEvent.preventDefault();
console.log(`Foo Test Web Component clicked!`);
});
One way would be to overload the addEventListener function and capture the function reference
(needs some more work to make it work with nested elements, you get drift)
Or use a custom method addClick(name,func) so the user can still add any listener
<script>
customElements.define(
"my-element",
class extends HTMLElement {
connectedCallback() {
this.clicked = (evt)=>{
document.body.append("component handler")
}
this.onclick = (evt) => {
this.clicked(evt);
}
}
addEventListener(name, func) {
this.clicked = func;
}
}
);
document.addEventListener('DOMContentLoaded', () => {
document.querySelector('my-element')
.addEventListener('click', event => {
document.body.append(`user handler`);
});
});
</script>
<my-element>Hello Web Components World!</my-element>
You could also use good old onevent handlers:
<script>
customElements.define(
"my-element",
class extends HTMLElement {
connectedCallback() {
this.onclick = (evt) => console.log("component handler")
}
}
);
document.addEventListener('DOMContentLoaded', () => {
let el = document.querySelector('my-element');
el.onclick = event => console.log(`user handler`, el.onclick);
});
</script>
<my-element onclick="console.log('inline')">Hello Web Components World!</my-element>
So I'm using a data table which has an active element. When that active elment changes I store the name of the active element in a property of my polymer element. Then I display this String property in a div.
Now I know for certain that the property change works, because I console.log it after a change, the div displaying the property doesn't update and continually displays the default value I have set.
export class ProjectsOverview extends PolymerElement {
static get template() {
return html`
...
<div>{{currentProject}}</div>
...
`
}
static get properties() {
return {
currentProject: {
type: String,
value: "placeholder",
notify: true,
reflectToAttribute: true
}
};
}
connectedCallback() {
super.connectedCallback();
const grid = this.shadowRoot.querySelector('vaadin-grid');
grid.addEventListener('active-item-changed', function(event) {
const item = event.detail.value;
grid.selectedItems = [item];
if (item) {
this.set('currentProject', item.name);
} else {
this.set('currentProject', '');
}
console.log(this.currentProject);
});
}
}
My expected result would be that every time the currentProject property is updated, the div displaying the property updates as well.
The active-item-changed callback does not have its context bound to the Polymer instance (i.e., this is the grid and not the Polymer component). Instead of the function expression, use an arrow function to automatically bind this to the correct context.
// grid.addEventListener('active-item-changed', function(event) { // DON'T DO THIS
grid.addEventListener('active-item-changed', (event) => {
/* this is the Polymer instance here */
this.set('currentProject', ...);
})
Your scope is wrong. You're using an anonymous function so when you try to set currentProject, you do that when your this is your anonymous function. Use .bind(this) to fix your problem.
grid.addEventListener('active-item-changed', function(event) {
const item = event.detail.value;
grid.selectedItems = [item];
if (item) {
this.set('currentProject', item.name);
} else {
this.set('currentProject', '');
}
console.log(this.currentProject);
}.bind(this));
I am trying to use drag and drop on background image in a div but nothing is working. I did not find any drag and drop module for image in meteor. Is there any module or any default function in meteor to drag a background image. After uploading image is coming in div background now i want that user can drag that image and can set it's position. This is my code where i am showing image in background after uploading.
<div id="edit-image" class="text-center {{page}} {{isIosDevices}} {{profileHeader}}" style="{{myCoverPicture}}">
{{> uploaderbg profileHeader="profileHeader" userProfile=this.profile fromProfile=true}}
</div>
======= Interact JS ==================
'click .text-center': function (e) {
var isDraggable = interact('#test-img').draggable(); // true
}
<div id="my-image" class="text-center" style="">
<img src="{{myPicture}}" id="test-img" />
</div>
=================================================
Template.dragImgBg.onCreated(function helloOnCreated () {
const instance = this;
var ImageAxis1 = Meteor.user().profile.imageAxis;
values=ImageAxis1.split(' ');
instance.offsetx = new ReactiveVar(values[0]);
instance.offsety = new ReactiveVar(values[1]);
//console.log(ImageAxis1);
// fixed in this example
instance.bgUrl = new ReactiveVar(Meteor.user().profile.coverPicture);
})
Template.dragImgBg.helpers({
offsetx() {
return Template.instance().offsetx.get()
},
offsety() {
return Template.instance().offsety.get()
},
bgUrl() {
return Template.instance().bgUrl.get()
}
})
let active = false
Template.dragImgBg.events({
'mouseup' (/* event, templateInstance */) {
active = false
},
'mouseout .img-bg-movable' (/* event, templateInstance */) {
active = false
},
'mousedown .img-bg-movable' (/* event, templateInstance */) {
active = true
},
'mousemove'(event, templateInstance) {
if (!active) {
return
}
const movementx = event.originalEvent.movementX;
const movementy = event.originalEvent.movementY;
const oldx = templateInstance.offsetx.get();
const oldy = templateInstance.offsety.get();
let data = $('#data_img_pos')[0];
data.value = (oldx + movementx)+" "+(oldy + movementy);
templateInstance.offsetx.set(oldx + movementx);
templateInstance.offsety.set(oldy + movementy);
}
})
<template name="dragImgBg">
<div id="edit-image" class="img-bg-movable bg-img text-center {{page}} {{isIosDevices}}" style="background-position: {{offsetx}}px {{offsety}}px;background-image: url({{bgUrl}});">
{{> uploaderbg profileHeader="profileHeader" userProfile=this.profile fromProfile=true}}
</div>
</template>
After realizing, that this is not trivial in Blaze using third party libraries I tried to write some custom code.
Consider the following Template:
<template name="dragImgBg">
<div class="img-bg-movable" style="background-position: {{offsetx}}px {{offsety}}px;background-image: url({{bgUrl}});"></div>
</template>
with the following (examplatory) CSS:
.img-bg-movable {
width: 600px;
height: 250px;
overflow: hidden;
border: solid 1px #AAAAAA;
cursor: grab;
}
.img-bg-movable:active:hover {
cursor: grabbing;
}
As you can see the div is dynamically accepting styles, such as background image url (the one you get from your uploaded images) and x / y offset for the position.
The values for those styles are saved in reactive sources like a ReactiveVar and provided by simple helpers:
Template.dragImgBg.onCreated(function helloOnCreated () {
const instance = this
instance.offsetx = new ReactiveVar(0)
instance.offsety = new ReactiveVar(0)
// fixed in this example
instance.bgUrl = new ReactiveVar('https://upload.wikimedia.org/wikipedia/commons/3/3f/Caldwell_68_Corona_Australis_Dark_Molecular_Cloud.jpg')
})
Template.dragImgBg.helpers({
offsetx() {
return Template.instance().offsetx.get()
},
offsety() {
return Template.instance().offsety.get()
},
bgUrl() {
return Template.instance().bgUrl.get()
}
})
In order to change these values (and thus move the image) there needs to be some events that check, whether the element has been left-mouse-pressed and the mouse is moved.
If so, the delta values of the mouse-move are added to the reactive offset x / y sources. If the mouse is released or moved outside the image the values won't be applied.
let active = false
Template.dragImgBg.events({
'mouseup' (/* event, templateInstance */) {
active = false
},
'mouseout .img-bg-movable' (/* event, templateInstance */) {
active = false
},
'mousedown .img-bg-movable' (/* event, templateInstance */) {
active = true
},
'mousemove'(event, templateInstance) {
if (!active) {
return
}
const movementx = event.originalEvent.movementX
const movementy = event.originalEvent.movementY
const oldx = templateInstance.offsetx.get()
const oldy = templateInstance.offsety.get()
templateInstance.offsetx.set(oldx + movementx)
templateInstance.offsety.set(oldy + movementy)
}
})
The originalEevnt refers to the original event that is wrapped by the Template's jQuery event. You may customize the Template your needs.
If you know for example the dimensions of the image you could stop updating the position of offsetx or offsety reach these boundaries.
If you want to make this persistent (like for a user profile page) you can save the values of bgUrl (or the image file id of the uploaded image) and the offset x / y values in a collection and load these vlaues in onCreated 's autorun routine.
I have a modal window in Angular 4 that works fine but if the user clicks on the background / parent page the modal is closed.
I have found some solutions that suggest using backdrop='static' and keyboard=false when opening the modal but our modal uses a local Dialog class with a BehaviorSubject object so is opened using the .next method. I've also tried setting these attributes using div config but to no avail.
Therefore I'm looking for another solution, maybe using CSS or another setting / attribute that can be directly applied to the parent page or modal HTML.
See below for some of the relevant code.
dialog.component.ts:
constructor(private location: PlatformLocation,
private _dialog: DialogService,
private router: Router) { }
open() {
this.showDialog = true;
const body = document.body;
body.classList.add('cell-modal-open');
}
close() {
this.dialog = undefined;
}
private handleDialog(d: Dialog) {
if (!d) {
this.close();
} else if (d.template) {
if (this.showDialog) {
this.close();
}
this.dialog = d;
this.open();
}
}
ngOnInit() {
this.subscription = this
._dialog
.getDialog()
.subscribe({
next: (d) => { this.handleDialog(d); console.log('subscribed dialog') },
error: (err) => this.handleDialogError(err)
});
this.initialiseRoutingEventListeners();
}
dialog.service.ts
private d: Dialog = { template: null, size: DialogSizeEnum.XLarge };
private dialogSubject = new BehaviorSubject<Dialog>({ template: null, size: DialogSizeEnum.XLarge });
constructor() { }
showDialog(template: TemplateRef<any>, size = DialogSizeEnum.XLarge, requiresAction = false) {
Object.assign(this.d, { template: template, size: size, requiresAction: requiresAction });
if (this.d !== null) {
this.dialogSubject.next(this.d);
}
}
getDialog(): BehaviorSubject<Dialog> {
return this.dialogSubject;
}
clear() {
this.dialogSubject.next(null);
}
Any suggested approaches are welcome!
Added flag to the close() method and adding condition to only set to undefined if true (i.e. from a valid location).
I have custom polymer component that will load my translations for whole application. Here is the code:
<link rel="import" href="../polymer/polymer-expressions/polymer-expressions.html">
<link rel="import" href="../polymer/core-ajax/core-ajax.html">
<polymer-element name="nz-i18n" hidden>
<script type="text/javascript">
Polymer('nz-i18n', {
/**
* our messages container
*/
messages: {},
/**
* what loading is in progress
*/
loading: {},
created: function() {
var self = this;
// create new expression, to be used for translate method
PolymerExpressions.prototype.$$ = function(key, params) {
// IMPORTANT !!! the scope here is the element we call this function from
// set this element as parent of the translator
self.parent = this;
// get translation
return self.translateMessage(key, params);
};
// restore loaded messages from local storage
//this.restoreFromLocalStorage();
},
/**
* Load messages from local storage
*/
restoreFromLocalStorage: function() {
// check if we have translations already loaded
try {
if (translations = localStorage.getItem('nz-messages')) {
// convert JSON string representation to object
this.messages = JSON.parse(translations);
return true;
}
} catch (e) {
// nothing to do
// we will load translations on demand
}
},
/**
* Translate message by given key and additional parameters
*
* IMPORTANT !!!do not use translate as the method name
* there is such a property in the element
*
* #param key - key to be translated
* #param params - additional parameters
*/
translateMessage: function(key, params) {
// set default parameters if not defined
if (!params || params == 'undefined') {
var params = {};
}
if (!params.module) {
params.module = 'System';
}
var msg;
if (this.messages[params.module]) {
// module messages are already loaded
try {
// try to get translation
msg = this.messages[params.module].messages[key] || key;
// key with multiple forms has been provided
if (typeof(msg) == "object") {
if (params.n != '' && params.n != 'undefined') {
//get index if the translation in function of the rules
eval('idx = ' + this.messages[params.module].pluralRules.replace('n', params.n) + ';');
msg = msg[idx] || key;
} else {
msg = msg[0];
}
}
} catch (e) {
//no translation - return the key
msg = key;
}
} else {
// module messages are not loaded
// start loading
this.loadTranslations(params.module);
// this will be processed very customly
msg = '';
}
return msg;
},
/**
* Load messages for the module requested
*
* #param module - messages module
* #param force - if we have to force loading even if
* messages for the module are already loaded
*/
loadTranslations: function(module, force) {
// set defaults
if (!module) {
module = 'System';
}
// check if translations for this module are loaded
// or if loading is in progress
if (!this.loading[module] && (force || !this.messages[module])) {
// noooooooo - we will load them
this.loading[module] = true;
// create ajax request
ajax = document.createElement('core-ajax');
ajax.auto = true;
ajax.method = 'GET';
ajax.handleAs = 'json';
ajax.headers = {
"Accept": "application/json",
"Content-type": "application/json"
};
ajax.url = window.basePath + 'api/translations';
ajax.params = {"module": module};
// register event listeners for the response and post response processing
ajax.addEventListener('core-response', this.handleResponse);
ajax.parent = this;
// do not uncomment this - http://xhr.spec.whatwg.org/
//ajax.xhrArgs = {sync: true};
}
},
/**
* Process messages loading request
*/
handleResponse: function() {
// IMPORTANT !!!! core-ajax scope is here
if (this.response) {
for (module in this.response) {
// add current response to the translations
this.parent.messages[module] = this.response[module];
// remove loading flag for this module messages
delete this.parent.loading[module];
}
// set translations in local storage
localStorage.setItem('nz-messages', JSON.stringify(this.parent.messages));
}
}
});
</script>
</polymer-element>
I have also another element that will be used as a frameset and will host all my other application elements:
<link href="../../polymer/core-header-panel/core-header-panel.html" rel="import">
<link href="../../polymer/core-toolbar/core-toolbar.html" rel="import">
<polymer-element name="nz-frameset">
<template>
<link href="nz-frameset.css" rel="stylesheet">
<core-header-panel flex>
<!-- HEADER -->
<core-toolbar justify="between">
<img id="logo" src="../../images/logo.png" />
<div id="title">{{ $$('header_title') }}</div>
</core-toolbar>
<!-- CONTENT -->
<div class="content">{{ $$('num', {n: 4}) }}</div>
</core-header-panel>
<!-- FOOTER -->
<core-toolbar bottomJustify="around">
<footer class="bottom">
{{ $('footer') }}
</footer>
</core-toolbar>
</template>
<script type="text/javascript">
Polymer('nz-frameset', {
ready: function() {
},
});
</script
</polymer-element>
And here is my body(all imports needed are in the HEAD):
<body fullbleed vertical layout unresolved>
<!-- INITIALIZE TRANSLATOR -->
<nz-i18n></nz-i18n>
<!-- LOAD FRAMESET -->
<nz-frameset flex vertical layout></nz-frameset>
</body>
The problem is that when I open my APP for the first time and no translations are loaded yet, after I update my messages container the expressions does not re-bind and i can not see any text. On refresh(messages are in the local storage already), everything works like a charm.
Any help? Thanks!
One issue that I saw right from the get-go is that the expressions will only be evaluated once since there is no observable value in it, e.g. the observer doesn't see a variable reference and can detect changes.
This might be a hack, but I would pass a "changeable variable" and filter it in the expression, e.g.
<div id="title">{{ n_translate | $$('header_title') }}</div>
Now you need to fire an event whenever you load a new translation, in your handle handleResponse just add:
this.fire("translationChanged");
In every module that uses translations, you need to add a event observer:
this.addEventListener("translationChanged", function() {
this.n_translate = new Date(); // some changed value
});
There is no easy way to re-trigger polymer expressions, actually I don't know of any other than little hacks (for template lists, etc) that in the end cause more problems then help.