// Emacs settings: -*- mode: Fundamental; tab-width: 4; -*- //////////////////////////////////////////////////////////////////////////// // // // Andrew's Album Applications: photos-js.txt // // // // Assumes the availability of definitions from common-js.txt // // // // Copyright (c) 2004-2005, Andrew Birrell // // // //////////////////////////////////////////////////////////////////////////// // Assumes the availability of definitions from common-js.txt // // Global variables // var wSize; // window size var mainSize = Object(); // size of main image display area var dropZone; // dropzone element var reading; // page element to show while reading a page var scaling; // page element to show current scale var title; // title element var parents; // element for parent links var parentsTxt; // HTML for top-level parent link var writing; // element for "writing" message var editInner; // inside of edit dialog var editTitle; // edit dialog title type-in element var linkAnchor; // in "Link" button var btns = new Object(); // button details var dlogs = new Object(); // pop-up dialogs var dlogPositioned = new Object(); var thisPage = null; // attributes for current page var user = ""; // default user for editing var pwd = ""; // default password for editing var jumpTarget = null; // if set, switch to here instead var timer = null; // auto-play timer, for cancelling var autoRoot = null; // root directory for auto-play var preloadServer = false; // variant of auto-play to preload server cache var interval = 4000; // auto-play timer interval var timerScaleOn = null; // timer for showing "scaling" dlog var timerScaleOff = null; // timer for hiding "scaling" dlog var cacheP = new Cache(50); // cache of photo XML responses var cacheF = new Cache(50); // cache of folder XML responses var cacheHits = 0; var cacheMisses = 0; var parentFrag = // Template HTML fragment for parent link '')" + '" title="Go back to "<#TITLE>""><#TITLE>'; var folderFrag = // Template HTML fragment for folder listing '')" + '" title="Go to "<#TITLE>"">' + '' + '')" + '" title="Go to "<#TITLE>""><#TITLE>'; var photoFrag = // Template HTML fragment for photo listing '')" + '"> '; var mainFrag = // Template HTML fragment for main screen '' + ' ' + 'style="width: 1px; height: 1px" src="<#SRC>">'; var scalingFrag = 'Image area is now
<#XPX> x <#YPX> pixels'; // // Button management and the "reading" progress dialog // function Button(id, action, photoTip, folderTip) { // Constructor for a Button object this.element = document.getElementById(id); if (this.element) { this.txt = this.element.innerHTML; this.photoHTML = '' + this.txt + ''; this.folderHTML = '' + this.txt + ''; } this.disable(); } Button.prototype.enable = function() { // Enable a button if (this.element) { this.element.innerHTML = (thisPage.isPhoto ? this.photoHTML : this.folderHTML); } this.enabled = true; this.visible = true; } Button.prototype.disable = function() { // Disable a button if (this.element) { this.element.innerHTML = '' + this.txt + ''; } this.enabled = false; this.visible = true; } Button.prototype.hide = function() { // Remove button from the screen if (this.element) this.element.innerHTML = ""; this.enabled = false; this.visible = false; } function noteReading() { // Show the "reading" dialog and disable buttons as appropriate if (reading.style.display != 'block') { reading.style.display = "block"; moveToCenter(reading, wSize); startProgressBar("readingWidget"); btns.next.disable(); btns.prev.disable(); btns.skip.disable(); if (btns.auto.visible) btns.auto.disable(); btns.edit.disable(); if (dlogs.editForm.style.display == 'block') { editTitle.blur(); editInner.style.visibility = "hidden"; } } } function endOfReading() { // enable/disable buttons as appropriate for current page, // and dispense with the "reading" dialog stopProgressBar(); reading.style.display = "none"; if (preloadServer) { btns.next.disable(); btns.prev.disable(); btns.skip.disable(); btns.edit.disable(); } else { if (thisPage.nextImage != "" || thisPage.isPhoto) { btns.next.enable(); } else { btns.next.disable(); } if (thisPage.prevImage != "" || thisPage.isPhoto) { btns.prev.enable(); } else { btns.prev.disable(); } if (thisPage.skipImage != "") { btns.skip.enable(); } else { btns.skip.disable(); } btns.edit.enable(); } if (timer) { btns.auto.hide(); btns.pause.enable(); btns.resume.hide(); btns.stop.enable(); } else if (autoRoot) { btns.auto.hide(); btns.pause.hide(); btns.resume.enable(); btns.stop.enable(); } else { if (thisPage.autoImage != "") { btns.auto.enable(); } else { btns.auto.disable(); } btns.pause.hide(); btns.resume.hide(); btns.stop.disable(); } } // // Scaling the main image // function computeMainSize() { // Compute appropriate values for current size of "td.main" element // and adjust it in the DOM. // NOTE: in Safari 1.3.1, td.main is at the top of the image, which // can be below the top of the main table. (In IE 6, both return the // same top value.) var mainTable = document.getElementById("mainTable"); var main = document.getElementById("main"); var bottom = document.getElementById("bottomWrapper"); var mainPos = getElementPos(mainTable); wSize = windowSize(); var h = wSize.y - mainPos.y - bottom.offsetHeight; main.style.height = "" + h + "px"; mainSize.x = wSize.x; mainSize.y = h; } function scaleMainImage() { // Make the main image, if any, fit the "td.main" element // Used for onresize events, and when loading the page var mainImage = document.getElementById(thisPage.path); if (mainImage) { if (thisPage.isPhoto) { var xScale = mainSize.x / thisPage.photoActualW; var yScale = mainSize.y / thisPage.photoActualH; var scale = Math.min(1.0, Math.min(xScale, yScale)); mainImage.style.width = "" + Math.round(thisPage.photoActualW * scale) + "px"; mainImage.style.height = "" + Math.round(thisPage.photoActualH * scale) + "px"; } else { mainImage.style.height = "" + mainSize.y + "px"; } } } function scaleOn() { timerScaleOn = null; scaling.style.display = 'block'; var temp = scalingFrag.replace(/<#XPX>/, mainSize.x); temp = temp.replace(/<#YPX>/, mainSize.y); scaling.innerHTML = temp; moveToCenter(scaling, wSize); if (timerScaleOff) clearTimeout(timerScaleOff); timerScaleOff = window.setTimeout(scaleOff, 1000); } function scaleOff() { if (scaling.style.display == 'block') scaling.style.display = 'none'; } function doResize() { // Respond to window resizing scaleOff(); computeMainSize(); if (thisPage) { scaleMainImage(); // Showing the "scaling" window during resize causes significant // flicker in IE, so we defer it until dragging stops if (timerScaleOn) clearTimeout(timerScaleOn); timerScaleOn = window.setTimeout(scaleOn, 100); } } // // Prefetching Images // // We could get images pre-loaded by placing them into some HTML // and handing that to the browser. But then there'd be no way // to cancel the load requests if we want to go somewhere else. // By doing them sequentially ourselves, we get that control. var imgPreload = new Image(); // image being preloaded imgPreload.onload = stepImagePreload; var imgQueue = null; // queue of images to preload var imgQueueTail = null; function initiateImageLoad() { if (imgQueue) imgPreload.src = imgQueue.src; } function stepImagePreload() { // Current preload has completed; initiate next if (imgQueue) imgQueue = imgQueue.next; if (imgQueue) { // execute next asynchronously, to avoid recursive overflow setTimeout(initiateImageLoad, 10); } } function enqueueImage(src) { // enqueue an image for preloading var queue = new Object(); queue.src = src; queue.next = null; if (imgQueue) { imgQueueTail.next = queue; } else { imgQueue = queue; initiateImageLoad(); } imgQueueTail = queue; } function cancelImagePreload() { // Dispense with the image preload queue imgQueue = null; } // // Page interpretation // function getTitle(node) { // Return the value of the "title" child of given XML node var titles = node.getElementsByTagName("title"); var child = titles[0].firstChild; return child.nodeValue; } function buildPage(responseXML) { // Construct and return a page object based on the given XML var doc = responseXML.documentElement; newPage = new Object(); newPage.responseXML = responseXML; newPage.path = doc.getAttribute("path"); newPage.title = getTitle(doc); newPage.nextImage = doc.getAttribute("next"); newPage.prevImage = doc.getAttribute("prev"); newPage.skipImage = doc.getAttribute("skip"); var photoMaxW = parseInt(doc.getAttribute("photoMaxW")); var photoMaxH = parseInt(doc.getAttribute("photoMaxH")); var starLevel = doc.getAttribute("starLevel"); newPage.starLevel = (starLevel ? parseInt(starLevel) : null); newPage.parents = ''; // parent links HTML var i; var parents = doc.getElementsByTagName("parent"); newPage.lastParent = ""; for (i = 0; i < parents.length; i++) { var child = parents[i]; var chtitle = getTitle(child); var chpath = child.getAttribute("path"); if (i == 0) { chtitle = parentsTxt; } else { newPage.parents += '  >  '; } var temp = parentFrag.replace(/<#PATH>/g, chpath); temp = temp.replace(/<#TITLE>/g, htmlspecials(chtitle)); newPage.parents += temp; newPage.lastParent = chpath; newPage.autoRoot = chpath; // use last parent for photos } if (doc.nodeName == "photo") { newPage.isPhoto = true; newPage.autoImage = // enable auto iff next is in this folder (newPage.autoRoot == "." || newPage.nextImage.indexOf(newPage.autoRoot) == 0 ? newPage.nextImage : ""); var width = parseInt(doc.getAttribute("width")); var height = parseInt(doc.getAttribute("height")); var src = doc.getAttribute("src"); var raw = doc.getAttribute("raw"); var size = doc.getAttribute("size"); var date = doc.getAttribute("date"); var exposure = doc.getAttribute("exposure"); newPage.photoMaxW = photoMaxW; newPage.photoMaxH = photoMaxH; newPage.photoActualW = width; newPage.photoActualH = height; if (preloadServer) { newPage.html = "







" + "Preloading the server-side cache," + " with no image display and no delays.

" + "Click \"Stop\" to exit from this mode."; } else { var temp = mainFrag.replace(/<#SRC>/, src); temp = temp.replace(/<#RAW>/, raw); temp = temp.replace(/<#ID>/, newPage.path); temp = temp.replace(/<#SIZE>/, "\n" + size); newPage.html = temp; } newPage.commentary = (date ? htmlspecials(date) : " ") + "
" + (exposure ? htmlspecials(exposure) : " ") } else { // folder newPage.isPhoto = false; if (parents.length > 0) { newPage.parents += '  >  '; } newPage.parents += '' + (parents.length == 0 ? parentsTxt : htmlspecials(newPage.title)) + ''; newPage.autoImage = doc.getAttribute("first"); newPage.autoRoot = newPage.path; // use self for folders content = '

'; var children = doc.getElementsByTagName("folder"); var folderCount = children.length; var lCol = 0; // child index for left column var rCol = Math.round((folderCount+0.5)/2); var i; for (i = 0; i < folderCount; i++) { var sub; if (i%2 == 0) { content += (i == 0 ? "" : ""); content += ""; sub = children[lCol]; lCol++; } else { sub = children[rCol]; rCol++; } var chpath = sub.getAttribute("path"); var chtitle = getTitle(sub); var width = sub.getAttribute("width"); var height = sub.getAttribute("height"); var src = sub.getAttribute("src"); var temp = folderFrag.replace(/<#PATH>/g, chpath); temp = temp.replace(/<#WIDTH>/, width); temp = temp.replace(/<#HEIGHT>/, height); temp = temp.replace(/<#SRC>/, src); temp = temp.replace(/<#TITLE>/g, htmlspecials(chtitle)); content += temp; } if (i != 0) content += "
"; children = doc.getElementsByTagName("photo"); var photoCount = children.length; for (i = 0; i < photoCount; i++) { var sub = children[i]; var chpath = sub.getAttribute("path"); var chtitle = getTitle(sub); var width = parseInt(sub.getAttribute("width")); var height = parseInt(sub.getAttribute("height")); var src = sub.getAttribute("src"); var temp = photoFrag.replace(/<#PATH>/g, chpath); temp = temp.replace(/<#TITLE>/g, htmlspecials(chtitle)); temp = temp.replace(/<#WIDTH>/, width); temp = temp.replace(/<#HEIGHT>/, height); temp = temp.replace(/<#SRC>/, src); var lMargin = 6 + Math.floor((photoMaxW-width)/2); // Vertically, we can just use a fixed margin, because our style // lays the images out with "vertical-align: center". This // way rows are closer together when there are no portrait-mode // photos. temp = temp.replace(/<#MARGINL>/, lMargin); temp = temp.replace(/<#MARGINR>/, 6+photoMaxW-(width+lMargin)); temp = temp.replace(/<#MARGINT>/, 4); temp = temp.replace(/<#MARGINB>/, 0); content += temp; } newPage.commentary = (folderCount > 0 ? "" + folderCount + " folders" : " ") + (folderCount > 0 && photoCount > 0 ? " and" : "") + "
" + (photoCount > 0 ? "" + photoCount + " photographs" : " ") newPage.html = content + '
'; } return newPage; } function displayPage() { // Update the display based on the current page attributes setCookie("AndrewAlbumApp", thisPage.path); var commentary = document.getElementById("commentary"); if (thisPage.commentary) { commentary.innerHTML = thisPage.commentary; } else { commentary.innerHTML = " 
 "; } dropZone.innerHTML = thisPage.html; title.innerHTML = htmlspecials(thisPage.title); parents.innerHTML = thisPage.parents; showStarLevel(); editTitle.value = thisPage.title; if (dlogs.editForm.style.display == 'block') { editInner.style.visibility = "visible"; editTitle.focus(); editTitle.select(); } scaleMainImage(); linkAnchor.href = '#' + thisPage.path; endOfReading(); } // // Page access // var fetching = new Object(); // paths currently being fetched from server function getXML(path, jumpTo) { // Fetch XML for given path. If it's already in cache, return the XML; // if it's already being fetched, do nothihng, otherwise initiate fetch // "path" was urlencoded by the server-side script. // Iff "jumpTo", arrange to display the page when it arrives. var cached = cacheP.read(path); if (!cached) cached = cacheF.read(path); if (cached) { if (jumpTo) jumpTarget = null; // cancel any in-progress jumpTarget return cached; } if (jumpTo) { jumpTarget = path; noteReading(); } if (!fetching[path]) { var thisReq = new Object(); thisReq.requestedPath = path; fetching[path] = thisReq; get(thisReq, "photos.php?op=xml&path=" + path); } } function loadPage(responseXML) { // Load the given page's responseXML into the window. // Also initiate page prefetching, and refresh auto-display timer thisPage = buildPage(responseXML); cancelImagePreload(); fetching = new Object(); // abandon previous page prefetching displayPage(); if (thisPage.path == autoRoot) manual(); if (!preloadServer) { // pre-fetch nearby pages if (thisPage.autoImage != "") getXML(thisPage.autoImage, false); if (thisPage.nextImage != "") getXML(thisPage.nextImage, false); if (thisPage.lastParent != "") getXML(thisPage.lastParent, false); } if (timer) timer = window.setTimeout(doNext, (preloadServer ? 10 : interval)); } function handleFailure(thisReq) { // Completion procedure called on failures // Redisplay the current page (undo "showReading") cancelImagePreload(); fetching = new Object(); if (thisPage) displayPage(); } function handleResult(thisReq) { // Completion procedure: interpret the result, assumed to be XML var responseXML = thisReq.xmlhttp.responseXML; // alert(thisReq.xmlhttp.responseText); var doc = responseXML.documentElement; if (doc.nodeName == "save") { // Result of editSave var status = doc.getAttribute("status"); var path = doc.getAttribute("path"); if (thisReq.isPhoto) { cacheP.flush(path); } else { cacheP = new Cache(50); // parent names might have changed cacheF = new Cache(50); } editInner.style.visibility = "visible"; writing.style.display = 'none'; if (status == "ok") { if (editStayOpen) { doNext(); } else { closeDlogs(); jumpTo(thisPage.path); } } else { alert("Failed: " + status); } } else if (doc.nodeName == "photo" || doc.nodeName == "folder") { // Interpret the XML response for a folder or image page var path = doc.getAttribute("path"); var thisCache = (doc.nodeName == "photo" ? cacheP : cacheF); thisCache.write(path, responseXML); if (jumpTarget == thisReq.requestedPath) { // Note that the server can return a page with a different // path, for example if the one we requested doesn't exist jumpTarget = null; loadPage(responseXML); } else if (fetching[path]) { fetching[path] = false; // enqueue preload requests for our images, if we're still OK if (preloadServer) { // don't preload any images } else if (doc.nodeName == "photo") { var src = doc.getAttribute("src"); enqueueImage(src); } else { // folder var children = doc.getElementsByTagName("folder"); for (var i = 0; i < children.length; i++) { var sub = children[i]; var src = sub.getAttribute("src"); enqueueImage(src); } children = doc.getElementsByTagName("photo"); for (var i = 0; i < children.length; i++) { var sub = children[i]; var src = sub.getAttribute("src"); enqueueImage(src); } } } } else { alert('Unknown result, nodeName="' + doc.nodeName +'"'); alert(thisReq.xmlhttp.responseText); handleFailure(thisReq); } } function jumpTo(path) { // Jump to given path if (timer) clearTimeout(timer); var cached = getXML(path, true); if (cached) { cacheHits++; loadPage(cached); } else { cacheMisses++; } return false; } // // The top-level subroutines, mostly invoked from the HTML page // function containedJump(candidate) { // If candidate would move us outside of our folder (or autoRoot), // jump to the bounding folder; otherwise jump to the candidate var target = candidate; var container = (autoRoot ? autoRoot : (thisPage.isPhoto ? thisPage.lastParent : null)); if (container) { if (container == ".") { if (candidate == "") target = container; } else { if (candidate.indexOf(container) != 0) target = container; } } if (target != "") jumpTo(target); } function doNext() { // Perform the "next" operation. containedJump(thisPage.nextImage); return false; } function doPrev() { containedJump(thisPage.prevImage); return false; } function doSkip() { containedJump(thisPage.skipImage); return false; } function auto(event) { // start automatic mode autoRoot = thisPage.autoRoot; preloadServer = (event.shiftKey ? true : false); if (preloadServer) { // flush cache so that we actually use the server-side cacheP = new Cache(50); cacheF = new Cache(50); } if (!timer) timer = window.setTimeout("jumpTo(thisPage.autoImage)", 50); btns.auto.hide(); btns.pause.enable(); btns.resume.hide(); btns.stop.enable(); return false; } function pause() { // suspend automatic mode without losing track of root if (timer) clearTimeout(timer); timer = null; btns.auto.hide(); btns.pause.hide(); btns.resume.enable(); btns.stop.enable(); return false; } function resume() { // continue after pause if (!timer) timer = window.setTimeout(doNext, 50); btns.auto.hide(); btns.pause.enable(); btns.resume.hide(); btns.stop.enable(); return false; } function manual() { // stop automatic mode if (timer) clearTimeout(timer); timer = null; var wasPreloading = preloadServer; if (preloadServer) { // flush cache so that future pre-fetches will pre-fetch images cacheP = new Cache(50); cacheF = new Cache(50); } autoRoot = null; preloadServer = false; btns.auto.enable(); btns.pause.hide(); btns.resume.hide(); btns.stop.disable(); if (wasPreloading && thisPage) jumpTo(thisPage.path); return false; } function doParent(path) { // Jump to given path, cancelling auto-play if (autoRoot) manual(); jumpTo(path); return false; } function up() { // Go up one folder level if (thisPage.lastParent != "") doParent(thisPage.lastParent); return false; } function hideShowActions() { // Hide or show the "actions" menu body var body = document.getElementById("actionsBody"); var openClose = document.getElementById("openClose"); if (body.style.display == 'none') { body.style.display = 'block'; openClose.src = 'open.gif'; } else { body.style.display = 'none'; openClose.src = 'closed.gif'; } return false; } function closeDlogs() { // Close all dialog elements for (var dlog in dlogs) { var style = dlogs[dlog].style; if (style.display != 'none') style.display = 'none'; } return false; } function openDlog(dlogName) { closeDlogs(); dlog = dlogs[dlogName]; dlog.style.display = 'block'; if (!dlogPositioned[dlogName]) { moveToCenter(dlog, wSize); dlogPositioned[dlogName] = true; } if (dlog == dlogs.editForm) { if (timer) pause(); var editUser = document.getElementById("editUser"); var editPwd = document.getElementById("editPwd"); editUser.value = user; editPwd.value = pwd; if (user == "") { editUser.focus(); } else if (pwd == "") { editPwd.focus(); } else { editTitle.focus(); } } return false; } var editStayOpen; // leave the dialog up after save completion function editSave(stayOpen) { editStayOpen = stayOpen; user = document.getElementById("editUser").value; pwd = document.getElementById("editPwd").value; setCookie("pachyuser", user, null, "/"); setCookie("photopwd", pwd, null, "/", null, true); var title = document.getElementById("editTitle").value; editInner.style.visibility = "hidden"; writing.style.display = 'block'; if (thisPage.isPhoto) { cacheP.flush(thisPage.path); cacheF.flush(thisPage.lastParent); } else { cacheP = new Cache(50); // parent names might have changed cacheF = new Cache(50); } var now = new Date(); var nowSec = Math.round(now.getTime() / 1000); var hmac = md5HmacHex(utf8("AndrewAlbumApp\x00xmlSave\x00" + user + "\x00" + nowSec + "\x00" + thisPage.path + "\x00" + title), utf8(pwd)); var saveRequest = new Object(); saveRequest.isPhoto = thisPage.isPhoto; get(saveRequest, "photos.php", "path=" + thisPage.path + "&op=xmlSave&title=" + encodeURIComponent(title) + "&user=" + encodeURIComponent(user) + "&time=" + nowSec + "&hmac=" + hmac); return false; } function starNumber(star) { var id = star.id; return parseInt(id.substring(4)); } function setStarDisplay(n, src) { for (var i = 1; i <= n; i++) { var x = document.getElementById("star" + i); x.src = src; } for (var i = n+1; i <= 5; i++) { var x = document.getElementById("star" + i); x.src = "whiteStar.gif"; } } function showStarLevel() { // display current star level, or hide the stars var stars = document.getElementById("stars"); if (!thisPage || thisPage.starLevel == null) { stars.style.display = "none"; } else { setStarDisplay(thisPage.starLevel, "redStar.gif"); stars.style.display = "block"; } } function starOver(star) { // mouse over a star setStarDisplay(starNumber(star), "blueStar.gif"); } function starClick(star) { // mouse click on a star var n = starNumber(star); thisPage.starLevel = n; thisPage.responseXML.documentElement.setAttribute("starLevel", n); showStarLevel(); } function init() { if (!document.getElementById) { alert('This browser has no "getElementById" support.' + ' Redirecting to the non-scripted page'); location.replace("noscript.php"); return; } dropZone = document.getElementById("dropZone"); reading = document.getElementById("reading"); scaling = document.getElementById("scaling"); title = document.getElementById("titleText"); title.innerHTML = ""; parents = document.getElementById("parents"); parentsTxt = parents.innerHTML; parents.innerHTML = ""; showStarLevel(); dlogs.about = document.getElementById("about"); dlogs.details = document.getElementById("details"); dlogs.editForm = document.getElementById("editForm"); dlogs.link = document.getElementById("link"); writing = document.getElementById("writing"); editInner = document.getElementById("editInner"); editTitle = document.getElementById("editTitle"); linkAnchor = document.getElementById("linkAnchor"); btns.auto = new Button("auto", "auto(event)", "Play all the photos in this folder, from here onward", "Play all the photos in this folder, including " + "those in its sub-folders"); btns.pause = new Button("pause", "pause()", "Suspend the slideshow, remembering the starting position", "I'm not here"); btns.pause.hide(); btns.resume = new Button("resume", "resume()", "Resume the slideshow", "I'm not here"); btns.resume.hide(); btns.stop = new Button("stop", "manual()", "Stop the slideshow", "I'm not here"); btns.prev = new Button("prev", "doPrev()", "Move to the previous photo", "Move to the previous folder"); btns.next = new Button("next", "doNext()", "Move to the next photo", "Move to the next folder, including sub-folders"); btns.skip = new Button("skip", "doSkip()", "Skip the remainder of this folder", "Move to the next folder, skipping this folder's sub-folders"); btns.edit = new Button("edit", "openDlog('editForm')", "Edit this photo's title", "Edit this folder's title"); computeMainSize(); window.onresize = doResize; if (!createXMLHttp()) { alert('This browser has no "XMLHTTP" support.' + ' Redirecting to the non-scripted page'); location.replace("noscript.php"); return; } var dest = ""; if (location.hash && location.hash != "") { dest = location.hash.substring(1); } else { var prevPath = getCookie("AndrewAlbumApp"); if (prevPath) dest = prevPath; } user = getCookie("pachyuser"); if (!user) user = ""; dropZone.innerHTML = ""; jumpTo(dest); // Enable the UI document.getElementById("parentBtns").style.visibility = "visible"; document.getElementById("prevnextBtns").style.visibility = "visible"; document.getElementById("privBtns").style.visibility = "visible"; }