This may seem like a duplicate question, and to some extent, it is, but I have already been through many similar questions, and sadly, none have suited my need. I would really appreciate problem-specific advice.
My main problem in the JavaScript code here is that I cannot access the values in the variables RememberText20 and RememberFullText, in function TextLimiter, from function ReadMoreLessText. The "Message" is an argument for the ReadMoreLessText function, which essentially matches the element clicked to the correct value in the aforementioned variables, which are themselves arrays.
*I know there is nothing wrong with the arrays themselves, as they retain their values as they are supposed to, because a simple alert() proves this. Similarly, there is nothing wrong with the Message argument, as the function ReadMoreLessText works fine with other values.
My simple problem is that I cannot access the values in the aforementioned variables, from the ReadMoreLessText function, although they are global variables, as they should be.
I would really appreciate a problem-specific answer here. Thank you in advance.
// JavaScript Document
//Start Text250
window.onload = function TextLimiter() {
for (y = 0; y < 6; y++) {
FullText = document.getElementsByClassName("Introduction")[y].innerHTML;
TextLength = FullText.length;
RememberFullText = [];
RememberFullText[y] = FullText;
var Text250 = FullText.substr(0, 250) + "...";
RememberText250 = [];
RememberText250[y] = Text250;
if (TextLength > 250) {
document.getElementsByClassName("Read_More")[y].innerHTML = "Read More→";
document.getElementsByClassName("Introduction")[y].innerHTML = Text250;
} else {
document.getElementsByClassName("Read_More")[y].innerHTML = "";
}
}
};
//End Text250
//Start ReadMoreLessText
var ReadMore = function(Message) {
var ScreenText = document.getElementsByClassName("Introduction")[Message].innerHTML;
if (ScreenText === RememberText250[Message]) {
document.getElementsByClassName("Introduction")[Message].innerHTML = RememberText250[Message];
} else {
document.getElementsByClassName("Introduction")[Message].innerHTML = RememberText250[Message];
}
};
//End ReadMoreLessText
Try defining RememberFullText and RememberText250 outside the enclosing for loop.
window.onload = function TextLimiter() {
RememberFullText = [];
RememberText250 = []
for (y = 0; y < 6; y++) {
...
As written they are set to an empty array in each iteration of the loop. Hence only the last entry of each array will be retained after the loop has finished.
I don't see your variables declared as globals. Do you have a var RememberText20, RememberFullText; outside any function?
Related
I am trying to write a javascript program which stores the value from an input element in an array when a button is clicked. The array is the split and each individual letter added to a span element and then appended to the document. The idea is to create a typing effect using setTimeout.
I am running into an issue creating a closure within the loop, so currently the setTimeout function always returns the final value of the iteration.
The function in question is at the bottom of the code block and called addTextToBoard();
var noteButton = document.querySelector('[data-js="button"]');
noteButton.addEventListener("click",function() {
var messageIn = document.querySelector('[data-js="input"]');
var message = messageIn.value;
postToBoard(message);
});
function postToBoard(val) {
var noteBoard = document.querySelector('[data-js="noteboard"]');
var newElement = document.createElement('div');
newElement.classList.add('noteboard__item');
noteBoard.appendChild(newElement);
setTimeout(function(){
newElement.classList.add('active');
}, 200);
addTextToBoard(newElement, val);
}
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(function(x){
return function() {}
el.appendChild(letter);
}(i),1000);
}
}
I believe I am close, I'm just not fully understanding the syntax for creating the closure. If someone could give poke in the right direction, without necessarily giving the full solution that would be great.
I essentially tried to paste in the following code snippet from here but I've missed something somehwere along the way!
setTimeout(function(x) { return function() { console.log(x); }; }(i), 1000*i);
Best,
Jack
You are close.
Since the "letter" variable changes, you'll add only the last letter over and over again. You need to "save" the current letter on the setTimeout() callback function, One way to go is like this:
function appendMyLetter(letter) {
return(function() {
el.append.Child(letter);
});
}
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(appendMyLetter(letter), 1000);
}
}
This way, the appendMyLetter() function gets called with a different parameter (one for each letter) and returns a function with the correct "stored" value to be called by setTimeout().
EDIT
Looking at your setTimeout() code closely
setTimeout(function(x){
return function() {}
el.appendChild(letter);
}(i),1000);
It would work fine, if you used the proper parameters and used the appendChild() inside the returned function, like so:
setTimeout(function(x){
return(function() {
el.appendChild(x);
});
}(letter),1000);
You can create an immediately-invoked function expression IIFE to create a closure
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
(function(index) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(function(){
el.appendChild(letter);
},1000);
})(i);
}
}
I dont know if this will work but here you go a slight change in operator:
letter.innerHTML += wordArray[i];
if you dont get the effect you imagined you will get you better try to increment the timer by i like this
setTimeout(function(){
...
},1000*i);
Why is m "undefined" in this code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for(var i=0;i<currentViewModel.availableReports().length;i++) {
if(currentViewModel.availableReports()[i].id == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
I call getReport() as an onclick event and I want to send the report object to a view (modal) I can do a foreach on the availableReports and it's all there. When I run through the debugger, it loops through the array and finds the right one. But why can't I pull it out of the array? "m" remains undefined the the function returns undefined.
What am I missing here?
EDIT: there is a follow up question here:
Can knockout.js wait to bind until an onClick?
You just need to change if(currentViewModel.availableReports()[i].id ... to if(currentViewModel.availableReports()[i].id() ... because after mapping id will become an observable, i.e. function.
Updated code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id() == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
Demo - Fiddle.
I'll repeat the solution from #NikolayErmakov's answer here, but want to add two things to get a more complete answer. You end with:
...m remains undefined and the function returns undefined.
What am I missing here?
You're missing two things:
The var m bit of the first statement inside the if is hoisted to the top of the current scope (the top of the function). This is why the debugger can tell you what m is, even if you never reach the line of code it's on.
If a function invocation reaches the end of a function (as is the case for you, since you never go inside the if) without seeing an explicit return statement, it will return undefined.
To better understand this, you should interpret your function like this:
currentViewModel.getReport = function(reportId) {
var m;
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id == reportId) {
m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
return undefined;
}
Some people (e.g. Douglas Crockford) do recommend placing var statements at the top of a function, though it's a matter of style to some degree. I don't think many people explicitly return undefined at the end of a function, though in your case I might be explicit about that scenario and return null (or throw an Error even).
As promised, I'll repeat the actual solution, as I concur with the other answer:
you need to invoke id as a function to get its value (because the mapping plugin will map to observable()s.
In addition:
I'd retrieve the array only once
I'd suggest using === instead of ==
Here's my v0.5 version:
currentViewModel.getReport = function(reportId) {
var m = null, reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
m = reports[i];
return m;
}
}
return m;
}
But I'd optimize it to this v1.0:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
return reports[i];
}
}
return null;
}
For completeness, here's another version that utilizes filter on arrays:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports().filter(function(r) { return r.id() === reportId; });
return reports.length >= 1 ? reports[0] : null;
}
Background
I have a plain JS array, initially empty. I later populate it with values. The values sent to it are numbers that are Knockout observables. Later, I want to compare those values to values in another, knockout observable array. My problem is that whenever I pass the index of the current item in my array loop, and pass that index value (a number!), the array returns a function. To get an idea, look at the JS that follows.
Note that my project and actual script is viewable on JSBin. Further, to view the problem in the console, you have to add assignments, then press 'sort'.
JSBin: http://jsbin.com/fehoq/177/edit]1
JS
//example script that follows actual script
var _this = this;
//initialize my array
this. lowest = [];
// I want to compare values in lowest to values in this array
this.scores = ko.observableArray();
// method that does comparison
this.myMethod = function(){
// initialize my helper, k
var k;
...
// loop through one array
ko.utils.arrayForEach(_this.scores(), function (score) {
// make sure my value is a number...
if (!isNaN(parseFloat(score()))) {
// this is important, I need to current index for comparison
k = _this.scores.indexOf(score);
console.log(k);
// this is where things break - it prints a function, not a value!
console.log(_this.lowest[k]);
// useless check, the value is a function, so they're always different
if (score()!=_this.lowest[k]){
// do stuff
}
}
}
}
Update
Putting the method I'm using, maybe someone will notice something I missed given that my syntax is correct(?).
this.mean = (function(scores,i) {
var m = 0;
var count = 0;
var k;
ko.utils.arrayForEach(_this.scores(), function(score) {
console.log([typeof score(), score()]);
if (!isNaN(parseFloat(score()))) {
console.log(i);
console.log(_this.lowest[i]);
if (score() != _this.lowest[i]) {
m += parseFloat(score());
count += 1;
}
}
});
if (count) {
m = m / count;
return m.toFixed(2);
} else {
return 'N/A';
}
});
}
Update 2
Just in case someone else wanders over here since my problem isn't solve still. The following code is how I set the value of lowest:
this.dropLowestScores = function() {
ko.utils.arrayForEach(_this.students(), function(student){
var comparator = function(a,b){
if(a()<b()){
return 1;
} else if(a() > b()){
return -1;
} else {
return 0;
}
};
var tmp = student.scores().slice(0);
tmp.sort(comparator);
student.lowest = ko.observableArray(tmp.splice((tmp.length-2),tmp.length-1));
});
};
Outstanding Questions, 5/9/2014
Jeremy's script runs but without the desired effects. For example, console.log(_this.lowest[k]) prints undefined, just as mine does. Further, the matched scores aren't skipped, which they should be.
Jeremy's script specifies lowest as a ko.observable. My script also now has lowest as a ko.observable, but why shouldn't a plain JS array work for this? I only need lowest to update when the button it's bound to is clicked, and those bindings are already taken care of.
That is how observables work in Knockout.
When you create one, you are creating a function.
var myObservable1 = ko.observable(); // Create it.
var myObservable2 = ko.observable("Hola!"); // Create it with a value.
console.log(typeof myObservable2); // It is indeed a function
console.log(typeof myObservable2()); // That returns a string
console.log(myObservable2()); // And get the value.
EDIT BASED ON QUESTION IN COMMENTS
var koTest = ko.observableArray();
koTest.push("Line0");
koTest.push("Line1");
koTest.push("Line2");
koTest.push("Line3");
koTest.push("Line4");
var jsTest = [];
jsTest.push("Line0");
jsTest.push("Line1");
jsTest.push("Line2");
jsTest.push("Line3");
jsTest.push("Line4");
alert(koTest()[2]);
alert(jsTest[2]);
alert(koTest()[2] === jsTest[2]);
Test Code
I went ahead and make a runnable test of your code and everything was working just fine for me. I had to make some assumptions about the contents of _this -- in particular the declaration of lowest, which I made an observableArray based on how you were accessing it.
Anyways, this code runs:
var _this = {
scores: ko.observableArray(),
lowest: ko.observableArray()
};
var mean = (function(scores) {
var m = 0;
var count = 0;
var k;
ko.utils.arrayForEach(_this.scores(), function(score) {
console.log([typeof score(), score()]);
if (!isNaN(parseFloat(score()))) {
k = _this.scores.indexOf(score);
console.log(k);
console.log(_this.lowest[k]);
if (score() != _this.lowest[k]) {
m += parseFloat(score());
count += 1;
}
}
});
if (count) {
m = m / count;
return m.toFixed(2);
} else {
return 'N/A';
}
});
for (var i = 0; i < 10; i++) {
_this.scores.push(ko.observable(i));
}
var m = mean();
alert(m);
I have a function which works just fine the first if I call it just once, but when I call it repeatedly within a for loop, I get the following error:
TypeError: getNamedRange is not a function, it is string.
Doing a search on this error gives me a clue that this is a javascript error, not a Google Apps Script error. I haven't worked much with javascript, but I suspect it may have something to do with how I return the value from the function.
This is the code which calls the function:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var baseSheet = ss.getSheetByName("Base");
var catCol = 9;
var riskAreaColumn = 10;
var numRows = baseSheet.getDataRange().getNumRows();
// I am not using this var, should I be?
var data = baseSheet.getDataRange().getValues();
var cell;
var rangeName;
var range;
var rule;
for(var i=2; i<numRows; i++){
cell = baseSheet.getRange(i, riskAreaColumn);
rangeName = getNamedRange("CategoryRiskRange",baseSheet.getRange(i, catCol).getValue());
range = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(rangeName);
rule = SpreadsheetApp.newDataValidation().requireValueInRange(range).build();
cell.setDataValidation(rule);
}
SpreadsheetApp.flush();
}
This is the function being called:
function getNamedRange(categoryRange, category) {
var categoryList = SpreadsheetApp.getActive().getRangeByName(categoryRange).getValues();
for (var i = 0; i < categoryList.length; i++) {
if (categoryList[i][0] == category) {
getNamedRange = categoryList[i][1];
return getNamedRange;
}
}
}
The first time through the for loop works, the second time gives me the aforementioned error. Thank you for reading this, I hope it's clear.
you are overwriting the function definition here:
getNamedRange = categoryList[i][1];
this will work:
if (categoryList[i][0] == category) {
return categoryList[i][1];
}
Javascript doesn't interpret things until it gets to them, and is very happy to redefine things when you tell it to.
The first time through, it sees
function getNamedRange(categoryRange, category)
and says "oh, a function! Cool!" But in that function, you have the line
getNamedRange = categoryList[i][1];
and it says "Oh, so getNamedRange is something else now. Okay, I'm fine with that."
Rename your variable, and you should be fine.
function createTextFields(obj) {
for (var i = 0; i < obj.length; i++) {
var dataDump = {};
for (var key in obj[i]) {
var textField = Ti.UI.createTextField(pm.combine($$.labelBrown, {
left: 200,
height:35,
value:obj[i][key],
width:550,
keyboardType:Ti.UI.KEYBOARD_NUMBER_PAD,
layout:'horizontal',
backgroundColor:'transparent',
id:i
}));
dataDump[key] = textField.value;
var callback = function (vbKey) {
return function (e) {
dataDump[vbKey] = e.source.value;
};
}(key);
}
globalData.push(dataDump);
}
}
I am using the simlar code for Adding the data and it works fine. I posted the problem yesterday and it got resolved...
Last Object is always getting updated?
Now when i go to edit page, it shows me four text fields or number of text fields added... now when i edit something and click on save... the value get's updated on the fourth or the last TextFields Object...
Don't define functions inside loops. Computationally expensive and leads to problems, like this one. Here's a fix that should solve it:
function createTextFields(obj) {
var callback = function (vbKey, localDump) {
return function (e) {
localDump[vbKey] = e.source.value;
};
}
var i;
var max = obj.length;
for (i = 0; i < max; i++) {
var dataDump = {};
for (var key in obj[i]) {
dataDump[key] = textField.value;
var callBackInstance = function(keyn, dataDump);
}
globalData.push(dataDump);
}
}
JavaScript does not have block level scope, so your variables dataDump and callback, though "declared" inside for-loops actually belong to the function. As in, you're saving a value to dataDump, then you're overwriting it, each time you go through the loop. Which is why finally only the code that operated on the last value remains.
Take a look at What is the scope of variables in JavaScript? too.