I'm new to Firebase, and I'm going to build a list of item collected from server with following criterias:
max 10 items
order by item value (eg. 'count')
this is my code
FBRef.orderByChild("count").limitToLast(10).on("child_added", function(snapshot){}
and it worked fine, but when a new record inserted, is there a Firebase method that can check and auto update the list if the new item is in top 10 ? Or I need to to it myself ?
var FBRef = new Firebase('yours.firebaseio.com');
FBRef.orderByChild("count").limitToLast(5).on("child_added", function(snapshot){
var data = snapshot.val();
var html = "<li>value: " + data.value + " - count: " + data.count + "</li>";
$("#result").prepend(html);
});
$(document).ready(function () {
$("#inputaddbtn").click(add);
});
function add(){
var value = $("#inputvalue").val();
var count = $("#inputcount").val();
$("#inputcount").val("");
$("#inputvalue").val("");
FBRef.push({
value : value,
count : count
})
}
You'll need to monitor more events and handle more arguments to keep the list limited.
As a first step: Firebase will fire a child_removed event when an element has been removed from the location or (in your case) falls outside of the query.
You can handle that event to remove the child from the list:
var FBRef = new Firebase('yours.firebaseio.com');
var query = FBRef.orderByChild("count").limitToLast(5);
query.on("child_added", function(snapshot){
var data = snapshot.val();
var html = "<li id='key_"+snapshot.key()+"'>value: " + data.value + " - count: " + data.count + "</li>";
$("#result").prepend(html);
});
query.on("child_removed", function(snapshot){
$('#key_'+snapshot.key()).remove();
});
You'll note that I give the li an id when the child is added and then remove the li based on that id when Firebase tells me to.
This covers simple adding/removing items. For a complete app that handles all cases, you should also handle child_changed and child_moved. And you'll probably also want to handle the second argument to the callbacks, which indicates the key of the item that the child needs to go after. But maybe your use-case doesn't require handling these cases, in which case the above is enough.
Related
var ref = firebase.database().ref("games/" + gameId + "/patterns");
ref.on("child_changed", function(snapshot){
var pattern = snapshot.key;
console.log(pattern);
});
This is giving only the key.
What if I want to extract the player name whose status = 0
I know the snapshot contains every child data I need to extract because when i use
var ref = firebase.database().ref("games/" + gameId + "/patterns");
ref.on("child_changed", function(snapshot){
var value = snapshot.val();
console.log(value);
});
I am getting the data like this..
I am not able to get the child's data as needed.
The value you extract is a regular JavaScript/JSON object, so you can access its members as you would do with any other JavaScript object. Your 1, 2, 3 level keys will be converted to an array by Firebase, which means it adds a null item at the start of that array. For more about that, see: Firebase database returns null at beginning of each new node
To loop over all child nodes under the child of patterns that was changed and get the players whose status is 0 you'd do something like:
var ref = firebase.database().ref("games/" + gameId + "/patterns");
ref.on("child_changed", function(snapshot){
snapshot.forEach(function(child) {
if (child.val() && child.val().status == "0") {
console.log(child.val().player);
});
});
});
I have a table in my mysql db with columns id and drivers_license_number. Now data in drivers_license_number column is stored in plaintext, and I want to encrypt it.
This table contains about 400000 records. I have read about problems with LIMIT and OFFSET issue and try to use in my query late row lookups.
So first of all I need to get data from db, then encrypt the right field and update db with this encrypted field. I don't understand, how to organize my code to send parameters with limit and offset to db. I need to work in loop in this situation?
function getUpdatedEncryptedField(db_table_name, field_name) {
return getDataFromDb(db_table_name, field_name).then(function(result){
return encryptData(result, field_name).then(function (result){
var promises = result.map(function(item) {
var data = [item[field_name], item.id];
return updateFieldNameData(db_table_name, field_name, data);
});
return q.all(promises);
});
});
}
After the first pass I will get for example first 1000 records and how to move forward to get another 1000 rows?
function getDataFromDb(db_table_name, field_name, limit, offset) {
var sql = 'SELECT ds.id, ' + field_name + ' FROM ( SELECT id FROM ' + db_table_name + 'WHERE ' + field_name +
' IS NOT NULL ORDER BY id LIMIT ' + limit + ', ' + offset + ') d JOIN ' + db_table_name + ' ds ON ds.id = d.id ORDER BY ds.id;'
return db.modio.executeSql(sql);
}
You can use a carring/high order function to update the chunks by e.g 1000 and get the next 1000 records until the 400000, like so:
function getUpdatedEncryptedField(db_table_name, field_name) {
var idFrom = 0;
var idTo = 1;
return function getDataFromDB(db_table_name, field_name){
idFrom = idTo;
idTo += 1000;
console.log(id range is ${idFrom}-${idTo}`);
// call your db to get the recods
}
}
// assign high order func to updateChunks var, which is again a func
var updateChunks = getUpdatedEncryptedField('studentsDB','creditCard')
// so each time you execute updateChunks() the values idFrom & idTo are updated
// updateChunks()
// id range is 1-1001
// updateChunks()
// id range is 1001-2001
// and so on
// create a loop until you get all the table records, e.g.
// pay attention to define 'i' with let and not with var because of async loop
for(let i=0; i <= 400000/1000; i++){
// call the updateChunk func here
// at eaach itteration it will ask for the next chunk of records
updateChunks()
}
`
Of course you should update a little bit your mysql query, to get the range of Ids from - to, this is out of the question scope.
Hope helps, good luck.
The code for my add-in takes a search term, then displays a list of matching links on a table. Each link is supposed to insert itself into a word document when it's clicked, but I can't figure out how to pass variables in to a jQuery .click() function.
Currently, no matter what option I click, the URL of the link that gets inserted into the word document is always the URL of the last item on the list. So for example, if I had 3 results in the table: Facebook, Instagram and Yahoo, then whatever option I click, the URL that gets inserted is always http://www.yahoo.com
function displayLinks() {
// Empty pre-existing results
$('#linksTable').empty();
var filteredLinks = [];
// Grab value from search box
var searchTerm = document.getElementById('linksSearchField').value;
// Separate all links containing search term and put them in a filtered list
for (var i = 0; i < numberOfLinks; i++) {
if (sortedLinks[i].linkName.toLowerCase().includes(searchTerm.toLowerCase())){
filteredLinks.push(sortedLinks[i]);
}
}
// Get length of filtered links array
var numberOfSearchResults = filteredLinks.length;
// Populate table with loop
if (searchTerm != '') {
for (var x = 0; x < numberOfSearchResults; x++) {
var table = document.getElementById('linksTable');
var row = table.insertRow(x);
var nameCell = row.insertCell(0);
var linkName = filteredLinks[x].linkName;
var linkNameFormattedForID = linkName.replace(/([ &/!*^%$##+_-])+/g);
var linkURL = filteredLinks[x].linkURL;
// Add link to table
nameCell.innerHTML = "<a href='javascript:void(0);' id='" + linkNameFormattedForID + "'>" + linkName + "</a>";
// Code to add link to word document
$('#' + linkNameFormattedForID).click(linkName, function (linkName) {
Word.run(function (context) {
const doc = context.document;
const originalRange = doc.getSelection();
originalRange.insertHtml("<a href='" + linkURL + "'>" + linkName.currentTarget.innerText + "</a>", "Start");
originalRange.insertText("Refer to ", "Start");
originalRange.load("text");
return context.sync()
})
.catch(function (error) {
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
});
}
}
}
I think I could maybe fix the problem by defining the linkURL variable within the click function itself, but the issue is that I can't access filteredLinks[x] inside of it. I can access the filteredLinks array on its own, but it can't read x, even though the click function is contained within the loop?
As a last-resort super hacky fix, I think I could just change the ID of each item to include it's URL, then extract it from linkName.currentTarget.innerText, but I'd rather not do that unless I really have to.
Thanks in advance!
The problem is that var in JavaScript is function scoped, and because the click event gets invoked after the for loop ends, the value of x will always be the final value of x. There's more information in pleanty of blog posts like this one: https://medium.com/front-end-developers/es6-variable-scopes-in-loops-with-closure-9cde7a198744
The solution:
Use let or const instead of var
A slightly worse solution:
wrap the contents of the for loop inside an IIFE
for (var x = 0; x < numberOfSearchResults; x++) {
(function(x){
...
})(x);
}
Here's a challenging one. I'm relatively new to scripting but have an idea I want to get working.
I have a script that is dynamically generating drop-down lists based on an array: each item in that array creates a dropdownlist.
function getDropDownLists(inputArray, grp) { //input an array and the UI Group we're adding these DDLs to
try {
eval(grp + "Array = [];"); //Creates an array to store the DDLs we're about to create
var listName; //Create variable to store name of DDL we're creating as we iterate through inputArray
for (var i = 0; i < inputArray.length; i++) {
listName = grp + "SrcLevel_" + i.toString(); //Get the name for the DDL we're about to create
eval('var ' + listName + ' = ' + grp + '.add("dropdownlist",[0,0,150,25])'); //add a DDL for the current array item
eval(listName + '.add("item","' + listName + '")'); //This line creates an item in each dropdown to tell me its name
eval(grp + "Array[" + i + "] = " + listName + ";"); //Adds newly created DDL to the storage array
}
} catch (e) {
alert("Error on line " + e.line + ":\n" + e.message);
}
}
When I call this function (it may not work perfectly here as I've cleaned it up a bit for display purposes) it correctly creates all my dropdownlists. However, I want to create onChange events for each of these to reference the previous one in the created storage array and change its contents. I know how to make the onChange events work if these were known dropdownlists, but every project I'll be working on is different and I'd like to get this to work without having to retool every time the project requirements change.
For example, when I call getDropDownLists(['mom','dad','baby'],family), I would get three dropdownlists: familySrcLevel_0,familySrcLevel_1,familySrcLevel_2. How would I then create onClick events for each of these dropdownlists, knowing that I won't always know how many there are? Is such a thing possible? It has to be done in Extendscript.
The solution was to create another eval statement that contained the onChange function in it at the end of the function above.
I have implemented searchbox using jQuery. Here is the code which sends search term and
after that I receive Json which I use to make list of matched searched items.
The problem is that on each keyup I delete all matched items :
$("#realPlaceForSearchItems").html("");
because if I don't that I get duplications when searching for product if I enter "pro" and then type "d". (I am appending list items to the list) Is it possible to achieve that I somehow just delete elements that do not match "prod" (which previously matched "pro" ofcourse) and that elements that match prod stay untouched after typing "d".
$("#searchInput").keyup(function () {
$this = $(this);
$('#realPlaceForSearchItems').show();
$("#realPlaceForSearchItems").html("");
var seachedTerm=$this.val();
if ($this.val().length> 2)
{
$("#realPlaceForSearchItems").html("");
$('#realPlaceForSearchItems').show();
$.ajax({
type: "POST",
url: ROOT + "Filter/Search/",
data: {term: $this.val()},
success: function (data)
{
var Link = $("#searchTemplate>li>a");
var placeForProductId=$("#searchTemplate>li>a>input");
var placeForPicture = $("#searchTemplate>li>a>div>img");
var placeForProductName = $("#searchTemplate>li>a>div>div");
var placeForPrice= $("#searchTemplate>li>a>div>span");
$.each(data.productsWereSeached, function () {
console.log("sddsd", data.totalrows);
var imagesFolder="/Content/images/";
var pathToProduct="/ProductDetails/Index/"
var slash = "/";
Link.attr("href", pathToProduct + this.Id);
placeForProductId.val(this.Id);
if (this && this.Picture) //for the case there is no any picture there would be error cant read propery or undefined
placeForPicture.attr("src", imagesFolder + this.Id + slash + this.Picture.FileName);
else
placeForPicture.attr("src", "");
placeForProductName.html(this.Name);
placeForPrice.html((parseFloat(this.Price) / 100.0).toString() + " kn");
$listItem = $("#searchTemplate").html();
$("#realPlaceForSearchItems").append($listItem);
});
$("#nOfMatchedProducts").val(data.totalrows);
if (data.totalrows > 2)
{
var searchurl="/Search/ShowMoreSearched?term="
$showMoreItem = $("#showMoreItem").html();
$("#showMoreItem>li>a").attr("href",searchurl+seachedTerm);
$("#realPlaceForSearchItems").append($showMoreItem);
}
},
failure: function ()
{
}
});
}
});
$.each(data.productsWereSeached, function () {
if($('a[href="'+pathToProduct + this.Id+'"]').length == 0) {
console.log("sddsd", data.totalrows);
var imagesFolder="/Content/images/";
var pathToProduct="/ProductDetails/Index/"
var slash = "/";
Link.attr("href", pathToProduct + this.Id);
placeForProductId.val(this.Id);
if (this && this.Picture) //for the case there is no any picture there would be error cant read propery or undefined
placeForPicture.attr("src", imagesFolder + this.Id + slash + this.Picture.FileName);
else
placeForPicture.attr("src", "");
placeForProductName.html(this.Name);
placeForPrice.html((parseFloat(this.Price) / 100.0).toString() + " kn");
$listItem = $("#searchTemplate").html();
$("#realPlaceForSearchItems").append($listItem);
}
});
Psst... I'm assuming you also want to limit calls to the server and that your search result list is not wildly huge!
So! Benefit to your current approach is you don't have to manage/compare any existing data set. This makes things easier when a search for "pro" changes to a search for "cro" or any other change that makes the previous AJAX call irrelevant. But, like you said, it leaves you with this clear then re-add items inefficiency when you search for "prod" after "pro".
Idea:
Store the most recent AJAX call criteria in a global.
If new search value includes the latest AJAX search value, filter/hide items in the existing data set which do not match the new criteria. Do not perform a new search.
If new value does not include the latest AJAX search value: clear current data set, update AJAX search value, execute new AJAX call
Pass the index from $.each (http://api.jquery.com/jQuery.each/) into your function, then use it to select the search result you will replaceWith (http://api.jquery.com/replaceWith/) the element you just built. In using this method, your four LI elements within the search results UL must exist before a search.keyup is executed.
Do this by changing two lines...
$.each(data.productsWereSeached, function (index) {
... all of the existing code in the loop stays the same except ...
$("#realPlaceForSearchItems LI:eq(" + index + ")").replaceWith($listItem);
});