I'm new to javascript, and scratching my head over this issue:
I used to use the following to grab a bunch of product titles from the page:
CODE 1:
var productTitles = document.querySelectorAll(".product-title");
Then I used the following code to ad a list of these titles to a form's textarea field:
CODE 2:
var MyTextAreaField = document.querySelector("#my-textarea-field");
MyTextAreaField.value = [...productTitles].map(el=>el.textContent).filter(txt=>txt !== 'None').join('\n');
The above worked great, however, I just changed CODE 1 to be a function instead (in order to conditionally return product titles)
The below code is just a rough example of what it looks like:
CODE 3:
var productTitleOne = document.querySelectorAll(".product-title1");
var productTitleTwo = document.querySelectorAll(".product-title2");
var productTitleThree = document.querySelectorAll(".product-title2");
function createProductTitles() {
if (productTypeOne == null) {
return productTitleOne.textContent;
} else if (productTypeTwo == "None") {
return productTitleTwo.textContent;
} else {
return productTitleThree.getAttribute("data-qty") + 'x' + selectionItem.textContent ;
}
}
Problem is, now code 2 no longer works, because it is no longer an Array
So, I tried doing this (adding my function to a variable):
var productTitles = createProductTitles();
But the following still doesn't work, because it's still not really an array
MyTextAreaField.value = [...productTitles].map(el=>el.textContent).filter(txt=>txt !== 'None').join('\n');
So how do I get the result of my function to post to my textarea field?
The problem is the value you're returning on createProductTitles in the Code 1 you're using the array returned by var productTitles = document.querySelectorAll(".product-title"); in the Code 3 you're returning the textContent of that selector, i.e. return productTitleOne.textContent;.
It's important to make a distinction between these two codes because they're sending different data types one is returning an array and the other is returning a string.
So, you need to change your function to return the same the result of the querySelectorAll function
var productTitleOne = document.querySelectorAll(".product-title1");
var productTitleTwo = document.querySelectorAll(".product-title2");
var productTitleThree = document.querySelectorAll(".product-title2");
function createProductTitles() {
if (productTypeOne == null) {
return productTitleOne;
} else if (productTypeTwo == "None") {
return productTitleTwo;
} else {
return productTitleThree
}
}
and then use your function
var productTitles = createProductTitles();
Your function in CODE 3 needs to change.
document.querySelectorAll() returns a NodeList (similar to an array). So it's no good to then try and access the textContent property or call getAttribute() as you do in that function, both of which should instead be called (if desired) on the individual Nodes in the NodeList.
You can modify that function so that the calls you have made take place on the individual Nodes using the spread operator and map function, similarly to how you did in CODE 2:
function createProductTitles() {
if (productTypeOne == null) {
return [ ...productTitleOne].map( productTitles => productTitles.textContent );
} else if (productTypeTwo == "None") {
return [ ...productTitleTwo].map( productTitles => productTitles.textContent );
} else {
return [...productTitleThree].map( productTitles => productTitles.getAttribute("data-qty") + 'x' + selectionItem.textContent );
}
}
This function will return an array of string values that you can do with as you wish, such as:
createProductTitles().filter(txt=>txt !== 'None').join('\n');
Related
For the code below, I wanted to make the _formsOk function work for both Javascript arrays and "JQuery objects". In function1(), I tried to create a Javascript array with all DOM elements except those that have a parent element with id="objectTypesContainer". Basically, function1() filters out the DOM elements I don't want before calling _formsOk() function, which does the actual form validation.
function1() {
var allForms = $('form:not(.vv_hidden)', this.selectMarketsContainer);
var nonObjectTypeForms = [];
allForms.each(function () {
if ($(this).parent().attr("id") !== "objectTypesContainer"){
nonObjectTypeForms.push($(this)[0]);
}
});
return this._formsOk(nonObjectTypeForms);
},
_formsOk: function($forms) {
var formOk = true;
console.log(typeof $forms)
$forms.each(function () { // This line fails
var validator = $(this).validate(DEFAULT_VALIDATION_OPTIONS);
if (!(validator && validator.form())) {
formOk = false;
}
});
return formOk;
},
However, I realized that because nonObjectTypeForms is now a JS Array rather than a "JQuery Object", the line marked (// This line fails) now fails.
The original code looked like this:
function1() {
var allForms = $('form:not(.vv_hidden)', this.selectMarketsContainer); // This is a "JQuery object", so no error occurs
return this._formsOk(allForms);
},
_formsOk: function($forms) {
var formOk = true;
console.log(typeof $forms)
$forms.each(function () { // This line fails
var validator = $(this).validate(DEFAULT_VALIDATION_OPTIONS);
if (!(validator && validator.form())) {
formOk = false;
}
});
return formOk;
},
Is there a way I can convert a JS array into a JQuery object ? I don't want to change _formsOk function definition just yet.
Instead of putting all elements in a new array, just use .filter() from the jQuery object.
allForms.filter(function () {
return $(this).parent().attr("id") !== "objectTypesContainer")
});
This will remove all the items you don't need in your selection and now allForms will only have the wanted elements.
I am working through a code challenge and I need to return a string into a variable if the guess passed in attemptAnswer(guess) matches the answer property's value. My test is failing currently saying that the variable response is undefined.
Is this some sort of binding issue?
...curious how this problem could be resolved.
Thank you!
class Sphinx {
constructor() {
this.name = null;
this.riddles = [];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) { this.riddles.shift() };
}
attemptAnswer(guess) {
this.riddles.forEach( (element, index) => {
if (guess === element.answer) {
this.riddles.splice(index, 1);
return "That wasn't that hard, I bet you don't get the next one."
};
})
}
}
//test
const response = sphinx.attemptAnswer('short');
assert.equal(response, 'That wasn\'t that hard, I bet you don\'t get the next one');
When you return in attemptAnswer() you're actually retuning to the inner forEach callback function you defined: (element, index) => {..., not the outer attemptAnswer() method.
Instead of immediately returning within your forEach loop, you can set a variable outside this loop called result, and then return the result once your forEach loop is complete.
Also, currently, you're not creating a new instance of Sphinx, which means you don't have an object which can call the attemptAnswer() method. To fix this add new Sphinx() to create a new Sphinx object.
See example below:
class Sphinx {
constructor() {
this.name = null;
this.riddles = [{"answer":"short"}];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) {
this.riddles.shift()
};
}
attemptAnswer(guess) {
let res = "";
this.riddles.forEach((element, index) => {
if (guess === element.answer && !res) {
// no need for splice as it will skip an entry
res = "That wasn't that hard, I bet you don't get the next one.";
};
})
return res;
}
}
const response = new Sphinx();
response.collectRiddle({"answer":"short"});
console.log(response.attemptAnswer('short'));
you're never calling collectRiddle so this.riddles is always [] and the forEach block is never entered, therefore, not returning anything, so, the return value is undefined
you should have a variable called found right before the loop, if you find a match, set it to truethen return the string depending on the found variable :
note : the string inside the function is different from the one you're comparing it to (it has backslashes and ends with a dot) so the test will always be falsy
class Sphinx {
constructor() {
this.name = null;
this.riddles = [];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) {
this.riddles.shift()
};
}
attemptAnswer(guess) {
var found = false;
this.riddles.forEach((element, index) => {
if (guess === element.answer) {
found = true;
}
})
return found ? "Woohoo" : "That wasn't that hard, I bet you don't get the next one."
}
}
//test
const s = new Sphinx();
const response = s.attemptAnswer('short');
console.log(response === `That wasn't that hard, I bet you don't get the next one.`);
I assume you already did const sphynx = new Sphynx().
attemptAnswer() doesn't return anything, in Javascript, if you don't return anything, you basically return undefined. So it is normal that response is undefined.
In your case, I would use for-loop, instead of forEach.
attemptAnswer(guess) {
for (let i = 0; i < this.riddles.length; i++) {
if (guess === this.riddles[i].answer) {
this.riddles.splice(index, 1);
return "That wasn't that hard, I bet you don't get the next one.";
}
}
return "Not found";
}
Using .splice() inside forEach is not recommended
using forEach, will go through all the items inside the array, even if you already found your answer.
I'm trying to get either options or, ideally, dynamicTable passed from initializeTable to the applyTableFilters function and I'm having problems getting the expected values. I'm using List.js to make a table dynamic and I need to pass or recreate the dynamicTable object so I can go ahead and use it to filter the table.
Here is the function that creates the List.js object from the HTML table:
function initializeTable(options) { // initializes table to be dynamic using List.js functions
var dynamicTable = new List("table-content", options);
dynamicTable.on("updated", function (list) { // writes a message to the user if no results are found
if (list.matchingItems.length == 0) {
document.getElementById("no-results").style.display = "block";
}
else {
document.getElementById("no-results").style.display = "none";
}
});
console.log(dynamicTable);
console.log(options);
console.log(arguments.length);
applyTableFilters.bind();
}
I've tried different methods to pass the variables to the function below. I tried .call, applyTableFilters(args), and .apply, but the problem is that I do not want the function to execute from inside here, only when the click event from the button goes off (not shown in these functions).
This is the function I want to pass the object to and proceed to make the filter functions using it:
function applyTableFilters(dynamicTable) {
var form = document.getElementById("filter-form");
//console.log(options);
//var dynamicTable = new List("table-content", options);
console.log(dynamicTable);
var filters = form.querySelectorAll('input[type="checkbox"]:checked');
dynamicTable.filter(function (item) {
console.log(item);
console.log(item._values);
if (item.values().id == 2) {
return true;
}
else {
return false;
}
//var filterStrings = [];
//console.log(filters);
//for (var i = 0; i < filters.length; i++) {
// var filterVal = filters[i].value;
// var filterString = "(" + item.values().column == filterVal + ")"; // filterVal.contains(item.values().column) ||
// filterStrings.push(filterString);
// console.log(filterVal);
// console.log(filterString);
//}
//console.log(filterStrings);
//var filterString = filterStrings.join(" && ");
//console.log(filterString);
//return filterString;
});
}
I've used:
applyTableFilters.bind(this, dynamicTable/options);
applyTableFilters.bind(null, dynamicTable/options);
applyTableFilters.bind(dynamicTable/options);
Switching between the two since I don't need both passed if one ends up working, etc. I always get a mouse event passed in and that's not even the right type of object I'm looking for. How can I get the right object passed? Also all the values in the first function are not empty and are populated as expected so it's not the original variables being undefined or null. Thanks in advance.
From your initializeTable function return a function that wraps the applyTableFilters function with the arguments you want.
Then assign the returned function to a var to be executed later.
function initializeTable(options) {
var dynamicTable = new List("table-content", options);
// other stuff
return function () {
applyTableFilters(dynamicTable)
}
}
// other stuff
var applyTableFiltersPrep = initializeTable(options)
// later, when you want to execute...
applyTableFiltersPrep()
JSFiddle example
So, I have this little code in my js file:
window.onload = function Equal() {
var a = 'b1'
var b = 'box1'
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
var a = 'b2'
var b = 'box2'
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
The function itself is not important (it equals checkboxvalues set in the localstorage), but I execute it 2 times. First time with var a & b set to 'b1' & 'box1'. Then I run the script again (same script), but with var a & b set to 'b2' & 'box2'. Now, this code works, but my question is if there is a shorter way to write this? I can imagine some sort of array with a loop, but I could not get it to work for some reason. The 2 variables are pairs, and I know this might be a dumb question, but I can't find the answer anywhere.
You can use a second function which will accept the local storage key and the checkbox id like
window.onload = function Equal() {
setCheckboxState('box1', 'b1');
setCheckboxState('box2', 'b2');
}
function setCheckboxState(id, key) {
document.getElementById(id).checked = 1 == localStorage.getItem(key);
}
You might separate common logic into another function
window.onload = function Equal() {
function extractFromStorage(a, b) {
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
extractFromStorage('b1', 'box1');
extractFromStorage('b2', 'box2');
}
function doTheStuff(a, b) {
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
window.onload = function Equal() {
doTheStuff('b1', 'box1');
doTheStuff('b2', 'box2');
}
?
This is how I would do it.
There are several problems with your code.
You do not check that the element you are stetting an attribute to
exists. You do not check if the localStorage item you get is
defined.
You pollute the global name space with the function name Equal.
That function should not be named with a capital as it is not a Object generator.
There is no need to use setAttribute and removeAttribute, in
fact removeAttribute makes no sense in this case as you can not
remove the checked attribute from the element. BTW why use setAttribute here and not for window.onload?
The checked attribute is either true or false, it does not use the
string "checked"
Binding the load event via the onload attribute is not safe as you may
block 3rd party code, or worse 3rd party code may block you.
There is no error checking. DOM pages are dynamic environments, pages
have adverts and content from many places that can interfer with your
code. Always code with this in mind. Check for possible errors and deal with them in a friendly way for the end user. In this case I used an alert, not friendly for a normal user but for you the coder.
My solution.
// add an event listener rather than replace the event listener
window.addEventListener(
"load", // for the load event
function(){
// the update function that is called for each item;
var update = function(item){
// the right hand side equates to true if the localstorage
// is equal to "1". LocalStorage allways returns a string or
// undefined if the key is not defined.
item.element.checked = localStorage[item.storageName] === "1";
}
// safe element getter
var getElement = function(eId){
var e = document.getElementById(eId); // try and get the element
if(e === null){ // does it exist?
throw "Missing element:"+eId; // no then we can not continue
// the program stops here unless
// you catch the error and deal with
// it gracefully.
}
return e; //ok return the element.
}
// Item creator. This creates a new item.
// sName is the local storage name
// eId id the element ID
var item = function(sName, eId){
return {
storageName: sName, // set the loaclStorage name
element:getElement(eId); // get the element and check its safe
};
}
// make it all safe
try{
// create an array of items.
var items = [
item("b1","box1"),
item("b2","box2")
];
// for each item update the element status
items.forEach(update);
}catch(e){
alert("Could not update page?");
}
}
);
Ok, so I'm a complete newbie to OOP in Javascript, apparently. I thought I understood it, but it appears I only know a small portion. Anyway, what I'm trying to do is setup an object to store and return data from an XML input by using a fairly simple string to retrieve data. I'd like to retrieve the data with a string similar to reader.getItem().getSubItem() or something like that.
Below is an example of what I attempted, but I get the error anonymous is not a function each time I try to do a call to fr.getType().isTexture() so obviously, I need to change something.
//Create the object by passing an XML element containing sub-elements
var fr = new FeatureReader(test.child(i));
alert(fr.getName()); //returns the object's name
alert(fr.getType().isTexture()); //"anonymous is not a function" error
function FeatureReader(feature) {
var feat = feature;
this.getName = function() {
return feat.name;
};
this.getType = new function() {
this.isTexture = new function() {
if (feat.type.texture == "yes") {
return true;
}
return false;
};
this.isModel = new function() {
if (feat.type.model == "yes") {
return true;
}
return false;
};
};
}
Now, obviously I could just remove the surrounding this.getType = function() {} around the this.isTexture and this.isModel to get my data, but for the sake of learning something, I'd like to see how it is recommended that I set this object up to get the returned values using a string similar to what I mentioned in the first and second paragraphs.
When you do this:
this.isTexture = new function() {
if (feat.type.texture == "yes") {
return true;
}
return false;
};
you're setting the "isTexture" property to the object constructed, not to that function. If you drop the new keyword from the statement, you'll be setting "isTexture" to be a function.
An expression of the form new <some-function> evaluates to an object, in other words.
edit — your "getType" property will also be an object, for the same reason. However, I think this would work:
alert( fr.getType.isTexture() );
Also note that your if statement can be simplified:
return feat.type.texture == "yes";
What you can do is simply assign an object instead of using new:
function FeatureReader(feature) {
var feat = feature;
this.getName = function() {
return feat.name;
};
this.getType = {
isTexture: function() {
return feat.type.texture == "yes";
},
isModel: function() {
return feat.type.model == "yes";
}
};
}
Then use the method like:
instance.getType.isTexture()
Note that you don't need to return true or false, as returning an expression that evaluates to boolean like a == b
returns a boolean value.