Utilizing browser back/forward buttons in a vue.js app - javascript

I have an app that has a bunch of dropdown components that alter state. . The state of the dropdowns control a main datatable that has info based around the params. Cool.
I want to also alter the URL so users can share urls with their coworkers and simply have the datatable show up without choosing dropdown parameters.
Example: http://localhost:8080/?region=372&pc=341&pc=375 or http://localhost:8080/?region=372&pc=341&vendor=123456
Right now I've got this working by manually watching the dropdown parameters selected (via watch) and building a url and using this.$router.push to update browser history when things change. I have a feeling this isn't totally correct, but it's the only reliable way I've been able to make the url update consistently.
The main issue is that when I hit the browser's back/forward buttons, the components do not update their state based on the url. The application remains in it's current state if I hit back/forward. How do I properly go about this?

It looks like you're effectively trying to bind the state of your dropdown to your URL query. VueRouter, unfortunately, doesn't have a baked-in solution for this.
I'm not sure how your data is structured, so let's assume you have an object dropdownParams containing the relevant parameters:
dropdownParams: {
region: 372,
pc: 341,
vendor: 123456,
}
You need to set a watcher on dropdownParams to update the query in the $route when those are updated. Use $router.replace instead of $router.push so that the history doesn't change:
watch: {
dropdownParams(params) {
// format the data however you want this is an example with lodash
let query = _.pickBy(params, p => !_.isEmpty(p));
this.$router.replace({ query: query });
}
}
Then, in the mounted hook, you can set the dropdownParams object based on any relevant $route.query parameters, if they already exist:
mounted() {
let keys = _.keys(this.dropdownParams);
_.assign(this.dropdownParams, _.pick(this.$route.query, keys));
}
Now, when the user changes the dropdown parameters, those changes will be reflected in the URL. And, if the user loads the component with URL query parameters already present, the component will set those values to the dropdown.

Related

Is it possible to reset specific item in Apollo Client Cache?

I have a query which uses fetchMore and the relayPagination which works fine for lazy loading when using the variables page and perPage, the issue now is I'm trying to refresh the query whenever i update the other variables for filtering like date and type which values can be either debited or credited. It fetches the data, but then appends the incoming data to apollo cache instead of replacing the old data with the new one.
for example in this sample typePolicy
PaginatedBooks: {
fields: {
allBooks: {
merge: relayPagination()
}
}
}
AllProducts: {
// Singleton types that have no identifying field can use an empty
// array for their keyFields.
keyFields: [],
},
I only want to reset PaginatedBooks
I've tried use fetchPolicy of no-cache but this stops pagination and fetchMore from working as i can't merging existing and incoming data. I opted to use client.resetStore(): https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.resetStore
but this also refetches other active queries and causes the ui to flicker. So far looking through the documentation and github repo I can't seem to find anything or anyone who has tried to do something similar, so I'm hoping I can get some insight and probably be offered a better solution. Thanks in advance

Meteor react design - nested component: function call

I'm currently facing a problem with Meteor and React, where i know some partly solutions but they don't work and imo none of them is pointing in the true direction.
The situation:
All is about an fitness app: I have a structure that represents exercises for customers, while each exercise can have a defined number of sets (a set is how often a exercise should be done). Each set has some properties (all the user can manipulate within the font-end).
Now i have the following component structure with some map-functions (state properties are in {}):
Training {customers,exercises,datetime,otherinfos}
- Overview {customers,exercises}
exercises.map():
- Exercise {exercise,customers}
customers.map():
- Customer {exercise,customer}
exercise.sets.map()
Set {exercise, customer, set, valuesofset}
From a UI-perspective (react) this all works without problems.
Now the idea is to have a button "Save" within the Training component. When the button is pressed, I want to save the state of all Set-Components in a "sets" collection (if it has other values than the default placeholder ones) and at the same time save the Training-Component in a "trainings" collection. But the training should also include information about what Sets are integrated (so at least the Set._id should be in the Training-Component state at time of Saving.
Here now my ideas so far:
Create refs from Training all the way down to all Sets and then, when pressing "Save" iterate over all refs and call a "Mongo.insert" from all Sets. Here i have the problem that i cannot return the inserted _id. Of course i could call a different function in each Component from Set all the way back to Training, but imo this is an overflow.
Try to manage the state of all sets within the Training state by calling a nested function. As i have onChangeHandler on the Inputs, this would always call a method in Training and check which one of the Sets was changed and then changes it. I have tried it this way, but it led to a very bad performance.
Create a temp-ID for Training, forward it to to the Sets (using the componentWillReceiveProps method) and when in Set, insert the Set in the database with the temp-ID. Then receive all Sets with temp-ID and use it to add the Training in the database. --> imo very complicated and I don't really want to do a database call if it is not necessary.
So currently i don't know how to solve this problem. The reason i try to separate "sets" and "trainings" is given through the fact, that later on i would like to give information about the last Set right next to the new empty Set whenever one is on the database. Any tips are welcome!
EDIT:
As suggested, there is also the possibility to solve the problem with Session. So therefor i have added the following code to Set:
componentDidMount() {
Tracker.autorun(() => {
Session.set(`set_${this.state.id}`, {
...this.state
});
});
}
My idea was then to iterate over all Session-Keys from Training which start with "set_" - unfortunately there is no function to that holds all Keys.
Second idea was to to use an array as value for a Session-pair. However, it's quite a procedure to handle the update of the reactive Set component (copy array from session, check whether an element is available or not, create a new one or update the existing one).
EDIT2:
I think i got a solution with Session:
Object.getOwnPropertyNames(Session.keys)
did the trick to get all SessionKeys! Thank you for your help!
If you do not want to use Redux or pass parent bound callbacks in the child component, you can try Session to store data at app level which can be accessed(set/get) in any component
https://docs.meteor.com/api/session.html
In your case, you may set values of "Set" in Session and access it in Training. You may also need https://guide.meteor.com/react.html#using-withTracker. Using withTracker will help in doing reactive update of the database on change of any Session variable.

How to pass params to child window through ui-router

I have a requirement where I have a parent window with a button. Clicking on the button performs some logic, computes some values and opens a child window with those params. I'm trying to find a good way to do this. My current implementation is as follows.
Inside my ng-click method I have
let location = this.$state.href('moveout', { entityUuid: this.entityUuid, count: count, params: params, options: options);
let unDockedWindow = this.$window.open(location, '', 'scrollbars=no,fullscreen=yes,toolbar=no');
This successfully opens a child window with url that looks like the below
#/moveout?entityUuid=8eb6c75e-5a06-333c-a4cf-a97d90995722&count=something&params=something&options=something
I'm able to access the data in the destination like so
$state.params.entityUuid
My problem however is that, some of these attributes can be complex, like params and options. And I don't necessary want to have a url that has so many params and looks really ugly.
I'm trying to see if there is a way to open a child window with a number of params with ui-router without passing them as query string params.
I don't think I want to use ui-sref since some of the values that need to be passed need to be computed based on some logic. Hence doing it inside ng-click.
Any help, greatly appreciated.

Cyclic Reactivity Pattern in Meteor

The use case (tl;dr):
On a search page, the user picks 0 or more 'markets' from a dropdown. The search results then filter to only show results which contain at least one of those 'markets'. Alternatively, the user can visit a URL with '?markets=A,B,C' query parameters set, to see results filtered to those markets. Of course, when the user picks options from the dropdown, the URL must be altered accordingly, so that they can bookmark/share that URL to share that view with someone. Herein lies the issue - we want the URL to update when the dropdown does, and the dropdown to update when the URL does, creating a cyclic dependency. How can we implement this such as to not set off an infinite loop when one is changed, as I am currently experiencing?
Technical implementation:
There are two templates: search_page and search_filters. The latter is a component included in the former.
In search_page, there is a helper searchFilterArgs(), which reactively gets the value of 'markets' from the query params (using universe:reactive-queries), and sets the data context for search_filters accordingly. It also passes a setFilters callback to the component. When the component calls the callback, search_page sets the workflow parameter in the URL to the passed value.
Template.search_page.helpers({
searchFilterArgs(){
const instance = Template.instance();
const splitOrEmptyArray = function(filter_key) {
if (UniUtils.url.getQuery(filter_key)){
return UniUtils.url.getQuery(filter_key, false).split(',');
} else {
return [];
}
};
return {
setFilters: instance.setFilters,
//get each query parameter as a non-reactive source, '' if not set, then convert to array
selectedMarkets: splitOrEmptyArray('markets'),
}
},
...
});
Template.search_page.onCreated(function() {
this.setFilters = (key, value) => {
if (UniUtils.url.getQuery(key, true) !== value){
UniUtils.url.setQuery(key, value);
}
};
});
In the search_filters component, there is an autorun on Template.currentData() that sets the dropdown to the 'markets' value in the data context. There is also an 'onChange' handler for the dropdown that calls the 'setFilter' callback with the value when the dropdown is changed.
Observed behaviour:
When the page is loaded, workflows is initialized to be empty. Then, I choose a market ("Sales") from the dropdown. The page then enters an infinite loop of changing the URL and changing the dropdown. Upon setting breakpoints, it was revealed that after I changed the dropdown value, the callback fired with the correct value, and the URL query parameters were set correctly. Then the component's data context is changed, which somehow causes the dropdown onChange handler to fire, but with the value "", rather than "Sales".
Workaround:
For now, I've changed the searchFilterArgs helper to retrieve the URL query parameters in a non-reactive way. Hence, when the page is first visited with a certain URL, the market parameter is passed in to the search_filters component. After this, any time the dropdown in the component is changed, the callback is fired, and the URL changed. Since the URL is retrieved non-reactively, this doesn't trigger a consequent change in the dropdown. It works fine for our previous use case, where the URL would only be set when opening the page, and not changed by the user after that. However, we now have code that will change the URL parameters on behalf of the user, and the filters should update accordingly.
Do you have experience with circular dependencies like this in Meteor? Is there a pattern to tackle this problem?
(I'm using Meteor 1.3.2 and Semantic-UI for the dropdown).

Getting state from URL string with Angular UI Router

I'm using state-based routing (Angular UI Router v0.2.7) in a project and looking for a way to get the current state (name) from a given URL string.
Something like:
$state.get([urlString]) returns stateName:String or state:Object
I need this method to check if a state exists to a given URL because not all URLs are mapped to a state in my project. Using Play Framework as backend, some URLs (e.g., login form) are not mapped to a state because they using different templates then the Angular (main) part of my application.
For those "none-Angular" pages (i.e., not covered by a state) I would do a reload. To identify URLs not covered by a state I need the method mentioned above. Planned to do it like this:
$rootScope.$watch(function() { return $location.path(); }, function(newUrl, oldUrl) {
if(newUrl !== oldUrl) {
if (!$state.get(newUrl)) {
$window.location.assign(newValue);
}
}
}
Already checked the docu but there is no such method.
Any ideas?
It's all or nothing. If you plan to use ui-router make sure all your URLs resolve to a state. If the state doesn't exist it will go to the otherwise state. It's possible to have optional parameters.
An alternative is to use .htaccess redirects to catch the URL and redirect you before it hits the ui-router.
Provide more details and we can see what the best option is.
Try using this will get the current sate name.
var stateName = $state.current.name;

Categories