Binding to a Custom Element in a Template Part in Aurelia - aurelia

I've created a plunkr to illustrate the problem I'm having.
I'm in the middle of creating a dashboard which at the moment contains four different items. Each of these items I'm creating as Custom Elements then wrapping them in a Custom Element called Widget to give them a frame, title and styling. Here's what that snippet looks like:
<widget title="A Widget" icon="fa-question">
<template replace-part="item-template">
<child-element text.bind="$parent.$parent.someText"></child-element>
</template>
</widget>
For reference the widget view looks like this:
<template>
<require from="./widget.css!"></require>
<div class="widget">
<div class="widget-header">
<i class="fa ${icon}"></i>
<h3>${title}</h3>
</div>
<div class="widget-content">
<template replaceable part="item-template"></template>
</div>
</div>
</template>
and the view model is:
import {bindable} from "aurelia-framework";
export class WidgetCustomElement {
#bindable title;
#bindable icon;
#bindable show; // This is something I want the child element
// to be able to bind to and control but haven't
// got there yet!!
}
But notice I'm trying to bind data from the ViewModel into the child-element where the child-element looks like this:
import {bindable} from "aurelia-framework";
export class ChildElementCustomElement {
#bindable text;
}
and the view:
<template>
<p>The widget passed us : ${text}</p>
</template>
The problem is no matter what expression I use (and here I'm currently trying $parent.$parent.someText) I can't get the binding to work.
Should this work? I've also tried defining the variable someText in the main ViewModel as `#bindable someText' but that throws the following exception:
Unhandled promise rejection TypeError: Cannot read property 'some-text' of null
at BindableProperty.initialize (https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/github/aurelia/templating#0.14.4/aurelia-templating.js:2448:33)
at new BehaviorInstance (https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/github/aurelia/templating#0.14.4/aurelia-templating.js:2199:23)
at HtmlBehaviorResource.create (https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/github/aurelia/templating#0.14.4/aurelia-templating.js:2821:30)
at https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/github/aurelia/templating#0.14.4/aurelia-templating.js:3385:27
at f (https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/npm/core-js#0.9.18/client/shim.min.js:1415:56)
at https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/npm/core-js#0.9.18/client/shim.min.js:1423:13
at b.exports (https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/npm/core-js#0.9.18/client/shim.min.js:453:24)
at b.(anonymous function) (https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/npm/core-js#0.9.18/client/shim.min.js:1625:11)
at Number.f (https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/npm/core-js#0.9.18/client/shim.min.js:1596:24)
at q (https://cdn.rawgit.com/jdanyow/aurelia-plunker/v0.4.0/jspm_packages/npm/core-js#0.9.18/client/shim.min.js:1600:11)

I am not sure whether you are looking for this or not, however, introducing a #bindable text property in WidgetCustomElement makes this a whole lot easier. I worked on your plunk and introduced a #bindable wtext; in WidgetCustomElement, used this property to bind someText in app view like below:
<widget title="A Widget" icon="fa-question" wtext.bind="someText">
<template replace-part="item-template">
<child-element text.bind="wtext"></child-element>
<!--<child-element text.bind="$parent.$parent.someText"></child-element>-->
</template>
</widget>
And this works. However as I said I am not sure whether this approach works for you or not. What I have assumed is your widget will mostly contain one single child element and if this assumption is correct then this should work, however in case one-to-many mapping (many child element in single widget), something else is needed.
Hope this helps.
EDIT: I forked your plunk to make the changes and here is the link to that.

I have since stumbled across something in the Aurelia document detailing Template Parts. In example.js a bind() method is defined which assigns the bindingContext to a local member this.$parent.
This means all I have to do is defined the following in the view model for my widget:
bind(bindingContext) {
this.$parent = bindingContext;
}
Then the child-element is bound with the following:
<child-element text.bind="$parent.someText"></child-element>
I've forked my original plunkr to demonstrate it working.
Given repeat-for provides it's own $parent I had mistakenly believed there was one automatically defined here!!

Related

Is Tailwind class binding possible through Storyblok?

I'm trying to develop some components that will be used by our content editors in Storyblok and there's a use case where we would like to define layout properties (using Tailwind's classes) through props that will be coming from Storyblok components.
As an example,
I am passing the width prop through storyblok giving a value of w-1/2 which is a Tailwind class. As you see on the right the class is applied just fine to the element but there's no actual impact on the page. I have tried the same with many other classes (either for background or border colors or for text styling etc, tried to use Tailwind classes as props coming from Storyblok but didn't work).
My only guess is that Nuxt is a server side application and the CSS gets compiled on build time, therefore any new class binding to the DOM will not reflect the actual CSS that they represent. Is this right? If yes, is there a way to make this happen and work?
The code for the widthSetter component is as simple as that
<template>
{{blok.width}}
<div v-editable="blok" :class="[ blok.width ]">
<component
v-for="value in blok.blocks"
:key="value._uid"
:is="value.component"
:blok="value"
/>
</div>
</template>
<script lang="ts" setup>
const props = defineProps({
blok: {
type: Object,
required: true,
},
})
</script>
You need to add Complete Class Names.
As there is no w-1/2 in your code, TW won't generate the class.
You can workaround the issue by adding the class to safelist.
Doc: https://tailwindcss.com/docs/content-configuration#safelisting-classes
module.exports = {
safelist: ['w-1/2'],
//...
}
Then w-1/2 utility will be generated regardless if it shows up in your code or not.

How to transfer data from an input field to another js file

I'm new to vue and I have a simple component that takes in text.
<template>
<div class="row">
<input type="text" id="chatInput" v-model="chatValue">
<br/><br/><br/><br/>
</div>
</template>
<script>
export default{
name: 'chat-room',
data: function () {
return {
chatValue: ''
}
}
}
So far, the chatValue variable binds to the input tag. I would like to get the value of the chatValue into another js file so I can manipulate the value.Also, I was looking at the vue documentation and it seemed state management seemed to do the trick but I had a hard time understanding the concept. Can I implement state management to execute my desire? If not, how may I?
Data that is shared between components should be owned by a module that provides an interface to the data item. The most common way this is done is that a parent component owns the data and children of that parent accept the data item as a prop, emitting events when the data item should change.
That is described in the documentation here.
If the components are less closely related, the data can be made more akin to a global variable by the use of an event bus, described here.

Processing local components with vue

I have the following code:
<my-messages>
<message>Hello</message>
<message>World</message>
</my-messages>
For now, I did my <my-messages> component be renderized as:
<div class="Messages">
<!-- slot here -->
</div>
And I like to do the same for <message>, but the problem is that I receiving the error Unknown custom element: <message>. Except if I change my code to:
<my-messages inline-template>
<message>Hello</message>
</my-messages>
It seems a bit hack, once that I should declare the [inline-template] to all <my-messages> components, instead of it be treated directly from this component as a default rule (eg. an option as inlineTemplate: true should do the work, if it exists).
The expected render should be like:
<div class="Messages">
<div class="message">Hello</div>
<div class="message">World</div>
</div>
My component currently:
export default {
components: {
// Should process <message> sub-component.
message: require('./Messages.Message.vue'),
}
}
Edit: on reality, the inline-template seems mixing both <div>s from template, and not nesting it.
inline-template is not a hack. I think The problem is you're not registering message components at the same place where you're using my-messages component.
So the parent component that has my-messages as a child can't understand message, you need to register it in the parent too, when you use inline-template the scope changes and and whatever is inside will be treated as inner content. You can find it in the docs
EDIT
There isn't a way to have <message> to be only usable as a child of <my-messages>, you could however throw an exception if it's misused
mounted() {
if (!this.$parent.$el.classList.contains('my-message')) {
this.$destroy();
throw new Error('You must wrap the message in a my-message');
}
}
Note that this supposes that the class my-message is available in the root element, this way you can use any wrapper element.

Two-way binding between parent and child custom element in Aurelia

I thought I was trying to do something very simple but I just can't make this work. This entire example is on plunkr
I have a very basic custom element that present a #bindable data member that it displays and monitors with a changed event. It look like this:
import {bindable} from "aurelia-framework";
export class ChildElementCustomElement {
#bindable childData;
childDataChanged(value) {
alert("Child Data changed " + value);
}
}
and the view:
<template>
<div style="border: solid 1pt green;">
<h2>This is the child</h2>
This is the child data : ${childData}
</div>
</template>
The parent shows the child element but I want a member in its view model that's bound to the child so any change in the parent member is automatically reflected in the child. Here's the parent code:
import {bindable} from "aurelia-framework";
export class App {
parentData = "this is parent data";
}
and the view:
<template>
<h1>Two-way binding between parent and child custom elements</h1>
<require from="./child-element"></require>
<child-element childData.bind="parentData"></child-element>
<hr/>
<label>The following is the parent data:</label>
<input type="text" value.bind="parentData"></input>
</template>
What I'd like to see is any updates typed in the input field will automatically appear in the child (plus the changed event fires) but the child doesn't appear bound at all! I've also tried swapping bind for two-way just in case the convention has bound one-way but that still hasn't worked.
Please highlight my stupidity :) because currently I'm stuck thinking this should just work.
The default convention for #bindable is to turn the camel-cased property names to attribute names using the naming convention 'myProperty' -> 'my-property' (dash-casing).
From the documentation:
#bindable({
name:'myProperty', //name of the property on the class
attribute:'my-property', //name of the attribute in HTML
changeHandler:'myPropertyChanged', //name of the method to invoke when the property changes
defaultBindingMode: bindingMode.oneWay, //default binding mode used with the .bind command
defaultValue: undefined //default value of the property, if not bound or set in HTML
})
The defaults and conventions are shown above. So, you would only need
to specify these options if you need to deviate.
So you need to write child-data.bind in your HTML
<child-element child-data.bind="parentData"></child-element>
Plunkr

When does binding to ref attribute become valid in Aurelia?

This is a follow up to this question: Access a DOM element in Aurelia
Is there a hook in the Screen Activation Lifecycle which allows me to run code after ref bindings have been set up? Currently it seems like there is a period of time after the activate hook is called when the ref bindings are not set up yet and then at some point they get activated. I tested this by adding a <div ref="myDiv"></div> to near the bottom of welcome.html in a cloned version of the latest (v0.13.0) skeleton-navigation repo and testing the existence of the reference in the view-model like this:
export class Welcome{
heading = 'Welcome to the Aurelia Navigation App!';
firstName = 'John';
lastName = 'Doe';
testMyDiv() {
console.log("Getting my div")
console.log(this.myDiv)
}
get fullName(){
this.testMyDiv()
return `${this.firstName} ${this.lastName}`;
}
welcome(){
alert(`Welcome, ${this.fullName}!`);
}
}
A snippet of the bottom of the template...
<button type="submit" class="btn btn-default">Submit</button>
</form>
<div ref="myDiv"></div>
</section>
</template>
This is a snapshot of what I see in the console...
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 <div ref=​"myDiv" class=​"au-target">​</div>​
welcome.js:10 Getting my div
welcome.js:11 <div ref=​"myDiv" class=​"au-target">​</div>​
(continues)
The print outs like this goes on indefinitely. You can see that fullName() is being called regularly to update the screen if the name changes (I assume this is the dirty checking)... but you can see that at the beginning there is a period when the referenced div is NOT valid as a property of the view-model, and then it IS valid. Can someone explain this? Is there a way to hook into the view-model after the ref becomes valid?
In general, bindings are processed and available after the bind callback. However, in this case since you need to access the DOM element, you will need the ViewModel to be bound and attached to the view, so use the attached callback.
class ViewModel {
bind() {
this.refItem == undefined; // true
}
attached() {
this.refItem == undefined; // false
}
}
As you noted in the comments, more information on the activator callbacks is available here: http://aurelia.io/docs.html#extending-html