Bloodhound does not cache data from remote fetches in local storage - caching

I am trying to load autocompletion information of people's names for typeahead and then not have to query the server again if I already have a result.
For example if i search a person's name and the data for that person (among others) gets retrieved from a remote query, when I delete the name and search for the surname instead I want to have the previously cached names with that surname to show up. What actually happens is that the results are again retrieved from the server and the suggested.
Caching only works while typing a single word ("Mic" -> "Mich" -> "Micha" -> "Michael").
TL;DR: I want to cache results from bloodhound in Local Storage not only from prefetch (which cannot be applied to my situation) but from remote as well and use that before querying remote again.
What i currently have is
function dispkey(suggestion_object){
console.log(suggestion_object);
return suggestion_object["lastname"] + ", " + suggestion_object["firstname"];
}
var engine = new Bloodhound({
name: 'authors',
local: [],
remote: 'http://xxxxxx.xxx/xxxx/xxxxxxxxxx?query=%%QUERY',
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.val);
},
queryTokenizer: function (s){
return s.split(/[ ,]+/);
},
});
engine.initialize();
$('.typeahead').typeahead({
highlight: true,
hint: true,
minLength: 3,
},
{
displayKey: dispkey,
templates: {
suggestion: Handlebars.compile([
'<p id="author_autocomplete_email_field" >{{email}}</p>',
'<p id="author_autocomplete_name_field">{{lastname}} {{firstname}}</p>',
].join(''))},
source: engine.ttAdapter(),
});
I haven't found something similar and i am afraid there is no trivial solution to this.
P.S.: I also noticed that datumTokenizer never gets called
datumTokenizer: function(d) {
console.log("Lalalalala");
return Bloodhound.tokenizers.whitespace(d.val);
},
when i used this, "Lalalalala" was never outputted in the chrome debug console.

As jharding mentioned it's not possible to have remote suggestions pulled from localstorage at this point.
However, I recently worked on a small project where I needed to store previous form inputs for future use in typeahead.js. To do this I saved an array of form input values to localstorage.
var inputs = ['val1', 'val2', 'val3', 'val4'];
localStorage.setItem('values', JSON.stringify(inputs));
I then retrieved the array for use in the typeahead field.
var data = JSON.parse(localStorage.getItem('values'));
$('input').typeahead({
minLength: 3,
highlight: true,
},
{
name: 'data',
displayKey: 'value',
source: this.substringMatcher(data)
});
You can view my full source here.

Related

Updating Apollo Cache for external query after entity mutation

I'd like to display a list of users, based on a filtered Apollo query
// pseudo query
if (user.name === 'John) return true
User names can be edited. Unfortunately, if I change a user name to James, the user is still displayed in my list (the query is set to fetch from cache first)
I tried to update this by using cache.modify:
cache.modify({
id: cache.identify({
__typename: 'User',
id: userId,
}),
fields: {
name: () => {
return newName; //newName is the input new value
},
},
});
But I'm not quite sure this is the correct way to do so.
Of course, if I use refetchQueries: ['myUsers'], I get the correct result, but obviously, this is a bit overkill to refetch the whole list every time a name is updated.
Did I miss something?

GraphQL vs Normalized Data Structure Advantages

From Redux docs:
This [normalized] state structure is much flatter overall. Compared to
the original nested format, this is an improvement in several ways...
From https://github.com/paularmstrong/normalizr
:
Many APIs, public or not, return JSON data that has deeply nested objects. Using data in this kind of structure is often very difficult for JavaScript applications, especially those using Flux or Redux.
Seems like normalized database-ish data structures are better to work with on front end. Then why GraphQL is so popular if it's whole language style is revolved around quickly getting any nested data? Why do people use it then?
This kind of discussion is off-topic on SO ...
it's not only about [normalized] structures ...
graphql client (like apollo) takes care of all data fetching related nuances (error handling, cache, refetching, data conversion, and many more) also but hardly doable with redux.
Different use cases, you can use both:
keep (complex) app state in redux,
handle data fetching in apollo (you can use it for local state, too).
Let's look at why we want to normalize the cache and what kind of work we have to do to get a normalized cache.
For the main page we fetch a list of TODOs and a list of high priority TODOS. Our two endpoints return the following data:
{
all: [{ id: 1, title: "TODO 1" }, { id: 2, title: "TODO 2" }, { id: 2, title: "TODO 2"}],
highPrio: [{ id: 1, title: "TODO 1" }]
}
If we would store the data like this into our cache, we have a difficult time updating a single todo, because we have to update the todo in every array we have in our store or might have in our store in the future.
We can normalize the data and only store references in the array. This way we can easily update a single todo in a single place:
{
queries: {
all: [{ ref: "Todo:1" }, { ref: "Todo:2" }, { ref: "Todo:2" }],
highPrio: [{ ref: "Todo:1" }}]
},
refs: {
"Todo:1": { id: 1, title: "TODO 1" },
"Todo:2": { id: 2, title: "TODO 2" },
"Todo:3": { id: 3, title: "TODO 3" }
}
}
The downside is, that this shape of data is now much harder to use in our list component. We will have to transform the cache a lot, roughtly like so:
function denormalise(cache) {
return {
all: cache.queries.all.map(({ ref }) => cache.ref[ref]),
highPrio: cache.queries.highPrio.map(({ ref }) => cache.ref[ref]),
};
}
Notice how now updating Todo:1 inside of the cache will update all queries that reference the todo automatically, if we run this function inside of the React component (this is often called a selector in Redux).
The magical thing about GraphQL is that it is a strict specification with a type system. This allows GraphQL clients like Apollo to globally identify objects and normalise that cache. At the same time it can also automatically denormalise the cache for you and update objects in the cache automatically after a mutation. This means that most of the time you have to write no caching logic at all. And this should explain why it is so popular: The best code is no code!
const { data, loading, error } = useQuery(gql`
{ all { id title } highPrio { id title }
`);
This code automatically fetches the query on load, normalizes the response and writes it into the cache. Then denormalizes the cache back into the shape of the query using the cache data. Updates to elements in the cache automatically update all subscribed components.

Firestore - Using cache until online content updates

I am starting with Firestore. I've read docs and tutorials about the offline data persistence but I have not really clear if Firestore downloads data again even if the content hasn't been modified.
For example, if I have a query where the results will be updated once a week and I don't need that the app download the content again until the changes were made, what is the best way in terms of efficiency to write the code?
Thanks!
You want to use the "snapshot listener" API to listen to your query:
https://firebase.google.com/docs/firestore/query-data/listen#listen_to_multiple_documents_in_a_collection
Here's some JavaScript as an example:
db.collection("cities").where("state", "==", "CA")
.onSnapshot(function(querySnapshot) {
var cities = [];
querySnapshot.forEach(function(doc) {
cities.push(doc.data().name);
});
console.log("Current cities in CA: ", cities.join(", "));
});
The first time you attach this listener Firestore will access the network to download all of the results to your query and provide you with a query snapshot, as you'd expect.
If you attach the same listener a second time and you're using offline persistence, the listener will be fired immediately with the results from the cache. Here's how you can detect if your result is from cache or local:
db.collection("cities").where("state", "==", "CA")
.onSnapshot({ includeQueryMetadataChanges: true }, function(snapshot) {
snapshot.docChanges.forEach(function(change) {
if (change.type === "added") {
console.log("New city: ", change.doc.data());
}
var source = snapshot.metadata.fromCache ? "local cache" : "server";
console.log("Data came from " + source);
});
});
After you get the cached result, Firestore will check with the server to see if there are any changes to your query result. If yes you will get another snapshot with the changes.
If you want to be notified of changes that only involve metadata (for example if no documents change but snapshot.metadata.fromCache changes) you can use QueryListenOptions when issuing your query:
https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/QueryListenOptions

bootstrap-typeahead's displayfield using multiple values

I'm sorry if this is a duplicate question but I do not understand the answers of other people. I'm using Twitter Bootstrap Ajax Typeahead Plugin (https://github.com/biggora/bootstrap-ajax-typeahead/) to search emails from data that comes from an SQL query. This is the code I use with a php file, where I use people's emails as valueField and people's names as displayField and it works well.
inputSearch.typeahead({
ajax: {
url: urlAjax + '?requete=rechercheannuaire',
displayField: "description",
valueField: "id",
triggerLength: 2,
method: "get",
loadingClass: "loading-circle",
preProcess: function(data){
if(data.type === "error")
{
return false;
}
return data.datas;
}
},
onSelect: function(data){
//alert("assez tot");
data.text = data.value;
//console.log(data);
$("#chercherinvite").val(data.text);
return data;
}
});
The problem is that I have to be able to search "Dujardin" as well as "Du Jardin" and I cannot find a way to assign multiple values to displayField. If someone could explain how typeahead works, I'd be thankfull, I don't understand the documentation.
According to the plugin documentation, you cannot assign multiple values to the displayField option. However, it is possible for you to re-write events.
After a quick lookup into the source code of bootstrap-ajax-typeahead, we can figure out that the "matcher" event is used as the filter for displaying - or not - values to the user.
To allow to match both "Du jardin" and "Dujardin", we have to manipulate strings. Here, I suggest you to :
Remove any diacritic character
Remove any non-word character (all except [A-Za-z0-9_])
Remove any underscore
Set the string to lowercase
To do #1, I suggest you to use this fantastic script by rdllopes.
I wrote a POC. Here is the JSON source (called "source.json"):
[
{ "id": 1, "name": "jdupont#example.com - Jean Du Pont"},
{ "id": 2, "name": "jdupont2#example.com - Jean Dupont"},
{ "id": 3, "name": "jdupont3#example.com - Jéan Dupônt"},
{ "id": 4, "name": "mbridge#example.com - Michel Bridge"}
]
And here is the script that I used for matching elements :
$('#search').typeahead({
// Our source is a simple JSON file
ajax: 'source.json',
// Display field is a list of names
displayField: 'name',
// And value field a list of IDs
valueField: 'id',
matcher: function(item)
{
// For both needle and haystack, we :
// 1. Remove any diacritic character
// 2. Remove any non-word character (all except [A-Za-z0-9_])
// 3. Remove any underscore
// 4. Set the string to lowercase
var needle = removeDiacritics(this.query).replace(/[^\w]/gi, '').replace('_', '').toLowerCase();
var haystack = removeDiacritics(item).replace(/[^\w]/gi, '').replace('_', '').toLowerCase();
// Does the needle exists in haystack?
return ~haystack.indexOf(needle);
}
});

Twitter typeahead.js remote and search on client

As of my understanding typeahead.js got three ways of fetching data.
Local: hardcoded data
Prefetch: Load a local json file, or by URL
Remote: Send a query to the backend which responds with matching results
I want to fetch all data from the backend and then
process it on the client.
The data my server responds with got the following structure:
[{id:2, courseCode:IDA530, courseName:Software Testing, university:Lund University},
{id:1, courseCode:IDA321, courseName:Computer Security, university:Uppsala University}, ...]
I want it to search on all fields in each entry. (id, courseCode, courseName, university)
I wanna do more on the client and still fetching one time for each user (instead of every time a user are typing), I probably misunderstood something here but please correct me.
You should re-read the docs. Basically there are two things you need:
Use the prefetch: object to bring all the data from the backend to the client only once (that's what you are looking for, if I understand correctly.)
Use a filter function to transform those results into datums. The returned datums can have a tokens field, which will be what typeahead searched by, and can be built from all your data.
Something along the lines of:
$('input.twitter-search').typeahead([{
name: 'courses',
prefetch: {
url: '/url-path-to-server-ajax-that-returns-data',
filter: function(data) {
retval = [];
for (var i = 0; i < data.length; i++) {
retval.push({
value: data[i].courseCode,
tokens: [data[i].courseCode, data[i].courseName, data[i].university],
courseCode: data[i].courseCode,
courseName: data[i].courseName,
template: '<p>{{courseCode}} - {{courseName}}</p>',
});
}
return retval;
}
}
}]);

Resources