H.ui.Control with React - here-api

I'm looking to add a react element with H.ui.Control. Is this possible? and how might it be done?
// sudo code of what I did
componentDidMount() {
...
let button = new H.ui.Control(this.myButtonControl);
button.setPosition('top-left');
this._ui.addControl('button-control', button);
...
}
myButtonControl() {
return <button className="H_btn">Hello World</button>
}
A new <div class="H_ctl"></div>, appears where the control was suppose to be, but not the button.

While it's not exactly what I wanted to do, I did find a solution. I created a generic class that extends H.ui.Control, in this case ButtonGroupControl.
class ButtonGroupControl extends H.ui.Control {
constructor(buttons: []) {
super();
this._buttons = buttons;
this.addClass('H_grp');
}
renderInternal(el, doc) {
this._buttons.forEach((button, i) => {
let btn = doc.createElement('button');
btn.className = 'H_btn';
btn.innerText = this._buttons[i].label;
btn.onclick = this._buttons[i].callback;
el.appendChild(btn);
})
super.renderInternal(el, doc);
}
}
export default ButtonGroupControl;
Then, inside my map component, I created passed array of items into the control, like so:
const mapToolsControl: ButtonGroupControl = new ButtonGroupControl([
{
label: 'Add Field',
callback: () => {
console.log('callback: adding field');
}
},
{
label: 'Remove Field',
callback: () => {
console.log('callback: remove field');
}
}
]);
Lastly, I added the control to the map like:
this._map.addControl('map-tools-control', mapToolsControl);
This results in the following (it's a link because I don't have enough points to embed yet):
Screenshot of Result

Here is what i have done (adding two buttons to the map)
var U_I = new H.ui.UI(map);
var container = new H.ui.Control();
container.addClass('here-ctrl here-ctrl-group');
var button = new H.ui.base.Element('button', 'here-ctrl-icon map_control');
container.addChild(button);
button.addEventListener('click', function() { alert(1); });
var button = new H.ui.base.Element('button', 'here-ctrl-icon map_center');
container.addChild(button);
button.addEventListener('click', function() { alert(2); });
container.setAlignment('top-right');
U_I.addControl('myControls', container );
U_I.addControl('ScaleBar', new H.ui.ScaleBar() );
the rendering is made by css (here is an extract)
button.here-ctrl-icon {
width: 25px;
height: 25px;
margin: 2px 0 0 2px;
}
.map_control { background: url("images/map_control.png") no-repeat scroll 0 0 transparent; }
.map_center { background: url("images/map_center.png") no-repeat scroll 0 0 transparent; }
H.ui.base.Button(); is not working ... it creates a div
It is not possible to add attributes to button such as alt or title thru the api.
I still have to deal with the addEventListener ... (not working !)
the result :
my new nice controls

Related

Angular animation on scroll

I build an webpage with angular, each module is an component it has an animation in it but it run's only when the page opens but i need to perform the animation while the component is visibile on the screen. i just tried below like hide and show the component by checking the scrollY of the page. is there any better way to do it?
#HostListener('window:scroll', ['$event']) onWindowScroll(e: any) {
if (window.pageYOffset < 180) {
this.heroShown = 0;
} else {
this.heroShown = 1;
}
console.log(e.target['scrollingElement'].scrollTop);
console.log(document.body.scrollTop);
console.log(window.pageYOffset);
}
`
for that you can use a Intersection Observer.
The observer fires an event when the element is visible.
So when the event fires you can start your animation.
private createObserver() {
const options = {
rootMargin: '0px',
threshold: this.threshold,
};
const isIntersecting = (entry: IntersectionObserverEntry) =>
entry.isIntersecting || entry.intersectionRatio > 0;
this.observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (isIntersecting(entry)) {
this.subject$.next({ entry, observer });
}
});
}, options);
}
a other way to archive this is using a framwork like gsap
There you can use something like a scrolltrigger.
Check the docs here.

Hide web component until browser knows what to do with it

Similar to this question:
How to prevent flickering with web components?
But different in that I can't just set the inner HTML to nothing until loaded because there is slotted content, and I don't wish to block rendering the page while it executes the web component JS.
I thought I could add CSS to hide the element, and then the init of the webcomponent unhides itself, but then that CSS snippet needs to included where ever the web component is used, which is not very modular, and prone to be forgotten
I am working on modal component, here's the code (although I don't think its particularly relevant:
<div id="BLUR" part="blur" class="display-none">
<div id="DIALOGUE" part="dialogue">
<div id="CLOSE" part="close">
X
</div>
<slot></slot>
</div>
</div>
const name = "wc-modal";
const template = document.getElementById("TEMPLATE_" + name);
class Component extends HTMLElement {
static get observedAttributes() { return ["open"]; } // prettier-ignore
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
if (this.initialised) return; // Prevent initialising twice is item is moved
this.setupEventListners();
this.init();
this._upgradeProperty("open");
this.initialised = true;
}
init() {}
get(id) {
return this.shadowRoot.getElementById(id);
}
_upgradeProperty(prop) {
/*
Setting a property before the component has loaded will result in the setter being overriden by the value. Delete the property and reinstate the setter.
https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
*/
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
// Setup Event Listeners ___________________________________________________
setupEventListners() {
this.get("CLOSE").addEventListener("click", () => this.removeAttribute("open"));
this.get("BLUR").addEventListener("click", () => this.removeAttribute("open"));
// If the dialogue does not handle click, it propagates up to the blur, and closes the modal
this.get("DIALOGUE").addEventListener("click", (event) => event.stopPropagation());
}
// Attributes _____________________________________________________________
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case "open":
// Disabled is blank string for true, null for false
if (newValue === null) this.hideModal();
else this.showModal();
}
}
// Property Getters/Setters _______________________________________________
get open() { return this.hasAttribute("open"); } // prettier-ignore
set open(value) { value ? this.setAttribute("open", "") : this.removeAttribute("open"); } // prettier-ignore
// Utils & Handlers _______________________________________________________
showModal() {
this.get("BLUR").classList.remove("display-none");
// Disable scrolling of the background
document.body.style.overflow = "hidden";
}
hideModal() {
this.get("BLUR").classList.add("display-none");
// Renable scrolling of the background
document.body.style.overflow = "unset";
}
}
window.customElements.define(name, Component);
Q: How do I hide a web component until the browser knows what to do with it?
A: Here's a solution with outside CSS. Make use of the :defined pseudo class:
class X extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).append(document.createElement('slot'));
}
}
foo.onclick = () => {
customElements.define('ab-cd', X);
foo.disabled = true;
foo.textContent = 'registered!';
}
ab-cd:not(:defined) { display: none; }
<ab-cd>text</ab-cd>
<button id="foo">click to register component</button>
I have tried to see where :defined can cause a FOUC
Only when you apply the display:none too late
<my-element>:not(:defined) { display:none }</my-element>
<style>
my-element:not(:defined) {
border: 2px solid red;
}
my-element:defined {
background: pink;
}
</style>
<style id="STYLE"></style>
<button id="BTN_STYLE">click to style component</button>
<button id="BTN_DEFINE">click to register component</button>
<script>
BTN_STYLE.onclick = () => {
STYLE.innerHTML = `my-element:not(:defined) {display:none}`;
BTN_STYLE.remove();
}
BTN_DEFINE.onclick = () => {
customElements.define('my-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).innerHTML = `constructed`;
}
connectedCallback(){
setTimeout(() => this.shadowRoot.innerHTML = `connected after 3s`,3e3);
}
});
BTN_DEFINE.remove();
}
</script>

Drag and Drop in meteor

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.

How to disable parent page of modal in angular

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).

Toggling multiple tools on Paper.js

Can someone show me a working example (JSFiddle or otherwise) of how to have two tools on Paper.js that a user can click on to draw different shapes, say one for circles and one for squares?
Thanks!
You've got at least a couple of options here,
1. Activate Tool from paper.tools
Paper.js allows you to activate a Tool by calling tool.activate(), which causes only that particular Tool to receive Tool events, such as mousedown, mousedrag etc ...
When you create a new Tool via new paper.Tool(), that Tool is added in paper.tools so you can lookup for the Tool within that Array and call tool.activate() on it.
An example:
window.onload = () => {
// Setup Paper
paper.setup(document.querySelector('canvas'))
// Find a Tool in `paper.tools` and activate it
const activateTool = name => {
const tool = paper.tools.find(tool => tool.name === name)
tool.activate()
}
// Tool Path, draws paths on mouse-drag.
// Note: Wrap each Tool in an IIFE to avoid polluting the
// global scope with variables related with that Tool.
;(() => {
const tool = new paper.Tool()
tool.name = 'toolPath'
let path
tool.onMouseDown = function(event) {
path = new paper.Path()
path.strokeColor = '#424242'
path.strokeWidth = 4
path.add(event.point)
}
tool.onMouseDrag = function(event) {
path.add(event.point)
}
})()
// Tool Circle, draws a 30px circle on mousedown
;(() => {
const tool = new paper.Tool()
tool.name = 'toolCircle'
let path
tool.onMouseDown = function(event) {
path = new paper.Path.Circle({
center: event.point,
radius: 30,
fillColor: '#9C27B0'
})
}
})()
// Attach click handlers for Tool activation on all
// DOM buttons with class '.tool-button'
document.querySelectorAll('.tool-button').forEach(toolBtn => {
toolBtn.addEventListener('click', e => {
activateTool(e.target.getAttribute('data-tool-name'))
})
})
}
html,
body,
canvas {
width: 100%;
height: 100%;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-core.js"></script>
<button
class="tool-button"
data-tool-name="toolPath">
Draw Paths
</button>
<button
class="tool-button"
data-tool-name="toolCircle">
Stamp Circles
</button>
<canvas resize></canvas>
2. Create a ToolStack Class
However, I find it far more practical to create a ToolStack Class since it allows me to add additional methods later on, i.e isToolActive(), onToolSelect() (for adding is-active classes to DOM tool buttons) etc..
The ToolStack should then implement various methods for handling your Tools, the first and foremost being an activateTool method, that will lookup for a Tool by name and call it's tool.activate() method.
An example:
window.onload = () => {
// Setup Paper
paper.setup(document.querySelector('canvas'))
// Toolstack
class ToolStack {
constructor(tools) {
this.tools = tools.map(tool => tool())
}
activateTool(name) {
const tool = this.tools.find(tool => tool.name === name)
tool.activate()
}
// add more methods here as you see fit ...
}
// Tool Path, draws paths on mouse-drag
const toolPath = () => {
const tool = new paper.Tool()
tool.name = 'toolPath'
let path
tool.onMouseDown = function(event) {
path = new paper.Path()
path.strokeColor = '#424242'
path.strokeWidth = 4
path.add(event.point)
}
tool.onMouseDrag = function(event) {
path.add(event.point)
}
return tool
}
// Tool Circle, draws a 30px circle on mousedown
const toolCircle = () => {
const tool = new paper.Tool()
tool.name = 'toolCircle'
let path
tool.onMouseDown = function(event) {
path = new paper.Path.Circle({
center: event.point,
radius: 30,
fillColor: '#9C27B0'
})
}
return tool
}
// Construct a Toolstack, passing your Tools
const toolStack = new ToolStack([toolPath, toolCircle])
// Activate a certain Tool
toolStack.activateTool('toolPath')
// Attach click handlers for Tool activation on all
// DOM buttons with class '.tool-button'
document.querySelectorAll('.tool-button').forEach(toolBtn => {
toolBtn.addEventListener('click', e => {
toolStack.activateTool(e.target.getAttribute('data-tool-name'))
})
})
}
html,
body,
canvas {
width: 100%;
height: 100%;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-core.js"></script>
<button
class="tool-button"
data-tool-name="toolPath">
Draw Paths
</button>
<button
class="tool-button"
data-tool-name="toolCircle">
Stamp Circles
</button>
<canvas resize></canvas>

Resources