Aurelia custom element binding - aurelia

I'm trying to build a custom element in Aurelia. At this point, this is what I have:
item.html
<template>
<span>${someProperty}</span>
</template>
item.ts
import {bindable} from 'aurelia-framework';
class Item {
#bindable someProperty: string;
}
parent.html
<template>
<require from="./item"></require>
<item repeat.for="item of items"></item>
</template>
parent.ts
class Parent {
items: Item[];
loadItems() {
// at this point, I'm sure that items is getting populated.
this.items = dataservice.loadItems();
}
}
I can't seem to find anything in the documentation that covers this scenario. What I'm getting, is that the span is empty. I'm not getting any errors in the console. Am I going about this the right way?

You need to bind to the item's someProperty. The following assumes that items[] is an array of strings.
<div repeat.for="item of items">
<item someProperty.bind="item"></item>
</div>
Sorry about the formatting, I'm on my phone.

You need to use the custom element and the bindable property. You also need to register the class as a custom element. Try this:
item.html
<template>
<span>${someProperty}</span>
</template>
item.js
import {bindable, customElement} from 'aurelia-framework';
#customElement('item')
class Item {
#bindable someProperty: string;
}
parent.html
<template>
<require from="./item"></require>
<item repeat.for="item of items" someProperty.bind="item"></item>
</template>
parent.ts
class Parent {
items: Item[] = [
'trees',
'swans',
'capes',
'a horse',
'triangles',
'witches',
'a different horse'
];
}
For more information, take a look at a few of my blogs on custom elements and custom attributes like this one: http://davismj.me/blog/semantic-custom-element/

Related

adding a custom class to the div element

i have a custom component where i am passing some data to render some css styles
like
<title :profile="true" :class="true"/>
in my component
i have a div as:
<div class="tabletitle">
i want if my custom class :class is true, i should add a new class called as flexdisplay to it like
<div class="tabletitle flexdisplay">
what i am missing here, i tried passing the value to data as false but it just throwing random errors
Lets assume your parent component is like
<title :profile="true" :showClass="true"/> <!-- modified props name from class to showClass
and in your child component, as you said you have a div like below
<div class="tabletitle">
what you can do is change the above div to the below format
<div :class="['tabletitle', {'flexdisplay': enableClass}]"> <!-- where enableClass will be a computed property
Inside the script tag of your child component, define props and computed properties like below
<script>
export default {
props: {
showClass: {
type: Boolean,
default: false
},
}
computed: { // The reason I am doing with computed is for better reactivity
enableClass() {
return this.showClass;
}
}
}
</script>
You can use class condition syntax
<div :class="{ "active": isActive }"></div>
This will put the active class if the isActive condition is true
Note that you can combine the class and :class attributes to put either class conditionnaly and classic class
<div class="myClass" :class="{ "active": isActive }"></div>
For example in your case if you have the boolean class props your component will look like this :
<template>
<div class="tabletitle" :class={"flexdisplay": isClass}>
...
</div>
</template>
<script>
export default {
props: {
isClass: {
type: Boolean,
default: true
}
}
}
</script>
**Note : ** i've changed the class props by isClass to better understand and do not confuse with the class keywork

passing object to component using v-for

I am trying to send a series of objects that are in an array to a child component using v-for, but when I try to access them from the child component, it tells me that the props are not defined.
Im using Quasar Framework actually
This is how I pass the data:
<div class="row justify-center">
<foo
v-for="brand in brands"
:key="brand.id"
:brand="brand"
></foo>
</div>
<script>
import foo from "src/components/foo.vue";
export default {
components: {
foo
},
data() {
return {
brands: []
};
},
methods: {
async getData() {
let x = await get.getData();
this.brands = x.data;
console.log(this.brands);
}
},
mounted() {
this.getData();
}
};
</script>
brands is an array that obtains two objects from a request made to a local database, which I have already verified that it receives the data correctly
And this is the component file and how I try to get the properties:
<q-card class="my-card" flat bordered>
<q-img
:src="require(`../assets/${brand.img}`)"
:alt="brand.img + ' Logo'"
/>
<div class="text-h5 q-mt-sm q-mb-xs">{{ brand.name }}</div>
<div class="text-caption text-grey">
<p>
{{ brand.price }}
</p>
</div>
<script>
export default {
name: "foo",
props: ["brand"],
data() {
return {
expanded: false
};
},
};
</script>
but when I try to execute the code it gives me the following error:
Error in render: "Error: Cannot find module './undefined'
I know one way to make it work, and it is by creating a property for each of the object's values, for example:
<component
v-for="brand in brands"
:key="brand.id"
:name="brand.name"
:price="brand.price"
></component>
But I dont think thats the correct way to do this....
try to change
import component from "src/components/component.vue";
to
import foo from "src/components/component.vue";
on your components section you just call foo instead of foo:component
I am not sure, but:
Looks like ${brand} is empty. Your function GetData() is async, so the <foo> is created before the GetData() has its data set/returned.
You can change
<foo v-for="brand in brands" :key="brand.id" :brand="brand"></foo>
To
<foo v-if="brands.length> 0" v-for="brand in brands" :key="brand.id" :brand="brand"></foo>
To make sure that the element is renderd after the data if set.
Note: v-if is when the html is rendered, v-show is just a css display hide, but the html is always renderd

How to set $ref to child component elements from parent component in vuejs?

This is the parent component:
<template>
<upload-block
:imSrc="LargeIcon"
inputName="LargeIcon"
:inputHandler="uploadAppIcon"
inputRef="LargeIcon"
:uploadClickHandler="handleUploadIcon"></upload-block>
</template>
<script>
export default class ParentCom extends Vue {
//all props for <upload-block></upload-block> component defined here
handleUploadIcon(event) {
const icon_type = event.currentTarget.getAttribute("data-type");
let appImgElem = this.$refs[icon_type];
appImgElem.click();
}
async uploadAppIcon(event) {
//code
}
}
</script>
And this is the child component:
<template>
<div class="upload-div" #click="uploadClickHandler" :data-type="inputName">
<img v-if="imSrc" :src="imSrc">
<div v-else class="upload-icon-block">
<span>
<font-awesome-icon class="upload-icon" icon="arrow-circle-up" size="lg"></font-awesome-icon>
<br>Click to upload
</span>
</div>
<!-- <spinner variant="primary" :show="true"></spinner> -->
<input style="display:none" type="file" :ref="inputRef" :name="inputName" #input="inputHandler">
</div>
</template>
<script>
#Component({
props: {
imSrc: String,
inputRef: String,
inputName: String,
inputHandler: Function,
uploadClickHandler: Function
}
})
export default class ChicdCom extends Vue {
}
</script>
The problem I am facing in the handleUploadIcon method in which I am not able to get the input element via ref.
It is showing Cannot read property 'click' of undefined in this line appImgElem.click();
But when I move the file input to the parent component, it's works fine. So can you plz help me how to set the ref to child component elements from parent as currently is it not setting.
Thanks
Well you could add a ref to upload-block in the parent component:
<upload-block ref="upload" ... >
Then in the handleUploadIcon you can acces your input: this.$refs.upload.$refs[icon_type]
But I would try to move handleUploadIcon to the child component if I were you.

Binding complex object to a component

Introduction
My goal is to create custom element in aurelia so that I can reuse it across the application.
In this context I've created component called operator-detail (operator-detail.html and operator-detail.js) which will holds information about operator and my plan is to reuse it in several places in application.
In this use case I have electornicRegistrationForm object which holds reference to operatorDetails and legalRepresentative. Both instances are injected into electornicRegistrationForm module and will be used as part of wizard allowing user to create a document which will be printed later on.
This electronicRegistraionForm is injected into operatorStep component.
operator-detail.html component I've included in operatorStep.html and confirm that component has been rendered correctly.
Problem
How to pass (bind) property operator from operatorStep.js to operator-detail.html component so that values from object operator are displayed (binded) in a two way binding manner.
In following example, value from this.operator.firstName 'First name from operator step' don't get displayed in operator-detail.html component.
Source code
electronicRegistrationForm.js:
import { OperatorDetail } from 'common/operator-detail';
import { LegalRepresentative } from 'common/legalRepresentative';
import { inject } from 'aurelia-framework';
#inject(OperatorDetail, LegalRepresentative)
export class ElectronicRegistrationForm {
constructor(operatorDetail, legalRepresentative) {
this.operator = operatorDetail;
this.legalRepresentative = legalRepresentative;
}
}
operator-detail.js
import {inject, NewInstance} from 'aurelia-framework';
import {ValidationRules, ValidationController} from 'aurelia-validation';
#inject(NewInstance.of(ValidationController))
export class OperatorDetail {
constructor(validationController) {
this.validationController = validationController;
this.firstName = '';
}
attached() {
ValidationRules
.ensure('firstName').displayName('Ime').required()
.on(this);
}
}
operator-detail.html
<template bindable="operatorvalue">
<div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="firstName" t='first_name'></label>
<input type="text" class="form-control" id="firstName" value.bind="operatorvalue.firstName ">
</div>
</div>
</div>
</template>
operatorStep.js
import { ElectronicRegistrationForm } from 'model/electronicRegistrationForm';
import { inject, NewInstance } from 'aurelia-framework';
import { RegistrationWizard } from 'registration/registrationWizard';
import { WizardStep } from 'registrationSteps/wizardStep';
import { ValidationController } from 'aurelia-validation';
import {bindable, bindingMode} from 'aurelia-framework';
#inject(ElectronicRegistrationForm, RegistrationWizard, NewInstance.of(ValidationController))
export class OperatorStep extends WizardStep {
#bindable({ defaultBindingMode: bindingMode.twoWay }) operator;
constructor(electronicRegistrationForm, regWiz, validationController) {
super(electronicRegistrationForm, regWiz, validationController);
this.operator = electronicRegistrationForm.operator;
this.operator.firstName='First name from operator step';
this.representative = electronicRegistrationForm.legalRepresentative;
}
}
operatorStep.html
<template>
<require from="common/operator-detail"></require>
<form validation-renderer="bootstrap-form">
<operator-detail operatorvalue.bind="operator"></operator-detail>
</form>
</template>
Declaring a bindable property on a template is for when you have a View without a ViewModel.
The bindable="operatorvalue" in your operator-detail.html doesn't work because you also have a ViewModel defined for this element. If you want to keep it this way then simply remove the bindable="operatorvalue" from the template and instead declare it in your ViewModel like so:
import {inject, NewInstance, bindable} from 'aurelia-framework';
import {ValidationRules, ValidationController} from 'aurelia-validation';
#inject(NewInstance.of(ValidationController))
export class OperatorDetail {
#bindable operatorvalue;
constructor(validationController) {
this.validationController = validationController;
this.firstName = '';
}
attached() {
ValidationRules
.ensure('firstName').displayName('Ime').required()
.on(this);
}
}
operator-detail.html would then become:
<template>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="firstName" t='first_name'></label>
<input type="text" class="form-control" id="firstName" value.bind="operatorvalue.firstName ">
</div>
</div>
</div>
</template>
Alternatively, you could make the bindable property declaration in the template work by simply deleting operator-detail.js and putting the validation stuff elsewhere. Then, rather than injecting OperatorDetail into the registration form you could new up the operator object like so:
import { LegalRepresentative } from 'common/legalRepresentative';
import { inject } from 'aurelia-framework';
#inject(LegalRepresentative)
export class ElectronicRegistrationForm {
constructor(legalRepresentative) {
this.operator = {};
this.legalRepresentative = legalRepresentative;
}
}
Both ways will work.
Working without ViewModels means putting the validation logic in electronicRegistrationForm.js for instance, which also means less code to write.
Working with ViewModels gives you more encapsulation, which is generally preferable if your view-specific logic is more complex than what you've shown here.
EDIT
In case you're using Google Chrome, I highly recommend using the aurelia context extension. You could then inspect the html and see that operator was defined in operator-step, but operatorvalue was undefined in operator-detail. This would tell you that the operatorvalue bindable declaration must be wrong, rather than something else.

Aurelia Custom Elements Inside of Custom Elements & Sharing Variables

How do I access & share variables between custom elements? I have the following files...
tip.html
<template>
<div class="tip-container">
<content select="tip-trigger"></content>
<content select="tip-content"></content>
</div>
</template>
tip.js
export class Tip {}
tip-trigger.html
<template>
<span class="tip-trigger" click.trigger="showTip()">
<content></content>
</span>
</template>
tip-trigger.js
export class TipTrigger {
showTip() {
console.debug(this);
}
}
tip-content.html
<template>
<span class="tip">
<content></content>
<span class="tip__close tip__close--default">×</span>
<span class="tip__color"></span>
</span>
</template>
tip-content.js
export class TipContent {}
In my Tip class I would like to have a variable name visible. When showTip is triggered visible would be set to true, which I would then use to add a class in tip-content.html. How can I share variables between these custom elements to do this?
The idea is to create an element to show tip pop-ups where any type of content can be the trigger and any type of content can be displayed when triggered. Basic example:
<tip>
<tip-trigger><button>?</button></tip-trigger>
<tip-content><div>Here is some helpful info...</div></tip-content>
</tip>
Here is a solution to your problem in Plunker.
Note that the tip-trigger and tip-content elements are just replaceable parts of the template. They don't needed to be components themselves (that confused me a lot in the "original" custom elements article).
app.html:
<template>
<require from="tip"></require>
<tip>
<tip-trigger><button>?</button></tip-trigger>
<tip-content><div>Here is some helpful info...</div></tip-content>
</tip>
</template>
tip.html:
<template>
<div class="tip-container">
<div>
<div click.trigger="showContent()">
<content select="tip-trigger"></content>
</div>
</div>
<div show.bind="contentVisible">
tip content:
<content select="tip-content"></content>
</div>
</div>
</template>
tip.js:
export class Tip {
showContent(){
this.contentVisible = !this.contentVisible;
}
}
Do you just need to turn Tip into a service-like class and import it?
export class Tip {
constructor() {
this.visible = false;
}
show() {
this.visible = true; // Or whatever to show the content
}
hide() {
this.visible = false;
}
}
Then:
import {inject} from 'aurelia-framework';
import {Tip} from './tip';
#inject(Tip)
export class TipTrigger {
constructor(tip) {
this.tip = tip;
}
showTip() {
this.tip.show();
// Or I suppose you could access 'visible' directly
// but I like the implementation details a method call offers.
}
}
*Disclaimer: This is untested.