When you are creating a custom element in Aurelia, is there a way to take all of the extra attributes that aren't bound and copy them to a specific element inside the template. For example, say I create a custom element called "my-custom-element" with a bindable property called "name".
<my-custom-element name="MyName" class="my-class" data-id="1" />
My template would be the following:
<template>
<input name.bind="name" type="text" />
</template>
What I would like to have rendered is:
<input name="MyName" class="my-class" data-id="1" />
So in other words I would like any addition attribute(s) that isn't put in by Aurelia or isn't bound to a property to be available for me add to an element in my template. I could add a bindable property for class because that is pretty common, something like the "data-" attributes could be anything.
I would also like to see if it can support the a containerless custom element.
It's just HTML, so you could probably inject the element itself in to the VM for the custom element, and then ref the input element. Then you'd use plain old DOM methods to inspect the custom element. It'd probably be a bit brittle, but something like the below should work:
import {inject, customElement, bindable} from 'aurelia-framework';
#customElement('my-custom-element')
#inject(Element)
export class MyCustomElement {
#bindable name;
constructor(el) {
this.el = el;
}
attached() {
const attributes = this.el.attributes;
for( const attr of attributes ) {
let {name, value} = attr;
if(name != 'au-target-id') {
const dotLocation = name.indexOf('.');
if( dotLocation > -1 ) {
name = name.substring(0, dotLocation);
}
if(name !== 'name') {
this.myInput.setAttribute(name,value);
}
}
}
}
}
I've created a runnable gist here.
Related
I have a custom attribute with a method to show and hide some HTML content, I've attached the attribute to an element in a view model.
How can I call a method defined in the custom attribute from the view model?
To access the custom attribute's view-model, just put the custom attribute on the element a second time, but this time put .ref="viewModelPropertyName" on to the attribute. Then, in the parent view-model, you can access methods on the attribute using viewModelPropertyName (or whatever name you gave it). You can see an example of this here: https://gist.run/?id=9819e9bf73f6bb43b07af355c5e166ad
app.html
<template>
<require from="./has-method"></require>
<div has-method="hello" has-method.ref="theAttribute"></div>
<button click.trigger="callMethod()">Call method</button>
</template>
app.js
export class App {
callMethod() {
const result = this.theAttribute.someMethod('blah');
}
}
has-method.js
export class HasMethodCustomAttribute {
someMethod(foo) {
console.log('someMethod called with foo = ' + foo + ', this.value = ' + this.value);
return `Hello ${foo}`;
}
}
There are some ways to do it, but I believe the ideal would be binding a property from your custom-attribute to your view-model. For example:
MyCustomAttribute {
#bindable showOrHide; //use this to show or hide your element
}
MyViewModel {
visible = false;
}
Usage:
<div my-custom-attribute="showOrHide.bind: visible"></div>
So, whenever you change visible you will also change showOrHide.
Nevertheless, is good to remember that Aurelia already has a show and if custom-attributes:
<div show.bind="visible" my-custom-attribute></div>
<div if.bind="visible" my-custom-attribute></div>
Make sure if you really need to create this behaviour in your custom-attribute.
This can be done without the need for a ref. Here is an example that shows how.
It calls a showNotification method on the custom attribute from the custom element using the custom attribute.
In the custom attribute:
#bindable({ defaultBindingMode: bindingMode.twoWay }) showNotificationCallback: ()=> void;
bind() {
this.showNotificationCallback = this.showNotification.bind(this);
}
showNotification() {
// Your code here
}
In the custom element view (Note the absence of parens in the value of this binding):
<div notification="show-notification-callback.bind: showSaveSuccessNotification;></div>
In the custom element view-model:
// Show the save success view to the user.
if (typeof this.showSaveSuccessNotification=== 'function') {
this.showSaveSuccessNotification();
}
I want to call a child component's function from its parent. I have a way to do it, but I want to know if I'm missing a better way.
From Ashley Grant's blog post about accessing a custom element's viewModel from a custom attribute, I see that Aurelia adds au to the element and you can access the viewModel through that. So, if I add a nested component with a ref, like this:
<template>
<nested-element ref="childElement"></nested-element>
</template>
I can call a function on it like this:
this.childElement.au.controller.viewModel.someFunction();
This feels roundabout. I was hoping I would be able to access a nested element's viewModel through the parameters to a hook that the parent implements, such as created(owningView, myView) but I can't find a path to it.
Have I missed a better way?
Edit: I forgot to add that I need a return value from the function I'm calling, so having access to the viewmodel itself is preferable
ref gives you the element. view-model.ref gives you the element's view model.
<template>
<nested-element view-model.ref="childViewModel"></nested-element>
</template>
Call it like this in the parent view-model:
this.childViewModel.someFunction();
If you only have one instance of the nested-element or don't care if multiple nested-elements respond to the event. Then you could use standard Javascript event functionality for this:
bar.html
<template>
<h1>${value}</h1>
<input type="text" value.bind="value"></input>
<foo>text</foo>
</template>
bar.ts
import {bindable} from 'aurelia-framework';
export class Bar {
#bindable value;
public valueChanged(newValue, oldValue) {
var event = new CustomEvent("some-event-name", { "detail": { message: "Hello from Bar", oldValue, newValue } });
document.dispatchEvent(event);
}
}
foo.html
<template>
<h1>${value}</h1>
</template>
foo.ts
import {bindable} from 'aurelia-framework';
export class Foo {
constructor() {
document.addEventListener("some-event-name", (e) => {
console.log('hello here is Foo, recieved event from Bar : ', e);
}, true);
}
}
Almost give up on Aurelia, I'm struggling with adding custom elements dynamic in Aurelia,
lets say we have a custom tag:
view my-element.html:
<template> My Element ${name} </template>
viewmodel: my-element.js:
export class MyElement {
#bindable name = '';
}
so I try to manually add this tag, in another view:
<template>
<button type="button" click.delegate="createMyElement()">Remove</button>
</template>
another viewmodel:
export class App {
createMyElement() {
//how to do it in here to create element
//<my-element name='name1'></my-element>
}
}
I looked this link https://gist.run/?id=762c00133d5d5be624f9, but it needs a container reference
<div ref="container"></div>
I dont want to specify a container, instead I want it to be append to current view.
I also tried using aurelia-compiler from https://github.com/gooy/aurelia-compiler, when I try to import it, it was able to locate file'gooy/aurelia-compiler', but I got this error:
Error invoking Compiler. Are you trying to inject/register something that doesn't exist with DI?
Can someone please help? thanks.
You could inject the view's html element and use it as a "container". Like this:
import {inject} from 'aurelia-framework';
import {ViewFactory} from './view-factory';
#inject(Element, ViewFactory)
export class App {
//...
constructor(element, viewFactory) {
this.element = element;
this.viewFactory = viewFactory
}
}
Then, use this.element in the insert method:
this.dispose = this.viewFactory.insert(this.element, this.viewHtml, viewModel);
Running example:
https://gist.run/?id=9d5e7a60cd02e55618829a304df00621
Hope this helps!
Rather than trying to manually inject views through your viewModel (controller), try creating new viewModels from which to generate views. So, like this:
home.html
<template>
<my-element repeat.for="name of names" name.bind="name"></my-element>
<button click.delegate="addName()">Create My Element</button>
</template>
home.js
export class HomeViewModel {
constructor() {
this.names = []
}
addName() {
this.names.push('Jim');
}
}
Edit: This question used to be titled "Getting parent via DI when parent is the same type in Aurelia" but due to how my elements are nested, it makes more sense to just bind the parent to the element, so the title has been changed to reflect this.
If I have a custom element, Thing which has a child Thing (which has a another child Thing, etc), how can I inject the parent instance when the class is the same?
export class Thing {
static inject = [Thing]; // <-- no reference to Thing as we are inside the class
constructor(parentThing) {
this.parent = parentThing;
}
}
As a further complexity, the root Thing element will have no parent, so the DI needs to allow for optional injection.
I don't think your problem can be solved with DI. Thing has to be #transient if you want to have several instances of it. This means that the container will not hold references to things it creates.
This problem doesn't look right or necessary to use DI. If an element need to receive some specific input data from its consumer, #bindable would be my natural first thinking. So how about creating a #bindable parentThing in Thing?
In other hand, if what you want is to access parent binding context, consider bind() component life cycle.
Here's how to do that: https://gist.run?id=b075366d29f2400d3cc931f6fc26db24
app.html
<template>
<require from="./thing"></require>
<thing name="A">
<thing name="B">
<thing name="C">
<thing name="D">
</thing>
</thing>
</thing>
</thing>
</template>
app.js
export class App {
}
optional-parent.js
import {resolver} from 'aurelia-dependency-injection';
#resolver()
export class OptionalParent {
constructor(key) {
this.key = key;
}
get(container) {
if (container.parent && container.parent.hasResolver(this.key, false)) {
return container.parent.get(this.key)
}
return null;
}
static of(key) {
return new OptionalParent(key);
}
}
thing.html
<template>
<h3>
My Name is "${name}".
<span if.bind="parent">My parent's name is "${parent.name}".</span>
<span if.bind="!parent">I don't have a parent.</span>
</h3>
<content></content>
</template>
thing.js
import {bindable, inject} from 'aurelia-framework';
import {OptionalParent} from './optional-parent';
#inject(OptionalParent.of(Thing))
export class Thing {
#bindable name;
constructor(parent) {
this.parent = parent;
}
}
A few days ago I asked this question 2 way databinding in Aurelia custom elements - bind custom element to parent viewmodel
Now I need to be able to reuse the allSelectableValues from my custom element (my-custom.js) in my parent element (create.js).
I need this for a custom value converter I have on create.js which contains some Ids which I need to display names for instead, by looping through the array of elements, currently fetched and residing in my custom element.
**create.html**
<td>${d.SomeID | allSelectableValuesMapping}</td>
and
**value-converters/all-selectable-values-mapping.js**
export class AllSelectableValuesMappingValueConverter {
toView(value) {
for(let item in allSelectableValues) {
if (item.SomeID == value){
return item.Name;
}
}
}
}
In the ideal world I'd have hoped something like this would have worked:
**my-custom.js**
async attached() {
this.allSelectableValues= await await this.myService.getAllValues();
this.parent.allSelectableValues = this.allSelectableValues;
}
But my custom element have no idea of the parent which is requiring it.
Does anyone have an idea how to set the parent's allSelectableValues equal to the custom element's allSelectableValues from within the custom element? Or is there another, better way of achieving it, while still maintaining the two-way databound custom element?
Something like this ?
Please take extra note of the #customElement('CustomElement') declarator above the export class CustomElement line of code.
Custom Element View Model
import {inject} from 'aurelia-framework';
import {customElement} from 'aurelia-framework';
import {bindable} from 'aurelia-framework';
#customElement('CustomElement')
export class CustomElement {
#bindable arrItems
}
Custom Element HTML
<template>
<div repeat.for="item of arrItems">$(item.someProperty}</div>
</template>
Parent View Model
export class ParentViewModel {
parentArrItems = [];
}
Parent HTML
<template>
<require from="customelement"></require>
<CustomElement arrItems.bind="parentArrItems"></CustomElement>
</template>