Aurelia: Trigger update on one custom element when a value in another custom element changes? - aurelia

I've just recently asked a question ( Refreshing i18n translated string interpolated values in Aurelia ) regarding how to refresh i18n string interpolated values when a select input field (with language ids) changes. I received a great answer, however now I realized that there was another requirement.
It's not only string interpolated values that needs to be refreshed.
In my page-specific templates I have some select fields (custom elements), which gets their option values from injecting a "service" class. That service is responsible for doing the http fetch request, and returning a promise to the select field (custom element).
Now here's the problem. When the global (language) select field from my site-wide nav-bar custom element changes, and i18n refreshing happens using the notification approach proposed in the link above. How would I then also re-fetch my different select input custom elements inside the template, but with the new language id from the language select in the nav-bar?
The only solution I know so far is to do window.location (router.navigate(sameroute) didn't trigger a refresh) and refresh the current page whenever the language select changes, however that's obviously not a great way to handle this.
I'll try to see if I can put together a plunkr, since all this may sound a little confusing.

#chrismbeckett from in https://gitter.im/Aurelia/Discuss gave me this hint:
"For those types of inter-component syncs, use the EventAggregator.
Pub a 'lang-changed' event and let any component do what it needs to
update itself"
So in nav-bar.js i did this:
let payload = { 'lngId': this.appLngId};
this.eventAggregator.publish('lang_changed', payload);
and in the custom element which were to be refreshed I put this inside the attached() function:
this.eventAggregator.subscribe('lang_changed', payload => {
alert(payload.lngId)
self.myTodosService.getMyTodos(payload.lngId);
.then(function(data){
self.myTodos = data;
})
});

Related

Initially hide first group in Vue-Formulate repeatable group

I'm using Vue-Formulate's Repeatable Groups. For my requirements:
A group is optional to add
If a button is pressed to add a group, then the fields in the group must be validated
The form should not initially show the group; it should show the button to add a group
For example, the desired initial appearance is in the following screenshot, which I generated after clicking the "remove" / X button in the linked codesandbox:
I've mocked this up at codesandbox here: Vue Formulate Group with Button to Start
Is this possible? If so, how?
May 2021 UPDATED WORKAROUND
In #braid/vue-formulate#2.5.2, the workaround below (in Research: A hack that seems to UPDATE: USED TO work) no longer works, using a slot to override the close button, save a ref, and trigger a click does. See also the related feature request at https://github.com/wearebraid/vue-formulate/issues/425.
<script>
export default {
// ... fluff omitted
async mounted() {
await this.$nextTick();
if (!this.hasMessages) {
// See also feature request at https://github.com/wearebraid/vue-formulate/issues/425
this.$refs.closeButton.click();
}
},
};
</script>
<template>
<FormulateInput
type="group"
name="rangeMessages"
:minimum="0"
repeatable>
<!-- See https://vueformulate.com/guide/inputs/types/group/#slots -->
<template #remove="{removeItem}">
<button ref="closeButton" #click.prevent="removeItem"/>
</template>
</FormulateInput>
</template>
Research - Vue-Formulate's Docs
In Vue-Formulate's docs on input with type="group"'s props and slots, there is a minimum prop. I've set that to zero, but that doesn't change the initial appearance. I do see multiple slots, but I'm not quite sure which one to use or if I could use any of them to achieve what I want; it seems like default, grouping, and repeatable might be useful in preventing the initial display of the first group.
Research - Vue-Formulate's Tests
I see that FormulateInputGroup.test.js tests that it('repeats the default slot when adding more', so the default slot is the content that gets repeated. According to the docs, the default slot also receives the index as a slot prop, so that could be useful.
Research - Vue Debugger
The item which I want to initially remove is at FormulateInputGroup > FormulateGrouping > FormulateRepeatableProvider > FormulateRepeatable > FormulateInput:
When I remove the initial item to match the desired initial layout, the group hierarchy changes to:
<FormulateInput><!-- the input type="group" -->
<FormulateInputGroup>
<FormulateGrouping/>
<FormulateAddMore>...</FormulateAddMore>
</FormulateInputGroup>
</FormulateInput>
Based on this change, I would expect that I need to modify FormulateGrouping to get the desired initial appearance, but I haven't found in the source what items are available to me there.
Research: A hack that seems to UPDATE: USED TO work
This hack worked in v2.4.5, but as of 2.5.2, it no longer works. See top of post for an updated workaround.
In the mounted hook, when I first render the form, I can introspect
into the formValues passed to v-model to see if the group lacks an
initial elements that is filled out. If so, then I can make use of a
ref msgs on the FormulateInput of type group to then call
this.$refs.msgs.$children[0].$children[0].removeItem(), which
triggers an initial remove of the (empty) item. This is super hacky,
so I'd prefer a better way, but it kind of works. The only problem is
that it validates the fields when clicking on the button, before any
input has been entered.
This is a fair question, and Vue Formulate used to support the behavior of just using an empty array to begin with, however it became clear that it was confusing to users that their fields would not show up without an empty object [{}] when they bound the model, so a change was made to consider an initial value of an empty array an "empty" field and pre-hydrate it with a value. Once that initial hydration is completed however, you can easily re-assign it back to an empty array and you're good to go. This is easily done in the mounted lifecycle hook:
...
async mounted () {
await this.$nextTick()
this.formData.groupData = []
}
...
Here's a fork of your code sandbox: https://codesandbox.io/s/vue-formulate-group-with-button-to-start-forked-32jly?file=/src/components/Reproduction.vue
Provided solutions weren't working for me but thanks to previous answer I've managed to find this one:
mounted(){
Vue.set(this.formData, "groupData", [])
},
which does same effect as
data(){
formData: {
groupData: [],
},
},
mounted(){
this.formData.groupData = []
},

How to add a link (or any html) to a getter response in Vuex?

I'm building a blockchain explorer using Vue and Vuex and I stumbled upon a problem I can't solve thus far.
Essentially I have a getter that returns a full transaction info based on operation name. For instance, if the operation is po_transfer, I want to give user details on what type of asset, quantity, sender and receiver of this particular operation (#mat sent 10 BTC to #ned).
Since I collect data from different applications and games of one particular blockchain, I have dozens of these operations (transfer, open, cancel, sell, purchase, issue, build, explore, upgrade and so forth). Thus, I created a getter that returns all of that in a human readable form, instead of po_transfer.
It works perfectly with text but I need to add a link in my response as well.
In our previous example (#mat sent 10 BTC to #ned) #mat and #ned should be links that lead to correspondent websites that I set up for them.
So my question is - how to I add link inside of the Vuex getter response?
I already tried to use plain javascript with document.body.appendChild(myLink) but it only shows an href text (http://somewebsite.com/#mat), instead of actually giving me an embedded link (#mat) that leads to this href.
Here's what I have now:
dappInfo: (state, getters) => (op, id) => {
if (op === 'transfer') {
return getters.dappJsonByTrxId(id).from + ' sent ' +
getters.dappJsonByTrxId(id).amount +
` to #${getters.dappJsonByTrxId(id).to}`
}
}
dappJsonByTrxId: (state) => (id) => {
return state.transactions.find(t => t.transaction_id === id).operations[0][1] || ''
}
I need this getters.dappJsonByTrxId(id).from to return #mat.
<a href=`example.com/${getters.dappJsonByTrxId(id).from}> #${getters.dappJsonByTrxId(id).from}<a/>
doesn't work as well. It returns me <a>..</a> in a text form.
I assume you're not supposed to return html in getters but I really need to solve this one because everything else is working. Would really appreciate any help. Thanks in advance.
P.S. You can check the demo of explorer right here.
You can output string value containing all your html tags <a href=".... /> composed in a getter and then output it as html using v-html directive, f.e. <div v-html="yourJsString"/>.
When composing string with html, ecmaScript templates aka string interpolation may come in handy.

Aurelia Validation and event publish not updating values in View

I was using Event aggregate publish method to update view from another view. when I created publish and subscribe data was able to get in view model , but that is not updating in view with assigned variables. If I use JavaScript to fill value it's working but not able to trigger validate controller.
Expected result is:
From the subscribe method I have to fill the view values.
After that it should trigger validate on value changes.
In below gist run I have two view and view-model. One is registration-form and second one is data-form. In data-form I have table with data, on click of each row I am publishing one event with selected row data, and this published event I was subscribing in side of registration-form view-modal. For more details look into below gist run.
Gist run: https://gist.run/?id=8416e8ca1b9b5ff318b79ec94fd3220c
Two possible solutions: (to your "undefined" alert)
Change alert(this.email); to alert(myself.email);
Use the thick arrow syntax for the subscribe function. Change:
this.ea.subscribe('MyrowData', function(obj){
to
this.ea.subscribe('MyrowData', obj => {
This syntax allows the new function to preserve the context/scope of the parent, which means that this.email will still be accessible.
Another suggestion:
You could simplify your data-form.js function as follows:
Change:
this.eventAggregator.publish('MyrowData', this.items[this.items.indexOf(item)]);
to:
this.eventAggregator.publish('MyrowData', item);
This will work because you've already provided item in your function call click.delegate="PopulateData(item, $event)".
In fact, you could even delete $event and event from the two PopulateData definitions (in data-form.js and data-form.html).
When I implement these suggestions, your data is also validated correctly.

VueJS Component Input Sync

I want to create components which have input which two-way bind to the local scope of the component.
Without a component, I would create a new Vue instance and then set my data to what I need. Then using v-model, bind an input to that data and it can be manipulated from the input.
However, converting the same code to a component, I cannot for the life of me get any input in a component to bind to its data. I have tried props, :data.sync, data attributes but no matter what I have tried, the input within a component does nothing.
I have created a JSFiddle to illustrate this:
https://fiddle.jshell.net/f0pdmLhy/2/
What I would like to happen is the input in the component to two way bind to the err variable, just like the non component version underneath.
How would I accomplish this?
I basically want to create components that I can instansiate with ajax data and then populate the inputs. The inputs could then update the data and I can use a save method to send the data to the server. Can this even be done using components?
So there are a couple of things:
The external resource you were using was somehow faulty. I've used
jsfiddle default Vue instance and it works fine.
When you declare a component, you should not define the data as an object, but as a function returning an object. Read here: https://vuejs.org/guide/components.html#Component-Option-Caveats
A working example here: https://fiddle.jshell.net/by4csn1b/1/
Yes, with components, the reactivity can be accomplished just like with an instance.
One catch with components, is that data must be a function that returns an object.
Also, to maintain the two way binding, use v-model in your input.
Vue.component('ii', {
template: '<span>{{err}}</span><input type="text" v-model="err"><hr>',
data: function () {
return {
err: 123
}
}
})
Fiddle: https://fiddle.jshell.net/f0pdmLhy/25/

Yii2 Gridview get all selected row for all pagination

I wrapped my gridview with Pjax widget like this
\yii\widgets\Pjax::begin();
gridview
\yii\widgets\Pjax::end();
in order to make the gridview make ajax request when I click on each pagination.
I also use ['class' => 'yii\grid\CheckboxColumn'], in column as well.
and I find that when I'm on first pagination I checked some rows and then go to second page and check some rows but when I go back to first page what I've checked is gone.
My question is how can I keep all checkedrow for all pagination
With current conditions (Pjax, multiple pages, yii\grid\CheckboxColumn) it's impossible because of the way it works.
When you click on the pagination links all GridView html content is replaced by new one that comes from the AJAX response.
So obviously all selected checkboxes on the previous page are gone.
Few possible ways to solve that:
1) Write custom javascript and server side logic.
As one of the options, you can send AJAX request to server with parameter meaning that user has chosen to select all data for the bulk delete operation (or use separate controller action for bulk deletion). In this case actually we don't need to get the selected data from user, because we can simply get them from database (credits - Seng).
2) Increase number of displayed rows per page.
3) Use infinite scroll extension, for example this.
4) Break desired action in several iterations:
select needed rows on first page, do action (for example, delete).
repeat this again for other pages.
You can get selected rows like that:
$('#your-grid-view').yiiGridView('getSelectedRows');
[infinite scroll] : http://kop.github.io/yii2-scroll-pager/ will work good if you do not have any pjax filters. If you have filters also in play, do not use this plugin as it does not support pjax filters with it. For rest of the applications it is perfect to use.
Update1 : it seems to be straight forward than expected, here is the how I accomplished it
Add following lines to the checkbox column
'checkboxOptions' => function($data){
return ['id' => $data->id, 'onClick' => 'selectedRow(this)'];
}
Now add following JS to the common js file you will have in your project of the page where this datagrid resides
var selectedItems=[]; //global variable
/**
* Store the value of the selected row or delete it if it is unselected
*
* #param {checkbox} ele
*/
function selectedRow(ele){
if($(ele).is(':checked')) {
//push the element
if(!selectedItems.includes($(ele).attr('id'))) {
selectedItems.push($(ele).attr('id'));
}
} else {
//pop the element
if(selectedItems.includes($(ele).attr('id'))) {
selectedItems.pop($(ele).attr('id'));
}
}
}
Above function will store the selected row ids in the global variable array
Now add following lines to pjax:end event handler
$(document).on('pjax:end', function () {
//Select the already selected items on the grid view
if(!empty(selectedItems)){
$.each(selectedItems, function (index,value) {
$("#"+value).attr('checked',true);
});
}
});
Hope it helps.
I just solved this problem and it works properly with Pjax.
You may use my CheckboxColumn. I hope this can help. The checked items are recorded with cookies.
You can read the part with //add by hezll to understand how to fix it, because I didn't provide a complete general one.
Hope it works for you.
https://owncloud.xiwangkt.com/index.php/s/dGH3fezC5MGCx4H