Normalizing a string - google-apps-script

I am pretty new at coding so I would appreciate any advice or suggestion.
I have a script which normalizes the header, however, it ignores the numbers at the beginning and I have run into an issue, where I have multiple columns with the same header title so I have added a number in front to differentiate them. For example "1.Project #"
The purpose of the script is to do a mail merge. I was wondering if there is a way to modify it so that it does not ignore the number in front? Please see script below:
// Normalizes a string, by removing all alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum(letter)) {
continue;
}
if (key.length == 0 && isDigit(letter)) {
continue; // first character must be a letter
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
Thank you in advance!

Related

How to conditionally format in google calendar when title is longer than 1 character?

Apologies in advance for any unclarities, I am extremely new to coding.
I found this code by Rick Pastoor (https://rickpastoor.com/2019/05/30/google-calendar-color-coder.html) to automatically colour code events in my Google Calendar based on what character the events start with (in this case !, [ and #).The code is for Google Apps Script
function ColorEvents() {
var today = new Date();
var nextweek = new Date();
nextweek.setDate(nextweek.getDate() + 7);
Logger.log(today + " " + nextweek);
var calendars = CalendarApp.getAllOwnedCalendars();
Logger.log("found number of calendars: " + calendars.length);
for (var i = 0; i < calendars.length; i++) {
var calendar = calendars[i];
var events = calendar.getEvents(today, nextweek);
for (var j = 0; j < events.length; j++) {
var e = events[j];
var title = e.getTitle();
if (title[0] == "[") {
e.setColor(CalendarApp.EventColor.CYAN);
}
if (title[0] == "!") {
e.setColor(CalendarApp.EventColor.RED);
}
if (title[0] == '#') {
e.setColor(CalendarApp.EventColor.GREEN);
}
}
}
}
Now instead of an event starting with !, I want it to colour code if an event starts with a word say:
if (title[0] == "Vacation") {
e.setColor(CalendarApp.EventColor.RED);
This however doesn't work. Anything with just one character (letters, numbers, signs) work, but more than 1 character doesn't work and I was wondering how I could solve this. Thank you in advance!
In your situation, how about the following modification?
From:
if (title[0] == "!") {
To:
if ((/^Vacation/).test(title)) {
For example, if you want to ignore the uppercase and lowercase, you can use if ((/^Vacation/i).test(title)) {.
Reference:
test()
title[0] returns the first character of the event title, however you are looking to match more than the first character.
To match all event titles that contain the word "Vacation" you could do:
if (title.includes("Vacation")) {
e.setColor(CalendarApp.EventColor.RED);
}
To match all events titles that are exactly equal to the word "Vacation" you could do:
if (title === "Vacation") {
e.setColor(CalendarApp.EventColor.RED);
}
To match only event titles that begin with the word "Vacation" you could do:
const searchWord = "Vacation"
if (title.slice(0, searchWord.length) === searchWord) {
e.setColor(CalendarApp.EventColor.RED);
}
Note that all the above are case-sensitive meaning an event named "vacation days" would not be colored. I would recommend instead lowercasing both your search word and the title before comparison. For example:
if (title.toLowerCase().includes("vacation")) {
e.setColor(CalendarApp.EventColor.RED);
}

xpath in apps script?

I made a formula to extract some Wikipedia data in Google Seets which works fine. Here is the formula:
=regexreplace(join("",flatten(IMPORTXML(D2,".//p[preceding-sibling::h2[1][contains(., 'Geography')]]"))),"\[[^\]]+\]","")&char(10)&char(10)&iferror(regexreplace(join("",flatten(IMPORTXML(D2,".//p[preceding-sibling::h2[1][contains(., 'Education')]]"))),"\[[^\]]+\]",""))
Where D2 is a URL like https://en.wikipedia.org/wiki/Abbeville,_Alabama
This extracts some Geography and Education data from the Wikipedia page. Trouble is that importxml only runs a few times before it dies due to quota.
So I thought maybe better to use Apps Script where there are much higher limits on fetching and parsing. I could not see a good way however of using Xpath in Apps Script. Older posts on the web discuss using a deprecated service called Xml but it seems to no longer work. There is a Service called XmlService which looks like it may do the job but you can't just plug in an Xpath. It looks like a lot of sweating to get to the result. Any solutions out there where you can just plug in Xpath?
Here is an alternative solution I actually do in a case like this.
I have used XmlService but only for parsing the content, not for using Xpath. This makes use of the element tags and so far pretty consistent on my tests. Although, it might need tweaks when certain tags are in the result and you might have to include them into the exclusion condition.
Tested the code below in both links:
https://en.wikipedia.org/wiki/Abbeville,_Alabama#Geography
https://en.wikipedia.org/wiki/Montgomery,_Alabama#Education
My test shows that the formula above used did not return the proper output from the 2nd link while the code does. (Maybe because it was too long)
Code:
function getGeoAndEdu(path) {
var data = UrlFetchApp.fetch(path).getContentText();
// wikipedia is divided into sections, if output is cut, increase the number
var regex = /.{1,100000}/g;
var results = [];
// flag to determine if matches should be added
var foundFlag = false;
do {
m = regex.exec(data);
if (foundFlag) {
// if another header is found during generation of data, stop appending the matches
if (matchTag(m[0], "<h2>"))
foundFlag = false;
// exclude tables, sub-headers and divs containing image description
else if(matchTag(m[0], "<div") || matchTag(m[0], "<h3") ||
matchTag(m[0], "<td") || matchTag(m[0], "<th"))
continue;
else
results.push(m[0]);
}
// start capturing if either IDs are found
if (m != null && (matchTag(m[0], "id=\"Geography\"") ||
matchTag(m[0], "id=\"Education\""))) {
foundFlag = true;
}
} while (m);
var output = results.map(function (str) {
// clean tags for XmlService
str = str.replace(/<[^>]*>/g, '').trim();
decode = XmlService.parse('<d>' + str + '</d>')
// convert html entity codes (e.g.  ) to text
return decode.getRootElement().getText();
// filter blank results due to cleaning and empty sections
// separate data and remove citations before returning output
}).filter(result => result.trim().length > 1).join("\n").replace(/\[\d+\]/g, '');
return output;
}
// check if tag is found in string
function matchTag(string, tag) {
var regex = RegExp(tag);
return string.match(regex) && string.match(regex)[0] == tag;
}
Output:
Difference:
Formula ending output
Script ending output
Education ending in wikipedia
Note:
You still have quota when using UrlFetchApp but should be better than IMPORTXML's limit depending on the type of your account.
Reference:
Apps Script Quotas
Sorry I got very busy this week so I didn't reply. I took a look at your answer which seems to work fine, but it was quite code heavy. I wanted something I would understand so I coded my own solution. not that mine is any simpler. It's just my own code so it's easier for me to follow:
function getTextBetweenTags(html, paramatersInFirstTag, paramatersInLastTag) { //finds text values between 2 tags and removes internal tags to leave plain text.
//eg getTextBetweenTags(html,[['class="mw-headline"'],['id="Geography"']],[['class="wikitable mw-collapsible mw-made-collapsible"']])
// **Note: you may want to replace &#number; with ascII number
var openingTagPos = null;
var closingTagPos = null;
var previousChar = '';
var readingTag = false;
var newTag = '';
var tagEnd = false;
var regexFirstTagParams = [];
var regexLastTagParams = [];
//prepare regexes to test for parameters in opening and closing tags. put regexes in arrays so each condition can be tested separately
for (var i in paramatersInFirstTag) {
regexFirstTagParams.push(new RegExp(escapeRegex(paramatersInFirstTag[i][0])))
}
for (var i in paramatersInLastTag) {
regexLastTagParams.push(new RegExp(escapeRegex(paramatersInLastTag[i][0])))
}
var startTagIndex = null;
var endTagIndex = null;
var matches = 0;
for (var i = 0; i < html.length - 1; i++) {
var nextChar = html.substr(i, 1);
if (nextChar == '<' && previousChar != '\\') {
readingTag = true;
}
if (nextChar == '>' && previousChar != '\\') { //if end of tag found, check tag matches start or end tag
readingTag = false;
newTag += nextChar;
//test for firstTag
if (startTagIndex == null) {
var alltestsPass = true;
for (var j in regexFirstTagParams) {
if (!regexFirstTagParams[j].test(newTag)) alltestsPass = false;
}
if (alltestsPass) {
startTagIndex = i + 1;
//console.log('Start Tag',startTagIndex)
matches++;
}
}
//test for lastTag
else if (startTagIndex != null) {
var alltestsPass = true;
for (var j in regexLastTagParams) {
if (!regexLastTagParams[j].test(newTag)) alltestsPass = false;
}
if (alltestsPass) {
endTagIndex = i + 1;
matches++;
}
}
if(startTagIndex && endTagIndex) break;
newTag = '';
}
if (readingTag) newTag += nextChar;
previousChar = nextChar;
}
if (matches < 2) return 'No matches';
else return html.substring(startTagIndex, endTagIndex).replace(/<[^>]+>/g, '');
}
function escapeRegex(string) {
if (string == null) return string;
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
My function requires an array of attributes for the start tag and an array of attributes for the end tag. It gets any text in between and removes any tags found inbetween. One issue I also noticed was there were often special characters (eg  ) so they need to be replaced. I did that outside the scope of the function above.
The function could be easily improved to check the tag type (eg h2), but it wasn't necessary for the wikipedia case.
Here is a function where I called the above function. the html variable is just the result of UrlFetchApp.fetch('some wikipedia city url').getContextText();
function getWikiTexts(html) {
var geography = getTextBetweenTags(html, [['class="mw-headline"'], ['id="Geography']], [['class="mw-headline"']]);
var economy = getTextBetweenTags(html, 'span', [['class="mw-headline"'], ['id="Economy']], 'span', [['class="mw-headline"']])
var education = getTextBetweenTags(html, 'span', [['class="mw-headline"'], ['id="Education']], 'span', [['class="mw-headline"']])
var returnString = '';
if (geography != 'No matches' && !/Wikipedia/.test(geography)) returnString += geography + '\n';
if (economy != 'No matches' && !/Wikipedia/.test(economy)) returnString += economy + '\n';
if (education != 'No matches' && !/Wikipedia/.test(education)) returnString += education + '\n';
return returnString
}
Thanks for posting your answer.

How to select all underlined text in a paragraph

I'm trying to create a google apps script that will format certain parts of a paragraph. For example, text that is underlined will become bolded/italicized as well.
One docs add-on I have tried has a similar feature: https://imgur.com/a/5Cw6Irn (this is exactly what I'm trying to achieve)
How can I write a function that will select a certain type of text and format it?
**I managed to write a script that iterates through every single letter in a paragraph and checks if it's underlined, but it becomes extremely slow as the paragraph gets longer, so I'm looking for a faster solution.
function textUnderline() {
var selectedText = DocumentApp.getActiveDocument().getSelection();
if(selectedText) {
var elements = selectedText.getRangeElements();
for (var index = 0; index < elements.length; index++) {
var element = elements[index];
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
var textLength = text.getText().length;
//For every single character, check if it's underlined and then format it
for (var i = 0; i < textLength; i++) {
if(text.isUnderline(i)) {
text.setBold(i, i, true);
text.setBackgroundColor(i,i,'#ffff00');
} else {
text.setFontSize(i, i, 8);
}
}
}
}
}
}
Use getTextAttributeIndices:
There is no need to check each character in the selection. You can use getTextAttributeIndices() to get the indices in which the text formatting changes. This method:
Retrieves the set of text indices that correspond to the start of distinct text formatting runs.
You just need to iterate through these indices (that is, check the indices in which text formatting changes), which are a small fraction of all character indices. This will greatly increase efficiency.
Code sample:
function textUnderline() {
var selectedText = DocumentApp.getActiveDocument().getSelection();
if(selectedText) {
var elements = selectedText.getRangeElements();
for (var index = 0; index < elements.length; index++) {
var element = elements[index];
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
var textRunIndices = text.getTextAttributeIndices();
var textLength = text.getText().length;
for (let i = 0; i < textRunIndices.length; i++) {
const startOffset = textRunIndices[i];
const endOffset = i + 1 < textRunIndices.length ? textRunIndices[i + 1] - 1 : textLength - 1;
if (text.isUnderline(textRunIndices[i])) {
text.setBold(startOffset, endOffset, true);
text.setBackgroundColor(startOffset, endOffset,'#ffff00');
} else {
text.setFontSize(startOffset, endOffset, 8);
}
}
}
}
}
}
Reference:
getTextAttributeIndices()
Based on the example shown in the animated gif, it seems your procedure needs to
handle a selection
set properties if the selected region is of some format (e.g. underlined)
set properties if the selected region is NOT of some format (e.g. not underlined)
finish as fast as possible
and your example code achieves all these goals expect the last one.
The problem is that you are calling the text.set...() functions at each index position. Each call is synchronous and blocks the code until the document is updated, thus your run time grows linearly with each character in the selection.
My suggestion is to build up a collection of subranges from the selection range and then for each subrange use text.set...(subrange.start, subrange.end) to apply the formatting. Now the run time will be dependent on chunks of characters, rather than single characters. i.e., you will only update when the formatting switches back and forth from, in your example, underlined to not underlined.
Here is some example code that implements this subrange idea. I separated the specific predicate function (text.isUnderline) and specific formatting effects into their own functions so as to separate the general idea from the specific implementation.
// run this function with selection
function transformUnderlinedToBoldAndYellow() {
transformSelection("isUnderline", boldYellowOrSmall);
}
function transformSelection(stylePredicateKey, stylingFunction) {
const selectedText = DocumentApp.getActiveDocument().getSelection();
if (!selectedText) return;
const getStyledSubRanges = makeStyledSubRangeReducer(stylePredicateKey);
selectedText.getRangeElements()
.reduce(getStyledSubRanges, [])
.forEach(stylingFunction);
}
function makeStyledSubRangeReducer(stylePredicateKey) {
return function(ranges, rangeElement) {
const {text, start, end} = unwrapRangeElement(rangeElement);
if (start >= end) return ranges; // filter out empty selections
const range = {
text, start, end,
styled: [], notStyled: [] // we will extend our range with subranges
};
const getKey = (isStyled) => isStyled ? "styled" : "notStyled";
let currentKey = getKey(text[stylePredicateKey](start));
range[currentKey].unshift({start: start});
for (let index = start + 1; index <= end; ++index) {
const isStyled = text[stylePredicateKey](index);
if (getKey(isStyled) !== currentKey) { // we are switching styles
range[currentKey][0].end = index - 1; // note end of this style
currentKey = getKey(isStyled);
range[currentKey].unshift({start: index}); // start new style range
}
}
ranges.push(range);
return ranges;
}
}
// a helper function to unwrap a range selection, deals with isPartial,
// maps RangeElement => {text, start, end}
function unwrapRangeElement(rangeElement) {
const isPartial = rangeElement.isPartial();
const text = rangeElement.getElement().asText();
return {
text: text,
start: isPartial
? rangeElement.getStartOffset()
: 0,
end: isPartial
? rangeElement.getEndOffsetInclusive()
: text.getText().length - 1
};
}
// apply specific formatting to satisfy the example
function boldYellowOrSmall(range) {
const {text, start, end, styled, notStyled} = range;
styled.forEach(function setTextBoldAndYellow(range) {
text.setBold(range.start, range.end || end, true);
text.setBackgroundColor(range.start, range.end || end, '#ffff00');
});
notStyled.forEach(function setTextSmall(range) {
text.setFontSize(range.start, range.end || end, 8);
});
}

How to use a for loop with .createChoice in Google Apps Script to create a quiz from a sheet?

I am using Google Apps Script to generate Google Forms from a Sheet. Questions are in rows and question choices are in columns.
Here is a link to the Google sheet if needed.
It is a straightforward task when using .setChoiceValues(values)
if (questionType == 'CHOICE') {
var choicesForQuestion = [];
for (var j = 4; j < numberColumns; j++)
if (data[i][j] != "")
choicesForQuestion.push(data[i][j]);
form.addMultipleChoiceItem()
.setChoiceValues(choicesForQuestion);
}
However, when I try to use .createChoice(value, isCorrect), the parameters call for value to be a string and isCorrect to be Boolean.
An example without a loop looks like this:
var item = FormApp.getActiveForm().addCheckboxItem();
item.setTitle(data[3][1]);
// Set options and correct answers
item.setChoices([
item.createChoice("chocolate", true),
item.createChoice("vanilla", true),
item.createChoice("rum raisin", false),
item.createChoice("strawberry", true),
item.createChoice("mint", false)
]);
I can not figure out how to add the loop. After reading over other posts, I have tried the following:
if (questionType == 'CHOICE') {
var questionInfo = [];
for (var j = optionsCol; j < maxOptions + 1; j++)
if (data[i][j] != "")
questionInfo.push( form.createChoice(data[i][j], data[i][j + maxOptions]) );
form.addMultipleChoiceItem()
.setChoices(questionInfo);
}
optionsCol is the first column of questions options
maxOptions is how many options are allowed by the sheet (currently 5). The isCorrect information is 5 columns to the right.
However, this not working because the array questionsInfo is empty.
What is the best way to do this?
Probably your issue is related to the method you reference--Form#createChoice--not existing. You need to call MultipleChoiceItem#createChoice, by first creating the item:
/**
* #param {Form} formObj the Google Form Quiz being created
* #param {any[]} data a 1-D array of data for configuring a multiple-choice quiz question
* #param {number} index The index into `data` that specifies the first choice
* #param {number} numChoices The maximum possible number of choices for the new item
*/
function addMCItemToForm_(formObj, data, index, numChoices) {
if (!formObj || !data || !Array.isArray(data)
|| Array.isArray(data[0]) || data.length < (index + 2 * numChoices))
{
console.error({message: "Bad args given", hasForm: !!formObj, info: data,
optionIndex: index, numChoices: numChoices});
throw new Error("Bad arguments given to `addMCItemToForm_` (view on StackDriver)");
}
const title = data[1];
// Shallow-copy the desired half-open interval [index, index + numChoices).
const choices = data.slice(index, index + numChoices);
// Shallow-copy the associated true/false data.
const correctness = data.slice(index + numChoices, index + 2 * numChoices);
const hasAtLeastOneChoice = choices.some(function (c, i) {
return (c && typeof correctness[i] === 'boolean');
});
if (hasAtLeastOneChoice) {
const mc = formObj.addMultipleChoiceItem().setTitle(title);
// Remove empty/unspecified choices.
while (choices[choices.length - 1] === "") {
choices.pop();
}
// Convert to choices for this specific MultipleChoiceItem.
mc.setChoices(choices.map(function (choice, i) {
return mc.createChoice(choice, correctness[i]);
});
} else {
console.warn({message: "Skipped bad mc-item inputs", config: data,
choices: choices, correctness: correctness});
}
}
You would use the above function as described by its JSDoc - pass it a Google Form object instance to create the quiz item in, an array of the details for the question, and the description of the location of choice information within the details array. For example:
function foo() {
const form = FormApp.openById("some id");
const data = SpreadsheetApp.getActive().getSheetByName("Form Initializer")
.getSheetValues(/*row*/, /*col*/, /*numRows*/, /*numCols*/);
data.forEach(function (row) {
var qType = row[0];
...
if (qType === "CHOICE") {
addMCItemToForm_(form, row, optionColumn, numOptions);
} else if (qType === ...
...
}
References
Array#slice
Array#forEach
Array#map
Array#some
I am sure the above answer is very good and works but I am just a beginner and needed a more obvious (plodding) method. I am generating a form from a spreadsheet. Question types can include: short answer (text item), long answer (paragraph), drop down (list item), multiple choice, grid item, and checkbox questions, as well as sections.
I had to be able to randomize the input from the spreadsheet for multiple choice and sort the input for drop downs. I am only allowing one correct answer at this time.
The columns in the question building area of the spreadsheet are: question type, question, is it required, does it have points, hint, correct answer, and unlimited choice columns.
qShtArr: getDataRange of the entire sheet
corrAnsCol: index within the above of the column with the correct answer
begChoiceCol: index within the above of first column with choices
I hope this helps other less skilled coders.
/**
* Build array of choices. One may be identified as correct.
* I have not tried to handle multiple correct answers.
*/
function createChoices(make, qShtArr, r, action) {
// console.log('Begin createChoices - r: ', r);
let retObj = {}, choiceArr = [], corrArr = [], aChoice, numCol, hasCorr;
numCol = qShtArr[r].length - 1; // arrays start at zero
if ((qShtArr[r][corrAnsCol] != '') && (qShtArr[r][corrAnsCol] != null)) {
hasCorr = true;
choiceArr.push([qShtArr[r][corrAnsCol], true]);
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push([aChoice, false]);
}
} //end for loop for multiple choice options
} else {
hasCorr = false;
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push(aChoice);
}
} //end for loop for multiple choice options
}
if (action == 'random')
choiceArr = shuffleArrayOrder(choiceArr);
if (action == 'sort')
choiceArr.sort();
console.log('choiceArr: ', JSON.stringify(choiceArr) );
let choices = [], correctArr = [] ;
if (hasCorr) {
for ( let i = 0 ; i < choiceArr.length ; i++ ) {
choices.push(choiceArr[i][0]);
// console.log('choices: ', JSON.stringify(choices) );
correctArr.push(choiceArr[i][1]);
// console.log('correctArr: ', JSON.stringify(correctArr) );
}
make.setChoices(choices.map(function (choice, i) {
return make.createChoice(choice, correctArr[i]);
}));
} else { // no correct answer
if (action == 'columns' ) {
make.setColumns(choiceArr);
} else {
make.setChoices(choiceArr.map(function (choice, i) {
return make.createChoice(choice);
}));
}
}
}

function within a loop within a function?

I'm coding an email verification form in 3 parts.
Part 1 - check a single character against a list of allowed characters and return true/false.
Part 2 - check a string of characters as the part before or after the '#' using a loop calling the previous function to each successive character.
Part 3 - check a complete email that it includes only one '#', the substring before and after the '#' both satisfy part 2 and the substring following the '#' has only one full stop.
I've got part 1 down but my loop for part 2 is incorrect and returning true for all input values other than a blank form. here is the code -
function isValidEmailPart(part)
{ var emailPartInput = document.getElementById("isValidPartArg").value;
var emailPartLength = alert(emailPartInput.length);
{
if (emailPartInput.length == "")
{
return (false)
}
else
{
NUMBER_OF_CHARACTERS = alert((emailPartInput.length) - 1);
var i = 0;
{for(var i=0; i<NUMBER_OF_CHARACTERS; i++)
{
function isValidEmailChar()
{ var validChars = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,0,1,2,3,4,5,6,7,8,9,_,-,.,';
var emailPartInput = document.getElementById("isValidPartArg").value;
var charInput = emailPartInput.charAt(i);
var inputVar = validChars.indexOf(charInput);
if (inputVar < 0)
{
return (false)
}
}
}
return (true);
}
}
}
}
I know it must be something simple, there are no errors returning I have no idea what I'm doing wrong.
Please, consider the following things very carefully:
Define functions separately: you can call a function from another function BUT don't define a function inside a function
Make sure that your code is ok, pay attention to your code syntax: I found additional { for example. Usually your code editor highlights code syntax errors.
Pay attention to your code's indent: having a good indent helps you have a clearer view of your code and helps you find your potential code mistakes.
Review the different types of variables: in javascript, the variables can have different types: boolean, integer, float, string, etc. You can only compare variables of a same types (Do not mix carrots and potatoes!) and so, you cannot compare emailPartInput with an empty string "" for example.
Before reading the code bellow, you should try to search what was wrong it your code, and what has to be modified to make it work.
Check very carefully the comments I wrote in the code that follows (I took a lot of time to write them!)
The javascript functions:
// This functions verifies if a char 'my_char' is valid
function isValidEmailChar(my_char)
{
// 'my_char' is a i-th character of 'emailPartInput'
var output = false;
// 'validChars' is the array containing all the valid characters
var validChars = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'0','1','2','3','4','5','6','7','8','9','_','-','.'];
// We want to check if 'my_char' is in the array 'validChar'
// So, for each character in the array 'validChar', we check that there's at least
// 1 character in it which is equal to 'my_char'
for(var i=0; i<validChars.length; i++)
{
// 'output' is the result that the function 'isValidEmailChar' will return
// It is initially set to "false"
// The line below means: we store in 'output'
// the result of " output OR ['my_char' EQUALS the i-th character in the array 'validChars'] ".
// Which means that, in the end, 'output' will be "true" if there's at least one i-th character
// in the array 'validChars' where 'my_char' EQUALS the i-th character in the array 'validChars'.
output = (output || (my_char == validChars[i]));
}
// We return the output
// Note: It is better to define 1 'return' and not several
return output;
}
// This function verifies if a part of Email is valid
function isValidEmailPart(emailPartInput)
{
// 'emailPartInput' is the part of email
// 'output' is your function's result to be returned
var output = false;
alert("INPUT = "+emailPartInput);
var nb_of_characters = emailPartInput.length;
alert("number of characters = "+nb_of_characters);
if (nb_of_characters != 0)
{
output = true;
var i = 0;
while(output && i<nb_of_characters)
{
// 'is_character_valid' is a boolean value which is set to:
// - true: if the i-th character of 'emailPartInput' is valid
// - false: if not valid
var is_character_valid = isValidEmailChar(emailPartInput.charAt(i));
// The line below means that we store in the variable 'ouput' the result of
// 'output' AND 'is_character_valid', which means that:
// if there's at least one 'is_character_valid' set to false
// (= one i-th character of 'emailPartInput' is not valid)
// 'output' will then be equals to false
output = output && is_character_valid;
i++;
// We remark that if 'output' is false, we quit the 'while' loop
// because finding one invalid character means that 'emailPartInput' is invalid
// so, we do not need to check the other characters of 'emailPartInput'
}
}
else
{
alert("No emailPartInput has been input");
}
// We return the output
return output;
}
Here's a working example where you can test your functions:
<HTML>
<HEAD>
<SCRIPT language="javascript">
// This functions verifies if a char 'my_char' is valid
function isValidEmailChar(my_char)
{
// 'my_char' is a i-th character of 'emailPartInput'
var output = false;
// 'validChars' is the array containing all the valid characters
var validChars = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'0','1','2','3','4','5','6','7','8','9','_','-','.'];
// We want to check if 'my_char' is in the array 'validChar'
// So, for each character in the array 'validChar', we check that there's at least
// 1 character in it which is equal to 'my_char'
for(var i=0; i<validChars.length; i++)
{
// 'output' is the result that the function 'isValidEmailChar' will return
// It is initially set to "false"
// The line below means: we store in 'output'
// the result of " output OR ['my_char' EQUALS the i-th character in the array 'validChars'] ".
// Which means that, in the end, 'output' will be "true" if there's at least one i-th character
// in the array 'validChars' where 'my_char' EQUALS the i-th character in the array 'validChars'.
output = (output || (my_char == validChars[i]));
}
// We return the output
// Note: It is better to define 1 'return' and not several
return output;
}
// This function verifies if a part of Email is valid
function isValidEmailPart(emailPartInput)
{
// 'emailPartInput' is the part of email
// 'output' is your function's result to be returned
var output = false;
alert("INPUT = "+emailPartInput);
var nb_of_characters = emailPartInput.length;
alert("number of characters = "+nb_of_characters);
if (nb_of_characters != 0)
{
output = true;
var i = 0;
while(output && i<nb_of_characters)
{
// 'is_character_valid' is a boolean value which is set to:
// - true: if the i-th character of 'emailPartInput' is valid
// - false: if not valid
var is_character_valid = isValidEmailChar(emailPartInput.charAt(i));
// The line below means that we store in the variable 'ouput' the result of
// 'output' AND 'is_character_valid', which means that:
// if there's at least one 'is_character_valid' set to false
// (= one i-th character of 'emailPartInput' is not valid)
// 'output' will then be equals to false
output = output && is_character_valid;
i++;
// We remark that if 'output' is false, we quit the 'while' loop
// because finding one invalid character means that 'emailPartInput' is invalid
// so, we do not need to check the other characters of 'emailPartInput'
}
}
else
{
alert("No emailPartInput has been input");
}
// We return the output
return output;
}
function test() {
var my_input = document.getElementById("my_input").value;
var result = isValidEmailPart(my_input);
if(result)
alert("The part of email is valid");
else
alert("The part of email is NOT valid");
}
</SCRIPT>
</HEAD>
<BODY>
Enter you Email part here:
<INPUT type="text" id="my_input" value="" />
<button onclick="javascript:test();">Check the Email part!</button>
</BODY>
</HTML>
NB: The most important is to make sure that you understand what you wrote in your code and what was wrong.
I think you know that just copying a working won't be a benefit for you.
If you read my code, I hope you spent your time to understand it and to read the comments carefully (I took a lot of time to write them! :S)
You can check free online tutorials to learn javascript too! :)
Hope this helps. If you have any questions, do not hesitate to ask, I'll be glad to help.