I'm in need of some expert JavaScript advice. I'm coding using Electron.
The issue: I'm trying to capture the value selected from the second of two dropdown lists and pass it back into a JavaScript file. The code below is ordered as it would run. The dropdown code is not shown as it is simply populated by the viewProvinces function below. The first dropdown's id is "country-select" while the second is "province-select".
In this case, a link is clicked in Index.html which calls the anonymous function in Data.js. The anonymous function calls viewProvinces that populates the parray/data variables from the anonymous function which produces the alert showing the value returned.
(FYI) viewProvinces also populates the second dropdown (id province-select) by filtering based on the values produced in the first dropdown (id country-select). For example, if Afghanistan is selected as a country in the first, then only provinces from Afghanistan are shown in the second.
Moving on, viewProvinces calls Provinces which is an array populated when it calls getProvinces after querying a SQLite database for the source data.
ViewProvinces, Provinces, and getProvinces all work correctly. The link and the anonymous function are the issue and technically work in that they produce a result, but not the correct result. When the link is clicked it produces "object Object". I believe I understand why it is doing this, though I am not skilled enough (yet) to know how to fix it. I do not know how to adjust the code so that it returns the actual value(s) selected from the second (provinces) dropdown.
Put simply, the data is gathered from a SQL query that populates a series of arrays that further populates the dropdown list. The value of this list, once selected, should be returned back to the source JavaScript file into a variable (it fails here).
Apologies if this sounds convoluted, but I'm trying to be thorough. Any help in this matter would be greatly appreciated! Thank you!!
Index.html:
<a id="test-select" href="#">test</a>
Data.js:
$( "#test-select" ).click(function(e) {
e.preventDefault();
var parray = viewProvinces($("#country-select").val());
var data = $('#test-select').data(parray);
alert(data);
});
View.js:
function viewProvinces(ccode) {
var viewPro = Provinces(function(results) {
// Code only gets triggered when Provinces() calls return done(...);
var container = document.getElementById('province-select');
var fragment = document.createDocumentFragment();
results.filter(function(el) {
return el.ccode === ccode;
}).forEach(function(loc, index) {
var opt = document.createElement('option');
opt.textContent = loc.pname;
opt.value = loc.pcode;
fragment.appendChild(opt);
});
container.appendChild(fragment);
});
}
Model.js:
function Provinces(done) {
//Pull location values from data
return getProvinces(done);
}
Data.js:
function getProvinces(done) {
var sqlite3 = require('sqlite3').verbose();
var file = 'db/locations.sqlite3';
var db = new sqlite3.Database(file);
var stmt = 'SELECT Country.CountryId, Country.CountryCode, Country.CountryName, Province.ProvinceName, Province.ProvinceCode FROM Province INNER JOIN Country ON Province.CountryId = Country.CountryId'
var larray = [];
db.all(stmt, function(err, rows) {
// This code only gets called when the database returns with a response.
rows.forEach(function(row) {
larray.push({
ccode: row.CountryCode,
cname: row.CountryName,
pname: row.ProvinceName,
pcode: row.ProvinceCode
});
})
return done(larray);
});
db.close();
}
I have tried to answer your question via a fiddle based on your code created here but I had some trouble understanding a couple of things in your code, so I might have missed something. The main change I made was to make the Provinces(function(results) {.. function return the array of filtered provinces:
function viewProvinces(ccode) {
return Provinces(function(results) {
var provinces = [];
// Code only gets triggered when Provinces() calls return done(...);
var container = document.getElementById('province-select');
var fragment = document.createDocumentFragment();
results.filter(function(el) {
return el.ccode === ccode;
}).forEach(function(loc, index) {
var opt = document.createElement('option');
opt.textContent = loc.pname;
opt.value = loc.pcode;
fragment.appendChild(opt);
provinces.push(loc);
});
container.appendChild(fragment);
return provinces;
});
Once this is done, the parray is correctly setup:
var parray = viewProvinces($("#country-select").val());
However, I was confused when I read this code:
var data = $('#test-select').data(parray);
alert(data);
I assumed you were trying to save the provinces data in the link's store, so modified the code as follows to demo that it works:
$('#test-select').data({
provinces: parray
}); // Save the provinces array
var data = $('#test-select').data(); // Retrieve the provinces array
//Dump it on the console
$.each(data.provinces, function(index, province) {
console.log("Province[" + index + "].name: " + province.cname);
});
Related
I've been able to sort out the middle bit (the API seems to be called to just fine) along with the submenu displaying. Originally I thought that just the end part wasn't working but I'm now thinking that the selection part isn't either.
What am I doing wrong with the getSelection() and what do I need to do to insert a link into said selection? (to clarify, not to replace the text with a link, but to insert a link into the text)
//Open trigger to get menu
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Scry', 'serumVisions')
.addToUi();
}
//Installation trigger
function onInstall(e) {
onOpen(e);
}
//I'm not sure if I need to do this but in case; declare var elements first
var elements
// Get selected text (not working)
function getSelectedText() {
const selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var elements = selection.getRangeElements();
Logger.log(elements);
} else {
var elements = "Lack of selection"
Logger.log("Lack of selection");
}
}
//Test run
// insert here
// Search Function
function searchFunction(nameTag) {
// API call + inserted Value
let URL = "https://api.scryfall.com/cards/named?exact=" + nameTag;
// Grabbing response
let response = UrlFetchApp.fetch(URL, {muteHttpExceptions: true});
let json = response.getContentText();
// Translation
let data = JSON.parse(json);
// Jackpot
let link = data.scryfall_uri;
// Output
Logger.log(link);
}
// Test run
searchFunction("Lightning Bolt");
//Let's hope this works how I think it works
function serumVisions() {
const hostText = getSelectedText();
const linkage = searchFunction(hostText);
// Unsure what class I'm supposed to use, this doesn't
const insertLink = DocumentApp.getActiveDocument().getSelection().newRichTextValue()
.setLinkUrl(linkage);
Logger.log(linkage);
}
For the first part, I tried the getSelection() and getCursor() examples from the Google documentation but they don't seem to work, they all just keep returning null.
For the inserting link bit, I read all those classes from the Spreadsheet section of the documentation, at the time I was unaware but now knowing, I haven't been able to find a version of the same task for Google Docs. Maybe it works but I'm writing it wrong as well, idk.
Modification points:
In your script, the functions of getSelectedText() and searchFunction(nameTag) return no values. I think that this might be the reason for your current issue of they all just keep returning null..
elements of var elements = selection.getRangeElements(); is not text data.
DocumentApp.getActiveDocument().getSelection() has no method of newRichTextValue().
In the case of searchFunction("Lightning Bolt");, when the script is run, this function is always run. Please be careful about this.
When these points are reflected in your script, how about the following modification?
Modified script:
Please remove searchFunction("Lightning Bolt");. And, in this case, var elements is not used. Please be careful about this.
From your script, I guessed that in your situation, you might have wanted to run serumVisions(). And also, I thought that you might have wanted to run the individual function. So, I modified your script as follows.
function getSelectedText() {
const selection = DocumentApp.getActiveDocument().getSelection();
var text = "";
if (selection) {
text = selection.getRangeElements()[0].getElement().asText().getText().trim();
Logger.log(text);
} else {
text = "Lack of selection"
Logger.log("Lack of selection");
}
return text;
}
function searchFunction(nameTag) {
let URL = "https://api.scryfall.com/cards/named?exact=" + encodeURIComponent(nameTag);
let response = UrlFetchApp.fetch(URL, { muteHttpExceptions: true });
let json = response.getContentText();
let data = JSON.parse(json);
let link = data.scryfall_uri;
Logger.log(link);
return link;
}
// Please run this function.
function serumVisions() {
const hostText = getSelectedText();
const linkage = searchFunction(hostText);
if (linkage) {
Logger.log(linkage);
DocumentApp.getActiveDocument().getSelection().getRangeElements()[0].getElement().asText().editAsText().setLinkUrl(linkage);
}
}
When you select the text of "Lightning Bolt" in the Google Document and run the function serumVisions(), the text of Lightning Bolt is retrieved, and the URL like https://scryfall.com/card/2x2/117/lightning-bolt?utm_source=api is retrieved. And, this link is set to the selected text of "Lightning Bolt".
Reference:
getSelection()
I have empty array. Need to fill it by clicking some links (only if value of current index is not filled already).
$(document).ready(function () {
var genres_items = [];
$('.genre-fill-link').on('click', function() {
var genre_index = $(this).data('g-index'); // get id of genre
if(!genres_items[genre_index] { // want to check is this genre filled - get error, undefined variable
$.get('/get-genre-list/', {'genre-id', gener_index}, function(data) { // if genre is not filled yet, want to get data by ajax
gener_items[gener_index] = data;
});
}
}
console.log(genres_items); // get empty untouched array, even if links clicked
});
How to fill all elements of array genre_items (every element once by clicking .genre-link) ?
How to get values of this array in others handlers and callbacks afterwards?!
Javascript confused me =\ Please help
check my comments and try this :
$(document).ready(function () {
var genres_items = [];
$('.genre-fill-link').on('click', function() {
var genre_index = $(this).data('g-index'); // get id of genre
if(!genres_items[genre_index] { // want to check is this genre filled - get error, undefined variable
$.get('/get-genre-list/', {'genre-id', gener_index}, function(data){ // if genre is not filled yet, want to get data by ajax
genres_items[genre_index] = data;
console.log(genres_items);
});
}
}
});
I am trying to use the addPreSearch function to add a custom filter to a lookup field, but the function does not seem to execute fully before the results of the lookup are displayed. The code for this looks something like this:
function onFieldChange(executionContext) {
var formContext = executionContext.getFormContext();
formContext.getControl("test_code").removePreSearch(testFunctionFilter);
formContext.getControl("test_code").addPreSearch(testFunctionFilter);
}
function testFunctionFilter(executionContext) {
var formContext = executionContext.getFormContext();
var record1 = formContext.getAttribute("test_record1_link").getValue(); //get linked record
var record1FullId, record1Id, stringRecordId, idLength, record1Guid = "0";
if (record1 != null) {
record1Id = record1[0].id;
record1Id = record1FullId.slice(1, -1);
stringRecordId = record1FullId.toString();
idLength = stringRecordId.length;
//Guid when retrieved from tablet does not have parenthesis on each end
if (idLength == 36) {
record1Guid = record1FullId;
} else {
record1Guid = recordId;
}
}
var fieldValue;
Xrm.WebApi.retrieveRecord("test_record1", record1Guid, "?$select=test_field1")
.then(function(result1) {
fieldValue = result1.test_field;
var options = generateOptions(executionContext, fieldValue); //creates option string using retrieved fieldValue
Xrm.WebApi.retrieveMultipleRecords("test_record2", options)
.then(function(result) {
var codes = getCodes(result2, fieldValue);
filter = generateFilter(codes, record1Guid); //creates custom filter using provided parameters
console.log(filter); //displays filter correctly
formContext.getControl("test_codelookup").addCustomFilter(filter, "test_coderecord"); //not working?
});
});
}
The filter is generated correctly using the functions used above whose definitions aren't shown. That isn't the issue. I've tried creating a separate test function where I hard coded one of the filters that the function above generated, and the lookup displayed the correct results. The testFunctionFilter should run to completion before the results of the lookup are displayed, correct? Because the filter is logged to the console after the results of the lookup appear. Are the nested asynchronous Xrm.WebApi calls somehow causing the issue? I'm not quite sure what is wrong. Please advise.
You are right. Xrm.WebApi calls are always Asynchronous, which is unusable in this case of adding dynamic filter using addCustomFilter.
You have to use XMLHttpRequest and make that call as Synchronous by setting third parameter as false like below:
var req = new XMLHttpRequest();
req.open("GET", Xrm.Utility.getGlobalContext().getClientUrl() +
"/api/data/v9.0/test_record1?$select=test_field1", false);
In order to work around the async delay, I think you're going to have to reorganise your code:
Add a form OnLoad event and execute the query to retrieve test_field1 and cache the results in a parameter
In the OnChange event, remove the presearch filter, re-execute the query to retrieve test_field1 and update the same parameter (from onload)
In testFunctionFilter use the cached results rather than building the presearch filter from scratch
I'm in need of some minor assistance. I'm having trouble getting an array (larray3) populated with two other array objects (larray1 and larray2) to pass both from data.js into the subsequent model.js and view.js. Data.js correctly builds the multidimensional array however when the results are received in model.js/view.js I only receive the results for larray1. Because only the first values come thru I cannot tell if both larray1 and larray2 are actually passing thru. Can someone please tell me how I should alter my syntax in either model.js or view.js to access both array values or what else I could change? Thank you in advance.
data.js.
function getCountries(done) {
var sqlite3 = require('sqlite3').verbose();
var file = 'db/locations.sqlite3';
var db = new sqlite3.Database(file);
var larray1 = [];
var larray2 = [];
var larray3 = [];
db.all('SELECT * FROM Country', function(err, rows) {
// This code only gets called when the database returns with a response.
rows.forEach(function(row) {
larray1.push(row.CountryName);
larray2.push(row.CountryCode);
})
larray3.push(larray1);
larray3.push(larray2);
return done(larray3[0], larray3[1]);
});
db.close();
}
model.js
function Countries(done) {
//Pull location values from data
return getCountries(done);
}
view.js
function viewCountries() {
var viewCou = Countries(function(results) {
// Code only gets triggered when Countries() calls return done(...);
var container = document.getElementById('country-select');
var fragment = document.createDocumentFragment();
results.forEach(function(loc, index) {
var opt = document.createElement('option');
opt.innerHTML = loc;
opt.value = loc;
fragment.appendChild(opt);
});
container.appendChild(fragment);
})
}
In data.js you send two arguments to the done callback:
return done(larray3[0], larray3[1]);
This done function is passed through in your model.js:
return getCountries(done);
And that done is passed in from view.js:
Countries(function(results) { // ...
So it is this anonymous function (function(results) {...}) that is called in data.js. But notice that this function only has one parameter, so you're doing nothing with the second argument that data.js sends. result gets the value of larray3[0], but larray3[1] is not captured anywhere.
You could solve this in different ways. Personally, I think the design with two arrays is wrong from the start. I would not separate data that belongs in pairs (name and code) into two different arrays.
Instead make an array of objects, and pass that single array around:
In data.js:
rows.forEach(function(row) {
larray1.push({
name: row.CountryName,
code: row.CountryCode
});
})
return done(larray1);
In view.js:
opt.textContent = loc.name;
opt.value = loc.code;
Side-note: .textContent is preferred over .innerHTML when assigning plain text.
I would like to make a collection of all elements that are selected and not.
The dom element consists of several multiple select.
Each of them have the same users.
My goal is to create a collection of all users and for the user which are selected add an attribute with a specific value.
Here is my code js code (1) and here is the link http://jsfiddle.net/vxRtb/9/.
My code works, but I would like to dry the code because, maybe, lopping just on the first select to get all the user is not required.
Any hints how to dry the following js code?
Please read the comments on the js code for more info; thanks
P.S.:
1) I am using jQuery and underscore
2) From the server I get the html code, the same as in jsfiddle.net/vxRtb/9
$(function () {
var $selectElements = $('form .controls select');
var userCollection = [];
// Subroutine_1
// TODO Subroutine_1 and Subroutine_2 seems too close; any idea how to dry this code?
$.each($($selectElements[0]), function (i, teamElement) {
var $users = $(teamElement).find('option')
$.each($users, function (i, user) {
userCollection.push({
id: $(user).val(),
name: $(user).text(),
});
});
});
// Subroutine_2
$.each($selectElements, function (i, teamElement) {
var $teamElement = $(teamElement);
//var teamId = $teamElement.attr('id');
var teamName = $teamElement
.parent().parent().closest('.controls')
.find('input').val();
var $users = $teamElement.find('option:selected');
$.each($users, function (i, user) {
_.where(userCollection, {id: $(user).val()})[0].team = teamName;
});
});
console.log(userCollection);
});
This is a fairly old question and you've probably moved on from it, however, I took a try at this and here's what I came up with.
Analysis
The loops are very similar with the key difference of one loop builds up the team members and the other loop figures out their team. I think the DRY option is to only use one loop and test if a member has a team or not. There will be repetition as the user id's are non-unique, so some logic needs to be applied.
Pseudo Code
Create User Object Collection based off of the <option> tag.
Use getMyTeam to generate the team name
Group the User Objects by ID.
Filter User Objects taking only those with a team names
Filter User Objects without team names to show only uniq records
Code
I'm not sure if this is any better. Performance-wise, this should be fairly poor due to all the nesting. However, I guess the advantage is that the code becomes much more modular and you can easily change the rules if needed. jsFiddle
$(function () {
// Helper to find a team based on an options element
function getMyTeam() {
if (this.is(':selected')) {
var select = this.parent();
var teamId = select.attr('id').replace(/_participations$/, '_name');
return $('#' + teamId).val();
}
return undefined;
};
// Helper to return an object's id
function getId(obj) {
return obj.id;
};
// Helper to filter the team members by team name.
function filterMembersByTeam(members) {
var result = _.filter(members, function(record) {
return !_.isUndefined(record['team']);
});
if (result.length === 0) {
result = _.uniq(members, true, function(member) {
return JSON.stringify(member);
})
};
return result;
};
// 1. Select the users
var options = $('form .controls select option');
// 2. Build up the user data
var userCollection = _.map(options,
function(option) {
return {
id: $(option).val(),
name: $(option).text(),
team: getMyTeam.apply($(option))
};
});
// 3. Clean & filter the user data
userCollection =
_.flatten(
_.map( _.groupBy(userCollection, getId), filterMembersByTeam)
);
console.log(userCollection);
});