sencha touch routing extraparams kitchensink - extjs

I am trying to extend the routing in the Sencha Touch Kitchensink app as follows:
My data (in List store) are as follows:
{ category: 'fruit', str: 'tomato'},
{ category: 'fruit', str: 'green bean'},
{ category: 'vegetable', str: 'celery'},
{ category: 'vegetable', str: 'sprouts'},
{ category: 'notAVegetable', str: 'ketchup'},
{ category: 'notAVegetable', str: 'prune'}
I would like to show only those data selected by a particular category, such as "fruit"
In the Main.js controller, I am trying to do this by grabbing another parameter from the "List" node in the Demos TreeStore
routes: {
'demo/:id/:category': 'showViewById',
'menu/:id': 'showMenuById'
},
Where the showViewById action adds the extra parameter for use later
showViewById: function (id, category) {
var nav = this.getNav(),
view = nav.getStore().getNodeById(id);
console.log('view ' + id);
this.showView(view);
this.setCurrentDemo(view);
this.hideSheets();
// do stuff with category
},
I am trying to add and access 'category' as an extraParameter in my Demos.js store in the "List" tree node as follows:
{
text: 'List',
leaf: true,
id: 'list',
extraParams: {
category: 'fruit'
}
},
A few questions: Can I use an extraParameter to add this attribute to the Store? If so, how can I access it to use for my routing? I thought it would be available as metadata for my Demos store, but have not been able to access it.
Any alternatives short of creating multiple stores (one for "fruit", "vegetable", "notAVegetable," etc.) with filters on them to achieve the same thing?
TIA!

Related

Merge and sort arrays of objects JS/TS/Svelte - conceptual understanding

The goal is to display a recent activity overview.
As an example: I would like it to display posts, comments, users.
A post, comment and user object live in its corresponding arrays. All of the objects have a timestamp (below createdAt), but also keys that the objects from the different arrays don't have. The recent activites should be sorted by the timestamp.
(Ultimately it should be sortable by different values, but first I would like to get a better general understanding behind merging and sorting arrays / objects and not making it to complicated)
I thought of somehow merging the arrays into something like an activity array, then sorting it and looping over it and conditionally output an object with its keys depending on what kind of object it is?
If someone is willing to deal with this by giving an example, it would make my day. The best thing I could imagine would be a svelte REPL that solves this scenario. Anyway I'm thankful for every hint. There probably already are good examples and resources for this (I think common) use case that I didn't find. If someone could refer to these, this would also be superb.
The example I'm intending to use to get this conceptual understanding:
const users = [
{ id: 'a', name: 'michael', createdAt: 1 },
{ id: 'b', name: 'john', createdAt: 2 },
{ id: 'c', name: 'caren', createdAt: 3 }
]
const posts = [
{ id: 'd', topic: 'food', content: 'nonomnom' createdAt: 4 },
{ id: 'e', name: 'drink', content: 'water is the best' createdAt: 5 },
{ id: 'f', name: 'sleep', content: 'i miss it' createdAt: 6 }
]
const comments = [
{ id: 'g', parent: 'd', content: 'sounds yummy' createdAt: 7 },
{ id: 'h', parent: 'e', content: 'pure life' createdAt: 8 },
{ id: 'i', parent: 'f', content: 'me too' createdAt: 9 }
]
Edit: it would have been a bit better example with more descriptive id keys, like userId and when a post and comment object contains the userId. However, the answers below make it very understandable and applicable for "real world" use cases.
This is fun to think about and it's great that you're putting thought into the architecture of the activity feed.
I'd say you're on the right track with how you're thinking of approaching it.
Think about:
How you want to model the data for use in your application
How you process that model
Then think about how you display it
You have 3 different types of data and you have an overall activity feed you want to create. Each type has createdAt in common.
There's a couple of ways you could do this:
Simply merge them all into one array and then sort by createdAt
const activities = [...users, ...posts, ...comments];
activities.sort((a,b) => b.createdAt - a.createdAt); // Sort whichever way you want
The tricky part here is when you're outputting it, you'll need a way of telling what type of object each element in the array is. For users, you can look for a name key, for posts you could look for the topic key, for comments you could look for the parent/content keys to confirm object type but this is a bit of a brittle approach.
Let's try to see if we can do better.
Give each activity object an explicit type variable.
const activities = [
...users.map((u) => ({...u, type: 'user'})),
...posts.map((u) => ({...u, type: 'post'})),
...comments.map((u) => ({...u, type: 'comment'}))
];
Now you can easily tell what any given element in the whole activities array is based on its type field.
As a bonus, this type field can also let you easily add a feature to filter the activity feed down to just certain types! And it also makes it much simpler to add new types of activities in the future.
Here's a typescript playground showing it and logging the output.
As a typesafe bonus, you can add types in typescript to reinforce the expected data types:
eg.
type User = {
type: 'user';
name: string;
} & Common;
type Post = {
type: 'post';
topic: string;
content: string;
} & Common;
type UserComment = {
type: 'comment';
parent: string;
content: string;
} & Common;
type Activity = User | Post | UserComment;
To expand on the other answers, eventually you will want to show each element also differently, while you could do this with an if block testing the type that has been added to the object, this is not very scalable as a new type of block would require at least two changes, one to add the type to the activities array and one to add this new type to the if blocks.
Instead if we change our activities array as follows:
const activities = [
...users.map((u) => ({...u, component: UserCompomnent})),
...posts.map((u) => ({...u, component: PostComponent})),
...comments.map((u) => ({...u, component: CommentComponent}))
];
where UserComponent, PostComponent and CommentComponent are the different ways of presenting this data.
Then when you loop over your data to display them, we can use svelte:component and leverage that we already defined which component should be shown:
{#each acitivities as activity}
<svelte:component this={activity.component} {...activity} />
{/each}
Here's an approach using simple 'helper classes' so that the different objects can be distinguished when displayed REPL
<script>
class User {
constructor(obj){
Object.assign(this, obj)
}
}
class Post {
constructor(obj){
Object.assign(this, obj)
}
}
class Comment {
constructor(obj){
Object.assign(this, obj)
}
}
const users = [
{ id: 'a', name: 'michael', createdAt: 1652012110220 },
{ id: 'b', name: 'john', createdAt: 1652006110121 },
{ id: 'c', name: 'caren', createdAt: 1652018110220 }
].map(user => new User(user))
const posts = [
{ id: 'd', topic: 'food', content: 'nonomnom', createdAt: 1652016900220 },
{ id: 'e', topic: 'drink', content: 'water is the best', createdAt: 1652016910220 },
{ id: 'f', topic: 'sleep', content: 'i miss it', createdAt: 1652016960220 }
].map(post => new Post(post))
const comments = [
{ id: 'g', parent: 'd', content: 'sounds yummy', createdAt: 1652116910220 },
{ id: 'h', parent: 'e', content: 'pure life', createdAt: 1652016913220 },
{ id: 'i', parent: 'f', content: 'me too', createdAt: 1652016510220 }
].map(comment => new Comment(comment))
const recentActivities = users.concat(posts).concat(comments).sort((a,b) => b.createdAt - a.createdAt)
</script>
<ul>
{#each recentActivities as activity}
<li>
{new Date(activity.createdAt).toLocaleString()} -
{#if activity instanceof User}
User - {activity.name}
{:else if activity instanceof Post}
Post - {activity.topic}
{:else if activity instanceof Comment}
Comment - {activity.content}
{/if}
</li>
{/each}
</ul>

How to display only 4 items per page from an array react and express

I need quick help with React and Express. I have an array of data at my express server and fetched with react hooks useEffect. Currently, the response shows all the data on the same page, I want to display only 4 items per page? My React component has useState, useEffect and the JSX as normal. See my code below from Express. Any kind of help will be nice, thanks.
An array of data on Express server:
app.get('/api/surveyoptions', (req, res) => {
const surveyOptions = [
// { id: 5, title: "How often do you eat meat and dairy?" },
{ id: 1, name: "diet", label: "daily1", value: "daily1"},
{ id: 2, name: "diet", label: "daily2", value: "daily2" },
{ id: 3, name: "diet", label: "daily3", value: "daily3" },
{ id: 4, name: "diet", label: "daily4", value: "daily4" },
{ id: 5, name: "diet", label: "daily5", value: "daily5"},
{ id: 6, name: "diet", label: "daily6", value: "daily6" },
{ id: 7, name: "diet", label: "daily7", value: "daily7" },
{ id: 8, name: "diet", label: "daily8", value: "daily8" }
]
res.json(surveyOptions)
})
Not sure what your backend setup is and what database you're using, but in general you do something like this:
On frontend you make a request to the api, including the number of the page you need as a query parameter (e.g. you request '/api/surveyoptions?page=3').
*There are different styles of pagination: some prefer page number, some limit/offset. It really depends on the backend/database/etc. For short explanation of each style you can check this django-rest-framefork doc for example.
Server reads query params, finds pagination-related ones (e.g. we sent page=3, now server knows that we want the 3rd page of results), paginates results and send it back to the client. Server also should know how many items per page you want, or it could be a constant (e.g. always 10 items per page). Applying to your example, server would split surveyOptions array into chunks (10 items per chunk), and send you the 3rd one.
In react you store the page number in state, update it when user selects another page and sent along with the request as described in the first step.

Pushing an array of objects into Firebase Collection Angular 8

I am trying to add a document into an array studyList in my users collection.
So i have a collection users where i have name, etc.. and studyList.
When i click on a button buy into a DocumentItemComponent i want to add that document into this studyList array.
My code works partially because it adds the document into the array but when i click on another document it changes the first one, it doesn't add another document.
This is my code for the adding function:
addToStudyList(user) {
const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${user.id}`);
const data: UserInterface = {
studyList: [{
title: this.document.title,
language: this.document.language,
description: this.document.description,
cover: this.document.cover,
category: this.document.category,
field: this.document.field,
id: this.document.id,
author: this.document.userUid,
urlDocument: this.document.urlDocument
}]
}
return userRef.set(data, {merge: true});
}
Can you help me, please?
Thank you! Have a good day!
There is no direct way to update an array inside a document, but if you are using Firestore, it provides arrayUnion and arrayRemove functions which you can use for adding/removing unique items in the array.
From firestore documentation https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array :
Try this:
userRef.update({
studyList: firebase.firestore.FieldValue.arrayUnion(data)
});
This is because when you declare:
studyList: [{
title: this.document.title,
language: this.document.language,
description: this.document.description,
cover: this.document.cover,
category: this.document.category,
field: this.document.field,
id: this.document.id,
author: this.document.userUid,
urlDocument: this.document.urlDocument
}]
in this piece of code you are assigning just one object to the the studyList array which overwrites the existing array, instead you should utilize the existing user studyList array and push your new object into it, something like this:
addToStudyList(user) {
const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${user.id}`);
user.studyList.push({
title: this.document.title,
language: this.document.language,
description: this.document.description,
cover: this.document.cover,
category: this.document.category,
field: this.document.field,
id: this.document.id,
author: this.document.userUid,
urlDocument: this.document.urlDocument
});
const data: UserInterface = {
studyList: user.studyList
}
return userRef.update(data);
}

Kendo DataSource reading from Async/await method which uses Axios to fetch data

Using React with TypeScript
Please could somebody provide an example of how I might be able to use a Kendo DataSource to read from a method which internally uses Axios to prod an external API for JSON data..? I must have flown through 20 different versions of this code trying different approaches, nothing seems to fit...
All I'm trying to do currently is supply a Kendo ComboBox with an array of {id: number, name: string}
Very basic stuff at the moment, but I do have to use a similar approach to this later on with a Kendo Grid which handles server side sorting and pagination so I'd like to get this working now then that should be somewhat easier later on...
The reason I want to use Axios is because I've written an api.ts file that appends appropriate headers on the gets and posts etc and also handles the errors nicely (i.e. when the auth is declined etc...)
A basic example of what I'm trying, which isn't working is this: -
public dataSource: any;
constructor(props: {}) {
super(props);
this.dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read: function() {
return [{ id: 1, name: "Blah" }, { id: 2, name: "Thing" }];
}.bind(this)
},
schema: {
model: {
fields: {
id: { type: "number" },
name: { type: "string" }
}
}
}
});
}
<ComboBox
name="test"
dataSource={this.dataSource}
placeholder={this.placeholder}
dataValueField="id"
dataTextField="name"
/>
Anybody got any thoughts on this please? :)
Easy fix in the end...
this.dataSource = new kendo.data.DataSource({
transport: {
read: function(options: any) {
options.success([{ id: 1, name: "Blah" }, { id: 2, name: "Thing" }]);
}.bind(this)
},
schema: {
model: {
fields: {
id: { type: "number" },
name: { type: "string" }
}
}
}
});
2 things were wrong..
Removed the type: "odata",
and
Added the usage of options in
All working fine now with the async await function also, just passing the data into the options.success in the .then on the promise. Job done :-)

Proper redux store shape

What is the recommended way to handle this sort of store in redux? I can see a couple different options but am looking to evaluate the tradeoffs of each and see what is the more idiomatic approach.
These are the base models, think of a blog:
Post
Author
Comment
Let's say I have these pages:
View a paginated list of posts: /posts
View one post: /posts/:id
View author profile: /authors/:id
When I go to Page 1 I will fetch a list of posts from the server, ultimately, when I get this data from the server, and the reducers handle it, the store will look like this:
{
posts: {
byId: {
'post-id-1': {
title: 'Post 1', authorId: 'author-id-1'
},
'post-id-2': {
title: 'Post 2', authorId: 'author-id-2'
},
allIds: ['post-id-1', 'post-id-2']
},
authors: {
byId: {
'author-id-1': {
name: 'Author 1'
},
'author-id-2': {
name: 'Author 2'
},
allIds: ['author-id-1', 'author-id-2']
}
}
Now I can render the post title with the author's name. Now when a user clicks on a specific post they are going to go to Page 2 (/posts/:id). When they do this, how should this be handled:
Option 1 Reset the store posts when they leave the page, so the store "posts" and "authors" will be empty, and when Page 2 componentDidMount() fires then make another network request to download the post content + the author details for only that one post. If so, now the store will look like:
// when the user navigates away from `/posts`
{
posts: { byId: {}, allIds: []}
authors: { byId: {}, allIds: []}
}
// when the user loads `/posts/:id`
{
posts: {
byId: {
'post-id-1': {
title: 'Post 1', authorId: 'author-id-1'
},
allIds: ['post-id-1']
},
authors: {
byId: {
'author-id-1': {
name: 'Author 1'
}
allIds: ['author-id-1']
}
}
Option 2 Alternatively, should I keep the data in the store that I already have and do something like this:
// when componentDidLoad() for `/posts/1`
componentDidMount () {
// check the `store` to see if we already have this post loaded
// if we do, then do nothing
// if we do not, then fetch it from the server
if (this.props.getPostForId(1)) {
} else {
this.props.fetchPost({postId: 1})
}
}
Next, if the user clicks into an author's profile page /authors/1, and I want to fetch the posts that author has written. How does this make my store look? Following Option 1, I can clear out the posts store again before the user navigates, and then on the author's page I can fetch all the posts that author wrote so I can render them on the author's page. But if I do not clear out the full list of posts, and now I fetch the posts that only this author wrote (post 3, post 4, post 5, post 6, etc.), so I keep the posts I already have in the store and merge the results?
{
posts: {
'post-id-1': {
title: 'Post 1', authorId: 'author-id-1'
},
'post-id-2': {
title: 'Post 2', authorId: 'author-id-2'
},
'post-id-3': {
title: 'Post 3', authorId: 'author-id-1'
},
'post-id-4': {
title: 'Post 4', authorId: 'author-id-1'
},
},
authors: {
byId: {
'author-id-1': {
name: 'Author 1'
},
'author-id-2': {
name: 'Author 2'
},
allIds: ['author-id-1', 'author-id-2']
}
}
My main confusion is around how data in the store is shared between pages (components).
When loading one page, how do I know if I already have the data in the store that I need, or if I need to make a request to get it?
When do I clear out old data from the store? Do I ever do this? Should every component be responsible for both dispatching an action to load the data that it needs on mount? And also dispatching an action to remove that data on un-mount?

Resources