How to query firebase keys with a substring - javascript

I want to query Firebase that contains an input. for example in the database structure below, I would like to query all nodes that contains "4140"
so I use this code
var catref = Cataloguedatabase.ref("/Listing Search/");
return catref.once('value').then(function(snapshot){
snapshot.forEach(childSnapshot => {
let snapdb = childSnapshot.val();
let key = childSnapshot.key;
//use ES6 includes function
if(key.includes(schname)){
console.log(childSnapshot.val());
var searchresults = childSnapshot.val();
var container = document.getElementById('listing_gallery');
// container.innerHTML = '';
var productCard = `
<div class="col-sm-3">
<div class="card" onclick="gotoproduct(this);" id="${key}">
`
container.innerHTML += productCard;
}
})
})
This works but the problem is that it first query all the child and sort through the array.This is ok for node with few children but when it gets to thousands of children, it becomes impractical. is there a away i can only query key the contains the value in firebase and if not how else can i implement this?

As Doug says, there is no way to query for values that contain a certain string. But you can query for keys that start with a certain substring:
return catref.orderByKey().startAt("4140-").endAt("4140-\uf8ff").once('value').then(function(snapshot){
This will just match the child nodes whose key starts with 4140-.

What you're asking is not possible with Firebase Realtime Database. There are no substring queries.
If you're only interested in the first four characters for your query, what you can do is store that string as a child inside that node, then orderByChild() on that child to find only those items that begin with a certain four character sequence.
You can read more about ordering in the documentation.

Related

Cypress get content of several DIVs, and compose one long string

I have some divs that render this way:
<div class="customer-data-column">
<h3>Title:</h3>
<div>Name Lastname</div>
<div>123 Address xxx yyy</div>
<div>Chicago XY 33056</div>
<div>Country name</div>
</div>
This content is generated by:
{customerData.replaceAll("/r", "").split("\n").map(item => <div key={item}>{item}</div>)}
This data is coming from redux.
In console log (from redux data) the address appears this way:
Name Lastname\n123 Address xxx yyy\nChicago XY 33056\nCountry name
I want to check in Cypress if this address is correct, the same that is in redux.
I need some way that merges the content of the divs into one string, and adds the \n between each div.
I thought I could start this way:
cy.get('.customer-data-column').should($title => {
const store = Cypress.store.getState();
const reduxPath = store.customerData;
expect("not sure what to put here... how to merge").to.equals(reduxPath)
Can anyone please help?
===
EDIT
I made it almost work this way:
I added a class to the inner divs, so they render this way:
<div class="customer-data-column">
<h3>Title:</h3>
<div class="address-row">Name Lastname</div>
<div class="address-row">123 Address xxx yyy</div>
<div class="address-row">Chicago XY 33056</div>
<div class="address-row">Country name</div>
</div>
And the test:
cy.get('.address-row').then($divList => {
const textArray = Cypress.$.makeArray($divList).map(el => el.innerText)
const actual = textArray.join(textArray, '\n') // use join to array of strings into single string
expect(actual).to.eql(billToAddress)
})
However it still fails with such message:
assert expected Name LastnameName Lastname,23 Address xxx yyy,Chicago
XY 33056,Country name23 Address xxx yyyName Lastname,23 Address xxx
yyy,Chicago XY 33056,Country name7th FloorName Lastname,23 Address xxx
yyy,Chicago XY 33056,Country nameBrooklyn NY 11210Name Lastname,23
Address xxx yyy,Chicago XY 33056,Country nameCountry name to deeply
equal Name Lastname\n23 Address xxx yyy\n7th Floor\nBrooklyn NY
11210\nCountry name
Edit 2:
The solution that I found and works is this one:
.then(users => {
const billToAddress = users.response.body.filter(el => el.orderNumber === '3-331877')[0]
.billTo
cy.get('.address-row').each((item, index) => {
cy.log(cy.wrap(item).should('contain.text', billToAddress.split('\n')[index]))
})
})
Of course if somebody has a better way for achieving this test, I am open to learn more and code better.
If you make an array of the store data, an .each() loop can compare them.
const store = Cypress.store.getState()
const reduxData = store.customerData // expect 'Name Lastname\n123 Address xxx yyy\nChicago XY 33056\nCountry name'
const reduxDataArray = reduxData.split('\n')
cy.contains('.customer-data-column', 'Title:')
.find('div') // only divs inside '.customer-data-column'
.each(($div, index) => {
expect($div.text()).to.eq(reduxDataArray[index])
})
From other comment, it looks like cy.get('.custom-data-column') isn't strong enough to isolate this HTML you need to work on.
Perhaps cy.contains('.customer-data-column', 'Title:') is better.
All text at once
In this particular case you can test all text at once by globally removing \n
const store = Cypress.store.getState()
const reduxData = store.customerData // expect 'Name Lastname\n123 Address xxx yyy\nChicago XY 33056\nCountry name'
const reduxAllTexts = reduxData.replace(/\n/g, '')
cy.contains('.customer-data-column', 'Title:')
.find('div')
.invoke('text')
.should('eq', reduxAllTexts)
If the Cypress function yields multiple elements, we can join the text of those elements to create your string.
cy.get('.custom-data-column').find('div').then(($divList) => {
const store = Cypress.store.getState();
const reduxPath = store.customerData;
const textArray = $divList.map((x) => x.text()); // get the text values as an array
const actual = textArray.join(textArray, '\n'); // use join to array of strings into single string
expect(actual).to.eql(reduxPath);
});

How to map API with multiple objects in [Javascript, ES6]

I am working on the domain API I would like to display domain names and prices at the same time but They are in two different object. I always get undefined when I use this. So If I dataArray[0] or dataArray[1] can get all the result only cannot get both. Thank you for the help.
domain.Name inside of data.Domains object
domain.PriceInfo inside of data.Prices object.
const displayDomains = (data) => {
const dataArray = [data.Domains, data.Prices];
const htmlString = dataArray[(0, 1)]
.map((domain) => {
return `<div class="domains">
<h2>${domain.Name}</h2>
<div class="domain-price-container">
<sub class="discount-price">${domain.PriceInfo}</sub>
</div>
</div>`;
})
.join('');
If data.Domains and data.Prices are equal length arrays, then a straight forward workaround would be to simply loop over both arrays with common index,
const dataArray = [data.Domains, data.Prices];
let index = 0;
let htmlString = [];
for(index; index < sizeOfArray; index++) {
htmlString.push(
<div class="domains">
<h2>${dataArray[0][index].Name}</h2>
<div class="domain-price-container">
<sub class="discount-price">${dataArray[1][index].PriceInfo}</sub>
</div>
</div>
);
}
If both arrays have different sizes, insert all common elements upto smaller sizes out of two, and next insert leftover elements.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
The second argument passed into the .map callback is the index. That allows you to do something like this:
data.Domains.map((domain, index) => {
const price = data.Prices[index];
return <>; // whatever JSX you want here, including both domain and price information
})`

How to get all classes containing hyphen/dash(?) applied to any DOM element and save them into array in JavaScript?

How to get all classes containing hyphen/dash(?) applied to any DOM element and save them into array in JavaScript ?
My failing approach:
var getClasses = [];
var classesContain = [];
document.querySelectorAll('*').forEach( x => {
var y = x.className.split(' ');
getClasses.push(y);
});
getClasses.forEach( c => {
if(c.contains('-')){
classesContain.push(c);
}
});
Your attempt is fairly close to working. The problem is you're pushing an array into getClasses rather than the individual classes, and neither strings nor arrays have a standard contains method (there's includes, which is probably what you mean). Also, if you only want ones containing -, you can filter them out earlier:
let classesWithDash = new Set();
document.querySelectorAll('*').forEach(element => {
for (const cls of element.className.split(' ').filter(name => name.includes("-"))) {
classesWithDash.add(cls);
}
});
// Now, `classesWithDash` is a set containing the class names with dashes
// If you want an array:
classesWithDash = [...classesWithDash];
Live Example:
let classesWithDash = new Set();
document.querySelectorAll('*').forEach(element => {
for (const cls of element.className.split(' ').filter(name => name.includes("-"))) {
classesWithDash.add(cls);
}
});
// Now, `classesWithDash` is a set containing the class names with dashes
// If you want an array:
classesWithDash = [...classesWithDash];
console.log(classesWithDash);
<div class="foo foo-bar"></div>
<div class="another-one"></div>
<div class="nodash"></div>
(I have never understood why Set doesn't have an addAll method, or at least accept multiple values to add like push does...)
Use document.querySelectorAll() with an attribute selector to get all elements with a class that includes (=*) an hyphen. Convert the results to an array using Array.from(). Now iterate the elements with Array.flatMap(), get the classlist and convert to an array, and filter out classes that don't include a hyphen. Use a Set to make the items unique, and spread back to an array.
const result = [...new Set( // use a Set to get an array of unique items
Array.from(document.querySelectorAll('[class*="-"]')) // select all elements with classes that contain hyphens
.flatMap(el => Array.from(el.classList).filter(c => c.includes('-'))) // create an array of all classes with hyphens
)]
console.log(result)
<div class="something a-b something-else x-y"></div>
<div class="something"></div>
<div class="a-b c-d"></div>
The thing you are not realizing is that you are pushing an array of classes into your getClasses array. So you end up with an array of arrays, a.k.a. a 2-dimensional array.
Note, too, that you can do your extracting of classes and filtering to only those that contain dashes in one step, rather than having to process the list twice.
var classesContain = [];
document.querySelectorAll('*').forEach(x => {
var y = (x.className || '').split(/\s+/g); // use a regex to cater for multiple spaces
y.forEach(className => {
if (className.includes('-'))
classesContain.push(className);
});
});
console.log('Final class list: ', classesContain);
<div class="foo-bar bar-baz foo">
<div class="foo-baz">
<span class="single">Example markup</span>
</div>
</div>

How to split Json response and get specific part using angular

I am developing angular blockchain application using hyperledger composer tool.When i query the historian i got a response like in the below.
{
transactionType:"org.hyperledger.composer.system.AddParticipant"
}
I display the transaction type using follwing code snippet.
<div class="col-md-6">
{{participant.transactionType}}
</div>
The displayed part like this.
org.hyperledger.composer.system.AddParticipant
but I only want to display the 'AddParticipant' part in the response without 'org.hyperledger.composer.system.' part. How can I fix it?
For that just do little string manipulation. Make use of JS .split() method which splits string by argument character/string.
let arr = this.participant.transactionType.split(".");
then arr[arr.length-1] is your required string part which you can bind to view. Like use below {{txTyepe}} in template binding
this.txType = arr[arr.length-1];
you can use "substr" to pick a word from string but you need position of your word in your string first so :
const str = 'org.hyperledger.composer.system.AddParticipant'
let getPosition = str.indexOf('AddParticipant'); // get the position of AddParticipant
let getWord = str.substr(getPosition,13);
the length of AddParticipant is 13 also you can change the code above for better and cleaner and multi use code
const splitWord = (index)=>{
const str = 'org.hyperledger.composer.system.AddParticipant'
let strArray = str.split('.')
let getPosition = str.indexOf('AddParticipant'); // get the position of AddParticipant
let getWord = str.substr(getPosition,strArray[index].lenght); //you just need to change the number
return getWord;
}
console.log(splitWord(4));
You can also get the last "word" with regular expression :
<div class="col-md-6">
{{participant.transactionType.match(/\w+$/i)}}
</div>
When you see your historian data it'll look something like this
'$namespace': 'org.hyperledger.composer.system',
'$type': 'HistorianRecord',
'$identifier': '6e43b959c39bdd0c15fe45587a8dc866f1fa854d9fea8498536e84b45e281b31',
'$validator': ResourceValidator { options: {} },
transactionId: '6e43b959c39bdd0c15fe45587a8dc866f1fa854d9fea8498536e84b45e281b31',
transactionType: 'org.hyperledger.composer.system.IssueIdentity',
transactionInvoked:
Relationship {
'$modelManager': [Object],
'$classDeclaration': [Object],
'$namespace': 'org.hyperledger.composer.system',
'$type': 'IssueIdentity',
'$identifier': '6e43b959c39bdd0c15fe45587a8dc866f1fa854d9fea8498536e84b45e281b31',
'$class': 'Relationship' },
So, instead of taking transactionType you can use the transactionInvoked object. And then you can get whatever information you want from that object.
Finally your code should be like this
<div class="col-md-6">
{{participant.transactionInvoked.$type}}
</div>
In my case it will give me transaction type as just 'IssueIdentity'.

How to use vanilla JS to query with a single value a data attribute with array of values?

I have a series of blog posts that have an array of tags as a data-tags html attribute such as ["foo", "bar", "baz"]. I'm looking to query the DOM elements that have foo included in the data-tags array.
Example markup:
<li data-tags="['foo', 'bar', 'baz']">...</li>
I know it's possible to query if the data attributes were stored as a singular value, ie:
document.querySelectorAll('[data-specifc-tag="foo"]');
Is it even possible to select elements which arrays include a specific value?
(Vanilla JS only, no jQuery please)
To sum up, the query you need is
document.querySelectorAll('li[data-tags*=\'"foo"\']')
But you have to make sure that each element in your html array is enlosed within double quotes. You may change it to a single quote, but make sure to update the query.
You can search for multiple queries by adding more rules as follows
document.querySelectorAll('li[data-tags*=\'"foo"\']'+'[data-tags*=\'"bar"\']')
Below is a snippet that applies those queries based on some value you may be interested in. I made sure to use the same html structure you put in your question.
Edit:
I added one more function, queryAll, that allows for value queries. So, you can search for elements that must have more than one value.
function entry(search){
var string = '"' + search + '"';
return '[data-tags*=\'' + string + '\']';
}
function queryAll() {
var queries = Array.prototype.slice.call(arguments).map(function(a){
return '[data-tags*=\'' + a + '\']';
});
//example: document.querySelectorAll('li[data-tags*=\'"foo"\']');
return document.querySelectorAll('li'+ queries.join(''));
}
function query(search) {
var string = '"' + search + '"';
//example: document.querySelectorAll('li[data-tags*=\'"foo"\']');
return document.querySelectorAll('li[data-tags*=\'' + string + '\']');
}
query("foo1"); // One li
query("foo"); //Two li's
queryAll("foo1", "bar1"); //one li
queryAll("foo1", "bar1Not"); //nothing
<ul>
<li data-tags='["foo", "bar", "baz"]'>...</li>
<li data-tags='["foo1", "bar1", "baz1"]'>...</li>
<li data-tags='["foo2", "bar2", "baz2"]'>...</li>
<li data-tags='["foo", "bar2", "baz2"]'>...</li>
</ul>
Here's a vanilla js solution to your problem:
const posts = document.querySelectorAll('[data-tags]');
const fooPosts = [];
posts.forEach(post => {
if (/foo/.test(post.getAttribute('data-tags'))) {
fooPosts.push(post);
}
});
// output the filtered posts
console.log(fooPosts);
One liner alternative:
document.querySelectorAll('[data-tags]').forEach(post => /foo/.test(post.getAttribute('data-tags')) && console.log(post));
Or splitted:
document.querySelectorAll('[data-tags]').forEach(post =>
/foo/.test(post.getAttribute('data-tags')) && console.log(post));
Please, note that, as mentioned in the comments, your html markup is invalid. Data attributes should not contain complex data structure like arrays or objects. As suggested, consider performing a .join() on your array data and outputting it in the attribute [data-tags] as a string. In it each value can be separated by comma for readability.
Using the valid markup your solution will be slightly different:
const posts = document.querySelectorAll('[data-tags]');
const fooPosts = [];
posts.forEach(post => {
if (post.getAttribute('data-tags').indexOf('foo') > -1) {
fooPosts.push(post);
}
});
console.log(fooPosts);
The above code is also faster as it's using .indexOf() to filter the DOM nodes.
Here's the above code in a reusable function:
const filterNodesByAttr = (attr, tag) => {
let filtered = [];
const items = document.querySelectorAll(`[${attr}]`);
items.forEach(item => {
if (item.getAttribute(attr).indexOf(tag) > -1) {
filtered.push(item);
}
});
return filtered;
};
const fooPosts = filterNodesByAttr('data-tags', 'foo');
console.log(fooPosts);
You can use CSS to get any element on the page.
I.e.
Get elements with href's [href]
Get divs with data-type of price div[data-type=price]
var attributes = ['data-rel','type'];
attributes.forEach((at)=>{
document.querySelectorAll('['+at+']').forEach((element)=>{
element.append('<b>'+at+'</b>');
});
});
<p>Hello</p>
<p data-rel="title">World!</p>
<p type="emoji">:-)</p>

Categories

Resources