How replace a numeral in the string 'abc1def' w/ an array element?

In summary, the user of this code is trying to loop through a number of strings and replace numerals in them with an array element. They are also trying to replace specific characters with elements from another array. They are seeking help with how to replace these characters using parameters and a single function. The user has been learning JavaScript for about 3 weeks and this project has been a major education for them. They hope to acquire a job in JavaScript in the near future.
  • #1
deltapapazulu
84
12
Thread moved from the technical forums to the schoolwork forums
Summary:: I need to loop through a number of strings replacing the numerals in them with an array element so that e.g.: flos1Field, flos2Field, flos3Field, etc. become flosAField, flosBField, flosCField. The array would be something like: const letters = ["A", "B", "C"];

I put the question in title that way b/c easier to understand as a forum post title BUT this is actually what I need.

In the following code (see at bottom), how would I replace the underscore bounded 'a' in:
'flosType_a_withMacron()'
with array element:
JavaScript:
flos_Vowels[i]
?

Also how replace the 'ā' in:
putMacronedLetter("ā");
with:
JavaScript:
flos_MacronedVowels[i]
?

Thank you. Sorry had to use code tags for those because the bracket with 'i' in it was disappearing. How do I type a bracket with 'i' in it in regular formatted text here without it disappearing?

JavaScript:
const flos_Vowels = ["a","b","c","d","e"];
const flos_MacronedVowels = ["ā","ē","ī","ō","ū"];
       
for(let i = 0;i < 5;i++ )
{
    function flosType_a_withMacron()
    {
        putMacronedLetter("ā");
        moveCursorToEnd(element);
    }
}
 
Physics news on Phys.org
  • #2
deltapapazulu said:
Sorry had to use code tags for those because the bracket with 'i' in it was disappearing. How do I type a bracket with 'i' in it in regular formatted text here without it disappearing?
Let's clear this up first. PhysicsForums (PF) uses square brackets for formatting, specifically [i] denotes italics. One way to work around this is not to use i as a loop variable: [j] works fine! A better way is to put the code inside an inline code tag using the >_ icon in the toolbar, which is what I did in the previous sentence.

Now to the question: I'm not sure how much JavaScript you know so I'm going to take this in baby steps. First you need an object with keys as the things you want to replace and values the things to replace them with:
JavaScript:
const replacements = { a: 'ā', e: 'ē', i: 'ī', o: 'ō', u: 'ū' };
Next you need to create a regular expression to match the characters you want to replace:
JavaScript:
const regexp = /[aeiou]/gi;
You need a callback function that does the replacement:
JavaScript:
const replacer = (match) => {
  return replacements[match] ?? match;
};
Then you can put them together in a single line that uses the replaceAll() method to do all the work:
JavaScript:
const output = input.replaceAll(regexp, replacer);

You can also do all of this in one line which demonstrates how concisely we can do things like this in JavaScript:
JavaScript:
const output = input.replaceAll(/[aeiou]/gi, (match) => ({ a: 'ā', e: 'ē', i: 'ī', o: 'ō', u: 'ū' }[match] ?? match));

Note that we haven't coded any loops here: that is a feature of (modern) JavaScript programming, we tend to use the inbuilt concepts of iterators and callbacks whenever we can.
 
  • Like
Likes Astronuc, jim mcnamara, sysprog and 2 others
  • #3
Hi pbuk thank you for reply.

Posting this Q kind of made me look at some things that are probably more important for achieving fewer lines of code. Like declaring a single function which takes a parameter (vowel) rather than having 5 different functions (see code below) that do the same thing. I just don't know how to do that yet in this specific case. I understand parameters and used them in other functions just having trouble figuring out how to get the Button function itself to wrap all of that other stuff into one function and take the arguments of the macron vowels innerHTML in the buttons.

Been learning JS for about 3 weeks now. Went thru JS basics stuff a year ago, came back to it and decided to create something. Learned tremendous amount over last few weeks.
Here is full code for the macron buttons.

What I am going to do this weekend is take a break from project and really dig into some more lessons and JS texts. I created this just googling "how to's..." etc. w/o asking forum questions. This is actually my first forum Q on it.

Creating these macron typing buttons was a major education in the language for me and is first thing I've ever created in any programming language and hope to acquire a job in it at some point. Still have a lot to learn. Hopefully by late January I will start job hunting.

Here is a link to the full page I uploaded to 000webhostapp. This is obviously not an optimal or best practices example but I did manage to learning a lot of the language by doing this:
Latin Nouns Exercises page in Javascript

Here is the single file for it (see attached) and here is an image of what the page looks like. I made the input fields so light up green if correct value is entered, also managed to find a solution for keeping the cursor at the end of the value. Originally and annoyingly the user would click a macron button and cursor would go back to beginning after putting macroned letter.
Screenshot (1214).png

Buttons to place a macroned Latin character into an input field:
        var currentlySelectedField;
        
        for(let i=1; i <= 10; i++){
            document.getElementById("flosField"+i).addEventListener("click", getActElID);
            document.getElementById("flosField"+i).addEventListener("keyup", getActElID);               
        }
        function getActElID() {           
            currentlySelectedField = document.activeElement.id;
        }

        function type_a_withMacron() {
                    putMacronedLetter("ā");
                    moveCursorToEnd(element);
        }               
        function type_e_withMacron() {
                    putMacronedLetter("ē");
                    moveCursorToEnd(element);
        }                                   
        function type_i_withMacron() {
                    putMacronedLetter("ī");
                    moveCursorToEnd(element);
        }
        function type_o_withMacron() {
                    putMacronedLetter("ō");
                    moveCursorToEnd(element);
        }
        function type_u_withMacron() {
                    putMacronedLetter("ū");
                    moveCursorToEnd(element);
        }
        
        function moveCursorToEnd(el){
            var element = el;
            element.focus()
              if (typeof element.selectionStart == "number") {
                      element.selectionStart = element.selectionEnd = element.value.length;
                  } else if (typeof element.createTextRange != "undefined") {           
                      var range = element.createTextRange();
                      range.collapse(false);
                      range.select();
                  }
            return range.select();       
        }
        
        function putMacronedLetter(macron)
            {
            var macroned = macron;
            for(var i = 1;i <= 10;i++){
                if(currentlySelectedField == "flosField"+i){
                    var element = document.getElementById('flosField'+i);
                    var newString = element.value.slice(0, element.selectionEnd) + macroned + element.value.slice(element.selectionStart, element.value.length);
                    element.value = newString;
                    element.focus()
                }                   
            }
            return element.value;
            return element;
            }
 
  • Like
  • Love
Likes sysprog, berkeman and pbuk
  • #4
I love it: you have grasped the basics well and I have a soft spot for Latin!

Now is definitely a good time to do some reading around, and I'll give you some terms to search for:

git, github, eslint, prettier.io, react, vue.js, jestjs, mochajs, npm, github pages, netlify, bootstrap...
 
  • Like
Likes sysprog and deltapapazulu
  • #5
pbuk said:
I love it: you have grasped the basics well and I have a soft spot for Latin!

Now is definitely a good time to do some reading around, and I'll give you some terms to search for:

git, github, eslint, prettier.io, react, vue.js, jestjs, mochajs, npm, github pages, netlify, bootstrap...

quick note, I managed to find a solution for using one function versus five functions to use on macron putting buttons.

Previously I was supplying the argument manually in each of five functions and having something else trigger directing to the appropriate function. Using another eventListener I made it so user clicking on macron button automatically gets the macron letter value from e.g.: value="ā" for each button, stores in variable macron in the putMacronedLetter function.

It looks a bit messier and doesn't really save that many lines of code but I think is a more 'best practices' way.

JavaScript:
        var currentlySelectedField;
        var macron;
        const container = document.getElementById('container');
      
        container.addEventListener("click", putMacronedLetter);
      
        for(let i=1; i <= 10; i++){
            document.getElementById("flosField"+i).addEventListener("click", getActElID);
            document.getElementById("flosField"+i).addEventListener("keyup", getActElID);             
        }
      
        function getActElID() {         
            currentlySelectedField = document.activeElement.id;
        }

        function type_withMacron() {
            putMacronedLetter();
            moveCursorToEnd(element);
        }             
      
        function moveCursorToEnd(el){
            var element = el;
            element.focus()
              if (typeof element.selectionStart == "number") {
                      element.selectionStart = element.selectionEnd = element.value.length;
                  } else if (typeof element.createTextRange != "undefined") {         
                      var range = element.createTextRange();
                      range.collapse(false);
                      range.select();
                  }
            return range.select();     
        }
      
        function putMacronedLetter(event){
            const clickedThing = event.target;
            if(clickedThing.tagName == 'BUTTON'){
                macron = clickedThing.value;
                for(var i = 1;i <= 10;i++){
                    if(currentlySelectedField == "flosField"+i){
                        var element = document.getElementById('flosField'+i);
                        var newString = element.value.slice(0, element.selectionEnd) + macron + element.value.slice(element.selectionStart, element.value.length);
                        element.value = newString;
                        element.focus()
                    }                 
                }
            return element.value;
            return element;
            }
        }
 
  • #6
deltapapazulu said:
Posting this Q kind of made me look at some things that are probably more important for achieving fewer lines of code.

deltapapazulu said:
It looks a bit messier and doesn't really save that many lines of code but I think is a more 'best practices' way.

Note that 'saving lines of code' is not an objective in itself. What you should be aiming for is code that is "DRY" - this stands for Don't Repeat Yourself.

deltapapazulu said:
I created this just googling "how to's..." etc. w/o asking forum questions.
Unfortunately this has meant that you have used some outdated techniques and got into some bad habits. If I have time later I'll post a critique of your code highlighting these.
 
  • Like
Likes deltapapazulu
  • #7
Here is latest code for the macron button. Only thing is that if user clicks down on one button (continues to hold down) and drags cursor along to other button and releases, the word "undefined" shows up in the field. I will need to catch that with something. Another thing needs catching is if user highlights whatever text is in the field, 'flōs' let's say, and then clicks on macron letter 'ī' (e.g.), it enters the 'ī' but also the other highlighted text. So a highlighted 'flōs' becomes 'flōsīflōs' if 'ī' is selected while 'flōs' is highlighted.

the index html page

JavaScript:
        var currentlySelectedField;
        var inputField = "flosField";
        var mainNoun_english = "flower";
        var mainNoun_latin = "flos";
        var macronButtonsID = "flosMacronButtons";
        var macronButtonContainer;
        
        
        macronButtonContainer = document.getElementById(macronButtonsID);
        
        macronButtonContainer.addEventListener("click", putMacronedLetter);
        
        for(let i=1; i <= 10; i++) {
            document.getElementById(inputField+i).addEventListener("click", getActElID);
            document.getElementById(inputField+i).addEventListener("keyup", getActElID);                
        }
        
        function getActElID() {            
            currentlySelectedField = document.activeElement.id;
        }
        
        function putMacronedLetter(event) {
        
            const clickedThing = event.target;            
            const macron = clickedThing.value;
            
            for (let i = 1;i <= 10;i++) {
            
                if (currentlySelectedField == inputField+i) {
                
                    var element = document.getElementById(inputField+i);
                    var newString = element.value.slice(0, element.selectionEnd) + macron + element.value.slice(element.selectionStart, element.value.length);
                    element.value = newString;
                    element.focus()
                    
                    if (typeof element.selectionStart == "number") {
                      element.selectionStart = element.selectionEnd = element.value.length;
                    } 
                    else if (typeof element.createTextRange != "undefined") {           
                      var range = element.createTextRange();
                      range.collapse(false);
                      range.select();
                    }
                }    
            }
 
Last edited:
  • #8
pbuk said:
Note that 'saving lines of code' is not an objective in itself. What you should be aiming for is code that is "DRY" - this stands for Don't Repeat Yourself.Unfortunately this has meant that you have used some outdated techniques and got into some bad habits. If I have time later I'll post a critique of your code highlighting these.

give me about 24 hours to work on some things according to some stuff I remember when learning C/C++ a few years ago, in terms of things you mentioned. I'm working on it.

I am going to go thru some javascript Class declarations lessons today in a number of sources. It has been a while.
 
Last edited:
  • #9
deltapapazulu said:
give me about 24 hours to work on some things according to some stuff I remember when learning C/C++ a few years ago, in terms of things you mentioned. I'm working on it.
It's more about things like using `var` which has some unexpected behaviours (have you worked out how moveCursorToEnd(element); on line 18 works when element doesn't appear to be in scope?) - in modern JavaScript we always use `const` or `let` as appropriate - and using deprecated methods like createTextRange().

deltapapazulu said:
I am going to go thru some javascript Class declarations lessons today in a number of sources. It has been a while.
Sometimes classes are useful in JavaScript, they probably aren't here.
 
  • #10
deltapapazulu said:
Here is latest code for the macron button.
JavaScript:
//...

I have critiqued this code below.

JavaScript:
// var currentlySelectedField;
// Use `let` or `const` instead of `var`, and don't leave as `undefined`.
let currentlySelectedField = null;

// var inputField = "flosField";
// You don't need this.

// var mainNoun_english = "flower";
// var mainNoun_latin = "flos";
// Use `let` or `const` instead of `var`, and don't mix camelCase with
// snake_case.
let mainNounEnglish = "flower";
let mainNounLatin = "flos";

// var macronButtonsID = "flosMacronButtons";
// You don't need this.

// var macronButtonContainer;
// macronButtonContainer = document.getElementById(macronButtonsID);
// You don't need this.

// macronButtonContainer.addEventListener("click", putMacronedLetter);
// Putting listeners on parent elements like this has unintended consequences
// (this is causing the odd behaviour when you click and drag a button).
// Much better to attach separate events to buttons. The way I have done it
// uses buttons created with e.g. `<button data-macron="ō">ō</button>`.
document.querySelectorAll("button[data-macron]").forEach((el) => {
  el.addEventListener("click", putMacronedLetter);
});

// for (let i = 1; i <= 10; i++) {
// Indexing items from 0 is a good habit.
for (let i = 0; i < 10; i++) {
  // document.getElementById(inputField+i).addEventListener("click", getActElID);
  // Case sensitivity is inconsistent in HTML so it is better to always use
  // kebab-case for selectors eg id="flos-field-0". I have also used
  // the template string syntax to create the ids below.
  document
    .getElementById(`flos-field-${i}`)
    // document.getElementById(inputField+i).addEventListener("keyup", getActElID);
    // We can use the single `focus` event to replace `click` and `keyup`.
    .addEventListener("focus", getActElID);
}

// However there is a much better way of doing this which does not hard-code
// the number of input fields. Create the input fields using this syntax:
// <input data-flos-field /> and then you can just do:
document
  .querySelectorAll("input[data-flos-field]")
  .addEventListener("focus", getActElID);

function getActElID() {
  // currentlySelectedField = document.activeElement.id;
  // Why save the id, just save the DOM element and then you can use it directly later.
  currentlySelectedField = document.activeElement;
}

function putMacronedLetter(event) {
  const clickedThing = event.target;
  // const macron = clickedThing.value;
  // As noted above I am using the much more flexible and robust
  // `data-macron="..."` attribute instead of `value="..."` here. I am also
  // using _object destructuring_, a concise and powerful syntax.
  const { macron } = clickedThing.dataset;

  /*
  for (let i = 1; i <= 10; i++) {
    if (currentlySelectedField == inputField + i) {
      var element = document.getElementById(inputField + i);
      var newString =
        element.value.slice(0, element.selectionEnd) +
        macron +
        element.value.slice(element.selectionStart, element.value.length);
      element.value = newString;
      element.focus();

      if (typeof element.selectionStart == "number") {
        element.selectionStart = element.selectionEnd = element.value.length;
      } else if (typeof element.createTextRange != "undefined") {
        var range = element.createTextRange();
        range.collapse(false);
        range.select();
      }
    }
  }
  */
  // We don't need to iterate over all the fields, we know which one we have
  // selected so all of this becomes:
  const el = currentlySelectedField;
  el.value =
    el.value.slice(0, el.selectionEnd) +
    macron +
    // el.value.slice(el.selectionStart, el.value.length);
    // We don't need el.value.length here, it will slice to the end by default.
    // el.value.slice(el.selectionStart);
    // Oh hang on, this is where your bug duplicating a selection comes from.
    // This should be
    el.value.slice(el.selectionEnd);

  element.focus();

  // I haven't touched this part, I am not sure what it is trying to achieve.
  // If you want to keep it you should use element.createRange instead of
  // element.createTextRange.
  if (typeof el.selectionStart == "number") {
    el.selectionStart = el.selectionEnd = el.value.length;
  } else if (typeof el.createTextRange != "undefined") {
    var range = el.createTextRange();
    range.collapse(false);
    range.select();
  }
}
 
  • Like
Likes deltapapazulu
  • #11
Note however that you are now stretching the bounds of what is sensible to do with 'vanilla' JavaScript. See how difficult it is to create a page for a different noun? Now is probably time to learn about JavaScript frameworks, and in 2021 this means React (the most widely used and good for employment prospects), Vue (my favourite), or Angular (another alternative), or perhaps Ember, Meteor or Mithril.
 
  • Like
Likes deltapapazulu
  • #12
pbuk said:
I have critiqued this code below.

JavaScript:
// var currentlySelectedField;
// Use `let` or `const` instead of `var`, and don't leave as `undefined`.
let currentlySelectedField = null;

// var inputField = "flosField";
// You don't need this.

// var mainNoun_english = "flower";
// var mainNoun_latin = "flos";
// Use `let` or `const` instead of `var`, and don't mix camelCase with
// snake_case.
let mainNounEnglish = "flower";
let mainNounLatin = "flos";

// var macronButtonsID = "flosMacronButtons";
// You don't need this.

// var macronButtonContainer;
// macronButtonContainer = document.getElementById(macronButtonsID);
// You don't need this.

// macronButtonContainer.addEventListener("click", putMacronedLetter);
// Putting listeners on parent elements like this has unintended consequences
// (this is causing the odd behaviour when you click and drag a button).
// Much better to attach separate events to buttons. The way I have done it
// uses buttons created with e.g. `<button data-macron="ō">ō</button>`.
document.querySelectorAll("button[data-macron]").forEach((el) => {
  el.addEventListener("click", putMacronedLetter);
});

// for (let i = 1; i <= 10; i++) {
// Indexing items from 0 is a good habit.
for (let i = 0; i < 10; i++) {
  // document.getElementById(inputField+i).addEventListener("click", getActElID);
  // Case sensitivity is inconsistent in HTML so it is better to always use
  // kebab-case for selectors eg id="flos-field-0". I have also used
  // the template string syntax to create the ids below.
  document
    .getElementById(`flos-field-${i}`)
    // document.getElementById(inputField+i).addEventListener("keyup", getActElID);
    // We can use the single `focus` event to replace `click` and `keyup`.
    .addEventListener("focus", getActElID);
}

// However there is a much better way of doing this which does not hard-code
// the number of input fields. Create the input fields using this syntax:
// <input data-flos-field /> and then you can just do:
document
  .querySelectorAll("input[data-flos-field]")
  .addEventListener("focus", getActElID);

function getActElID() {
  // currentlySelectedField = document.activeElement.id;
  // Why save the id, just save the DOM element and then you can use it directly later.
  currentlySelectedField = document.activeElement;
}

function putMacronedLetter(event) {
  const clickedThing = event.target;
  // const macron = clickedThing.value;
  // As noted above I am using the much more flexible and robust
  // `data-macron="..."` attribute instead of `value="..."` here. I am also
  // using _object destructuring_, a concise and powerful syntax.
  const { macron } = clickedThing.dataset;

  /*
  for (let i = 1; i <= 10; i++) {
    if (currentlySelectedField == inputField + i) {
      var element = document.getElementById(inputField + i);
      var newString =
        element.value.slice(0, element.selectionEnd) +
        macron +
        element.value.slice(element.selectionStart, element.value.length);
      element.value = newString;
      element.focus();

      if (typeof element.selectionStart == "number") {
        element.selectionStart = element.selectionEnd = element.value.length;
      } else if (typeof element.createTextRange != "undefined") {
        var range = element.createTextRange();
        range.collapse(false);
        range.select();
      }
    }
  }
  */
  // We don't need to iterate over all the fields, we know which one we have
  // selected so all of this becomes:
  const el = currentlySelectedField;
  el.value =
    el.value.slice(0, el.selectionEnd) +
    macron +
    // el.value.slice(el.selectionStart, el.value.length);
    // We don't need el.value.length here, it will slice to the end by default.
    // el.value.slice(el.selectionStart);
    // Oh hang on, this is where your bug duplicating a selection comes from.
    // This should be
    el.value.slice(el.selectionEnd);

  element.focus();

  // I haven't touched this part, I am not sure what it is trying to achieve.
  // If you want to keep it you should use element.createRange instead of
  // element.createTextRange.
  if (typeof el.selectionStart == "number") {
    el.selectionStart = el.selectionEnd = el.value.length;
  } else if (typeof el.createTextRange != "undefined") {
    var range = el.createTextRange();
    range.collapse(false);
    range.select();
  }
}

Big thanks for this! Will incorporate this knowledge definitely.

What the if statement
JavaScript:
if (typeof el.selectionStart == "number") {
    el.selectionStart = el.selectionEnd = el.value.length;
  } else if (typeof el.createTextRange != "undefined") {
    var range = el.createTextRange();
    range.collapse(false);
    range.select();
  }
}
does is keep the cursor at the end of the string. Originally when user would click a macron button, if they would click another macron button right after that the character would feed in from beginning of string, the cursor would move all the way to the left. This keeps cursor at right most position in the string.

Another thing I fixed was if user clicked on macron button, held down, and dragged right and released, the word 'undefined' would appear in the input field. I thought I fixed that with:
JavaScript:
if (newString != 'undefined') {
        element.value = newString;
}

but it only worked if not already a character in the field. Once a char is in the field, now if user did the sloppy click-slide, 'newString' is no longer just 'undefined' but, e.g., 'ōundefined' which the if statement did not test for. So to fix this I made a new if statement with match() and a regex :

JavaScript:
var newString = element.value.slice(0, element.selectionEnd) + macron + element.value.slice(element.selectionStart, element.value.length);
                   
result = newString.match(/undefined/g);
if (result != "undefined") {
           element.value = newString;
}
}

which tests if 'undefined' is anywhere in the string at all, and if so it just doesn't put the value for newString into element.value but waits for a correct click again. Starting over with what is already in the field. The value 'ōundefined' is discarded and becomes 'ō' again. Etc.

There is yet another annoying behavior I want to fix then its on to a week of REACT and getting comfy with VSCODE. Then following week will look at some other stuff you mentioned.

The thing I want to fix is if user (for whatever reason) select highlights text in field and then clicks on macron button, it puts the macron PLUS all the selected stuff as well. So that a selected 'flōr' becomes 'flōrēflōr' if the user clicks macron button 'ē'.

By the way I love this kind of stuff, but I need a job so moving on.

Again thanks for critique! Will incorporate into my practices.

In subsequent post is full javascript section of Latin page w/o your critique updates to it yet. Just wanted to show you how I made it completely reusable.

.I made it so all the javascript except the variables at top can be completely reused for other nouns. The only thing I would need to update is the values of the variables at the top and obviously the data in the html which only takes a few minutes because I have everything uniquely name prefixed with LATIN NOUN + UNERSCORE, e.g.

flos_
mater_
arbor_

I use replace all and it doesn't break anything because the underscore is there. The only real grueling work is one by one entering the new Latin words with and without macrons into the html tags. I want to create a program that does this automatically. I go to wiktionary, copy the Latin table, paste it into some fields, then click a button to update all the appropriate innerHTML fields. My ultimate goal with this particular page is for it to have 1000 Latin nouns.

Here is latest file and site. I added two more noun pages which can be accessed by clicking on linked nouns.
https://thelatinroom1.000webhostapp.com/index.html
 
  • #13
btw just now getting to reviewing your critique. Making changes right now. Good stuff!
 
  • #14
@pbuk

ok so I implemented some of your critique:

1. Your suggested line:
JavaScript:
document.querySelectorAll("button[data-macron]").forEach((el) => { el.addEventListener("click", putMacronedLetter);});

in place of my:
JavaScript:
document.getElementById(macronButtonsID).addEventListener("click", putMacronedLetter);

did, in fact, make the undefined error catching if statement unnecessary. Before, when user would click on macron button, hold down and drag off of it, the string 'undefined' would appear in the field. Now it does not. But to get the macron value from the data attribute 'data-macron' in the macron html buttons, I had to (in function putMacronedLetter() )' replace:
JavaScript:
            const clickedThing = event.target;        
            const macron = clickedThing.value;

with

JavaScript:
            const clickedThing = event.target;        
            const macron = clickedThing.getAttribute('data-macron');

Here is full relevant code with my old lines commented out and your new implemented. One thing though, the format specifier ${i} of the for loop in your suggested code for consolidating the eventListener arguments "click" and "keyup" into one event listener with "focus" ended up freezing the page, nothing would work. But I did implement "focus" in one listener instead of "click" and "keyup" in two and that worked fine, just the format specifier creates error somehow.
All other of your suggestions appear to work and be major improvement. I am not fully done implementing all of it yet though.

HTML:
<!-- <div id="flos_MacronButtons" style="position:relative;left:230px;top:30px;">
            <button class="macronButtons" value="ā">ā</button>
            <button class="macronButtons" value="ē">ē</button>
            <button class="macronButtons" value="ī">ī</button>
            <button class="macronButtons" value="ō">ō</button>
            <button class="macronButtons" value="ū">ū</button>
        </div> -->
    
        <div id="flos_MacronButtons" style="position:relative;left:230px;top:30px;">
            <button class="macronButtons" data-macron="ā">ā</button>
            <button class="macronButtons" data-macron="ē">ē</button>
            <button class="macronButtons" data-macron="ī">ī</button>
            <button class="macronButtons" data-macron="ō">ō</button>
            <button class="macronButtons" data-macron="ū">ū</button>
        </div>

JavaScript:
        let currentlySelectedField = null;
        let inputField = "flos-field-";
        let mainNoun_english = "flower";
        let mainNoun_latin = "flos";
        //var macronButtonsID = "flos_MacronButtons";
        var fileAudio = "flos_Audio";
        var showHideAnswers = "#flos_showHideAnswers";
        var answerKey = "#flos_answerKey";
        var showHideMacrons = "#flos_showHideMacrons";
        var macrons = "#flos_Macrons";
        var showHideAudio = "#flos_showHideAudio";
        var audioButtons = "#flos_audioButtons";

    
        //document.getElementById(macronButtonsID).addEventListener("click", putMacronedLetter);
    
        document.querySelectorAll("button[data-macron]").forEach((el) => { el.addEventListener("click", putMacronedLetter);});
    
        for(let i=1; i <= 10; i++) {
            document.getElementById(inputField+i).addEventListener("focus", getActiveElementID);
            //document.getElementById(inputField+i).addEventListener("keyup", getActiveElementID);                  //document.getElementById(inputField+i).addEventListener("click", getActiveElementID);
        }
    
        function getActiveElementID() {       
            currentlySelectedField = document.activeElement.id;
        }
    
        function putMacronedLetter(event) {
    
            const clickedThing = event.target;       
            //const macron = clickedThing.value;
            const macron = clickedThing.getAttribute('data-macron');
        
            for (let i = 1;i <= 10;i++) {
        
                if (currentlySelectedField == inputField+i) {
            
                    var element = document.getElementById(inputField+i);
                    var newString = element.value.slice(0, element.selectionEnd) + macron + element.value.slice(element.selectionStart, element.value.length);
                    /*result = newString.match(/undefined/g);
                    if (result != "undefined") {
                        element.value = newString;
                    }*/
                    element.value = newString;
                    element.focus()
                
                    if (typeof element.selectionStart == "number") {
                      element.selectionStart = element.selectionEnd = element.value.length;
                    }
                    else if (typeof element.createTextRange != "undefined") {       
                      var range = element.createTextRange();
                      range.collapse(false);
                      range.select();
                    }
                }
            }   
        }
 
Last edited:
  • #15
deltapapazulu said:
1. Your suggested line... did, in fact, make the undefined error catching if statement unnecessary. Before, when user would click on macron button, hold down and drag off of it, the string 'undefined' would appear in the field. Now it does not.
Great! Learning point: it is usually best to listen to events on the DOM element that triggers them rather than a containing parent.

deltapapazulu said:
I had to (in function putMacronedLetter() )' replace:
JavaScript:
            const clickedThing = event.target;           
            const macron = clickedThing.value;

with

JavaScript:
            const clickedThing = event.target;           
            const macron = clickedThing.getAttribute('data-macron');
I had the equivalent code on line 64 in my post:

JavaScript:
  // As noted above I am using the much more flexible and robust
  // `data-macron="..."` attribute instead of `value="..."` here. I am also
  // using _object destructuring_, a concise and powerful syntax.
  const { macron } = clickedThing.dataset;

DomElement.dataset is a powerful tool worth learning about.

deltapapazulu said:
Here is full relevant code with my old lines commented out and your new implemented. One thing though, the format specifier ${i} of the for loop in your suggested code for consolidating the eventListener arguments "click" and "keyup" into one event listener with "focus" ended up freezing the page, nothing would work. But I did implement "focus" in one listener instead of "click" and "keyup" in two and that worked fine, just the format specifier creates error somehow.
Yes sorry, code should have been:
JavaScript:
document
  .querySelectorAll('input[data-flos-field]')
  .forEach((el) => el.addEventListener('focus', getActiveElementID));
 
  • Like
Likes deltapapazulu
  • #16
@pbuk

Here is your critique suggestions fully implemented. Resolved all bugs and did not require that bit of code I was using to keep cursor at end of string. Now what I want to do is use the querySelectorAll with data-flosfield="correctNounCase" to recode my value match tester, which currently looks like somebody's ANSI C homework from the dot com era LOL. I will compare the user entered value with the data attribute value within the current input element of focus rather than something outside the field.

JavaScript:
        let currentInputElementID = null;
  
        document
          .querySelectorAll("button[data-macron]")
          .forEach((el) => { el.addEventListener("click", putMacronedLetter);});
        
        document
          .querySelectorAll("input[data-flosfield]")
          .forEach((el) => { el.addEventListener("focus", function getActiveElementID () {
            currentInputElementID = document.activeElement.id; });});
      
        function putMacronedLetter(event) {
            const { macron } = event.target.dataset;
            const el = document.getElementById(currentInputElementID);
            el.value =
                el.value.slice(0, el.selectionEnd) +
                macron +
                el.value.slice(el.selectionEnd);
            el.focus()
        }

And here is the value match tester that I want to recode:

JavaScript:
        for(let i=1; i <= 20; i++){
                    document.getElementById(inputField+i).addEventListener("keyup", testValue);
                }
      
        function testValue() {
            var inputFieldtest = new Array();
            for (let i = 1; i <= 20; i++){
                inputFieldtest[i] = document.getElementById(inputField+i);
            }
            for (var j = 1;j <= 10; j++){              
                if (inputFieldtest[j].value == inputFieldtest[j+10].value)
                    inputFieldtest[j].style.borderColor = "#66ff00";
                else
                    inputFieldtest[j].style.borderColor = "red";
            }
        }
 
Last edited:
  • Like
Likes pbuk
  • #17
deltapapazulu said:
@pbuk
Now what I want to do is use the querySelectorAll with data-flosfield="correctNounCase" to recode my value match tester, which currently looks like somebody's ANSI C homework from the dot com era LOL. I will compare the user entered value with the data attribute value within the current input field of focus rather than something outside the field.
Yes you are catching on. Just one hint: make the attribute on the input elements something like data-noun-case="nominativeSingular" and then instead of hard-coding everything for different nouns you can do something like
Caution - untested:
nounData = {
  nominativeSingular: ['flos', 'flōs'],
  // ...
};

function testValue(ev) {
  const el = ev.target;
  // Note that the dataset API automagically transforms the kebab-case
  // attribute to a camelCase property.
  const [plain, macroned] = nounData[el.dataset.nounCase];
  if (el.value === plain || el.value === macroned) {
    // ...
  }
}
 
  • Like
Likes deltapapazulu
  • #18
pbuk said:
Yes you are catching on. Just one hint: make the attribute on the input elements something like data-noun-case="nominativeSingular" and then instead of hard-coding everything for different nouns you can do something like
Caution - untested:
nounData = {
  nominativeSingular: ['flos', 'flōs'],
  // ...
};

function testValue(ev) {
  const el = ev.target;
  // Note that the dataset API automagically transforms the kebab-case
  // attribute to a camelCase property.
  const [plain, macroned] = nounData[el.dataset.nounCase];
  if (el.value === plain || el.value === macroned) {
    // ...
  }
}

Will work with that.

Honestly what I would like is to make page (independent of any event) where if any field has an entered character in it, it is red and if correct value in it, it is green regardless of what user is doing. And if no characters are in field the border shows no color at all. I want to make this so that it is not 'event' necessary.

Defaults to red if any characters are in field that are not correct and defaults to no color if no characters are in field. And defaults to green if characters in field happen to match the data attribute value of that element.

I want to press on to complete this with the code looking relatively standard correct and have it be my first portfolio item. My next project, which I have already started will be a simple static page blog with the minimal components a simple blog would have. I will incorporate learning git / github into that project. I am also currently learning React. As unreasonable as this target date may sound, I need to be able to find a job doing this by end of January. My other tech job has dried up with co. I'm with. I do photosims for a friend whose co. (he owned) got acquired and now suddenly they just don't need photosims that much and I get paid per cell site that needs them. So for next 30 days it is balls to wall code learning. I even uninstalled BATTLEFIELD 5. That is how serious things have gotten LOL. I'm not sure how useful this would be without a degree to show for it, but I did teach myself through Calc 1, 2 and 3 (multivariable) and some DE and Linear Algebra.
 
  • #19
deltapapazulu said:
Honestly what I would like is to make page (independent of any event) where if any field has an entered character in it, it is red and if correct value in it, it is green regardless of what user is doing. And if no characters are in field the border shows no color at all. I want to make this so that it is not 'event' necessary.
This is exactly why dynamic JavaScript frameworks were developed. First Backbone and Knockout at the beginning of the last decade, then taken over by Google's Angular, Facebook's React and the independent Vue at the end.

Because of this I wouldn't take it too far as a portfolio project in its current state; on the other hand a "V2" rewrite would make a good second React project (after the tic-tac-toe tutorial).

Good luck with your learning and job search, it's sad to hear about BATTLEFIELD 5 but it's probably for the best!
 
  • #20
It's probably worth mentioning that once you adopt a modern JS framework, interacting directly with the HTML DOM (Document Object Model) becomes irrelevant, however IMHO it is essential to understand the principles of how JavaScript interacts with the DOM to grok what e.g. React is actually doing, as well as to appreciate just how painful and difficult to maintain it is to do it directly.
 
  • Like
Likes deltapapazulu
  • #21
@pbuk

Ok mentor, waded into a couple different Vue tuts last 2 days. Wanted to chose framework & stick w/ only 1 betw now and Jan 1. Choosing Vue. Waded into React a week ago, Vue seems somewhat less intimidating and I'm vibing with the buzz surrounding it so choosing Vue for 2 wks. Will begin learning React as well later.

One of things I have vision for is functionally simple, cosmetically 'muted' (not too much 'make-up'), online Latin language learning webpage. (Also I have friends who teach Latin at local schools).

I created this recently in Google Sheets and it is what kind of spurred me to see if I couldn't make something similar and even better as a webpage. The styling for the table headings/labels is exactly same for Latin nouns/adjectives in wiktionary. I have this set up where all one has to do to create more tables is go to Latin word in wiktionary, select/copy the table for given word, then bring and paste into another sheet in workbook that with a single paste automatically removes all macrons and places words in order in a grid easily transferable to the one shown here. It literally takes seconds for me to set up one of these table entries from the moment I search the Latin word in Google to the moment the study table shown here is created. I have 50 of these entries on this particular sheet. What I eventually want to do is create a website with similar and better functionality. One where all user would need to do to create a study table is copy table from wiktionary and paste into a grid on the page. The page would then create a new study table with one click.

(obviously for copyright issues, I would use a different styling on webpage : ) )

In this google sheet, to study, user wd simply delete entries of left table (not delete formatting of course!), the formulas are written so if no word in cell, the corresponding cell in the checks/skulls grid shows blank. The answer key is grayed out but still visible on the far right.
[EDIT: meant to mention, if user wants definition info on word, they hover over drab colored cell]

LatinAdjectivesQuizSheets.JPG
 
Last edited:
  • Like
Likes pbuk
  • #22
@pbuk

Ok will be probably creating new post elsewhere at some point relating to any obstacles I encounter with Vue. Just encountered one. Googling for solution. If don't find, will attempt to find an existing solution here in physics forums programming section. If still don't find will post problem for help.

e.g., just ran into this and looking for solution. Also vue project I just created not showing up in projects folder. This is not an officially ask for help post just forecasting that I will be resourcing this forum for Vue snags.

1639575956778.png
 
  • #23
deltapapazulu said:
One of things I have vision for is functionally simple, cosmetically 'muted' (not too much 'make-up'), online Latin language learning webpage. (Also I have friends who teach Latin at local schools).
...
Looks like a great subject for a portfolio site; obviously some intellectual property issues to think about if you were to take it live but that's for later. Good luck!

deltapapazulu said:
Ok will be probably creating new post elsewhere at some point relating to any obstacles I encounter with Vue. Just encountered one. Googling for solution. If don't find, will attempt to find an existing solution here in physics forums programming section. If still don't find will post problem for help.
I think I'm the only active poster on PF that works with Vue (and I'm away for a few days from tomorrow), but I have never had a problem searching for solutions.

deltapapazulu said:
e.g., just ran into this and looking for solution.
Network unavailable? Sounds like a firewall issue; whatever firewall software you are running on your PC is not letting node.js connect with the outside world.

deltapapazulu said:
Also vue project I just created not showing up in projects folder.
Not sure what you mean by 'projects folder'. Windows can do some strange stuff with user folders, I do all my development on Windows under a root folder (C:\Projects) which Windows tends to leave alone. This also helps keep it out of any backup and indexing which doesn't play nicely with software builds/version control bulk changes.
 
  • Like
Likes deltapapazulu
  • #24
@pbu

Yeah I just remembered that I actually have some backend stuff related to php/mysql running currently that might be issue. This was months ago I was doing some tutorials related to it. Right now cleaning/uninstalling some things and going to start from scratch.

Regarding my latin nouns page, I am moving on from that but I did manage to make some significant progress in it since last post about it. Replaced all var with let. Replaced all direct html references/ids etc with variables. Also improved / fixed bugs to the input field entry tester.

I made it where user could toggle a "Quiz Type" button to toggle between taking the quiz with macrons or without macrons. If they are only half done with quiz and click on "Quiz Type" it erases all fields and starts over with alternate quiz with or without macrons. And some other things.

The html is a mess and would break on any number of devices but the main value of this was it was a baptism into basic vanilla javascript. Turns out I really really enjoy lower level coding and honestly this wet my whistle to maybe get into something C/C++ related later on but right now my main focus is to acquire a job.

here is latest script for latin nouns page and see attached for index.html if you care to look at page. The 000webhost page is days old version.
JavaScript:
        let inputField = "flos-field-";
        let mainNoun_english = "flower";
        let mainNoun_latin = "flos";
        
        // --------- DIV ELEMENT IDs ---------------------------------//
        
        let macronButtonsDIV = "flos-MacronButtonsDIV";
        let macronButtonsDIVJQ = "#flos-MacronButtonsDIV";
        
        let answerKeyDIV = "flos-answerKeyDIV";
        let answerKeyDIVJQ = "#flos-answerKeyDIV";
        
        let answerKeyMacronsDIV = "flos-answerKeyMacronsDIV";
        let answerKeyMacronsDIVJQ = "#flos-answerKeyMacronsDIV";
        
        // -----------BUTTON ELEMENT IDs -----------------------------//
        
        let toggleShowHideKeyButtonID ="flos-toggleShowHideKeyButtonID";
        let toggleShowHideKeyButtonIDJQ ="#flos-toggleShowHideKeyButtonID";
        
        let toggleQuizTypeButtonID = "flos-toggleQuizTypeButtonID";
        let toggleQuizTypeButtonIDJQ = "#flos-toggleQuizTypeButtonID";
        
        // ----------LABEL IDs --------------------------------------//
        
        let quizTypeLabelID = "flos-quizTypeLabelID";
        let quizTypeLabelIDJQ = "#flos-quizTypeLabelID";
        
        let fileAudio = "flos-Audio";
        let audioButtonsDIV = "flos-audioButtonsDIV";
        let audioButtonsDIVJQ = "#flos-audioButtonsDIV";
        
        
/*--------------------------------------------------PUT MACRONED LETTER BUTTONS--------------------------------------------------------------*/                       
        
        let currentInputElementID = null;
    
        document
          .querySelectorAll("button[data-macron]")
          .forEach((el) => { el.addEventListener("click", putMacronedLetter);});
          
        document
          .querySelectorAll("input[data-inputfield]")
          .forEach((el) => { el.addEventListener("focus", function getActiveElementID () {
            currentInputElementID = document.activeElement.id; });});
        
        function putMacronedLetter(event) { 
            const { macron } = event.target.dataset;
            const el = document.getElementById(currentInputElementID);
            el.value =
                el.value.slice(0, el.selectionEnd) +
                macron +
                el.value.slice(el.selectionEnd);
            el.focus()
        }

/*----END of MACRON BUTTONS----*/

            
/*----------------------------------------------TOGGLE BETWEEN ENGLISH AND LATIN FEATURED NOUN--------------------------------------*/
        function toggleTrans(){
          let x = document.getElementById(mainNoun_latin);
          if (x.innerHTML === mainNoun_english) {
            x.innerHTML = mainNoun_latin;
          } else {
            x.innerHTML = mainNoun_english;
          }
        }
                
/*.......USER INPUT COMPARE TO MATCHKEY EVALUATOR.......*/
            
        for(let i=1; i <= 10; i++){
                    document.getElementById(inputField+i).addEventListener("keyup", testValue);
                    document.getElementById(inputField+i).addEventListener("paste", testValue);
                    document.getElementById(inputField+i).addEventListener("focus", testValue);
                }
        
        function testValue() {
            let inputFieldtest = new Array();
            let x = document.getElementById(answerKeyMacronsDIV);
            for (let i = 1; i <= 30; i++){
                inputFieldtest[i] = document.getElementById(inputField+i);
            }
            if ( x.style.display != "none" ) {
                for (let j = 1;j <= 10; j++){               
                    if (inputFieldtest[j].value == inputFieldtest[j+10].value) {
                        inputFieldtest[j].style.borderColor = "#66ff00";
                    } else if (inputFieldtest[j].value != "") {
                        inputFieldtest[j].style.borderColor = "red";
                    } else {
                        inputFieldtest[j].style.borderColor = "#808080";
                    }
                }
            } else {               
                for (let j = 1;j <= 10; j++){               
                    if (inputFieldtest[j].value == inputFieldtest[j+20].value) {
                        inputFieldtest[j].style.borderColor = "#66ff00";
                    } else if (inputFieldtest[j].value != "") {
                        inputFieldtest[j].style.borderColor = "red";
                    } else {
                        inputFieldtest[j].style.borderColor = "#808080";
                    }
                }
            }
        }

/*----------------------'CLEAR RESULTS BUTTON TO ERASE ALL VALUES IN INPUT FIELD----------------------------------------*/
        function eraseField() {
            for(let i=1;i<=10;i++){
                el = document.getElementById(inputField+i);
                el.value = "";
                el.style.borderColor = "#808080";
            }
            document.getElementById(inputField+1).focus()
        }

/*----------------------ALTERNATE TEXT IN BUTTONS----------------------------------------*/       
        function toggleShowHideKeyButtonText() {
            x = document.getElementById(toggleShowHideKeyButtonID);
            if (x.innerHTML == "Hide Key") {
                x.innerHTML = "Show Key";
            } else {
                x.innerHTML = "Hide Key";
            }
        }
        
/*        function toggleQuizTypeButtonText() {
            x = document.getElementById(toggleQuizTypeButtonID);
            if (x.innerHTML == "Without Macrons") {
                x.innerHTML = "With Macrons";
            } else {
                x.innerHTML = "Without Macrons";
            }
        }*/

/*------------------------JQUERY FOR SHOW/HIDE DIV BLOCKS: ANSWER KEY, AUDIO BUTTONS, MACRONS OVER LETTERS---------------*/
        $(document).ready(function(){
            $(toggleShowHideKeyButtonIDJQ).click(function(){
                $(answerKeyDIVJQ).fadeToggle(200);
                    });
                });
                
        $(document).ready(function(){
            $(toggleShowHideKeyButtonIDJQ).click(function(){
                $(audioButtonsDIVJQ).fadeToggle(200);
                    });
                });
                
        $(document).ready(function(){
            $(toggleShowHideKeyButtonIDJQ).click(function(){
                $(answerKeyMacronsDIVJQ).fadeToggle(200);
                    });
                });
                
        $(document).ready(function(){
            $(toggleQuizTypeButtonIDJQ).click(function(){
                $(macronButtonsDIVJQ).fadeToggle(200);
                    });
                });
                
        $(document).ready(function(){
            $(toggleQuizTypeButtonIDJQ).click(function(){
                $(answerKeyMacronsDIVJQ).fadeToggle(200);
                    });
                });
                
        $(document).ready(function(){
            $(toggleQuizTypeButtonIDJQ).click(function(){
                $(quizTypeLabelIDJQ).fadeToggle(200);
                    });
                });
                
        

/*-------------------------------------------AUDIO BUTTONS--------------------------------------------------------------*/
        let audx = new Array();
        for(let k=0; k<10; k++){
            audx[k] = document.getElementById(fileAudio+(k+1));
        }

/*-----------------------WINDOWS LOAD DIRECTIVES----------------------------------------------------*/
        window.addEventListener("pageshow", eraseField);
            
        function my_code(){
                x = document.getElementById(quizTypeLabelID);
                x.style.display = "none";
            }
            window.onload=my_code()
 
  • #25
@pbuk

Managed to rewrite the testValue() function using a foreach with event handler instead of the for loop. Also made it so the data it checks against is inside the user entry input element itself so testValue is not checking against a value in some other readonly invisible unnecessary input fields but checking inside the very element that takes a value from a user:
data-plain="flos"
data-macron="flōs"

HTML:
<input id="flos-field-1" type="text" data-macron="flōs" data-plain="flos">

Also eliminated toggle quiz type. Now if user enters correct value but without macron the background lights up very lightblue with darker blue border, if correct value but with macron, background is very light green with darker green border, and if incorrect value, lightred and red, etc.
Also working on new look. Eliminated "Toggle Quiz" button, now user just toggles visibility of answers key so that they become almost completely grayed out but still barely visible. Also wrote a function that makes millisecond delay between each readonly field value as they disappear and reappear. Also uses a toggle between style classes method, element.classList.toggle("alt-class"). Nifty!

JavaScript:
function toggleClass() {
        const el = new Array();      
        for (let i = 0; i < 10; i++) {
            el[i] = document.getElementById("field"+(i+1));
            foo = function timer() {
                el[i].classList.toggle("input-invisible");
            }
            setTimeout(foo, i * 20);
        }
    }
Right now I am trying to make it so the only event that triggers the testValue() function is "input". Problem is that if last character entered by user is a macroned letter, that is done with the put macron button which somehow hijinks the "input" event so that it doesn't register with the testValue function. When I added the "focus" event, suddenly it worked, but the reason is because in the putMacron function there is the line:
el.focus() which gives focus back to the input element thus triggering the event for testValue(). The reason I don't just use the "focus" event is because it does not work when tabbing away from field to next field. It leaves the entered value completely unevaluated, especially if no macrons where entered.

So both "input" and "focus" work perfect together. But I want to maybe include some line in the putMacron() function that cause the "input" event to trigger to evaluate value immediately upon macron entry so focus event is not necessary.

With the "input" event the field lights up red the moment a character is entered and turns either blue or green the moment the latest typed character achieves the full correct value, blue if value with no macrons, green if value with macrons. And I really like that functionality for this kind of quiz. The "focus" event alone does NOT do that. And I am trying to avoid using multiple events.

Here is full code for both the putMacron() and testValue() function:

JavaScript:
        let currentInputElementID = null;
   
        document
          .querySelectorAll("button[data-macron]")
          .forEach((el) => {
                el.addEventListener("click", putMacron);});
       
        document
          .querySelectorAll("input[data-macron]")
          .forEach((el) => {
                el.addEventListener("focus", function getActiveElementID () {
                    currentInputElementID = document.activeElement.id;
                    });});
               
        function putMacron(event) {
            const { macron } = event.target.dataset;
            const el = document.getElementById(currentInputElementID);
            el.value =
                el.value.slice(0, el.selectionEnd) +
                macron +
                el.value.slice(el.selectionEnd);
            el.focus()
        }
//----END of MACRON BUTTONS----*/

//.......USER INPUT COMPARE TO MATCHKEY EVALUATOR.......
               
        document
          .querySelectorAll("input[data-macron]")
          .forEach((el) => {
                //el.addEventListener("paste", testValue);
                el.addEventListener("focus", testValue);
                //el.addEventListener("click", testValue);  
                el.addEventListener("input", testValue);
                //el.addEventListener("keyup", testValue);
                //el.addEventListener("keydown", testValue);
                //el.addEventListener("keyup", testValue);
                });
               
        function testValue(event) {
            const { macron } = event.target.dataset;
            const { plain } = event.target.dataset;
            const el = document.getElementById(currentInputElementID);
                if (el.value == macron) {
                    el.style.borderColor = "green";
                    el.style.background = "#dcfede"; //lightgreen
                } else if (el.value == plain) {
                    el.style.borderColor = "blue";
                    el.style.background = "#d6f8ff"; //lightblue              
                } else if (el.value != "") {
                    el.style.borderColor = "red";
                    el.style.background = "#fff5f5"; //lightred
                } else {
                    el.style.borderColor = null;
                    el.style.background = null;                  
                }
            }

New look:
newlook.JPG
 
  • #26
Looking good, and you are really getting to grips with how to write DRY code in JavaScript.
deltapapazulu said:
So both "input" and "focus" work perfect together. But I want to maybe include some line in the putMacron() function that cause the "input" event to trigger to evaluate value immediately upon macron entry so focus event is not necessary.
I see you experimented with a few events: I often use the change event but I don't think that is going to help you here. Probably best to just rrigger the event at the end of putMacron with el.dispatchEvent('input').

Another improvement to consider is to do the styling with CSS classes and use el.classList.add/remove. This would make it easier to change your mind about how things are styled using e.g. dotted borders for some values, or use different styles in different circumstances which can help accessibility. On the subject of accessibility you could see how your page rates using a tool like the Lighthouse plugin for Chrome.
 
  • Like
Likes sysprog and deltapapazulu
  • #27
I also suggest you get to grips with git. If you host your code on GitHub you can have it deploy automatically to free servers (conditions apply) like GitHub Pages or Netlify and you can ditch that amateur web host.
 
  • Like
Likes sysprog
  • #28
pbuk said:
. . . I often use the change event but I don't think that is going to help you here. Probably best to just rrigger the event at the end of putMacron with el.dispatchEvent('input').

Your el.dispatchEvent('input') actually worked but needed a new event declaration.

JavaScript:
    var event = new Event("input");
    el.dispatchEvent(event);

Had to use 'var' to declare the new event.
'let' generated an "Identifier 'event' has already been declared" error.

Anyway I am happy with this solution because it seems native and logical rather than just a workaround, like pairing "focus" with "input" would be. I want an integral feature of the putMacron() function to be that it registers the putting of a macron as an "input" event that is 'hearable' by el.addEventListener("input", testValue);

JavaScript:
        function putMacron(event) { 
            const { macron } = event.target.dataset;
            const el = document.getElementById(currentInputElementID);
            el.value =
                el.value.slice(0, el.selectionEnd) +
                macron +
                el.value.slice(el.selectionEnd);
            el.focus()
            var event = new Event("input");
            el.dispatchEvent(event);
        }
                
        document
          .querySelectorAll("input[data-macron]")
          .forEach((el) => {
                el.addEventListener("input", testValue);            
           });
 
  • #29
deltapapazulu said:
Your el.dispatchEvent('input') actually worked but needed a new event declaration.

JavaScript:
    var event = new Event("input");
    el.dispatchEvent(event);
Good catch - this would be even better as new InputEvent('input').

deltapapazulu said:
Had to use 'var' to declare the new event.
'let' generated an "Identifier 'event' has already been declared" error.
Noooo! That is the JS engine working as it should do - you declared the variable 'event' on line 1 when you used it to name a function parameter
JavaScript:
        function putMacron(event) {
so the right fix is to choose a different name, or just create and fire the event in one statement:
JavaScript:
el.dispatchEvent(new InputEvent("input"));

Renaming variables that are already in use can break things so is something to be avoided - and the fact that let and const prevent this is one of the main reasons we don't use var any more.
 
  • Like
Likes deltapapazulu
  • #30
@pbuk

PREFACE NOTE: OBVIOUSLY, there is a standard way to do this. I just need to learn what it is.
--

My next major project, starting today is to make it so when user clicks on a new noun from a list of nouns, the appropriate data throughout the html file itself is repopulated with new noun data from some data interchange containing, e.g.:

terra terrae
terrae terrārum
terrae terrīs
terram terrās
terrā terrīs

I need it to automatically load to all appropriate positions in HTML file and become the new page for a user to study with.

HTML:
<input  id="vis-field-1" type="text" data-macron="vīs" data-plain="vis"><input id="vis-field-6"  type="text" data-macron="vīrēs" data-plain="vires"><br><br>
<input id="vis-field-2" type="text" data-macron="vīs" data-plain="vis"><input id="vis-field-7"  type="text" data-macron="vīrium"data-plain="virium"><br><br>
<input id="vis-field-3" type="text" data-macron="vī" data-plain="vi"><input id="vis-field-8"  type="text" data-macron="vīribus"data-plain="viribus"><br><br>
<input id="vis-field-4" type="text" data-macron="vim" data-plain="vim"><input id="vis-field-9"  type="text" data-macron="vīrēs" data-plain="vires"><br><br>
<input id="vis-field-5" type="text" data-macron="vī" data-plain="vi"><input id="vis-field-10"  type="text" data-macron="vīribus" data-plain="viribus">

becomes this with a single click by a user.

HTML:
<input  id="terra-field-1" type="text" data-macron="terra" data-plain="terra"><input id="terra-field-6"  type="text" data-macron="terrae" data-plain="terrae"><br><br>
<input id="terra-field-2" type="text" data-macron="terrae" data-plain="terrae"><input id="terra-field-7"  type="text" data-macron="terrārum"data-plain="terrarum"><br><br>
<input id="terra-field-3" type="text" data-macron="terrae" data-plain="terrae"><input id="terra-field-8"  type="text" data-macron="terrīs"data-plain="terris"><br><br>
<input id="terra-field-4" type="text" data-macron="terram" data-plain="terram"><input id="terra-field-9"  type="text" data-macron="terrās" data-plain="terras"><br><br>
<input id="terra-field-5" type="text" data-macron="terrā" data-plain="terra"><input id="terra-field-10"  type="text" data-macron="terrīs" data-plain="terris">

Part of the functionality of such a data load function will need to include:
1. replacing parts of strings in variable values so that
JavaScript:
let fileAudio = "terra-audio-";
let inputField = "terra-field-";

// becomes

let fileAudio = "vis-audio-";
let inputField = "vis-field-";

with a single click by an end user. The goal, obviously, is to avoid creating a thousand html files for a thousand different Latin words but instead using one html file that loads everything it needs to change to new noun from some data interchange that itself could be created in mere seconds as opposed to the 20 to 30 minutes it could take to duplicate an html page and manually replace all values, especially if I expand this project to include verbs with conjugation tables!

This would obviously save me months of work. I just need to learn how to do it.
 
  • #31
Again,
pbuk said:
This is exactly why dynamic JavaScript frameworks were developed. First Backbone and Knockout at the beginning of the last decade, then taken over by Google's Angular, Facebook's React and the independent Vue at the end.
Specifically I am referring to the concept of a ViewModel.

If you want to build it from vanilla HTML/JS then you will want something like this (I don't think you will need the id's any more):
HTML:
<input type="text" data-cas="nomS"><input type="text" data-cas="nomP"><br><br>
<input type="text" data-cas="accS"><input type="text" data-cas="accP"><br><br>
<input type="text" data-cas="genS"><input type="text" data-cas="genP"><br><br>
<input type="text" data-cas="datS"><input type="text" data-cas="datP"><br><br>
<input type="text" data-cas="ablS"><input type="text" data-cas="ablP"><br><br>
[/CODE]

And then you can populate the HTML with something like:

JavaScript:
const noun = nouns[selectedNoun];
document.querySelectorAll('[data-cas]').forEach((el) => {
  const cas = el.dataset.cas;
  // Can't remember if you can do it this way:
  el.dataset.macron = noun[cas].macron;
  el.dataset.plain = noun[cas].plain;
});

This is based on the data for the declensions something like this:

JavaScript:
const nouns = {
  vis: {
    nomS: { macron: 'vīs', plain: 'vis' },
    accS: { macron: 'vīs', plain: 'vis' },
    genS: { macron: 'vī', plain: 'vi' },
    datS: { macron: 'vīm', plain: 'vim' },
    ablS: { macron: 'vī', plain: 'vi' },
    nomP: { macron: 'vīrēs', plain: 'vires' },
    accP: { macron: 'vīrium', plain: 'virium' },
    genP: { macron: 'vīribus', plain: 'viribus' },
    datP: { macron: 'vīrēs', plain: 'vires' },
    ablP: { macron: 'vīribus', plain: 'viribus' },
    audioPrefix: './audio/vis-audio-',
  },
  terra: {
    // ...
  },
};
 
  • Like
Likes deltapapazulu
  • #32
@pbuk

Ok I will start only doing Vue and git/github related for next 10 days. I wanted to try to get a site up and running with about 100 nouns within next few days but that is probably unreasonable & not best use of my 'trying to get a job' time.

here is a screen of latest. I have stopped leaving links to that crappy webhost plus it doesn't display correctly on some devices. Obviously need to learn some things related to that.

functionality is pretty self-explanatory from this screen. Anyway I will move to learning only Vue/Git/Github for next 10 days. I really do need to become 'job-able' at this at least by end of January.

I have this so when user clicks "Toggle Visibility" it does so also to audio button texts as well. At top right I created a toggle alphabetical order Z -- A and A -- Z as well as a Shuffle randomly button.
If user enters a Latin word without macrons (that happens to have macrons, not all do) then field lights up blue.

screenofsiteforforum.JPG
 
  • Like
Likes pbuk
  • #33
deltapapazulu said:
it doesn't display correctly on some devices. Obviously need to learn some things related to that.
Yes, for now you should use a CSS framework of which Bootstrap is the most widely used.

deltapapazulu said:
Anyway I will move to learning only Vue/Git/Github for next 10 days. I really do need to become 'job-able' at this at least by end of January.
Yes, there is a lot to get to grips with here but it's all relevant stuff.
 
  • Like
Likes deltapapazulu
  • #34
so this worked!

Wrote a python html table scraper. All you have to do is change the Latin word in the url to another Latin word (has to be nominative singular) and run script and it prints out 2 separate tables for that noun, one with macrons and the other without. Also converts the list object to a string object. Will be very useful to me in the future.

Python:
from bs4 import BeautifulSoup
import requests
import pandas as pd
import unidecode

wiki_url = 'https://en.wiktionary.org/wiki/flos#Latin'
table_class = 'prettytable'
response = requests.get(wiki_url)
soup = BeautifulSoup(response.text, 'html.parser')

latinnoun_table = soup.find('table', attrs={'class': table_class})
df = pd.read_html(str(latinnoun_table))

print("-----------WITH MACRONS---------------\n")

print(df)

df_listToString = ' '.join([str(element) for element in df])

df_noMacrons=unidecode.unidecode(df_listToString)

print("\n-----------WITHOUT MACRONS------------\n")

print(df_noMacrons)

Output:

Code:
-----------WITH MACRONS---------------

[         Case Singular    Plural
0  Nominative     flōs    flōrēs
1    Genitive   flōris    flōrum
2      Dative    flōrī  flōribus
3  Accusative   flōrem    flōrēs
4    Ablative    flōre  flōribus
5    Vocative     flōs    flōrēs]

-----------WITHOUT MACRONS------------

         Case Singular    Plural
0  Nominative     flos    flores
1    Genitive   floris    florum
2      Dative    flori  floribus
3  Accusative   florem    flores
4    Ablative    flore  floribus
5    Vocative     flos    flores
 
  • #35
@pbuk

welp got it set up and secured thru AWS with a Custom Domain and HTTPS | S3, Route 53, CloudFront, Certificate Manager, etc. Definitely somewhat of a learning curve. Fortunately there are some good instruction videos available for it.

https://thenightwatch.ml/

Managed to get all the noun arrays created pretty quickly once I wrote a Google Sheets script to go with some crazy spreadsheet concatenation triggered by this querying formula

JavaScript:
=importxml(X5, "//table[@class='prettytable inflection-table inflection-table-la']//td")

I have the javascript setting all the values whenever user clicks on new noun. still have a lot of the audio to create for it. I slapped a brief English definition onto end of the noun arrays which is set with the others when user selects new noun. The English appears on mouse hover above inputs table.

Next thing I want to do to it is add a "next noun" and "previous noun" arrows right and left of the title "Answer Key."

Here is vid showing creation of the noun arrays in literally 2 seconds per:

 

Similar threads

Replies
3
Views
973
Replies
4
Views
1K
Replies
10
Views
2K
Replies
10
Views
11K
Replies
15
Views
2K
Back
Top