I check three elements for their availability. If they are defined, I do the same actions for each element. I would like to avoid the code being repeated. How can I write this in a shorter, more efficient way?
const studentElem = component.find("students").getElement();
if (studentElem != undefined) {
let studentNoice = studentElem.innerText;
studentNoice = studentNoice.replace('classNo', 'Reg No.');
studentElem.innerHTML = studentNoice;
}
const staffElem = component.find("staff").getElement();
if (staffElem != undefined) {
let staffNoice = staffElem.innerText;
staffNoice = staffNoice.replace('staffNo', 'Staff Rec.');
staffElem.innerHTML = staffNoice;
}
const parentElem = component.find("parents").getElement();
if (parentElem != undefined) {
let parentNoice = parentElem.innerText;
parentNoice = parentNoice.replace('ParentID', 'P-ID.');
parentElem.innerHTML = parentNoice;
}
You've got the right idea, this should be condensed. You can do this by passing the variable data into a function as arguments and making decisions based on those argument values.
You also don't need to explicitly check for !=undefinded. You can just check for the existence of the element if(elem). And, don't use .innerHTML when the string you are working with doesn't contain any HTML as .innerHTML has security and performance implications. Use .textContent instead.
From MDN:
It is recommended that you do not use innerHTML when inserting plain
text; instead, use Node.textContent. This doesn't parse the passed
content as HTML, but instead inserts it as raw text.
function foo(role, find, replace){
const elem = component.find(role).getElement();
// No need to check for !=undefined because that essentially means
// you are checking for a "truthy" value, which is what an if condition
// checks for by default, so checking the variable itself forces the
// value to be converted to a Boolean. If the element exists, the Boolean
// becomes true and if not, it is false.
if (elem) {
// Setting up variables that are only used once is a bit of a waste
// Just do the work here and there's less code to write and maintain
elem.textContent = elem.textContent.replace(find, replace);
}
}
// You then call the function and pass the variable data
foo("students", 'classNo', 'Reg No.');
foo("staff", 'staffNo', 'Staff Rec.');
foo("parents", 'ParentID', 'P-ID.');
Make a function!
function yourBuisnessLogic(componentName, subStr, newSubStr){
const elem = component.find(componentName).getElement();
if (elem != undefined) {
let notice = elem.innerText;
notice = notice.replace(subStr, newSubStr);
elem.innerHTML = notice;
}
}
and function calls :
yourBuisnessLogic("students",'classNo', 'Reg No.');
yourBuisnessLogic("staff",'staffNo', 'Staff Rec.');
yourBuisnessLogic("parents",'ParentID', 'P-ID.');
Related
I have a function that finds elements using jQuery. I then take that element, get a value from it and assign it to a variable. However, sometimes an element will not exist on the document and the function will return undefined. This is expected and if the element returned is undefined then the variable that gets assigned should also be undefined. Since I am using this function to assign variables many times I wanted to make it neat and on one line. I thought using conditionals might be a solution, however, doing this leads to really messy long lines and needing to call the function twice:
let html = $(response.body);
let language = getRowElementFromText('Language', html) ? getRowElementFromText('Language', html).find('a').text() : undefined;
let pages = getRowElementFromText('Page', html) ? parseInt(getRowElementFromText('Page', html).text()) : undefined;
Is it possible to resolve those issues and somehow pass the condition of the conditional to be used as the value? For example, in this pesudo code this would be the value of the conditional:
let html = $(response.body);
let language = getRowElementFromText('Language', html) ? this.find('a').text() : undefined;
let pages = getRowElementFromText('Page', html) ? parseInt(this.text()) : undefined;
If this not possible is there another more readable way I can accomplish this on one line?
It is/will be with the optional chaining operator that's new in ES2020 (available via transpilation today). the code would look like this (it only helps so much with the parseInt cas):
let language = getRowElementFromText('Language', html)?.find('a').text();
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^
let pageText = getRowElementFromText('Page', html)?.text();
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^
let pages = pageText ? parseInt(pageText) : undefined;
language would be undefined if the element wasn't found.
If you can't use it (because your target environments don't have it yet and you're not transpiling), you could pass in a function that getRowElementFromText will call if the element is found:
let html = $(response.body);
let language = getRowElementFromText('Language', html, el => el.find('a').text());
let pages = getRowElementFromText('Page', html, el => parseInt(el.text()));
getRowElementFromText would look like this:
function getRowElementFromText(text, html, callback) {
const result = /*...existing logic...*/;
return result === undefined ? undefined : callback(result);
}
Or perhaps like this (callback is optional):
function getRowElementFromText(text, html, callback) {
const result = /*...existing logic...*/;
return !callback || result === undefined ? result : callback(result);
}
Note: In the above I'm assuming getRowElementFromText does actually return undefined in some cases ("not found"), and returns a jQuery object in other cases (found). I flag this up because if it always returns a jQuery object (which may be empty), jQuery objects are never falsy (not even empty ones).
Add the following function to jQuery prototype. It receives a fallback string as an argument and when element is not found, returns fallback string:
$.prototype.getText = function(fallback = undefined) {
return this.length > 0 ? this.text() : fallback
}
console.log($("#foo").getText())
console.log($("#bar").getText("If the element does not exists, returns given fallback string"))
console.log(`Fallback string default value is: ${$("#baz").getText()}`)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label id="foo">If the element exists, return its inner text</label>
Usage:
$(selector).getText(fallbackString);
I'm writing a function that searches an array from a prompt using an IF statement. So for I get the output I want if it is successful, but it also outputs the failure.
// The array I'm searching through
var statesArray = new Array();
statesArray['WI'] = "Wisconsin";
statesArray['MN'] = "Minnesota";
statesArray['IL'] = "Illinois";
// Now I'm trying to let the user search for the full state name from the two-letter abbreviation.
var stateSearch = prompt("enter a two letter state abbreviation")
for(var key in statesArray){
var value = statesArray[key]
if(stateSearch == key){
alert(value);
}else{
alert("try again");
}
}
So if I type "WI" in the prompt, I get "Wisconsin" and "try again".
Loop is not appropriate for this. Loop will check all values of array and check found or not for all.
var statesArray = new Array();
statesArray['WI'] = "Wisconsin";
statesArray['MN'] = "Minnesota";
statesArray['IL'] = "Illinois";
// Now I'm trying to let the user search for the full state name from the two-letter abbreviation.
var stateSearch = prompt("enter a two letter state abbreviation");
let searchObj =statesArray[stateSearch];
if(searchObj == null){
alert("try again");
}else{
alert(searchObj);
}
Your for loop isn't exiting after it has found a match. Either put it in a function and return when you find a match or break the for loop
for(var key in statesArray){
var value = statesArray[key]
if(stateSearch == key){
alert(value);
break;
}else{
alert("try again");
}
}
function searchArray(arr){
for(var key in arr){
var value = arr[key]
if(stateSearch == key){
return value;
}
}
}
Note: Array was created to store a numbered list of elements. If you need to get values by keywords, it's better idea to use a common object instead:
var statesArray = {
WI: "Wisconsin",
MN: "Minnesota",
IL: "Illinois",
null: "Cancelled!"
};
var abbr = prompt("enter a two letter state abbreviation");
alert( statesArray[abbr] || "try again" );
// will return the value, if `abbr` key exists,
// and "try again" if statesArray[abbr] returned `undefined`
Instead of using a for loop, you could use:
var index = statesArray.indexOf(stateSearch);
which will set index to -1 if the value is not found, otherwise it will be set to the position in the array that the value was found.
Like some of the other comments and answers said, you're not breaking/exiting your loop early when a match is found, which is why you are continuing to iterate through even after you find the right value. Had you searched for MI, for example, you would see:
try again
Minnesota
try again
Firstly, it's generally considered a better practice, from a performance and ease of reading standpoint, to create Array literals than to use the JavaScript new keyword to create an Array instance.
Secondly, JavaScript does not technically allow for Associative Arrays (Hashes/HashMaps) (in other words, arrays with named indices). From the MDN Developer Documentation for Arrays:
Arrays cannot use strings as element indexes (as in an associative array) but must use integers. Setting or accessing via non-integers using bracket notation (or dot notation) will not set or retrieve an element from the array list itself, but will set or access a variable associated with that array's object property collection.
In your case, I would think a simple object or Map would suit you better. For Map:
// Define the Map.
const statesHashMap = new Map([
['WI', 'Wisconsin'],
['MN', 'Minnesota'],
['IL', 'Illinois']
]);
// You could also do:
statesHashMap.set('TX', 'Texas');
// Attain user input:
const stateSearchKey = prompt("Enter a two letter state abbreviation");
Then, to iterate over and find the right state, you'd have a few different options:
// Iterate with forEach.
statesHashMap.forEach((value, key) => {
if (stateSearchKey === key) {
console.log(value)
}
});
// Iterate with for..of
for (const key of statesHashMap.keys()) {
if (stateSearchKey === key) {
const state = statesHashMap.get(key);
console.log(state);
}
}
The problem with the forEach method above is that you can't break out without throwing an exception, which makes the for..of loop likely more favorable for you. If you want to show a message to the user when they don't get a state, you could use break statements:
// Iterate with for..of
for (const key of statesHashMap.keys()) {
if (stateSearchKey === key) {
const state = statesHashMap.get(key);
console.log(state);
break;
} else {
console.log('Try again');
break;
}
}
Or, to make it look nicer and as a better programming practice, encapsulate the operation in a function and return early when needed:
// Iterate with for..of
const findStateByAbbreviation = abbrev => {
for (const key of statesHashMap.keys()) {
if (stateSearchKey === key) {
const state = statesHashMap.get(key);
return console.log(state);
} else {
return console.log('Try again');
}
}
}
findStateByAbbreviation(stateSearchKey);
You should probably also use .toUpperCase() on the input from the user to ensure you match the key WI (for example) if the user provides wi.
Be wary of browser compatibility with the Map option, however.
Hope this helps.
First of all I am not an expert on JavaScript, in fact I am newbie.
I know PHP and there are functions to get all occurences of a regex pattern preg_match() and preg_match_all().
In the internet I found many resources that shows how to get all occurences in a string. But when I do several regex matches, it looks ugly to me.
This is what I found in the internet:
var fileList = []
var matches
while ((matches = /<item id="(.*?)" href="(.*?)" media-type="(?:.*?)"\/>/g.exec(data)) !== null) {
fileList.push({id: matches[1], file: matches[2]})
}
fileOrder = []
while ((matches = /<itemref idref="(.*?)"\/>/g.exec(data)) !== null) {
fileOrder.push({id: matches[1]})
}
Is there a more elegant way other than this code?
Using regexes on html is generally held to be a bad idea, because regexes lack sufficient power to reliably match a^n b^n arbitrarily nested occurrences such as balanced parens or HTML/XML open/close tags. Its also trivially easy to get data out of the DOM in JavaScript without treating it like a string, that's what the DOM is for. For example:
let mapOfIDsToFiles = Array.from(document.querySelectorAll('item'))
.reduce((obj, item) => {
obj[item.id] = item.href;
return obj;
}, {});
This has the added advantage of being much faster, simpler, and more robust. DOM access is slow, but you'll be accessing the DOM anyway to get the HTML you run your regexes over.
Modifying built-in prototypes like String.prototype is generally held to be a bad idea, because it can cause random breakages with third-party code that defines the same function but differently, or if the JavaScript standard gets updated to include that function but it works differently.
UPDATE
If the data is already a string, you can easily turn it into a DOM element without affecting the page:
let elem = document.createElement('div')
div.innerHTML = data;
div.querySelectorAll('item'); // gives you all the item elements
As long as you don't append it to the document, its just a JavaScript object in memory.
UPDATE 2
Yes, this also works for XML but converting it to DOM is slightly more complicated:
// define the function differently if IE, both do the same thing
let parseXML = (typeof window.DOMParser != null && typeof window.XMLDocument != null) ?
xml => ( new window.DOMParser() ).parseFromString(xml, 'text/xml') :
xml => {
let xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');
xmlDoc.async = "false";
xmlDoc.loadXML(xml);
return xmlDoc;
};
let xmlDoc = parseXML(data).documentElement;
let items = Array.from(xmlDoc.querySelectorAll('item'));
Note that if the parse fails (i.e. your document was malformed) then you will need to check for the error document like so:
// check for error document
(() => {
let firstTag = xmlDoc.firstChild.firstChild;
if (firstTag && firstTag.tagName === 'parsererror') {
let message = firstTag.children[1].textContent;
throw new Error(message);
}
})();
I came up with the idea of creating a method in String.
I wrote a String.prototype that simplyfy things for me:
String.prototype.getMatches = function(regex, callback) {
var matches = []
var match
while ((match = regex.exec(this)) !== null) {
if (callback)
matches.push(callback(match))
else
matches.push(match)
}
return matches
}
Now I can get all matches with more elegant way. Also it's resembles preg_match_all() function of PHP.
var fileList = data.getMatches(/<item id="(.*?)" href="(.*?)" media-type="(?:.*?)"\/>/g, function(matches) {
return {id: matches[1], file: matches[2]}
})
var fileOrder = data.getMatches(/<itemref idref="(.*?)"\/>/g, function(matches) {
return matches[1]
})
I hope this helps you too.
var status = result.locations[index].status;
var operator = result.locations[index].operator;
var original = result.locations[index].original;
var produced = result.locations[index].produced;
var href = result.locations[index].more;
I have the above which each need to be an if statement to check if there is content and my output is the below code.
if (result.locations[index] && result.locations[index].status){
var status = result.locations[index].status;
} else {
var status = '';
}
I would need to reproduce this per line from the code at the top of the post. What would be the best method to simplify each down to keep the code neater and not produce 5 lines of if statement when 1 or 2 would do.
var status = (result.locations[index] && result.locations[index].status ? result.locations[index].status : '');
Not sure why you want to, but:
var status = (result.locations[index] && result.locations[index].status) ? result.locations[index].status : ""
Your problem is trying to access a property of a "deep" javascript object using its path.
This is a common question :
Javascript: Get deep value from object by passing path to it as string
Accessing nested JavaScript objects with string key
There is no built-in way to do this in javascript.
There are plenty of libraries to do that, for example, with selectn, this would become something like (I have not tested it, so I don't know if the index part will work, but you get the idea) :
var status = selectn("locations." + index + ".status", result) || ''
If the structure of your objects is always the one above (that is, the property is just at one level of depth), and you're not expecting 'falsy', you could simply write the 'test' function yourself :
function safeGet(instance, propertyName, defaultValue) {
// As pointed by AlexK, this will not work
// if instance[propertyName] can be anything Falsy ("", 0, etc...)
// If it's possible, get a library that will do
// the full series of insane checks for you ;)
if (instance && instance[propertyName)) {
return instance[propertyName];
} else {
return defaultValue;
}
}
var location = result.locations[index]; // Potentially undefined, but safeGet will deal with it
var status = safeGet(location, "status", "");
var operator = safeGet(location, "operator", "DEFAULT_OPERATOR");
...
var status = result.locations[index] && result.locations[index].status || '';
However, better maje sure before, if result.locations[index] exists... else do whatever is to be done in your code..
I created a custom css selector engine function for my custom javascript library like so,
var catchEl = function(el) { // Catching elements by identifying the first character of a string
var firstChar = el[0],
actualNode = el.substring(1, el.length),
elements,
tempElems = [];
if (!document.querySelectorAll) {
try{
if(firstChar === "#") {//So, we can look for ids
tempElems.push(document.getElementById(actualNode));
} else if(firstChar === ".") {//or classes
elements = document.getElementsByClassName(actualNode);
for(i=0;i<elements.length;i++) tempElems.push(elements[i]);
} else {//or tags
elements = document.getElementsByTagName(el);
for(i=0;i<elements.length;i++) tempElems.push(elements[i]);
}
} catch(e) {};
} else {//but before everything we must check if the best function is available
try{
elements = document.querySelectorAll(el);
for(i=0;i<elements.length;i++) tempElems.push(elements[i]);
} catch(e) {};
}
return tempElems;
}
This function returns an array of elements. However, I turned my head around and tried to make it more flexible so that it can also return the window, document or this object, but was unsuccessful. Whenever I try to push the window object into the tempElems array, the array is still empty.
So, I want to know how to make this function return an array of elements when a string is passed through it or return the respective objects(window, document or this) as desired.
Note: I don't want to work with jQuery. So, please don't post any answers regarding jQuery.