"attached" or DOM-render equivalent for nested view-model.ref - aurelia

We have a page, "parent", which references a template via the view-model.ref called "child" in the parent.html. We change the data of this child template by clicking on items on the parent page which invokes the child function using OpenDetailsDiv. Say I use a button for this event like below:
parent.html
<child view-model.ref="clientusertasks"></child>
<input type="button" value="Click Me" click.trigger="OpenDetailsDiv" />
In this manner we can invoke a function on the "child" view-model from the parent view-model like so:
parent.js
import { inject } from 'aurelia-framework';
import { HttpClient } from 'aurelia-fetch-client';
import 'fetch';
import AuthService from 'AuthService';
import { BpoClientUserTasks } from './bpo-client-user-tasks';
#inject(HttpClient, AuthService, BpoClientUserTasks)
export class Parent {
smallDivObj = {};
freq = '';
period = '';
filterVal = '';
client = '';
constructor(http, AuthService, BpoClientUserTasks) {
http.configure(config => {
config
.withBaseUrl("WebServices.asmx/")
.withDefaults({
headers: {
'Accept': 'application/json'
}
});
});
this.http = http;
this.auth = AuthService;
this.clientusertasks = BpoClientUserTasks;
}
OpenDetailsDiv(myObject) {
this.clientusertasks.CallBPOClientUserService(this.freq, this.period, this.filterVal, myObject.TrueClient, myObject.Client);
}
}
All good so far. The "child" view-model has this function CallBPOClientUserService which looks like the following:
child.js
import { inject } from 'aurelia-framework';
import { HttpClient } from 'aurelia-fetch-client';
import 'fetch';
import AuthService from 'AuthService';
#inject(HttpClient, AuthService)
export class Child {
smallDivObj = {};
constructor(http, AuthService) {
http.configure(config => {
config
.withBaseUrl("WebServices.asmx/")
.withDefaults({
headers: {
'Accept': 'application/json'
}
});
});
this.http = http;
this.auth = AuthService;
}
attached() {
}
CallBPOClientUserService(freq, period, filterVal, client, displayClient) {
$('#TasksByClientByUserDiv').addClass("fade");
this.freq = freq;
this.period = period;
this.filterVal = filterVal;
this.client = client;
var mymethod = {
method: 'post',
body: JSON.stringify({
"session": this.auth.session,
"Client": client,
"FreqFilter": freq,
"FilterVal": filterVal
}),
headers: {
'content-type': 'application/json'
}
};
//alert(JSON.stringify(mymethod));
this.http.fetch('GetBPOTasksByClientByUserDiv', mymethod)
.then(response => response.json())
.then(info => {
this.tasksByClientByUser = JSON.parse(info.d);
//setTimeout("$('#TasksByClientByUserTable').tablesorter();", 100);
});
}
}
Notice that in the function CallBPOClientUserService we wish to call a tablesorter sort function to sort our table in the view AFTER the DOM has been rendered.
Usually I would call upon this function in the "attached" component lifecycle of the view-model. But you can see that the manner in which we are populating this view is from the view-model.ref of the "parent" page which renders the "attached" component for "child" useless for this case (it's only called once after all when the parent is loaded).
So to my question:
Is there an equivalent attached-like component that I tap into to call this tablesorter function?
I have a cheap work-around where I can use a setTimeout which I have commented in the function, but I'd rather do this correctly in Aurelia events and have a guarantee that the DOM has finished.

I believe I have 2 solutions to this problem which I'm satisfied with and will post them here.
The first is the recommendation above by Fabio to use the microTaskQueue.
Another solution is to use a custom bindable event to invoke the function on completion of the repeat.for on the table here...
<template>
<require from='../tablesorter-bind'></require>
<section id="TasksByClientDiv" class="SmallDivPanel ui-draggable BIBulletinSection100 SmallDivSection hellfire">
<small-div-header smalldivinfo.bind="smallDivObj"></small-div-header>
<div id="TasksByClientBox">
<div style="margin-top: 10px;font-size: 20px;">Frequency: ${freq} / Period: ${filterVal}</div>
<div id="TasksByClientTableDiv" class="SmallDivMainPanel">
<table id="TasksByClientTable" >
<thead class="tablesorter">
<tr>
<th>Client</th>
<th>Received</th>
<th>Prepped</th>
<th>Loaded</th>
<th>Doc Loaded</th>
</tr>
</thead>
<tbody>
<tr click.trigger="OpenDetailsDiv(x)" repeat.for="x of tasksByClient" table-id.bind="tableId">
<td>${x.Client}</td>
<td>${x.totLoaded}</td>
<td>${x.totLoaded}</td>
<td>${x.totPrepped}</td>
<td>${x.numDocLoaded}</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</template>
where tableId is defined in the View-Model as my tableID
I then setup the custom element like so:
tablesorter-bind.js
import {inject, customAttribute, TaskQueue} from 'aurelia-framework';
#customAttribute('table-id')
#inject(Element, TaskQueue)
export class TablesorterBind {
constructor(element, taskQueue) {
// "element" will be the DOM element rendered from the template
this.element = element;
this.taskQueue = taskQueue;
}
attached() {
}
bind(bindingContext, overridingContext) {
if (overridingContext.$last === true) {
this.taskQueue.queueMicroTask(
() => {
//This is the jQuery update call to the tablesorter function
$('#' + this.value).trigger('update');
}
);
}
}
}

Related

How could I use rxjs debounceTime and distinctUntilChanged with angular input events (e.g., click or input)

How could I use rxjs debounceTime and distinctUntilChanged with angular input events (input) or (click)
I can't use fromEvent because I also need to pass parameter (Example below)
<li *ngFor="let item of items>
<input [(ngModel)]="item.name" (input)="inputChanged(item.id)">
</li>
So I went with Subject (Example below)
<input type="text" placeholder="Type something..." (input)="inputFn($event, 'myParam')" #myInput>
#ViewChild("myInput", { static: true }) myInput;
private inputEvent = new Subject<any>();
ngOnInit() {
this.inputEvent
.pipe(
// map((obj: any) => obj.evt.target.value),
debounceTime(1000),
distinctUntilChanged()
)
.subscribe(res => {
console.log("res", res.evt.target.value);
});
}
inputFn(evt, param) {
this.inputEvent.next({evt, param});
}
In the above example, there is no use of distinctUntilChanged(). If I filter with map map((obj: any) => obj.evt.target.value) and look for value change (distinct) I am going to get only input value not parameter.
My requirement is - I want to wait until user finished entering text and also if user re-enter want to check if value is not same as previous and also want to get parameter.
You have to use in distinctUntilChanged operator the compare function
import { Component, ViewChild, OnInit } from '#angular/core';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
#ViewChild("myInput", { static: true }) myInput;
private inputEvent = new Subject<any>();
ngOnInit() {
this.inputEvent
.pipe(
debounceTime(1000),
distinctUntilChanged((previous: any, current: any) => previous.value === current.value)
)
.subscribe(res => {
console.log("res", res.evt.target.value);
});
}
inputFn(evt, param) {
this.inputEvent.next({evt, param, value: evt.target.value});
}
}
stackblitz

vuejs treeselect - delay loading does not work via vuex action

Using Vue TreeSelect Plugin to load a nested list of nodes from firebase backend. It's doc page says,
It's also possible to have root level options to be delayed loaded. If no options have been initially registered (options: null), vue-treeselect will attempt to load root options by calling loadOptions({ action, callback, instanceId }).
loadOptions (in my App.vue) dispatch vuex action_FolderNodesList, fetches (from firebase) formats (as required by vue-treeselect), and mutates the state folder_NodesList, then tries to update options this.options = this.get_FolderNodesList but this does not seems to work.
Here is the loadOptions method (in app.vue)
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
Vue errors out with Invalid prop: type check failed for prop "options". Expected Array, got String with value ""
I am not sure what am I doing wrong, why that does not work. A working Codesandbox demo
Source
App.vue
<template>
<div class="section">
<div class="columns">
<div class="column is-7">
<div class="field">
<Treeselect
:multiple="true"
:options="options"
:load-options="loadOptions"
:auto-load-root-options="false"
placeholder="Select your favourite(s)..."
v-model="value" />
<pre>{{ get_FolderNodesList }}</pre>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import Treeselect from "#riophae/vue-treeselect";
import "#riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
data() {
return {
value: null,
options: null,
called: false
};
},
components: {
Treeselect
},
computed: mapGetters(["get_FolderNodesList"]),
methods: {
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
}
};
</script>
Store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
folder_NodesList: ""
},
getters: {
get_FolderNodesList(state) {
return state.folder_NodesList;
}
},
mutations: {
mutate_FolderNodesList(state, payload) {
state.folder_NodesList = payload;
}
},
actions: {
action_FolderNodesList({ commit }) {
fmRef.once("value", snap => {
var testObj = snap.val();
var result = Object.keys(testObj).reduce((acc, cur) => {
acc.push({
id: cur,
label: cur,
children: recurseList(testObj[cur])
});
return acc;
}, []);
commit("mutate_FolderNodesList", result);
});
}
}
});
Any help is appreciated.
Thanks
It seems you are calling this.options which would update the entire element while only the current expanding option should be updated.
It seems loadOptions() is called with some arguments that you can use to update only the current childnode. The first argument seems to contain all the required assets so I wrote my loadTreeOptions function like this:
loadTreeOptions(node) {
// On initial load, I set the 'children' to NULL for nodes to contain children
// but inserted an 'action' string with an URL to retrieve the children
axios.get(node.parentNode.action).then(response => {
// Update current node's children
node.parentNode.children = response.data.children;
// notify tree to update structure
node.callback();
}).catch(
errors => this.onFail(errors.response.data)
);
},
Then I set :load-options="loadTreeOptions" on the <vue-treeselect> element on the page. Maybe you were only missing the callback() call which updates the structure. My installation seems simpler than yours but it works properly now.

How to pass value from template HTML to component to then be used in service

I want to fetch id from component.html into component.ts to pass it to a service.
.ts file is;
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http'
import { HttpErrorResponse } from '#angular/common/http/src/response';
import { SendUsingApiService } from '../send-using-api.service';
import { Router, ActivatedRoute } from '#angular/router';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { setDefaultService } from 'selenium-webdriver/chrome';
#Component({
selector: 'app-org-info',
templateUrl: './org-info.component.html',
styleUrls: ['./org-info.component.css'],
providers: [SendUsingApiService]
})
export class OrgInfoComponent implements OnInit {
orgData: string[] = [];
Id = 1;
editRecord:FormGroup;
constructor(private httpService: HttpClient, private _serv: SendUsingApiService,
private fb: FormBuilder, private _ar:ActivatedRoute, private _r:Router) {
this.editRecord = this.fb.group({
Id:['1', []],
OrganisationName:['', []],
ContactPerson:['', []],
ContactPersonHPNo:['', []],
ContactPersonEmailId:['', []]
});
}
ngOnInit() {
console.log(this._ar.snapshot.params.Id, "+ve");
this._ar.params.subscribe(() => {
this._serv.getUsers(this._ar.snapshot.params.Id).subscribe((res)=>{
console.log(res);
this.setUser(res);
});
});
}
I am getting the value for console.log(this._ar.snapshot.params.Id); as undefined "+ve".
I want to get the Id value in console.
As per requests I am adding html part, though little adjusted;
<td style="text-align: center;">
<a class="btn btn-basic" [routerLink]="['/org-info',data['Id']]" role="button" (click)="getOrgData(data.Id)">View</a>
</td>
I defined a property instead of Id = 1; (above)
paramId = '';
then, within ngOnInit;
ngOnInit() {
this.paramId = this._ar.snapshot.params.Id;
console.log(paramId, "+ve");
}
Doing this, I got the Id value instead of undefined.

Is that the right way to set an id in Vue.js component?

I am trying to integrate Phaser 3 with Vue.js 2.
My goal is to create a Vue.js component associated with a game canvas.
My initial solution was:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
mounted () {
this.id = 'game' + this._uid
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
This code attach the game canvas to my template. But to my surprise it only worked 'sometimes'.
I figured out, after hours of debugging, that my div element in the DOM wasn't updated with the id when I was instantiating my new Game.
So I came up with the solution of instantiating the id in the beforeMount () method as follow:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
beforeMount () {
this.id = 'game' + this._uid
},
mounted () {
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
It is working, but I would like to know if there is a more simple and elegant solution ?
One better solution for integrating Phaser.Game into the application is directly passing the config the HTML element, a configuration supported by Phaser.Game.
To get a reference to a HTML element in vue, you can use refs, these are basically id's, but local to the component itself, so there is not risk in creating conflicts.
<template>
<div ref="myDiv">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
game: null
}
},
mounted () {
var config = {
parent: this.$refs.myDiv,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
Vue3 sample:
<script setup>
import { ref,onMounted } from 'vue';
import Phaser from 'phaser'
const myDiv = ref(null)
let canvasWidth = 750;
let canvasHeight = 1450;
onMounted(() => {
const config = {
type: Phaser.AUTO,
parent: popWrap.value,
width: canvasWidth,
height: canvasHeight,
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
})
</script>
<template>
<div ref="myDiv">
</div>
</template>

Aurelia - Watch Dependency Value for Change

Suppose you have a class you are injecting into a another class or component. Is there a way to watch for changes on an attributed of the dependency you are injecting and act upon it?
For example, say you have the following app:
app.html
<template>
<input type="text" value.bind="item">
<button click.trigger="addToList()">Add</button>
<h3>Modded</h3>
<ul>
<li repeat.for="it of modded">${it}</li>
</ul>
<h3>Original</h3>
<ul>
<li repeat.for="it of dep.items">${it}</li>
</ul>
</template>
app.js
import {bindable, inject} from 'aurelia-framework';
import {Dep} from './dep';
#inject(Dep)
export class App {
constructor(dep) {
this.dep = dep;
}
attached() {
this.modifyItems();
}
addToList() {
this.dep.addItem(this.item);
}
modifyItems() {
this.modded = [];
for (let item of this.dep.items) {
this.modded.push(item.toUpperCase());
}
}
}
dep.js
export class Dep {
constructor() {
this.items = ['one', 'two', 'three'];
}
addItem(item) {
this.items.push(item);
}
}
Now, let's say that some other component modifies Dep.items. Is there a way to watch for changes in app.js on this.dep.items and then call modifyItems()?
Assume modifyItems() is more complex than this example so maybe a value converter is not the best option. (unless it is the only option I guess)
Here is working plunker with the above example: http://plnkr.co/edit/rEs9UM?p=preview
Someone pointed me to the BindingEngine.collectionObserver and it appears that is what I needed.
app.js:
import {inject} from 'aurelia-framework';
import {BindingEngine} from 'aurelia-binding';
import {Dep} from './dep';
#inject(Dep, BindingEngine)
export class App {
constructor(dep, bindingEngine) {
this.dep = dep;
let subscription = bindingEngine.collectionObserver(this.dep.items)
.subscribe((newVal, oldVal) => {
console.debug(newVal, oldVal);
this.modifyItems();
});
}
attached() {
this.modifyItems();
}
addToList() {
this.dep.addItem(this.item);
this.item = '';
}
modifyItems() {
this.modded = [];
for (let item of this.dep.items) {
this.modded.push(item.toUpperCase());
}
}
}
Here is the working pluker: http://plnkr.co/edit/Pcyxrh?p=preview