I've installed svg.js using NPM and imported the module into my custom element file via import "svg.js";,
import "svg.js";
class MyView2 extends PolymerElement {
static get template() {
return html`
<div id="drawing"></div>
`;
}
connectedCallback() {
super.ready();
var draw = SVG("drawing").size(300, 300);
var rect = draw.rect(100, 100).attr({ fill: "#f06" });
}
}
This should insert an SVG inside of the div, but is it's throwing this error in the console instead:
svg.js:3060 Uncaught TypeError: Cannot read property 'nodeName' of null
at new create (svg.js:3060)
at globalRef.SVG (svg.js:33)
at HTMLElement.connectedCallback (my-view2.js:42)
at my-view2.js:50
I'm fairly new to Polymer so not sure if i'm missing something obvious.
Your component has a Shadow Dom so SVG can't find it. Please try this:
let node = this.shadowRoot.querySelector('drawing');
let draw = SVG.adopt(node);
let rect = draw.rect(100, 100).attr({ fill: "#f06" });
Related
I am building a Web Component to be used in a Framework, which embeds a grid libary.
I have managed to get the grid to display by wrapping it in an HTMLElement Class
export default class DataGrid extends HTMLElement {
constructor() {
super();
let tmpl = document.createElement('template');
tmpl.innerHTML = `
<div id="lib-datagrid"></div>
`;
this._shadowRoot = this.attachShadow({mode: "open"});
this._shadowRoot.appendChild(tmpl.content.cloneNode(true));
this._rowData = [];
...
}
and
// Load the grid
customElements.define('my-grid', DataGrid);
I need to be able to pass data into the Grid via a DataGrid instance. However it seems that createElements.define() takes a Class rather than an object instance, so I don't have the option to create an Instance (new DataGrid()) and pass that in.
My theory is that I should be able to retrieve the created element via the dom tree, but my Web Component lives within a Shadow Dom and my Web Component isn't itself a Dom element (I think) i.e. no "this.getRootNode()" etc, but do have access to document and window.
Am I missing something in the createElement process or is there a way to find the root node of the current shadow dom?
** Edit - adding Top level WebComponent view
export default (state) => {
const { items, alert, loading } = state;
return (
<div>
<div className="card-top">
<my-grid></my-grid>
</div>
</div>
);
};
** Edit 2
I have found that coding the class extends HTMLElement in-line (rather than in a seperate js file) does allow me to update a reference from the objects connectedCallback() function.
let myobject = null;
customElements.define('my-grid2', class extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = `<p>
Hello
</p>`;
myobject = this;
}
});
Pending other suggestions - I will work with this and post an answer if it works out.
Note that as soon as you have access to an instance of your custom element, you can simply use constructor theft to create new instances:
// let's say you have a reference to `<my-grid />` in `el`:
const newEl = new el.constructor;
After customElements.define('my-grid', DataGrid), you have to actually create an element using:
const elm = document.createElement('my-grid');
const orElm = new DataGrid();
// Use `elm` or `orElm` to pass the data.
If your web component is added to DOM tree by some other means, then you must locate it using querySelector or equivalent like:
const elm = document.querySelector('my-grid');
But if it is nested within some other shadow-root, then querySelector won't be able to do it. For this purpose, you would have to precisely find the parent element, get its shadow root and then run querySelector on that shadow root, something like:
const shadowroot = parentElement.shadowRoot;
shadowroot.querySelector('my-grid');
On a side note, if you need to query or send the data from outside your web component, then it is probably code smell as you are breaking laws of encapsulation. There are other better ways to pass the data to the child component. Or else, you don't need shadow DOM API. Just use custom elements without shadow DOM.
I have found a way to do this.
By defining the HTMLElement class in-line and handling the grid wrapper object in the connectedCallback() function, I can get access to references for both the element created and the DataGrid wrapper object.
All the DataGrid wrapper requires is the shadowRoot created as a constructor parameter.
let myElement = null;
let myDataGrid = null;
// Load the grid
customElements.define('my-grid', class extends HTMLElement {
connectedCallback() {
let tmpl = document.createElement('template');
tmpl.innerHTML = `
<div id="lib-datagrid"></div>
`;
const shadow = this.attachShadow({mode: 'open'});
shadow.appendChild(tmpl.content.cloneNode(true));
myDataGrid = new DataGrid(shadow);
myElement = this;
}
});
With that I can now, later on, call a function on the DataGrid object to update data.
Just to save you typing
You can write this:
connectedCallback() {
let tmpl = document.createElement('template');
tmpl.innerHTML = `
<div id="lib-datagrid"></div>
`;
const shadow = this.attachShadow({mode: 'open'});
shadow.appendChild(tmpl.content.cloneNode(true));
myDataGrid = new DataGrid(shadow);
myElement = this;
}
as:
connectedCallback() {
this.attachShadow({mode: 'open'}).innerHTML = `<div></div>`;
myDataGrid = new DataGrid(this.shadowRoot);
myElement = this;
}
Basically what the title says. I checked DataGridSkin, and it has a Scroller skin part, but I can't find any attribute for the scrolling amount. I might have missed it.
Can anybody please help me on this issue? Thanks.
I suggest the following. You can find part of scrolling logic in VScrollBar class in mouseWheelHandler method.
var nSteps:uint = Math.abs(delta);
...
for (var vStep:int = 0; vStep < nSteps; vStep++)
{
var vspDelta:Number = vp.getVerticalScrollPositionDelta(navigationUnit);
...
In my case delta equals 3. And grid is scrolled for 3 renderers heights. You can extend VScrollBar class. Then create custom ScrollerSkin (copy from original one) and set you vertical bar there instead of default one. Then create custom DataGrid skin (again copy from original one) and set your custom scroller skin for Scroller there.
In custom VScrollBar class override mouseWheelHandler method. Example:
package {
import flash.events.Event;
import flash.events.MouseEvent;
import mx.core.IInvalidating;
import mx.core.mx_internal;
import mx.events.FlexMouseEvent;
import spark.components.VScrollBar;
import spark.core.IViewport;
import spark.core.NavigationUnit;
import spark.utils.MouseEventUtil;
use namespace mx_internal;
public class MyVScrollBar extends VScrollBar {
public function MyVScrollBar() {
}
override mx_internal function mouseWheelHandler(event:MouseEvent):void {
const vp:IViewport = viewport;
if (event.isDefaultPrevented() || !vp || !vp.visible || !visible)
return;
var changingEvent:FlexMouseEvent = MouseEventUtil.createMouseWheelChangingEvent(event);
if (!dispatchEvent(changingEvent))
{
event.preventDefault();
return;
}
const delta:int = changingEvent.delta;
var nSteps:uint = 1; //number of renderers scrolled!
var navigationUnit:uint;
var scrollPositionChanged:Boolean;
navigationUnit = (delta < 0) ? NavigationUnit.DOWN : NavigationUnit.UP;
for (var vStep:int = 0; vStep < nSteps; vStep++)
{
var vspDelta:Number = vp.getVerticalScrollPositionDelta(navigationUnit);
if (!isNaN(vspDelta))
{
vp.verticalScrollPosition += vspDelta;
scrollPositionChanged = true;
if (vp is IInvalidating)
IInvalidating(vp).validateNow();
}
}
if (scrollPositionChanged)
dispatchEvent(new Event(Event.CHANGE));
event.preventDefault();
}
}
}
Set nSteps variable to number of renderers you want to be scrolled. So in this example grid is scrolled by only one item on wheel.
I found a solution: I used the idea described above, but instead of creating a class that extends VScrollBar, I added a MouseWheelChanging event listener to the scroll bar in the Scroller skin:
<fx:Component id="verticalScrollBarFactory">
<s:VScrollBar visible="false" mouseWheelChanging="outerDocument.vScrollBarWheelChanging(event)"/>
</fx:Component>
internal function vScrollBarWheelChanging(event:MouseEvent):void
{ event.delta/=Math.abs(event.delta); }
You can set event.delta to the desired scroll amount.
In Flex 4, how can I change the cursor to a Bitmap image determined at runtime? All the examples I've seen use CursorManager.setCursor to set the cursor to a class specified at compile time.
What I want to do is change the cursor to a bitmap whose bitmapData is determined by the context.
package cursor
{
import flash.display.BitmapData;
import flash.display.PixelSnapping;
import mx.core.BitmapAsset;
public class RuntimeBitmap1 extends BitmapAsset
{
public static var staticBitmapData:BitmapData;
public function RuntimeBitmap1()
{
super(staticBitmapData);
}
}
}
Usage:
var bitmapData:BitmapData = new BitmapData(50, 50, false, 0x88888888);
RuntimeBitmap1.staticBitmapData = bitmapData;
cursorManager.setCursor(RuntimeBitmap1, 0);
I wanted to draw a UIComponent as a cursor.
I managed it using a combination of Maxims answer and this Flex Cookbox article. The only change I had to make to Maxim answer was a s follows:
public function RuntimeBitmap1()
{
super(RuntimeBitmap1.staticBitmapData);
}
Otherwise staticBitmapData came through as null in the constructor.
Here are a few simple steps to change the default cursor with a bitmap image:
Create your cursor of type Bitmap by using an image of your choice. You can also set the bitmapData dynamically during runtime.
var DEFAULT_CURSOR_IMAGE : Class;
var myCursorBitmap : Bitmap;
...
myCursorBitmap = new DEFAULT_CURSOR_IMAGE();
Register to receive mouse move events and update cursor position accordingly.
function onMouseMove(event : MouseEvent) : void
{
myCursorBitmap.x = event.localX;
myCursorBitmap.y = event.localY;
}
Hide the real cursor by using Mouse.hide().
Show your custom cursor. You may update cursor shape later by setting bitmapData dynamically.
addChild(myCursorBitmap);
...
myCursorBitmap.bitmapData = myNewCursor;
To restore the default cursor, remove your cursor bitmap from the stage and call Mouse.show().
I want to load an swf into a flex 4 application in order to use its classes.
var ldr:Loader=new Loader();
ldr.load(new URLRequest("file://path/to/fileswf"));
ldr.contentLoaderInfo.addEventListener(Event.INIT, loaded);
function loaded(evt:Event):void { addChild(ldr); }
I receive the error:
Error: addChild() is not available in this class. Instead, use addElement() or modify the skin, if you have one.
at spark.components.supportClasses::SkinnableComponent/addChild()[E:\dev\gumbo_beta2\frameworks\projects\spark\src\spark\components\supportClasses\SkinnableComponent.as:966]
at main/private:init/loaded()[C:\Documents and Settings\ufk\Adobe Flash Builder Beta 2\xpogames-toolkit-test\src\main.mxml:22]
If I change addChild() to addElement(), I receive the following compilation error:
1067: Implicit coercion of a value of type flash.display:Loader to an unrelated type mx.core:IVisualElement. main.mxml path/dir line 22 Flex Problem
Any ideas how to resolve this issue?
Create another container to place the displayObject in:
// container ( IVisualElement ) for DisplayObjects
var container:UIComponent = new UIComponent();
addElement( container );
// displayObject goes to container
var displayO:Sprite = new Sprite();
container.addChild( displayO );
well in flash builder 4 full version, there isn't any this.rawChildren.
The best approach to resolve the issue would be to convert each required class to a flex component and to use it on your flex application:
download and install flex component kit
http://www.adobe.com/cfusion/entitlement/index.cfm?e=flex_skins
create a movie clip
convert to flex component
add the relevant functions to this class
a skeleton for a class that is attached to a movieclip that is about to be converted to a flex component:
package {
import mx.flash.UIMovieClip;
import flash.text.TextField;
import flash.events.Event;
import flash.events.MouseEvent;
public dynamic class challenge_screen extends UIMovieClip {
public function challenge_screen() {
super();
}
}
}
this.rawChildren.addChild( ldr )
should work
private var _loader:SWFLoader = new SWFLoader();
private var _uicomponent:UIComponent = new UIComponent();
private function swfLoaded(event:Event):void
{
Alert.show("inside swf Loaded");
var content:DisplayObject =_loader.content;
_uicomponent.addChild(content);
}
public function loadSWF () : void
{
_loader.addEventListener(Event.INIT, swfLoaded);
_loader.load("http://intelliveysoft.com:5080/myelearn/Admin.swf");
addElement(_uicomponent);
}
Try this. It will work
I'm extending Loader(), is it possible to draw a border around the content without resorting to some hacks? I understand Loader can't have any additional children, otherwise I would just create a shape to the contents size and add it. But is there a way to coerce or cast the content object in order to use the drawing API on it?
Usually you would just add the content to a display object when it's loaded and manipulate it as much as you want:
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, eventCompleteHandler);
loader.load(new URLRequest(URL));
function eventCompleteHandler(event:Event):void
{
addChild(createElementWithBorder(event.target.content));
}
function createElementWithBorder(content:DisplayObject):DisplayObject
{
var container:Sprite = new Sprite();
container.addChild(content);
var border:Shape = container.addChild(new Shape()) as Shape;
border.graphics.lineStyle(2,0x000000);
border.graphics.drawRect(0, 0, content.width,content.height);
return container;
}
... But if you still annoyed you cannot use the Loader as a DisplayObjectContainer, using composition may be the clue (the following is just a quick example and needs to be accommodated of course):
public class LoaderBorder extends Sprite
{
private var loader:Loader;
public function LoaderBorder()
{
loader = new Loader()
}
public function load(url:String):void
{
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, eventCompleteHandler);
loader.load(new URLRequest(url));
}
private function eventCompleteHandler(event:Event):void
{
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, eventCompleteHandler);
var content:DisplayObject = event.target.content;
var border:Shape = new Shape();
border.graphics.lineStyle(2,0x000000);
border.graphics.drawRect(0, 0, content.width,content.height);
addChild(content);
addChild(border);
}
}
To be used like a Loader :
var test:LoaderBorder = new LoaderBorder()
addChild(test);
test.load(URL);
Yes you can CAST the content of the Loader.
When you load an image you can do this (after the image is fully loaded):
Bitmap(yourLoader.content);
And with a SWF, i assume you can do this (haven't tested it):
Sprite(yourLoader.content);
You can add a filter to the movieClip that contains the Loader.
I think "glow" or "drop shadow" could be a good effect for this. If you set it right.