Quotes Required in Google Script Function Parameters? - javascript

Running into a bit of a weird issue here. I'm still learning the ins and outs of writing functions for Google Apps, and my first real project is a "shipping dashboard" - basically, a spreadsheet where I can take a tracking number from UPS, FedEx, etc., and from it parse the carrier, generate a tracking link, that type of thing. I'm trying to set it up as a function where I can set the type of data being requested (say, carrier), a tracking number, and have it return said information. Here's where I'm at right now:
function trackingData(infoType,trackingNumber) {
//Regex for various carrier's tracking numbers.
var upsValue = /\b(1Z ?[0-9A-Z]{3} ?[0-9A-Z]{3} ?[0-9A-Z]{2} ?[0-9A-Z]{4} ?[0-9A-Z]{3} ?[0-9A-Z]|[\dT]\d\d\d ?\d\d\d\d ?\d\d\d)\b/i;
var fedexValue = /(\b96\d{20}\b)|(\b\d{15}\b)|(\b\d{12}\b)/;
var uspsValue = /\b(91\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d|91\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d\d\d)\b/i;
//First option: we want to know the carrier we're shipping with.
if (infoType == 'carrier') {
if (upsValue.test(trackingNumber)) {
return 'UPS';
}
else if (fedexValue.test(trackingNumber)) {
return 'FedEx';
}
else if (uspsValue.test(trackingNumber)) {
return 'USPS';
}
else return null;
}
The issue comes when passing a value for infoType - if I reference a cell, or set each infoType as a variable and put in a value directly when calling the formula it works just fine. However, if I call it by putting in a cell:
=infoType(carrier,trackingNumber)
I get:
error: Unknown range name carrier
The weird thing is it DOES work if I call it with:
=infoType("carrier",trackingNumber)
(note the quotes around "carrier").
I've looked all over the place to find a solution to keep from having to put the quotes around the formula when it is called but so far haven't had any luck. Anyone have ideas?

error: Unknown range name carrier
That error message is due to the way that the Spreadsheet is interpreting the function arguments.
A function parameter is either a reference or a data element (number, date, string). References can be to a single cell (A1) or range (A1..A23), to a range on a different sheet (Sheet2!A1), or a named range. When you entered carrier without quotes, it was interpreted as a named range. Read Named and protected ranges if you're interested.
Here's a little hint from the preamble text with the Google spreadsheets function list:
... don't forget to add quotation marks around all function components made of alphabetic characters that aren't referring to cells or columns.
Summary - if you want an argument to be passed as a string, put quotes on it.

Related

Getting empty array on contentControls officejs

Word.run( async (context) => {
var searchResults = context.document.body.search('moslim');
context.load(searchResults);
return context.sync().then(async function () {
if (searchResults.items.length > 0) {
// Get the first search result
var range = searchResults.items[0].getRange();
// range.clear();
// Insert a content control around the search result
var contentControl = range.insertContentControl();
// Set the tag of the content control
contentControl.tag = 'your tag';
contentControl.insertText(searchResults.items[0].text.toString(), 'Replace');
// Load the content control
context.load(contentControl);
await context.sync()
var getcontentControl = range.contentControls
context.load(getcontentControl)
await context.sync()
console.log(getcontentControl.items)
}
});
});
last console.log gives empty array but contentcontrol has been added the ms word document.
Can anyone help on this what I did wrong?
I don't think you did anything "wrong" It's just that, despite the fact that you are calling a method called insertContentControl on your Range object, office-js surrounds the range with the control (at least in the case you mention) in such a way that it is not actually added to the range.
The question is what you are actually trying to achieve and how to do that. (It wasn't obvious from the Question as it stands).
If you did the equivalent thing in VBA to your found text, it might be
myrange.ContentControls.Add wdContentControlRichText
In that case, in VBA the content control is in myrange, i.e.
myrange.ContentControls.Count
returns 1. But it wouldn't be in myrange if myrange did not "cover" any characters (i.e. myrange.Start=myrange.End )
I made a simple modification to your office-js code to select the range it found. I also commented out the code that puts text in the control
var contentControl = range.insertContentControl();
range.select();
// Set the tag of the content control
contentControl.tag = 'your tag';
//contentControl.insertText(searchResults.items[0].text.toString(), 'Replace');
If I run your example code with those changes, it's quite easy to find out where the VBA WOrd object model thinks the control and the range are:
Sub rangeandccinfo()
Debug.Print ActiveDocument.ContentControls(1).Range.Start, ActiveDocument.ContentControls(1).Range.End
'In office-js we set by using the Range. So they should really be the same (except maybe we shouldn't
'expect office-js to behave the same as the traditional object model.
Debug.Print Selection.Start, Selection.End
Debug.Print Selection.Range.ContentControls.count
Debug.Print Selection.Range.Information(wdInContentControl)
End Sub
In my experiment, the results are like this
3 9
3 9
0
True
But if I just select the same word manually and insert the CC using VBA like this:
Sub ccrange()
Dim r As Range
Debug.Print Selection.Start, Selection.End
Set r = Selection.Range
'should be the same
Debug.Print r.Start, r.End
r.ContentControls.Add wdContentControlRichText
Debug.Print r.Start, r.End
r.Select
Set r = Nothing
End Sub
then run the rangeandccinfo() Sub, I see this:
3 9
2 9
1
True
i.e. in office-js, it looks as if the range and the range of the Content Control have the same Start and End, whereas in VBA, the Range starts one character before the Range of the Content Control, and that seems to be what make the VBA count of COntent COntrols in the Range 1 rather than 0.
If you actually click at the beginning of the text in a Content Control and click the left arrow button (in a document with left-to-right text) you will see that the insertion point does not appear to move. And yet its Range does change.
So it's at least partly an implementation choice how to do this. Personally, I have found it quite difficult to work with the way VBA does it (its difficult to work out what's inside what). It's quite possible that the office-js team encountered a similar problem and just decided to do it a way that made more sense to them.

Using regex to test out of 4 words in an array has a perfect match

I was tasked with a project of creating a CV site were the user uploads a .docx file and the details is extracted from the file and is automatically inputted in the template designed by me,
I have been able to extract the details .docx file with JavaScript and the extracted details was kept in an array to make it easy to identify words with indexing. For example
[Adeola Emmanuel, adeolaemmanuel#gmail.com, pharmacist, 2 ketu ikorodu lagos, etc].
where i need help is not all CV uploaded by the user has the name coming first or email coming second but its sure that they come within 0,6 of the array so i decided to write a function that will help with that but its not working
var email = email(text.slice(0, 5));
function email(email) {
var re = /.{1,}#[^.]{1,}/ig;
if (!re.test(email)) {
email = text.slice(0, 1);
return email;
} else if (re.test(email)) {
email = text.slice(3, 5);
return email;
}
}
You can use the find array method:
function getEmail(arr) {
let re = /\S#[^.\s]/;
return arr.find(str => re.test(str));
}
let text = ["Adeola Emmanuel", "adeolaemmanuel#gmail.com", "pharmacist", "2 ketu ikorodu lagos"];
let email = getEmail(text.slice(0, 5));
console.log(email);
Some remarks:
{1,} in regular expressions can be shortened to just +
You actually don't need to test for multiple occurrences with +, since you would already accept one occurrence. So that also means you would be OK with just one non-point character after the #.
Neither of the regex suffixes (ig) have any use in your regex.
The .test method should get a string as argument, not an array. So you need to pass it email[0] for example.
For a full test of whether some string is a valid email address, the regular expression would be way more complex
When an if condition is false, there is no need to test the exact opposite in the else block: by exclusion that opposite condition will always be true when it gets executed.
The slice of an array is still an array, so returning text.slice(3, 5); in the else block does not make sense. You want to return a string.
You need a loop to inspect other array elements for as long as you don't have a match and have not reached the end of the array. So some loop construct is needed. You can use for, while, or any of the array methods that do such looping. find is particular useful in this case.
Don't give your function the same name as another variable (email) as only one value can be assigned to that variable (a function, a string, or still something else). So in your case you'll lose the function definition by the var initialisation.

How to write a test that validates whether a function has successfully printed out a list of strings (specified in an array) to the screen?

Background/Context
I am busy learning how to write modules (in my case: files containing a single module.exports function) and running them in the terminal (linux) using node.js.
Part of this process also requires that I write my own tests using the 'assert' module and run them using Mocha.
What I need to accomplish
I need to test whether a module taking in an array (list) of strings as a parameter is successfully printing out each string to the screen.
Printing out each string to the screen is not complicated.
My code : module 'draw.js'
// draw.js
------------------
module.exports = function(arrayOfStrings) {
arrayOfStrings.forEach(function(string) {
console.log(string);
});
};
This successfully prints out each string to the screen. However, even though I know it works I am required to write test for it.
The code should look something like this:
//draw_test.js
------------------
var draw = require('../draw');
var assert = require('assert');
describe('draw', function(){
it("should pass through parameter ['hey', 'there'] and print out 'hey' and 'there' to the screen on separate lines.", function() {
var result = draw(['hey', 'there']); // PROBLEM
assert.equal(result, /*insert expected result here*/);
});
});
The problem
I have commented in where the problem lies in the code above.
The trouble comes in where I need to declare a variable called 'result' which will serve as the 'test' result against what I expect.
There are two problems:
a) 'result' will be undefined because draw(arrayOfStrings) prints to the screen using console.log and does not/should not return anything.
=> How do I solve this? I have spent hours researching and thinking but I have come up with no obvious solution.
b) I can't compare 'result' against any one thing because it needs to validate that all strings are printed to the screen. But in order for them to be printed to the screen individually requires a loop.
=> So how can the test verify that each string is printing to the screen?
I am sure that my two 'problems' above can be solved with one fix, but I can't seem to discover what that fix should be.
Some more context : there must be something more
I am certain I am missing something.
a) I was required to name this module 'draw.js' because I am supposed to use it to 'draw' things using characters to the screen. I have the feeling that I am supposed to do something more than 'console.log' here to print to the screen (for that is what 'printing to the screen' means to me).
b) I am assuming that when they say the function "takes a list of strings as a parameter and prints each entry in the list to the screen" that they mean each entry (or string) should be printed on a separate line.
c) I am supposed to be using this function (or module) called 'draw' in the next few tasks. Here are the names of the modules that I have to create with given directions:
square_stars.js
"Can draw a 4x4 square using the * character. Use the draw function you created above to display the results of the function on the screen. All subsequent functions that need to draw something on the screen should use the draw function. Why do you think we need the draw function?"
// Please edit this to provide hint on how to approach!
square.js
"Takes a character as a parameter and draws a 4x4 square using it. Remember to use the draw function."
square_param.js
"Takes a character and dimensions as parameters, and can draw a square using them. Remember the draw function."
tri.js
"Can draw a triangle with a base of 4 using *."
//Please edit this to provide hint!
tri_char.js
"Takes a character as a parameter, and can draw a triangle with a base of 4 using it."
square_param_base.js
"Takes a character and a base size as parameters, and can draw a square using them."
//Please edit this to provide hint!
diamond.js
"Can draw a diamond shape using *. The center of the diamond should be 5 characters wide."
// Please edit this to provide hint!
diamond_base.js
"Takes a character as a parameter, and can draw a diamond shape using it. The base of the diamond should be 5."
diamond_char_base.js
"Takes a character and a base size as parameters, and can draw a diamond shape using them."
//Please edit this to provide hint!
The reason I am providing you with this list is not only because I foresee similar issues with those tasks as with this one, but also to give context. One of you might be able to tell me if what I am doing is appropriate for the tasks that lie ahead. And I truly do hope you will tell me whether I am on the right path or not.
I would also appreciate some hints (not answers) as to how I could approach the modules above.
But before you guide me in the right direction to accomplish the tasks, please tell me how I am supposed to configure a test using the 'assert' module that follows a similar layout as shown above to validate a function that prints to the screen (i.e. console.log) and doesn't return anything.
First of all, your question about the draw() function. There's no code to check if the thing was printed to console, because almost everytime console.log() prints the text (except errors etc). If you really want to check if the variable printed out was an array, check if it's an array:
function draw(params) {
// Don't continue if params are not an array
if (!Array.isArray(params)) return;
// If it's an array, do something here
}
To print a text to the page simply do your_element.innerHTML = your_text. To get through every part of the array use .map() function.
function draw(params) {
if (!Array.isArray(params)) return;
var inner = '';
params.map(function(param) {
inner += param + '<br>';
});
document.body.innerHTML += inner;
}
draw(['hey', 'there']);
Fiddle
Why do I use inner variable? Because if you use document.body.innerHTML += param + '<br>'; inside .map() function, remove the line above and below it and use draw function with array of for example 10000 elements, it will slow down the page, because JS sets innerHTML 10000 times, and with my code it sets the innerHTML of body only once.
About the square() function, I made one:
function square(character, size) {
// If character is not a string, then set it's default value to '*'
var ch = typeof character == 'string'
? character : '*',
// If size is not a number, then set it's default value to 4
s = typeof size == 'number' ? size : 4,
inner = '', i, j;
// Make a square
for (i = 0; i < s; i++) { // Rows
for (j = 0; j < s; j++) // One-line for
inner += ch; // Prepare the one line
inner += '<br>'; // After the end of the line add a linebreak
}
document.body.innerHTML += inner; // Set the innerHTML
}
square(); // Will print 4x4 square filled with '*'s
// Other examples:
square(-1, 10); // Will print 10x10 square filled with '*'s
square('&'); // Will print 4x4 square filled with '&'s
square('&', 16); // Will print 16x16 square filled with '&'s
Fiddle
About the triangle and diamond functions... Unfortunately it's not easy to do in my opinion.
Cheers
PDKnight

Javascript: Flexibility of function parameters?

The description of Javascript function parameters on W3Schools wasn't very clear, so I just want to clarify.
From my understanding, there isn't a type restriction; the "real value" of the parameters are passed into the method. Is there a way to pass objects or elements? Or is that what is meant by "real value"?
For example:
The function displayText meant to take input text and set a display to show a new word in the given input text, going to the next word every time it's called.
function displayText() {
var text = document.getElementById("words").value;
// Since text is initialized
// every time the method is called,
// it will always start at the beginning of the text area.
// Not sure how to fix this since making `text`
// a global variable doesn't work
var list = text.split(/[ \t\n]+/);
displayNext(list, "display");
}
There is a "helper" method, displayNext, which is supposed to shift to the next word in the list and sets the display to that word.
function displayNext(list, textboxID) {
document.getElementById(textboxID).innerHTML = list.shift();
}
This isn't working as it is intended. I'm fairly sure it's because I've mucked up something with the parameters, since displayNext sets innerHTML to null. list must have not passed properly. I'm not sure how to fix this.
I'm sure there's a more efficient way to do this, but this is a good opportunity to learn how Javascript parameters actually work, so I thought I'd ask.
JSFiddle
Based on the comments in your code, it sounds like you want displayText() to display the "next" word each time. To do that, you have to create some place to store some state about which word is the next one to display. As you have it now, you create a new array every time and always display the first word.
The simplest way is to create a variable outside your function in some lasting scope where you store the word number:
var wordNum = 0;
function displayText() {
var text = document.getElementById("words").value;
var list = text.split(/\s+/);
if (list.length !== 0) {
// make sure we aren't off the end of the list
wordNum = wordNum % list.length;
displayNext(list[wordNum++], "display");
}
}
function displayNext(text, textboxID) {
document.getElementById(textboxID).innerHTML = text;
}
For a lot more info about arguments to Javascript functions and even how you can detect what arguments were passed or overload arguments, see this answer: How to overload functions in javascript? and for more info about how arguments are passed: Javascript by reference vs. by value

What unicode character I can use to "flag" a string?

I want to represent an object that has several text properties, every one representing the same text value but in different languages. In case the user modifies a single field, the other fields should be revised, and I'm thinking on adding a single Unicode character at the beginning of the string of the other fields, and then to check for fields that need attention, I just have to check the value at obj.text_prop[0].
Which Unicode character can I use for this purpose? Ideally, it would be non-printable, supported in JS and JSON.
Such flagging should be done some other way, at a protocol level other than character level. For example, consider as making each language version an object rather than just a string; the object could then have properties such as needsAttention in addition to the property that contains the string.
But in case you need to embed such information into a string, then you could use ZERO WIDTH SPACE U+200B. As such it means line break opportunity, but this should not disturb here. The main problem is probably that old versions of IE may display it as a small rectangle.
Alternatively, you could use a noncharacter code point such as U+FFFF, if you can make sure that the string is never sent anywhere from the program without removing this code point. As described in Ch. 16 of the Unicode Standard, Special Areas and Format Characters, noncharacter code points are reserved for internal use in an application and should never be used in text interchange.
I would suggest you not to use strange characters in the beginning of the line. You can implement something like this:
<script type="text/javascript">
function LocalizationSet(){};
LocalizationSet.prototype.localizationItems = [];
LocalizationSet.prototype.itemsNeedAttention = [];
LocalizationSet.prototype.setLocalization = function(langId, text)
{
this.localizationItems[langId] = text;
this.itemsNeedAttention[langId] = true;
}
LocalizationSet.prototype.getLocalization = function(langId)
{
return this.localizationItems[langId];
}
LocalizationSet.prototype.needsAttention = function(langId)
{
if(this.itemsNeedAttention[langId] == null)
{
return false;
}
return this.itemsNeedAttention[langId];
}
LocalizationSet.prototype.unsetAttentionFlags = function()
{
for(var it in this.itemsNeedAttention)
{
this.itemsNeedAttention[it] = false;
}
}
//Example
var set = new LocalizationSet();
set.setLocalization("en","Hello");
set.setLocalization("de","Willkommen");
alert(set.needsAttention("en"));
alert(set.needsAttention("de"));
set.unsetAttentionFlags();
alert(set.needsAttention("en"));
set.setLocalization("en","Hi");
alert(set.needsAttention("en"));
//Shows true,true,false,true
</script>

Categories

Resources