Updating components based on sorted array. Angular (4.3) - javascript

Working with an array of data that we want to be able to sort for display in a component, and it doesn't seem to be sorting or updating the DOM, however I have a working code sample that properly demonstrates the concept, and it should be sorting, but in the angular app, it's simply not getting sorted.
The parent component that houses the original data stores the data on an Input parameter object called Batch, and the array we're sorting is on Batch.Invoices.Results. The event from the child component is fine, and the appropriate data is confirmed to bubble to the parent component.
The function that's supposed to sort the array looks like this:
public OnInvoiceSortChange({orderValue, orderAscending}){
console.log(`Invoice Sorting has been called. Value: ${orderValue} . Ascending? ${orderAscending}`);
console.log(`Before:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
const sortingArray = [...this.BatchViewModel.Invoices.Results];
if(orderAscending){
const sorted = sortingArray.sort((a, b) => a[orderValue] > b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log('Sorted');
console.log(sorted.map(x => x.VendorName));
} else {
const sorted = sortingArray.sort((a, b) => a[orderValue] < b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log(sorted.map(x => x.VendorName));
}
console.log(`After:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
}
All the console logs are for debugger visibility, and the output is this:
Where in my testing file (non-angular) looks like this:(where data is a direct copy of the array from the Angular app.
const ascendingData = [...data];
const descendingData = [...data];
const sortedDescending = descendingData.sort((a, b) => a['VendorName'] < b['VendorName']? 0 : 1)
const sortedAscending = ascendingData.sort((a, b) => a['VendorName'] > b['VendorName']? 0 : 1);
const vendorListAscending = sortedAscending.map(x => x.VendorName);
const vendorListDescending = sortedDescending.map(x => x.VendorName);
console.log(vendorListDescending);
console.log(vendorListAscending);
and the output looks like this:
So I see that the sorting should work, but it's just not happening in Angular.
How can I get the array sorted, and update the DOM as well?

The function you pass to sort is wrong. It is supposed to return a negative value for "less", a positive value for "greater" or zero for "equal". If orderValue is numeric then it's easiest to just return a[orderValue] - b[orderValue], if not then just change your 0 to -1.
(By the way, name orderKey could be a bit clearer maybe?)
I don't think angular has anything to do here, but I cannot tell now why you get different results. Anyway, your sort function is invalid (it states that a equals b, but at the same time b is greater than a), I hope fixing this function helps.

Related

Sort array based on intermediate model's attribute

I have three models (I am using Vue/Vuex-ORM on the frontend). Category, CategoryItem and Item.
I'm able to fetch an array of categories, and within each category is an array of items. An intermediate join model defines the relationships of these two models, and I am able to access as such:
// array of categories, each category has array of items
const categories = Category.query().where('pack_id', this.selectedPack.id).with('items').get();
categories.map(category => {
category.items.forEach(item => {
console.log('item.pivot: ', item.pivot); // pivot refers to join model
// how to order items based on item.pivot?
})
})
Within the .forEach, I can access the join model with item.pivot. What I am looking to do however, is sort each category's items based on item.pivot.position.
I started going down a path where the first line inside of the .map I defined a new empty array, and would then theoretically push in a new value based on whether the position was higher or lower, but I couldn't quite wrap my head around how to accomplish this.
Thanks!
Well just my luck. A half hour after posting this question, I figure it out! Here was what I did, in case anyone is curious.
categories() {
const categories = Category.query().where('pack_id', this.selectedPack.id).with('items').get();
categories.forEach(category => category.items.sort(this.compare));
return cats;
}
compare(a, b) {
let comparison = 0;
if (a.pivot.position > b.pivot.position) comparison = 1;
else if (a.pivot.position < b.pivot.position) comparison = -1;
return comparison;
},

ngx dropdown list get selected values

I am trying to use ngx dropdown list like this:
<ngx-dropdown-list [items]="categoryItems" id="categoriesofdata" [multiSelection]="true"
[placeHolder]="'Select categories'"></ngx-dropdown-list>
And I am getting all selected values like:
get selectedCategories() {
const items = this.categoryItems.filter((item: any) => item.selected);
return items.length ? JSON.stringify(items.map(item => ({
value: item.value
}))) : '';
}
and output looks like:
[{"value":"Surname"},{"value":"Address"}]
I want to get only for example Surname instead of value and Surname.
[0].value
How Can I do this?
Should I use for loop or is better option?
I think you're almost there, in fact you're doing a little too much. Your map function should just return the value you are interested in rather than creating a new structure.
get selectedCategories() {
const items = this.categoryItems.filter((item: any) => item.selected);
return items.length ? JSON.stringify(items.map(item => item.value)) : '';
}
Edit:
And as a personal preference, I would refactor to something like this:
get selectedCategories() {
if (!this.categoryItems.length) {
return '';
}
const surnames = this.categoryItems
.filter(item => item.selected)
.map(item => item.value);
return JSON.stringify(surnames);
}
I prefer to get out of a function early in the case where no further processing is required. And I would return the result of chained filter and map functions into a new surnames variable. The named variable signals the intention of the code, and keeps the array logic together.
This is just my preference though. Your code was almost there functionally.

JavaScript/React Native array(objects) sort

I'm starting with react-native building an app to track lap times from my RC Cars. I have an arduino with TCP connection (server) and for each lap, this arduino sends the current time/lap for all connected clients like this:
{"tx_id":33,"last_time":123456,"lap":612}
In my program (in react-native), I have one state called dados with this struct:
dados[tx_id] = {
tx_id: <tx_id>,
last_time:,
best_lap:0,
best_time:0,
diff:0,
laps:[]
};
This program connects to arduino and when receive some data, just push to this state. More specific in laps array of each transponder. Finally, I get something like this:
dados[33] = {
tx_id:33,
last_time: 456,
best_lap: 3455,
best_time: 32432,
diff: 32,
laps: [{lap:1,time:1234},{lap:2,time:32323},{lap:3,time:3242332}]
}
dados[34] = {
tx_id:34,
last_time: 123,
best_lap: 32234,
best_time: 335343,
diff: 10,
laps: [{lap:1,time:1234},{lap:2,time:32323},{lap:3,time:3242332}]
}
dados[35] = {
tx_id:35,
last_time: 789,
best_lap: 32234,
best_time: 335343,
diff: 8,
laps: [{lap:1,time:1234},{lap:2,time:32323},{lap:3,time:3242332},{lap:4,time:343232}]
}
This data in rendered to View's using map function (not a FlatList).
My problem now is that I need to order this before printing on screen.
Now, with this code, data are printed using tx_id as order, since it's the key for main array. Is there a way to order this array using number of elements in laps property and the second option to sort, use last_time property of element?
In this case, the last tx of my example (35) would be the first in the list because it has one lap more than other elements. The second item would be 34 (because of last_time). And the third would be tx 33.
Is there any way to to this in JavaScript, or I need to create a custom functions and check every item in recursive way?!
Tks #crackhead420
While waiting for reply to this question, I just found what you said.... :)
This is my final teste/solution that worked:
var t_teste = this.state.teste;
t_teste[33] = {tx_id: 33, last_time:998,best_lap:2,best_time:123,diff:0,laps:[{lap:1,time:123},{lap:2,time:456}]};
t_teste[34] = {tx_id: 34, last_time:123,best_lap:2,best_time:123,diff:0,laps:[{lap:1,time:123},{lap:2,time:456}]};
t_teste[35] = {tx_id: 35, last_time:456,best_lap:2,best_time:123,diff:0,laps:[{lap:1,time:123},{lap:2,time:456},{lap:3,time:423}]};
t_teste[36] = {tx_id: 36, last_time:789,best_lap:2,best_time:123,diff:0,laps:[{lap:1,time:123},{lap:2,time:456}]};
console.log('Teste original: ',JSON.stringify(t_teste));
var saida = t_teste.sort(function(a, b) {
if (a.laps.length > b.laps.length) {
return -1;
}
if (a.laps.length < b.laps.length) {
return 1;
}
// In this case, the laps are equal....so let's check last_time
if (a.last_time < b.last_time) {
return -1; // fastest lap (less time) first!
}
if (a.last_time > b.last_time) {
return 1;
}
// Return the same
return 0;
});
console.log('Teste novo: ',JSON.stringify(saida));
Using some simple helper functions, this is definitely possible:
const data = [{tx_id:33,last_time:456,best_lap:3455,best_time:32432,diff:32,laps:[{lap:1,time:1234},{lap:2,time:32323},{lap:3,time:3242332}]},{tx_id:34,last_time:123,best_lap:32234,best_time:335343,diff:10,laps:[{lap:1,time:1234},{lap:2,time:32323},{lap:3,time:3242332}]},{tx_id:35,last_time:789,best_lap:32234,best_time:335343,diff:8,laps:[{lap:1,time:1234},{lap:2,time:32323},{lap:3,time:3242332},{lap:4,time:343232}]}]
const sortBy = fn => (a, b) => -(fn(a) < fn(b)) || +(fn(a) > fn(b))
const sortByLapsLength = sortBy(o => o.laps.length)
const sortByLastTime = sortBy(o => o.last_time)
const sortFn = (a, b) => -sortByLapsLength(a, b) || sortByLastTime(a, b)
data.sort(sortFn)
// show new order of `tx_id`s
console.log(data.map(o => o.tx_id))
sortBy() (more explanation at the link) accepts a function that selects a value as the sorting criteria of a given object. This value must be a string or a number. sortBy() then returns a function that, given two objects, will sort them in ascending order when passed to Array.prototype.sort(). sortFn() uses two of these functions with a logical OR || operator to employ short-circuiting behavior and sort first by laps.length (in descending order, thus the negation -), and then by last_time if two objects' laps.length are equal.
Its possible to sort an object array by theire values:
dados.sort(function(a, b) {
return a.last_time - b.last_time;
});

how to print a javascript object's elements

i am new to javascript and i currently have an object printed to console when i use the following code:
clickEvents: {
click:function(target) {
console.log(target);
}
}
when i view console i can see the following object:
i am banging my head against a wall to write code that takes the object and prints it to a div using the .append() method. i am extermely new to working with javascript objects, and would appreciate any help trying to tease out an object and/or print the object data.
is events the object name? would i tease out the eventDate using something like events->eventDate?
I've made this over ~15 minutes so it's imperfect; there are types and edge cases surely unaccounted for and the design of the function could be better - not to mention that performing all of this as a giant string and then setting that as HTML is likely bad practice (I'm used to React now, ha!). Regardless, this will iterate over any array or object you pass to it and print it all in a big <ul> recursively.
const targetEl = document.querySelector('.js-target')
if (!targetEl) return
// Small helper functions
const isObj = data => typeof data === 'object' && !Array.isArray(data) && data !== null
const isArr = data => Array.isArray(data)
const dataToHTML = (data, noNode = false) => {
if (isObj(data)) {
const accumulator = Object.entries(data).reduce((acc, set) => acc + `<li><strong>${set[0]}</strong>: ${dataToHTML(set[1], true)}</li>`, '')
return `<ul>${accumulator}</ul>`
}
else if (isArr(data)) {
const accumulator = data.reduce((acc, item) => acc + dataToHTML(item), '')
return `<ul>${accumulator}</ul>`
}
else return noNode ? data : `<li>${data}</li>`
}
const logHTML = dataToHTML(exampleData)
targetEl.innerHTML = logHTML
Assuming that your data/variable is named exampleData.
Any questions pop them in the comments :-)
I'm not sure if you have a div that you want to append to already, but you would do something like this ->
document.getElementById("toBeAppendedTo").innerHTML = target.events[0].eventDate; where toBeAppendedTo is the id of the div you're trying to add this text to.
append() is a jquery function, not a javascript function.
That won't have any formatting and will just be the string value 07-28-2017 in a div.

Cyclejs and trigger events with values

I m trying to learn cyclejs and reactive programming, and I can't get how to manage events with values.
For example,I need to create four functions that makes some maths operations such as :
addition
substraction
divison
multiplication
Here's the code that I have :
function main ({DOM}) {
const secondNumber$ = DOM
.select('.second-number')
.events('change')
.map(ev => ev.target.value)
.startWith(0)
const firstNumber$ = DOM
.select('.first-number')
.events('change')
.map(ev => ev.target.value)
.startWith(0)
const addClick$ = DOM
.select('.add')
.events('click')
.merge(firstNumber$)
.merge(secondNumber$)
.filter(value => console.log(value))
.scan((nb1, nb2) => nb1 + nb2)
return {
DOM: addClick$.map(result =>
div([
input('.first-number',{type:'text'}),
input('.second-number',{type:'text'}),
button('.add', 'Add'),
h2('Result is : ' + result)
])
)
};
}
It doesn't work at all and I can't figure out in my mind what I m doing wrong out there.
I m looking for a simple explanation how can I make this working ? I feel just like the merging streams of secondNumber$ and firstNumber$ are not correct and I can't find why..
Any idea ?
EDIT : I got that I shouldn't use the operator I was using, but use withLatestFrom.
The fact is that I m using xstream and so I have to map / flatten :
import {
div,
h1,
input,
button
} from '#cycle/dom';
/**
* Counter
* #param {Object} sources Contains the inputs
* #return {Object} The output sinks
*/
function counter(sources) {
const input1$ = sources.DOM
.select('.input1')
.events('input')
.map(ev => ev.target.value)
.startWith(0);
const input2$ = sources.DOM
.select('.input2')
.events('input')
.map(ev => ev.target.value)
.startWith(0);
const add$ = sources.DOM
.select('.add')
.events('click');
const resultAddition$ = add$
.map(ev => input1$
.map(value => input2$
.map(value2 => Number(value) + Number(value2)))
.flatten())
.flatten()
.startWith(0);
return {
DOM: resultAddition$.map(item => {
console.log(item); // triggered each time an input is modified
return div([
h1(`Super new value : ${item}`),
input('.input1', {
attrs: {
type: 'text'
}
}),
input('.input2', {
attrs: {
type: 'text'
}
}),
button('.add', 'Ajouter')
]);
})
};
}
export default counter;
From now, I have got in mind what the code should do, mapping on each click the operation and flatten the two input$ to get my result only when clicking the button
The fact is that the result value is changing on input and not and click. And more important, it changes on input only after the first click on the add button that is not what I want to.
What am I doing wrong this time ?
Thanks for your replies
It seems like you want combineLatest, not merge.
Both combineLatest and merge are "combination operators". They bring multiple Observables together and output one Observable. However, combineLatest is for "AND" combinations, while merge is for "OR" combinations.
You probably need "AND", because you want the value from first-number AND the value from second-number. That said, you want those values only when an add click happens. In that case, there is a variant of combineLatest called withLatestFrom. It allows you to sample the values from first-number AND second-number, but only when the add click happens.
const addClick$ = DOM
.select('.add')
.events('click')
const added$ = addClick$
.withLatestFrom(firstNumber$, secondNumber$,
(click, first, second) => first + second
)
As a side note, you should never do something like .filter(value => console.log(value)). The function for filter is a predicate. It's supposed to be a "condition" function that returns a boolean. If you want to debug, use .do(value => console.log(value)).
PS: I'm assuming you were using RxJS v4.

Categories

Resources