How to Add an Expand All button to a javascript/HTML project - javascript

I currently have a page that has content that expands when you click on a term, but as soon as you click on a new term the old one closes and the new one expands. The terms are loaded in from a google sheet onto the page. This is on a HTML page but the javascript code to do the work is the following:
// Address of the Google Sheets Database
var public_spreadsheet_url = 'sheet link here';
// Column Names from Google Sheets Database
let questionsColumn = "Question";
let answersColumn = "Answer";
window.addEventListener('DOMContentLoaded', init) // Calls method init when Sheets has loaded
function init() {
Tabletop.init( { key: public_spreadsheet_url,
callback: showInfo,
simpleSheet: true } );
}
var unhiddenAnswer = "";
// Method that gets called when data has been pulled from Google Sheets
function showInfo(data) {
var editButton = '<center><a style="border-bottom: none" href="' + public_spreadsheet_url + '"><button class="button admin">Edit</button></a></center>';
// Injects the built HTML code into the div Dynamic
document.getElementById("dynamic").innerHTML = buildFAQTable(data) + editButton;
}
// Builds the HTML Table code from the Database Data
function buildFAQTable(data) {
var index = 0;
var content = '<h2>Title Here</h2><div style="padding:0px 5%">';
data.forEach(form => {
content += '<h1 class="faq_question" onClick="unhideAnswer(' + index + ')">' + data[index][questionsColumn] + '</h1>';
content += '<p id="answer' + index + '" class="hideAnswer">' + data[index][answersColumn] + '</p>';
index++;
});
// Extends body to accomdate for tall footer on very small devices (e.g. iPhone 5/5S/SE)
content += "<br></br><br></br>";
return content;
}
// When a FAQ Question gets clicked on, this method will hide the currently displaying answer (if any), and
// Unhide the answer corresponding to the clicked on answer.
// If the currently displaying answer is the same as the answer corresponding to the clicked on question,
// it will be hidden and no new answer will be unhidden
function unhideAnswer(number) {
var answerID = "answer" + number;
if (answerID != unhiddenAnswer) {
document.getElementById(answerID).classList.remove("hideAnswer");
}
if (unhiddenAnswer != "")
document.getElementById(unhiddenAnswer).classList.add("hideAnswer");
if (unhiddenAnswer == answerID)
unhiddenAnswer = ""
else
unhiddenAnswer = answerID;
}
I want to now add an expand all/ collapse all button to give the user the option to open and view all the terms at one if needed. However, if not using the expand all button, the regular open and close functionality above should be used. I am new to javascript and am at a loss on the best way to implement this. Any help would be appreciated.

add a answer class to every answer, then you can loop through all of them with this query selector
// in your buildFAQTable fucntion
content += '<p id="answer' + index + '" class="hideAnswer answer">' + data[index][answersColumn] + '</p>';
document.querySelectorAll('.answer').forEach(answer => {
// you can use toggle, add or remove to change the appearance of the answer
answer.classList.toggle('hideAnswer')
})
i would also recomend you to check out some of the newer javascript features like string interpolation and avoid using var, but it is not so important if you are just starting out.
(i also refactored some of your code, this might make it a bit more readable)
// Address of the Google Sheets Database
const public_spreadsheet_url = 'sheet link here';
// Column Names from Google Sheets Database
const questionsColumn = "Question";
const answersColumn = "Answer";
function toggleAnswer(num) {
const answer = document.getElementById(`answer${num}`);
answer.classList.toggle('hideAnswer');
}
function hideAll() {
document.querySelectorAll('answer').forEach(answer => {
answer.classList.add('hideAnswer');
})
}
function showAll() {
document.querySelectorAll('answer').forEach(answer => {
answer.classList.remove('hideAnswer');
})
}
function buildFAQTable(data) {
let index = 0;
let content = '<h2>Title Here</h2><div style="padding:0px 5%">';
for (i in data) {
content += `<h1 class="faq_question" onClick="unhideAnswer(${i})">${data[i][questionsColumn]}</h1>`;
content += `<p id="answer${i}" class="hideAnswer answer">${data[i][answersColumn]}</p>`;
}
content += "<br></br><br></br>";
return content;
}
function showInfo(data) {
const editButton = `<center><a style="border-bottom: none" href="${public_spreadsheet_url}"><button class="button admin">Edit</button></a></center>`;
document.getElementById("dynamic").innerHTML = buildFAQTable(data) + editButton;
}
window.addEventListener('DOMContentLoaded', () => {
Tabletop.init({
key: public_spreadsheet_url,
callback: showInfo,
simpleSheet: true
});
}, { once: true })

Related

How can I use jQuery to display the title of a photo from Flickr?

I made a search event handler in my HTML which takes the input and makes the search request to flickr's photo.search by appending the value after "&text=" to search by text. This then displays 7 relevant photos to the search term.
I've added a click event handler to then insert the details to my HTML file to display in a modal when the user clicks on a photo and then display the photo by setting the modal css to display. The issue comes when I try to display the photo's title under the image.
Search button event handler:
//search button function
function searchButton_handler(){
let input = $("#textInput").val();
let htmlStr = "";
$.get(SEARCH_REQ + "&text=" + input, function(data){
nrequest = data.photos.photo.length;
for (let i = 0; i < data.photos.photo.length; i++){
let photoID = data.photos.photo[i].id;
let photoTitle = data.photos.photo[i].title;
let photoObj = {"file": photoID, "title": photoTitle};
search_photos.push(photoObj);
getSizes(photoObj, photoID, photoTitle);
}
});
}
Get sizes from flickr:
//access getSizes array from flickr
function getSizes(photoObj, photoID){
let getSizeReq = GETSIZES_REQ + photoID;
$.get(getSizeReq, function(data){
let photoURL = data.sizes.size[2].source;
photoObj.full = data.sizes.size[data.sizes.size.length-3].source;
photoObj.file = photoURL;
display_search(search_photos);
});
}
Display search results:
//display photos returned from search
function display_search(search_photos){
let htmlStr = "";
for (let i = 0; i < search_photos.length; i++){
htmlStr += `<figure data-full="${search_photos[i].full}"><img src="${search_photos[i].file}" alt="${search_photos[i].title}" width="150" height="150"><figcaption>${search_photos[i].title}</figcaption></figure>`;
}
$("#item2").html(htmlStr);
$('figure').each(function(index){
$(this).click(function(){
$("#modal-container").css("display", "block");
$("#modal-content").attr("src", $(this).attr("data-full"));
})
});
}
Can somebody help me? I've tried everything I could think of and console logging it either returns undefined or an error. Any help would be appreciated, Thanks!

Clear followers from previous user search Github API

I'm trying to build a simple tool that will return a Github profile when you search someone's username. Everything seems to be working, except when I search for a different user, the list of followers from the previous user search don't clear.
For example, a user who has seven followers will suddenly have dozens of follower avatars displaying.
Can anyone tell me how to display the correct number of followers unique to each user when fetching different Github profiles?
var response = null;
var followers = null;
document.getElementsByTagName('button')[0].addEventListener('click', function(r) {
getUser(document.getElementsByTagName('input')[0].value);
});
function getUser(name) {
fetch('https://api.github.com/users/' + name)
.then(function(r) {
console.log(r.status);
return r.json();
})
.then(function(j) {
response = j;
assignValues();
getFollowers(j.followers_url);
});
}
function assignValues() {
document.getElementById('loader').style = 'display: none';
document.getElementById('avatar').src = response.avatar_url;
document.getElementById('name').innerText = response.name;
document.getElementById('username').innerText = response.login;
document.getElementById('location').innerText = response.location;
document.getElementById('bio').innerText = response.bio;
document.getElementById('count').innerText = 'Followers: ' + response.followers;
}
function getFollowers(url) {
fetch(url)
.then(function(r) {
return r.json();
})
.then(function(f) {
followers = f;
listFollowers();
});
}
function listFollowers() {
followers.forEach(function(f) {
var li = document.createElement('li');
li.innerHTML = ''+ '<img src="' + f.avatar_url + '" alt="' + f.login + '"/>'+ '';
document.getElementById('list').appendChild(li);
});
}
You need to clear that #list element in listFollowers function before starting appending new followers to it. For example:
var list = document.getElementById('list');
list.innerHTML = '';
followers.forEach(function(f) {
var li = document.createElement('li');
li.innerHTML = ''+ '<img src="' + f.avatar_url + '" alt="' + f.login + '"/>'+ '';
list.appendChild(li);
});
Three sidenotes here:
why do you use global variables response and followers in your rendering functions when you can pass those as arguments?
as you rerender a 'user profile' first, then wait for avatars' fetch, it's still possible for a user to see the followers of user X when looking for user Y. It's better to clear that section (and show some visual cue for loading data) immediately after the user is switched.
as order of fetch responses is not guaranteed, there's a potential race condition here: if user clicks on that button twice (with different inputs), the earlier input might get back later - and overwrite the former. You need to guard against this, either by storing the latest input value and checking the response, or by some other means.
You can test those by artificially throttling the network speed. And believe me, in real world most of your user will have all sorts of problems with that.

Kendo Grid - Window template button with update functionality

Right now I am using a window to view details that are not shown in the grid. I have made my own custom editor in the window as well which hides the details and replaces them with inputs.
Unfortunately I cannot get the Update button to have the same functionality as an update button in the kendo toolbar.
I am using transport and parameter map for my create which works perfectly. I just need to be able to hit the update, which I haven't been able to.
Here is a snippet of code for the template:
<li><b>Change Control Objective</b></li>
<li><textarea type="text" class="k-textbox k-input" data-bind="value:ChangeControlObjective">#= ChangeControlObjective #</textarea></li>
<li><b>Change Control Specifics</b></li>
<li><textarea type="text" class="k-textbox k-input" data-bind="value:ChangeControlSpecifics">#= ChangeControlSpecifics #</textarea></li>
<span class="k-update k-icon k-i-tick"></span>Save
I can't show my JS code but it is based off this dojo: http://dojo.telerik.com/abUHI
UPDATE:
I am able to hit the update in the parametermap off of my save button click but it's sending the old data to the update instead of the new. Here is the button click code:
$("#saveChanges").click(function () {
dataItem.dirty = true;
$("#ccrGrid").data('kendoGrid').saveChanges();
});
Each input has a data-bind attribute and the parametermap looks like this:
case "update":
var changeControlRequestId = options.ChangeControlRequestID;
var changeControlObjective = options.ChangeControlObjective;
var changeControlSpecifics = options.ChangeControlSpecifics;
var productAssociation;
if (options.AccountChangeInfo.ProductAssocation == undefined) {
productAssociation = "";
} else { productAssociation = options.ProductAssocation; }
var amortization;
if (options.AccountChangeInfo.Amortization == undefined) {
amortization = "";
} else { amortization = options.Amortization; }
var productType;
if (options.ProductChangeInfo.ProductType == undefined) {
productType = "";
} else { productType = options.ProductType; }
var productName;
if (options.ProductChangeInfo.ProductName == undefined) {
productName = "";
} else { productName = options.ProductName; }
var productDescription;
if (options.ProductChangeInfo.ProductDescription == undefined) {
productDescription = "";
} else { productDescription = options.ProductDescription; }
var productContract;
if (options.ProductChangeInfo.ProductContractualFeatures == undefined) {
productContract = "";
} else { productContract = options.ProductContractualFeatures; }
var productBehavior;
if (options.ProductChangeInfo.ProductBehavioralAssumptions == undefined) {
productBehavior = "";
} else { productBehavior = options.ProductBehavioralAssumptions; }
var evaluationBehavior;
if (options.ProductChangeInfo.ProductEvaluationBehavior == undefined) {
evaluationBehavior = "";
} else { evaluationBehavior = options.ProductEvaluationBehavior; }
var productStratification;
if (options.ProductChangeInfo.ProductStratificationRoutines == undefined) {
productStratification = "";
} else { productStratification = options.ProductStratificationRoutines; }
if (content.isreadonly == "True") {
alert("you have readonly access");
}
else {
var urlString = "env=" + content.env + "&allyid=" + content.userId + "&changeRequestID" + changeRequestID + "&changeControlObjective=" + changeControlObjective + "&changeControlSpecifics=" + changeControlSpecifics +
"&productAssociation" + productAssociation + "&amortization" + amortization +
"&productType" + productType + "&productName" + productName + "&productDescription" + productDescription +
"&productContract" + productContract + "&productBehavior" + productBehavior + "&evaluationBehavior" + evaluationBehavior +
"&productStratification" + productStratification;
return urlString;
I've been going through this a couple months ago. Per my extensive research there are 2 key sources for doing custom popup editing in Kendo in entire Internet ;) :
Custom editor template
I aslo created a simplified version of this for you here: http://jsbin.com/qudotag/
to cut the elements which can be expanded once you grap the key concepts. Note that this does not work fully as changes are not persisted. It is expected behaviour, as you would need to define the CRUD operations for the grid (what happens when save, cancel etc. is done).
How to deal with CRUD is available in the second source:
Crud with external form
Some heavy studying of these 2 along with going into some more depths of MVVM (which might be intimidating at first, but then really useful for much smoother work with Kendo) will get you going.
Edit: actually you could do with just first approach, which is easier and retain the state by refreshing the grid after cancel.

Jquery: Changing a value on a page as you check and uncheck checkboxe

I have this line of code that exist in a partial view. Jquery code resides in the index page hosting the partial view
<div class="paythisamountbtn">#Html.ActionLink(T("Pay This Amount"), "InvoiceCheckout", null, new { #target = "InvoiceCheckout", #class = "amebtn" }): #String.Format("{0:C}", Model.TotalDue)</div>
I have a checkbox on every row of data. This is all within a webgrid. Here is what it looks like below
#{
var gridColumns = new List<WebGridColumn>();
gridColumns.Add(grid.Column(format: (item) =>
{
var s = "<input type=\"checkbox\" name=\"InvoiceNumber\" id=\"IN" + item.InvoiceNumber.ToString() + "\" value=\"" + item.InvoiceNumber.ToString() + "|" + item.AmountDue + "\"";
if (item.IsSelected) {
s += "checked=\"true\"";
}
s+= "/>";
return s;
}
, style: "box"));
...
...
...
}
function SetViewSelected(c) {
var s = 0;
for (i = 0; i < $("input[name='InvoiceNumber']:checked").length; i++) {
invoice_details = $("input[name='InvoiceNumber']:checked")[i].value;
invoice_amount = invoice_details.split("|")[1];
s += parseFloat(invoice_amount);
}
alert(s);
//$('.paythisamountbtn').val(s);
}
As I check and uncheck the checkboxes, I want to be able to show the total on the line below as they change. At the moment the alert(s) gets me the new total
<div class="paythisamountbtn">#Html.ActionLink(T("Pay This Amount"), "InvoiceCheckout", null, new { #target = "InvoiceCheckout", #class = "amebtn" }): #String.Format("{0:C}", Model.TotalDue)</div>
I tried doing $('.paythisamountbtn').val(s); but the totaldue does not change as I check and uncheck the check boxes.
How can I do this please?
Use .text for a div
$('.paythisamountbtn').text(s)
To update the attribute value what you must do is the following:
$('.paythisamountbtn').text(parseInt($('.paythisamountbtn').text())+5);
To update the value each time you click one of the checkboxes you could do something like this:
$('#checkbox1').change(function() {
if($(this).is(":checked")) {
UpdateValueNow();
}
});

Convert JSON to a Multilevel Bulleted List Using JS

Can some one help me make the following JSON data:
{
"main": {
"label":"Main",
"url":"#main"
},
"project": {
"label":"Project",
"url":"#project"
},
"settings": {
"label":"Settings",
"url":"#settings",
"subnav":[
{
"label":"Privacy",
"url":"#privacy"
},
{
"label":"Security",
"url":"#security"
},
{
"label":"Advanced",
"url":"#advanced"
}
]
}
}
into the following bullets list using JS? Assuming you don't know what the first nodes are call labeled (e.g. "main", "project" <- these will be dynamically generated):
Main (#main)
Project (#project)
Settings (#settings)
Privacy (#privacy)
Security (#security)
Advanced (#advanced)
Thanks
Let's not use HTML string-hacking, shall we? That would break as soon as any of the data had characters like < or & in (or " in attribute values). Use DOM methods and you don't have to worry about character escaping:
function createNav(navs) {
var ul= document.createElement('ul');
for (name in navs) {
var nav= navs[name];
var a= document.createElement('a');
a.href= nav.url;
a.appendChild(document.createTextNode(nav.label));
var li= document.createElement('li');
li.id= 'nav-'+name;
li.appendChild(a)
if ('subnav' in nav)
li.appendChild(createNav(nav.subnav));
ul.appendChild(li);
}
return ul;
}
document.getElementById('navcontainer').appendChild(createNav(jsondata));
Most JS frameworks offer shortcuts to make this a bit less wordy. For example with jQuery:
function createNav(navs) {
var ul= $('<ul>');
for (name in navs) {
var nav= navs[name];
var li= $('<li>', {id: name});
li.append($('<a>', {href: nav.url, text: nav.label}));
if ('subnav' in nav)
li.append(createNav(nav.subnav));
ul.append(li);
}
}
$('#navcontainer').append(createNav(jsondata));
Note that either way, you're using an Object literal which means you get no control over the order the list of navs comes out. You have no guarantee that main will be above project. If you want a defined order, you will have to have the returned JSON data be an array.
My code is on JSfiddle.
As JSON parser I used this one.
The main code is a recursive renderer of the parsed JSON:
function recursive_parse(result) {
var html = '<ul>';
for (var k in result) {
html = html + '<li>' + result[k].label + ' (' + result[k].url + ')';
html = html + recursive_parse(result[k].subnav);
html = html + '</li>';
}
html = html + '</ul>';
return html;
}
var result = json_parse($("div#test1111").html());
var html = recursive_parse(result);
$("div#test2222").html(html);

Categories