How can Aurelia custom elements interact? - javascript

In Aurelia, I have a parent component that is composed of several other components. To keep this example simple, say that one component is a State dropdown, and another component is a City dropdown. There will be other components that depend on the selected city, but those two are enough to illustrate the issue. The parent view looks like this:
<template>
<compose view-model="StatePicker"></compose>
<compose view-model="CityPicker"></compose>
</template>
The idea is that you would pick a state from the StatePicker, which would then populate the CityPicker, and you would then pick a city which would populate other components. The routes would look like /:state/:city, with both being optional. If :state is omitted, then the url will automatically redirect to the first available state.
I'm using the EventAggregator to send messages between the components (the parent sends a message to the CityPicker when a state is selected). This is working, except for the initial load of the application. The parent is sending the message to the CityPicker, but the CityPicker component hasn't been activated yet, and it never receives the message.
Here's a Plunker that shows the problem. You can see that the city dropdown is initally empty, but it starts working once you change the state dropdown. Watch the console for logging messages.
So the question is: Is there a way to know that all the components are loaded before I start sending messages around? Is there a better technique that I should be using? Right now, the StatePicker sends a message to the parent that the state has changed, and then the parent sends a message to the CityPicker that the state has changed. That seems a little roundabout, but it's possible that the user could enter an invalid state in the url, and I liked the idea of being able to validate the state in one place (the parent) before all the various other components try to load data based on it.

The view/viewModel Pattern
You would want your custom elements to drive data in your viewModel (or in Angular / MVC language, controller). The viewModel captures information about the current state of the page. So for example, you could have a addressViewModel route that has state and city properties. Then, you could hook up your custom elements to drive data into those variables. Likewise, they could listen to information on those variables.
Here's an example of something you might write:
address.html
<state-picker stateList.one-way="stateList" value.bind="state" change.delegate="updateCities()"></state-picker>
<city-picker cityList.one-way="cityList" value.bind="city"></city-picker>
address.js
class AddressViewModel {
state = null;
city = null;
stateList = ['Alabama', 'Alaska', 'Some others', 'Wyoming'];
cityList = [];
updateCities() {
let state = this.state;
http.get(`API/cities?state=${state}`) // get http module through dependency injection
.then((response) => {
var cities = response.content;
this.cities.length = 0; // remove current entries
Array.prototype.push.apply(this.cities, cities);
});
}
}
If you wanted to get a little more advanced and isolate all of your state and city logic into their respective custom elements, you might try following this design pattern:
address.html
<state-picker value.bind="state" country="US"></state-picker>
<city-picker value.bind="city" state.bind="state"></city-picker>
address.js
class cityPickerViewModel {
#bindable
state = null;
cities = [];
constructor() {
// set up subscription that listens for state changes and calls the update
// cities function, see the aurelia documentation on the BindingEngine or this
// StackOverflow question:
// http://stackoverflow.com/questions/28419242/property-change-subscription-with-aurelia
}
updateCities() {
/// same as before
}
}
The EventAggregator Pattern
In this case, you would not want to use the EventAggregator. The EventAggregator is best used for collecting various messages from disparate places in one central location. For example, if you had a module that collected app notifications in one notification panel. In this case, the notification panel has no idea who might be sending messages to it, so it would just collect all messages of a particular type; likewise, any component could send messages whether or not there is a notification panel enabled.

Related

React JS how to remember the clicked item in a list when I pass to next page

I listed some countries in my Country.js page using ListItem. I created a CodeSandbox and this is the link for it:
My Code
I want the program to remember the clicked list item in the list in Country.js file and when I go to next page (press the next button) and back again (press the back button), the selected item before going to next page should also be selected. Sorry for the bad stylings, I did not add them.
Note: I implemented this feature in forms (radio group) but, I could not do this in a list.
The problem is that your state is currently at the level of your CountryList component so every time this component is unmounted, its state disappear.
What you need to do is lift up the state of the component in your UserForm component so it won't disappear when you change page
there is a lot solution for this case (holds data in):
a) context,
b) redux - redux persist - holds in persistence
c) sessionStorage
Of course, the state can be transferred between the components (but only one way - there is no two way data binding as in angular)
As context and redux are more global concepts - I suggest you save the data at each step to sessionStorage and finally collect that data and send it to API or whatever.
// Save data to sessionStorage
sessionStorage.setItem('Country', countryName);
sessionStorage.setItem('FilmSurvey', surveyName);
// Get saved data from sessionStorage
var dataCountry = sessionStorage.getItem('Country');
var dataSurvey = sessionStorage.getItem('FilmSurvey');
Then, when it works,that you refactor it somehow.
And general advice - I recommend using Hooks - they are more consistent and newer

how not to lose state from parent component to child component

I have a view which shows all posts. There's a filter above those posts and user can filter posts depending on what options he chooses. After he chooses the options, filtered posts get returned.
Let's say user filtered posts and after that clicked on one of the posts, it means that parent component which was showing posts will be destroyed. If now, user(who is on the specific post page) clicks back button, it will take him to all posts page, but filters won't be persisted since parent component got destroyed and then created.
One solution to persist filters and filtered posts after clicking back button from specific page is to use vuex. when user chooses filters, we store the object in vuex. when user clicks back button, store would already have the filters. The problem is following this way causes some problems for me and takes much more time.
Any other way you can think of ? I can't use keep-alive since it seems i can only use it for dynamic components and not any other way.
I see 2 options here:
Vuex - it's used for state management, best to use when you need to communicate between 2+ components. You can will need a set of methods that will update the filter values in your store, e.g.:
const store = {
category: null,
tag: null,
date: null
}
const actions = {
updateFilter({ commit }, payload) {
commit('updateFilter', payload); // example payload structure: { filterName: 'category', filterValue: 'reviews' }
}
}
const mutations = {
updateFilter(state, payload) {
state[payload.filterName] = payload.filterValue;
}
}
export default {
namespaced: true,
store,
actions,
mutations
}
And you need to bind these actions to via #click events on your website. Then you need to bind the values from the store with your filters method (probably also you'll want to execute filtering method when your posts list changes, so you can use watcher for example)
If you're using Vue router and history mode, you can store your filters via query params:
router.push({ path: 'blog', query: { category: 'reviews' }})
So your url will become blog?category=reviews - and when you change your url to clicked article and then click back, you'll go first to the url with latest query params set you had (but of course you need to create a method that will filter out on component create the post list based on provided filters)
The additional win for the 2nd option is that you'll be able to share the link with other people (so they will gonna see the filtered posts in the same way as you do).

How much of this business logic belongs in Vuex?

I have a simple app which pulls products from an API and displays them on-page, like this:
I've added Vuex to the app so that the search results as well as the product search array doesn't disappear when the router moves the user to a specific product page.
The search itself consists of the following steps:
show loading spinner (update the store object)
dispatch an action to access the API
update the store object with products, spinner
decide if the product list is exhausted
hide loading spinner
You get the idea.
With all of the variables stored in Vuex, it stands to reason all of the business logic should belong there as well, but should it really?
I'm talking specifically about accessing store params such as productsExhausted (when there are no more products to display) or productPage (which increments every time the infinite scroller module is triggered) etc.
How much logic - and what kind - belongs in Vuex? How much does not?
I was under the impression that Vuex is used for storage only but since all of the data is located there, fetching it all back to the Vue app only to send it all back seems like an overly verbose way to address the problem.
Vuex allows you to share data !
For everything that concerns the state of the app its pretty straightforward.
All the data that can be used by multiple components should be added
to the store.
Now concerning the business logic, even though I find its not really clear in the official documentation, it should follow the same principle.
What I mean is that logic that can be used by multiple components should be stored in actions.
Moreover actions allows you to deal with async operations. Knowing this, your code that pulls the data should definitely be stored in vuex's actions.
What I think you should do is to put the request inside an action, then mutate the state of your variables and automatically your UI will reflect the changes.
Moreover, a good pattern to apply is to convert most of the logic to a state logic. For instance consider this demo of a jumping snowman. In here the click action results on updating a value from the store. Although the interesting part is that one component uses the watch functionnality to be notified when the store changes. This way we keep the logic inside the component but use the store as an event emitter.
var store = new Vuex.Store({
state: {
isJumping: 0
},
mutations: {
jump: function(state){
state.isJumping++;
}
}
})
Vue.component('snowman', {
template: '<div id="snowman" :class="color">⛄</div>',
computed: {
isJumping: function(){
return this.$store.state.isJumping;
}
},
watch: {
isJumping: function(){
var tl = new TimelineMax();
tl.set(this.$el,{'top':'100px'})
tl.to(this.$el, 0.2, {'top':'50px'});
tl.to(this.$el, 0.5, {'top':'100px', ease: Bounce.easeOut});
}
}
})

Dom node insertion/removal without mutation observers

I'm building a Tour component in React whose purpose is to introduce the user to the web app's interface. Parts of the "Tour" involve validating the user's actions, (e.g. if the current step involves opening a modal, once the user does so, the "Tour" should progress otherwise it should show an error if the user tries to progress by clicking 'Next').
For this I need to detect changes in the DOM, (e.g. a modal being opened or a div with a specific class appearing). I've had some ideas about wiring up an 'onNext' function that progresses the tutorial once the user interacts with certain target elements (e.g. 'Open Modal' button), but this seems like a hack, I want to govern the progression of the tour only by the actual elements present in the DOM not by listening for clicks that will result in the necessary elements showing up eventually.
One of the big constraints is avoiding MutationObservers in addition to usage of jQuery. With that said, I'm interested in hunches about how to validate the dom, how would one use pure javascript and the dom to determine the addition and removal of elements?
I think you're best served by implementing a Flux architecture to handle this. Redux is a good fit.
Create a Redux Reducer for your tour progression. The state of this reducer should be a key that corresponds to the current step of the tour that the user is within.
All components used in the tour should have access to this tour state as a prop. Use this prop to determine functionality. I.e. for your example of a dialog that must be opened, the code might look like this, within a relevant component;
openModal(){
if(this.props.tourStep == 'prompt_modal_open'){
ActionCreator.progressTourStep();
}
// code for actually opening the modal goes here
},
someOtherAction(){
if(this.props.tourStep == 'prompt_modal_open'){
//Display error message here
} else {
//normal action result here
}
}
When the user is not taking the tour, simply set tourStep in the reducer to undefined, and any tour related functionality will be turned off.
Alternately, if you want to keep your components clean and "dumb", you can put this logic directly into the action creator with the help of Redux-Thunk;
ActionCreator.openModal = function(){
return function(dispatch, getState){
var state = getState();
if(state.tourStep == 'prompt_modal_open'){
dispatch({type: 'progress_tour_step'});
}
dispatch({type: 'open_modal'});
}
}
ActionCreator.someOtherAction = function(){
return function(dispatch, getState){
var state = getState();
if(state.tourStep != undefined){
dispatch({type: 'show_error'});
} else {
dispatch({type: 'some_other_action_type'});
}
}
}

Passing Huge data between Vue js routes

I have an application which searches for flights using Vue.js and Vue Router.
I have two components, first one is search, which is on the base route '/'. When user clicks on search, it will send a request to server and gets a huge list of flights.
Then I need to call the result component on '/results' route and show the results using v-for.
I have two questions, first, how can I manually redirect to '/results' after I get the results.
Second and more important, what is the proper way of passing the results data to results component to use?
Inside your results components, you can put transition hooks in the route object. Read here: http://vuejs.github.io/vue-router/en/pipeline/hooks.html
The activate hook runs when a component is activated, and the component wont appear until the activate hook has run. Here's the example on that page, which would be similar to yours:
route:{
activate: function (transition) {
return messageService
.fetch(transition.to.params.messageId)
.then((message) => {
// set the data once it arrives.
// the component will not display until this
// is done.
this.message = message
})
}
}
So basically when they click search you send them to /results and this hook will handle loading the data in between.
Here's an advanced example of a mail app using vue-router that shows off a lot of the transition hooks in action: https://github.com/vuejs/vue-router/tree/dev/example/advanced

Categories