I would like to provide a possibility to show my components in a bit different look and feel and thought using the decorator for it. Something like:
<body>
<my-component my-decorator></my-component>
</body>
.
#Component(
selector: 'my-component',
templateUrl: '.../my-component.html',
cssUrl: '.../my-component.css',
publishAs: 'comp',
)
class MyComponent {
MyComponent(final Element element) {
Logger.root.fine("MyComponent(): element = $element, element.attributes = ${element.attributes.keys}");
}
}
#Decorator(selector: '[my-decorator]')
class MyDecorator {
final Element element;
#NgOneWay('my-decorator')
var model; // is not used
MyDecorator(this.element) {
Logger.root.fine("MyDecorator(): element = $element, element.nodeName = ${element.nodeName}");
Logger.root.fine("MyDecorator(): element.shadowRoot = ${element.shadowRoot}, element.parent = ${element.parent}");
}
}
Unfortunately, it seems that my-decorator is processed before my-component so it is getting null shadowRoot property in the injected Element object.
It would be possible to check on existence of the my-decorator attribute within the my-component backing class, but that is clearly polluting the design.
UPDATE: Thanks to replay from Marko Vuksanovic, the following is now returning the :
#Decorator(selector: '[my-decorator]')
class MyDecorator extends AttachAware {
final Element element;
#NgOneWay('my-decorator')
var model; // is not used
MyDecorator(this.element) {
Logger.root.fine("MyDecorator(): element = $element, element.nodeName = ${element.nodeName}");
Logger.root.fine("MyDecorator(): element.shadowRoot = ${element.shadowRoot}, element.parent = ${element.parent}");
}
void attach() {
Logger.root.fine("attach(): element.shadowRoot = ${element.shadowRoot}");
}
}
The question still remains how to modify the styling of the shadow DOM.
Thanks in advance for any comments/ideas/solutions.
You can try using AttachAware and it's attach method. You should implement AttachAware interface in your decorator and/or component.
Here's link to Angular.dart docs - https://docs.angulardart.org/#angular-core-annotation.AttachAware
To change the styling of a ShadowDom component you can use element.shadowRoot to get the root of your web component. Shadow root is almost like 'document' object. You can use shadow root to get reference to any element and then you can easily modify it by applying styles as needed.
You could use something like
this.element.shadowRoot.querySelector('[some-attr]').innerHtml = "Modified by decorator" // disclaimer: not tested, but I hope you get the idea.
You can add a style tag to the shadowDom programmatically:
shadowRoot.append(new StyleElement()..text = ':host{background: red;}');
or
shadowRoot.append(new StyleElement()..text = "#import url('some.css')");
Related
How can i send data from client to server using html5 webcomponent
setting up data from server to client, is very easy, works like a charm
how ever cannot find solution to send data to server
Please assist, but Iam not going to use Lit or Polymer
#JavaScript
class SimpleComponent extends HtmlElement {
connectedCallback() {
this.innerHTML = '<input type="text" id="test"/>";
this._input = this.querySelector('#test');
this._input.onchange = function() {
***** i want to send the value to server ****
})
}
setInputValue(value) {
this._input.value = value;
}
}
customElements.define("simple-com",SimpleComponent);
Now Java at Server
#Tag("simple-com")
class SimpleComponent extends Component {
public SimpleComponent() {
}
public void setValue(String value) {
getElement().callJsFunction("setValue",value);
}
}
The main challenge compared to Polymer or LitElement is that an event handler defined using the pattern innerElement.onchange = function() {} will not be run with this referencing the custom element instance. This in turn means that trying to use this.$server won't work because this isn't pointing to the expected value even though $server is indeed present in the place where it's supposed to be.
The easiest way of fixing this is to change the code to use an arrow function (() => {}) instead of an explicit function. This works because arrow functions inherit this from the scope where the function is defined whereas explicit functions have this defined in different ways depending on how it is run. Another approach would be to store a reference to this in a separate variable (e.g. let root = this) and then reference that variable instead of this in the function (e.g root.$server.doSomething()).
Putting everything together, this is what the code looks like with my modifications to make everything work.
class SimpleComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = '<input type="text" id="test"/>';
this._input = this.querySelector('#test');
this._input.onchange = () => {
this.$server.handleChange(this._input.value);
};
}
setValue(value) {
this._input.value = value;
}
}
customElements.define("simple-com", SimpleComponent);
I need to dynamically insert social embeds and in order to run scripts, I want to insert them into head, similar to how jQuery's .html() is doing. How would I go about writing component that I can inject into other components but will always reference head of document? I tried this:
#Decorator(
selector: 'head'
)
#Injectable()
class ScriptLoader {
Element element;
ScriptLoader(this.element) {
}
loadScript(String url) {
ScriptElement script = new ScriptElement();
script.src = url;
script.setAttribute("async", "");
script.onLoad.listen((Event e) {
script.remove();
});
element.append(script);
}
}
and when page is loaded, this.element is a reference to head but when it is injected into other component, it throws with error No provider found for Element! (resolving ExecuteBindHTML -> ScriptLoader -> ScriptLoader -> Element). How would I go about implementing such component? I don't want to use querySelectors (because Angular).
You can get the head element with querySelector directly in the constructor.
#Injectable()
class ScriptLoader {
Element element;
ScriptLoader() {
element = querySelector("head");
}
}
Then add the ScriptLoader class to the bootstrap method directly.
I have a component that uses another component with a ngIf statement. I would like to only load the second component once the ngIf evaluates to true.
EDIT: found an article that can almost do what I need:
https://medium.com/#matanlurey/lazy-loading-with-angular-dart-14f58004f988. However, after the library loaded, it takes the whole view of the component. In my case, I need to insert it into a specific place in the html of the parent component.
Something like:
import '../other/edit_component.dart' deferred as otherEdit;
#Component(
selector: 'edit-component',
template: '<other-component *ngIf="canOther"></other-component>
<button type="button" (click)="go()"></button>',
directives: const [
otherEdit.OtherComponent
]
)
class EditComponent {
#Input()
bool canOther = false;
go() {
otherEdit.loadLibrary();
canOther = true;
}
}
I do not think you can do it directly. What you can do instead is using DynamicComponent from Angular2_components and pass the type after lazily loading it.
Just made it work. Used the DynamicComponent as example from rkj answer.
// the lib that has the component to be loaded
import 'package:somecomponent.dart' deferred as mycomponent;
class mainComponent {
// <div #holder></div> element that we will append the component
#ViewChild('holder', read: ViewContainerRef)
ViewContainerRef holder;
// this will load the component dynamically
final DynamicComponentLoader _componentLoader;
load() {
// clear the html
holder.clear();
// load the component dynamically
ComponentRef componentRef = await _componentLoader
.loadNextToLocation(componentType, holder);
// set some attributes like you would with [attributes]="somevar"
componentRef.instance
..attribute = somevar;
}
mainComponent(this. _componentLoader){}
}
I have a custom element like below:
<polymer-element>
<template if="{{primaryLoaded}}">
<template repeat="{{pData in primaryData}}">
<p>{{pData.propertyOne}}</p>
<p>{{someClass.someOperation()}}</p>
<template if="{{secodaryLoaded}}">
<p>{{secondaryData.someProperty}}</p>
<p>{{someClass.someOperation()}}</p>
</template>
</template>
</template>
</polymer-element>
and a corresponding dart file:
class CustomElement extends PolymerElement with ObservableMixin
{
#observable bool primaryLoaded = false;
#observable bool secondaryLoaded = false;
#observable var primaryData;
#observable var secondaryData;
#observable var someClass;
void created()
{
primaryData = toObservable(new List<var>());
secondaryData = toObservable(new List<var>());
}
void inserted()
{
someClass = new SomeClass();
loadPrimaryData().then((pData) {
primaryData = pData;
primaryLoaded = true;
loadSecondaryData().then((sData) {
secondaryData = sData;
secondaryLoaded = true;
});
});
}
}
Everything works fine for the primaryData. Its properties get printed and the call to someOperation() on someClass prints its data correctly.
The problem is in the nested template. Nothing under the <template if="{{secondaryLoaded}}"> gets displayed. Even the call to someClass.someOperation() fails to display anything.
Is there a problem with scope here? It looks like the outer template can reference the properties defined in the dart file without problem but the nested template can't.
I read about setting variables as globals in the template by setting a custom binding delegate here. However, I can't set the bindingDelegate in my custom element as isTemplate returns false.
Is there any other way of setting a global variable in a custom template? Or am I going about this all wrong?
It's a bug. You can star this issue to be notified of changes:
https://code.google.com/p/dart/issues/detail?id=12742
The only thing you can do for now is to turn nested templates into other polymer elements or figure out a way so the template isn't nested.
In your case, instead of using if="primaryLoaded" you could just set primaryData = null and the template shoud not display anything until primaryData is set, assuming primaryData is #observable.
We're currently looking at translating our JavaScript project to TypeScript. Our application relies heavily on custom developed jQuery UI widgets.
In our current code base, we're using a deep copy mechanism to inherit from widget definitions allowing us, for example, to declare a generic TableWidget as well as an OrdersTableWidget which defines more specific functions.
Therefore, I'd like to define my widget definitions as TypeScript classes and then bind an instance of these classes to jQuery.
For example
class MyWidget {
options: WidgetOptions;
_init(){
// general initialization
}
}
class MySecondWidget extends MyWidget {
_init(){
super._init();
// specific initialization
}
}
And then
$.widget("MyNameSpace.MyWidget", new MyWidget());
$.widget("MyNameSpace.MySeWidget", new MyWidget());
Furthermore, I'd like to denote my custom widgets as implementations of jQuery UI's Widget definition
class MyWidget implements Widget {
options: WidgetOptions;
_init(){
// general initialization
}
}
so I'm able to use the following syntax in TypeScript:
$(selector).MyWidget(options);
I know I have to work with the definition file (from DefinitelyTyped), however I have not yet found a reliable source explaining me how I should write custom jQuery UI Widgets in TypeScript. Has anyone got experience with this?
Any help greatly appreciated, as always!
I'm not sure you can write a class that implements the Widget interface, due to the lack of overloaded constructors. You could create a variable that is typed by the Widget interface.
A standard jQuery plugin would be represent in almost pure JavaScript and wouldn't use modules or classes as it ends up being wrapped up as part of jQuery, which itself isn't a module or class.
Here is an empty plugin called plugin that looks like any standard jQuery plugin, but you can see it takes advantage of the TypeScript type system and extends the JQuery interface to allow it to be called.
/// <reference path="jquery.d.ts" />
interface JQuery {
plugin(): JQuery;
plugin(settings: Object): JQuery;
}
(function ($) {
function DoSomething(someParamater: string) : void {
}
$.fn.plugin = function (settings) {
var config = {
settingA: "Example",
settingB: 5
};
if (settings) {
$.extend(config, settings);
}
return this.each(function () {
});
};
})(jQuery);
This would be called in the normal way.
$('#id').plugin();
So really, my answer is - you can't really do what you want because you are adding to the declared interfaces for jQuery rather than exposing them as modules. You could wrap the usage in a module, like an adaptor that abstracts the jQuery aspect away from the use in your TypeScript, or you can call your classes from inside the plugin, but the plugin or widget doesn't really fit into a module or class.
It might help to have a base class in typescript from which other widget classes may derive.
Its only purpose is to provide the base class semantic so you can access the base class'es members without having to resort to weak typing.
The trick is to remove all the members at runtime (in the constructor) -- otherwise you run into problems with the inheritance provided by the widget factory. For example, the option method would override the widget's original method which is not desired: we just want to be able to call it (in a statically typed way).
class WidgetBase {
public element:JQuery;
constructor() {
// remove all members, they are only needed at compile time.
var myPrototype = (<Function>WidgetBase).prototype;
$.each(myPrototype, (propertyName, value)=>{
delete myPrototype[propertyName];
});
}
/**
* Calles the base implementation of a method when called from a derived method.
* #private
*/
public _super(arg1?:any, arg2?:any, arg3?:any, arg4?:any) {
}
/**
* #private
*/
public _superApply(arguments) {
}
/**
* Gets or sets the value of the widget option associated with the specified optionName.
*/
public option(optionName:string, value?:any):any {
}
// ... further methods from http://api.jqueryui.com/jQuery.widget/
}
Then you can implement your own widget like this:
class SmartWidget extends WidgetBase {
constructor(){
super();
}
public _create() {
var mySmartOption = this.option('smart'); // compiles because of base class
this.beSmart(mySmartOption);
}
public _setOption(key:string, value:any) {
if (key === 'smart') {
this.beSmart(value);
}
this._super(key, value); // compiles because of base class
}
private beSmart(smartOne:any){
// ...
}
}
// register
jQuery.widget("myLib.smartWidget", new SmartWidget());
// assuming you are using https://github.com/borisyankov/DefinitelyTyped
declare interface JQuery{
smartWidget();
smartWidget(options:any);
smartWidget(methodName:string, param1?:any, param2?:any, param3?:any, param4?:any);
}
And finally, you can use your widget:
$(".selector").smartWidget({smart:"you"});