i18n on custom elements with #bindable does not work with 't.bind' - aurelia

I've noticed a similar issue has been fixed in https://github.com/aurelia/i18n/issues/123. But it was an issue in 't/i18n' with literal values.
But I'm facing an issue with 't.bind'
We have a use case in which we need to construct the expression and bind it to 't/i18n'
e.g:
<template>
<my-custom-element t.bind="messagekey" t-params.bind="{ param1: 10, param2: 10 }"></my-custom-element>
<my-custom-element t.bind="messagekey"></my-custom-element>
</template>
In the view-model we construct the messagekey as bellow:
this.messagekey = "[title]content_key";
When you run the application nothing is being written to the custom-element.
However, I tried this with 't' with literal values;
<template>
<my-custom-element t="[title]content_key" t-params.bind="{ param1: 10, param2: 10 }"></my-custom-element>
<my-custom-element t="[title]content_key"></my-custom-element>
</template>
and it worked.
Can someone help me?

Just to have this one handled, it sounded like a side effect not related to the i18n plugin. https://github.com/aurelia/i18n/issues/226

Related

$te method doesn't find existing key if it contains spaces

I have already made a [issue][1] on the official vue-i18n page, but solving this issue would really help me out thats why I'm asking here too.
I'm using the translation key directly as the english translation so some translation keys look like this 'Next':'Weiter' and others contain spaces or even full sentences: 'This text is translated.':'Dieser Text ist übersetzt.'.
So to prevent a million warnings about the missing english translations I want to check for the key first(this.$te('key')) but this doesn't seem to work when the key contains spaces. It returns false even if the translation key is there and the translation is working.
Does someone know a solution to this or do I have to wait until someone addresses my issue on the github page/fixes it on the package itself?
Edit: The problem is fixed in the latest version of vue-i18n. My other problem still stands though. The missing handler option doesn't prevent the warning being thrown, so this doesn't work either.
I solved it by adding my own translation method on the vue instance:
Vue.prototype.$trans = function (key) {
return i18n.te(key) ? i18n.t(key) : key;
}
This way it only translates the key if it actually exists, but still will get complicated if I want to use pluralization etc. so its not a long term solution.
[1]: https://github.com/kazupon/vue-i18n/issues/1309
The problem you are describing indeed exists in a version 7.3.2 but that version is almost 4 years old and current version 8.24.5 works as expected - see example below
Also it seems to me that better way to avoid unneeded error messages would be to supply custom missing handler
var messages = {
en: {},
de: {
'Test translation': 'Test Übersetzung'
}
}
Vue.use(VueI18n)
var i18n = new VueI18n({
locale: 'de',
messages: messages
})
new Vue({
i18n: i18n
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/vue-i18n#8"></script>
<!-- <script src="https://unpkg.com/vue-i18n#7.3.2/dist/vue-i18n.js"></script> -->
<div id="app">
<p>de (implicit): {{ $te("Test translation") }}</p>
<p>de: {{ $te("Test translation", 'de') }}</p>
<p>en: {{ $te("Test translation", 'en') }}</p>
</div>

Nuxt.js: Can I add a class using a ternary operator based on a Boolean from a prop?

I'm trying to use a Boolean in a prop to add a class based on a ternary operator. I'm obviously not doing it right because it is always evaluating to false.
<div :class="$style.inner + ' ' + (isRight == true ? 'is-right' : 'is-false')" :style="`color: #` + fontColor"></div>
If it's true, I need is-right added to the class, otherwise, this doesn't need to be added.
props: {
isRight: {
type: Boolean,
default: false
},
}
index.vue
<Signpost isRight=true/>
.is-right { padding-left:20% }
Looking at their Docs, I'm not sure I can do this with a Boolean actually.
To pass a Boolean to a prop you need to use v-bind or : like this:
<signpost v-bind:isRight="true" />
or
<signpost :isRight="true" />
Also if the only possible values for isRight is true or false you can shorten your ternary to just be:
:class="isRight ? 'is-right' : 'is-false'
I actually had a little syntax error for my prop that needed binding:
<Signpost :is-right="true"/>
And then on the div as #kissu said:
<div :class="[isRight ? 'is-right' : '']"></div>
Try passing the prop as :is-right="false" to have it passed down as Boolean and not as a basic String (is-right="false").
For the class, it can be written like as shown in the docs
<div :class="[isRight ? 'is-right' : 'is-false']"></div>
That way, you will even be able to do a strong equality check: isRight === true (comparing boolean to boolean).
VueJS devtools can help you see the type (thanks to the color).
There are some strongly recommended prop name casing recommendation that can be found here: https://v2.vuejs.org/v2/style-guide/#Prop-name-casing-strongly-recommended
props: {
greetingText: String // camel-cased here
}
<WelcomeMessage greeting-text="hi"/> <!-- kebab-cased here -->
Also, feel free to write ES6 template litterals, it may help the lecture. A working solution would be:
<div :style="`color: #${fontColor}`"></div>
Can also be done for your class, but I didn't want to risk a bad interpolation here.

Angular 5: Passing dynamic class name to "select" attribute of ng-content

I'm following THIS tutorial article to test how Angular Projection works. In this article I came across select attribute of ng-content, to which we can pass class name or attribute to select and target a particular ng-content.
Eg:
#Component({
selector: 'greet',
template: `
<ng-content select=".headerText"></ng-content>
<ng-content select="btnp"></ng-content>
`
<greet>
<h1 class="headerText">Hello</h1>
</greet>
<greet>
<button btnp>Click Here</button>
</greet>
The above example works fine. But, now what I want is to dynamically pass a class name to select like:
<ng-content select=".headerText{{some_id}}"></ng-content>
But, when I attempt this, I get error:
Can't bind to 'select' since it isn't a known property of
'ng-content'.
How can I achieve this?

Vue v-model input change mobile chrome not work

If i open https://v2.vuejs.org/v2/guide/forms.html#Text and edit text - no effect on typing text in mobile chrome. #keyup #input #keypress - v-model does not change when I'm typing
<input v-model="message" #keyup="log" placeholder="Edit">
<p>Edited: {{ message }}</p>
How can i fix it? I need get input value on typing (#keyup #input)
Update: After a lot of discussion, I've come to understand that this is a feature, not a bug. v-model is more complicated than you might at first think, and a mobile 'keyboard' is more complicated than a keyboard. This behaviour can surprise, but it's not wrong. Code your #input separately if you want something else.
Houston we might have a problem. Vue does not seem to be doing what it says on the tin. V-model is supposed to update on input, but if we decompose the v-model and code the #input explicitly, it works fine on mobile. (both inputs behave normally in chrome desktop)
For display on mobiles, the issue can be seen at...
https://jsbin.com/juzakis/1
See this github issue.
function doIt(){
var vm = new Vue({
el : '#vueRoot',
data : {message : '',message1 : ''}
})
}
doIt();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id='vueRoot'>
<h1>v-model</h1>
<div>
<input type='text'
v-model='message'
>
{{message}}
</div>
<h1>Decomposed</h1>
<div>
<input type='text'
:value='message1'
#input='evt=>message1=evt.target.value'
>
{{message1}}
</div>
</div>
I tried all solutions I could find on the internet, nothing worked for me. in the end i came up with this, finally works on android!
Trick is to use compositionupdate event:
<input type="text" ... v-model="myinputbox" #compositionupdate="compositionUpdate($event)">
......
......
methods: {
compositionUpdate: function(event)
{
this.myinputbox = event.data;
},
}
Ok, I dont know if there is another solution for this issue, but it can be solved with a simple directive:
Vue.directive('$model', {
bind: function (el, binding, vnode) {
el.oninput = () => (vnode.context[binding.expression] = el.value)
}
})
using it just like
<input v-$model="{toBind}">
There is an issue on the oficial repo, and they say this is the normal behavior (because the composition mode), but I still need the functionality
EDIT: A simpler solution for me was to just use #input.native. Also, the this event has (now?) a isComposing attribute which we can use to either take $event.data into account, or $event.target.value
In my case, the only scheme that worked was handling #keydown to save the value before the user action, and handling #keyup to process the event if the value had changed. NOTE: the disadvantage of this is that any non-keyboard input (like copy/paste with a mouse) will not work.
<md-input
v-else
:value="myValue"
ref="input"
#keydown="keyDownValue = $event.target.value"
#keyup="handleKeyUp($event)"
#blur="handleBlur()"
/>
With handleKeyUp in my case being:
handleKeyUp(evt){
if(evt.target.value !== this.keyDownValue){
this.$emit('edited', evt);
}
}
My use case was the following:
I requested a search endpoint in the backend to get suggestions as the user typed. Solutions like handling #compositionupdate lead to sending several several requests to the backend (I also needed #input for non-mobile devices). I reduced the number of requests sent by correctly handling #compositionStarted, but there was still cases where 2 requests were sent for just 1 character typed (when composition was left then, e.g. with space character, then re-entered, e.g. with backspace character).

Aurelia: Deleting array elements when changed to empty value

I have an array of strings bound to input elements:
<div repeat.for="i of messages.length">
<input type="text" value.bind="$parent.messages[i]">
</div>
I need to delete an element when the input content is deleted, without using dirty-checking.
This sounds easy - just delete the element which has empty value from the input.delegate handler, unfortunately this does not work due to an Aurelia bug #527. Here's a gist that tries this approach: https://gist.run/?id=e49828b236d979450ce54f0006d0fa0a
I tried to work around the bug by using queueTask to postpone deleting the array element, to no avail. And since the devs closed the bug because according to them it is a duplicate of a completely unrelated issue I guess it is not getting fixed anytime soon.
I am out of ideas how to implement this, so any suggestions are welcome.
Absolutely no need for any kind of dirty checking here! :)
Here's a working demo for your scenario: https://gist.run/?id=20d92afa1dd360614147fd381931cb17
$parent isn't needed anymore. It was related to pre-1.0 Aurelia versions.
If you use a variable instead of array indexes, you can leverage two-way data-binding provided by the input.
<template>
<div repeat.for="msg of messages">
<input type="text" value.bind="msg" input.delegate="onMessageChanged(msg, $index)">
</div>
</template>
So, your onChange event could be simplified like this:
msg holds the actual value of your current input.
i index will be used for deletion.
export class App {
messages = ['Alpha','Bravo','Charlie','Delta','Echo'];
onMessageChanged(msg, i){
if (!msg || msg.length === 0) {
this.messages.splice(i, 1);
}
}
}
There was a related question about a similar problem. This answer might give you more details about the main idea.
Ok, so the solution to this is not to use the buggy (in this case) aurelia 2-way binding, but to use 1-way binding and set the value from the input.delegate handler:
https://gist.run/?id=2323c09ec9da989eed21534f177bf5a8
The #marton answer seems to work at first sight, but it actually disables 2-way binding, so any changes to the inputs are not copied to the array. But it gave me an important hint how to solve the issue.
The equivalent of this html code:
<div repeat.for="msg of messages">
<input type="text" value.bind="msg">
</div>
is this:
for (let msg of messages) {
msg = 'something else'; // note: this does not change the contents of the array
}
See issue #444 for more details
Hence, this forces one-way binding. To fix this in the #marton solution, we only have to change the value from the input.delegate handler:
onMessageChanged(msg, i){
if (!msg || msg.length === 0) {
this.messages.splice(i, 1);//delete the element
}
else {
this.messages[i] = msg;//change the value
}
}