I've been trying to implement History.js. I've got some understanding of how getting and pushing states work, however I'm having particular trouble with the data storing component of the history along with using global variables.
As a simple example, I decided to try and set up a script which would change the colour of a html box upon being clicked. This would also trigger the history - essentially creating a history for clicking the box (and its colour being changed on each state of the history).
Is there any way to update a global variable based on the data (in this case, updating i per click) supplied in History State's data?
HTML:
<div id="box">CLICK ME</div>
<button id="back">Back</button>
<button id="forward">Forward</button>
CSS:
#box {
width: 300px;
height: 300px;
background-color: black;
color: white;
vertical-align: middle;
text-align: center;
display: table-cell;
}
JavaScript:
var History = window.History;
var i = 0;
if (History.enabled) {
var State = History.getState();
History.pushState({count:i}, $("title").text(), State.urlPath);
} else {
return false;
}
// Bind to StateChange Event
History.Adapter.bind(window,'statechange', function(){
State = History.getState();
console.log(State.data, State.title, State.url);
$(this).css('background-color', getColour());
});
// Trigger the change
$("#div").on("click", function() {
i++;
History.pushState({count:i},"State " + i,"?state=" + i);
});
function getColour() {
var colours = ["red", "orange", "yellow", "green", "aqua","blue", "purple", "magenta","black"];
if (i > colours.length - 1) {
i = 0;
}
if (i < 0) {
i = colours.length - 1;
}
return colours[i];
}
$("#back").on("click", function() {
History.back();
});
$("#forward").on("click", function() {
History.forward();
});
I'm also using JQuery, ajaxify-html5.js and scrollto.js as per recommendation by other threads.
Editable JSFiddle | Viewable JSFiddle
After playing around with this a ton (and reading more questions), I've figured it out. I'll detail what the solution means to any others who come across this.
JSFIDDLE VIEW SOLUTION | JSFIDDLE VIEW SOLUTION
First here's the final code. Note that the JavaScript has document.ready extras to get it working outside of JSFiddle.
It's also worth noting I took out ajaxify-html5.js and scrollto.js out, as they weren't needed (and were breaking the code somewhere).
HTML:
<div id="box">
<div id="count"></div>
<div id="colour"></div>
</div>
<button id="back">Back</button>
<button id="forward">Forward</button>
CSS:
#box {
width: 300px;
height: 300px;
background-color: white;
color: white;
vertical-align: middle;
text-align: center;
display: table-cell;
}
button {
width: 148px;
height: 40px;
}
#count, #colour {
background-color: black;
font-family: "Consolas";
}
JavaScript:
var History = window.History;
var i = 0;
var colour = getColour();
var colourName = getColourName();
$(document).ready(function() {
if (History.enabled) {
changeHistory();
}
else {
return false;
}
// Bind to StateChange Event
History.Adapter.bind(window,'statechange', function(){
State = History.getState();
i = State.data.count;
colour = State.data.colour;
colourName = State.data.colourName;
changeHistory();
});
// Trigger the change
$(document).on("click", "#box", function() {
i = i + 1;
colour = getColour();
colourName = getColourName();
changeHistory();
});
$(document).on("click", "#back", function() {
History.back();
});
$(document).on("click", "#forward", function() {
History.forward();
});
});
function getColour() {
var colours = ["rgb(220,45,45)", "orange", "rgb(230,230,50)", "rgb(15,210,80)", "rgb(100,220,220)","rgb(50,80,210)", "rgb(140,20,180)", "rgb(230,70,110)","grey"];
if (i > colours.length - 1) {
i = 0;
}
if (i < 0) {
i = colours.length - 1;
}
return colours[i];
}
function getColourName() {
var colourNames = ["Red","Orange","Yellow","Green","Light Blue","Blue","Purle","Pink","Grey"];
return colourNames[i];
}
// Make the changes
function changeHistory () {
$("#colour").html(colourName);
$("#count").html(i);
$("#box").css('background-color', colour);
History.pushState({count:i, colour: colour, colourName: colourName},"A Shade of " + colourName,"?colour=" + colourName);
}
So going back to what I wanted to achieve with the question:
Clicking the box would add history
Each history would hold variables required to affect global variables
Its worth noting the solution specifically uses variables from each iteration of the history to power the global variables, whereas the program itself uses the global variables. The variables used to power the interface never access the ones stored in history.
Let's break up the program into separate and simpler processes and functions. Much like other history.js solutions, there's things you require to get it working:
History.getState(): Gets the latest history item "from the stack"
History.Adapter.bind(window,'statechange', function() {}: An event listener which will trigger a function when the window has a statechange (history change in this case)
History.pushState({data}, title, url): Pushes a state into the "history stack". Holds an object (data), title (of the tab/window) and url to display.
Setting the history:
When a user clicks on the box, the program should:
increment the counter (i)
change the colour and colourName
add the new history stack object in
I decided to separate the first two features from the last one. The function changeHistory() is responsible for updating the contents of the box (from global variables) and pushing a new history object in (using global variables).
changeHistory() gets called whenever I want to add a new item of history in and update the contents in the box to reflect the new history - so at launch at when the box is clicked.
When the box is clicked, the first two criteria get met. Using the existing global variables and functions, the new colour and name are retrieved and set as the global variables.
This is how it should behave:
Box Click -> Increment i, Change variables -> Push History
Listen for the history change:
Once a history change has been made (either by clicking the box, pressing back/forward buttons or browser buttons), a change needs to occur.
By creating the variable State = History.getState(), we have an easy way of accessing the data from the latest history stack object.
We'll use this data from the history object.data to assign to the global variables. After updating the variables, we'll update the view using changeHistory().
This is how the model should work:
History changed -> Update globals from history -> Update View
History change will occur whenever someone presses back, forwards or the box, accounting for all possible changes.
Related
I've been building an image uploader that is now theoretically complete, but it is not working in Safari.
I initially thought the problem was because DataTransfer, or more specifically dataTransfer.files was not supported, but this is now supported in Desktop Safari (Version 14.1 +). Although there is no support at all in iOS I will be having the file-picker only (no drag and drop) on iOS so this isn't an issue.
The Problem / Scenario on Desktop Safari
When a user clicks the 'select files' link and submits the images, it all works OK.
When a user drags and drops images the file preview images are duplicated (e.g. 2 images shows 4 previews). When submitted, only the 2 images are uploaded though (which is correct obviously).
When a user clicks the 'select files' link instead of using the drag and drop functionality and then deletes an image from the image previews, the image previews are then duplicated (similar to the way the are in point 2. above).
Logically this would make me think the problem is with the change event listener, but I can't seem to fix it.
Any help would be hugely appreciated.
const dropZone = document.getElementById("drop-zone"),
showSelectedImages = document.getElementById("show-selected-images"),
fileUploader = document.getElementById("standard-upload-files");
dropZone.addEventListener("click", (evt) => {
// assigns the dropzone to the hidden input element so when you click 'select files' it brings up a file picker window
fileUploader.click();
});
// Prevent browser default when draging over
dropZone.addEventListener("dragover", (evt) => {
evt.preventDefault();
});
fileUploader.addEventListener("change", (evt) => {
// this function is further down but declared here and shows a thumbnail of the image
[...fileUploader.files].forEach(updateThumbnail);
});
function getFileListItems(files) {
var transferObject = new ClipboardEvent("").clipboardData || new DataTransfer()
for (var i = 0; i < files.length; i++) transferObject.items.add(files[i])
return transferObject.files;
}
dropZone.addEventListener("drop", (evt) => {
evt.preventDefault();
// assign dropped files to the hidden input element
if (evt.dataTransfer.files.length) {
fileUploader.files = getFileListItems([...fileUploader.files, ...evt.dataTransfer.files]);
}
// function is declared here but written further down
[...evt.dataTransfer.files].forEach(updateThumbnail);
});
// updateThumbnail function that needs to be able to handle multiple files
function updateThumbnail(file) {
if (file.type.startsWith("image/")) {
let uploadImageWrapper = document.createElement("article"),
removeImage = document.createElement("div"),
thumbnailElement = new Image();
// 'x' that deletes the image
removeImage.classList.add("remove-image");
removeImage.innerHTML =
'<svg id="remove-x" viewBox="0 0 150 150"><path fill="#000" d="M147.23,133.89a9.43,9.43,0,1,1-13.33,13.34L75,88.34,16.1,147.23A9.43,9.43,0,1,1,2.76,133.89L61.66,75,2.76,16.09A9.43,9.43,0,0,1,16.1,2.77L75,61.66,133.9,2.77a9.42,9.42,0,1,1,13.33,13.32L88.33,75Z"/></svg>';
// image thumbnail
thumbnailElement.classList.add("drop-zone__thumb");
thumbnailElement.src = URL.createObjectURL(file);
// appending elements
showSelectedImages.append(uploadImageWrapper); // <article> element
uploadImageWrapper.append(removeImage); // 'x' to delete
uploadImageWrapper.append(thumbnailElement); // image thumbnail
// Delete images
removeImage.addEventListener("click", (evt) => {
if (evt.target) {
var deleteImage = removeImage.parentElement;
deleteImage.remove();
fileUploader.files = getFileListItems([...fileUploader.files].filter(f => file !== f));
}
});
}
} // end of 'updateThumbnail' function
body {
margin: 0;
display: flex;
justify-content: center;
width: 100%;
}
form {
width: 30%;
}
#drop-zone {
border: 1px dashed;
width: 100%;
padding: 1rem;
margin-bottom: 1rem;
}
.select-files {
text-decoration: underline;
cursor: pointer;
}
/* images that are previewed prior to form submission*/
.drop-zone__thumb {
width: 200px;
height: auto;
display: block;
}
#remove-x {
width: 1rem;
height: 1rem;
}
#submit-images {
margin: 1rem 0;
}
#show-selected-images {
display: flex;
}
<form id="upload-images-form" enctype="multipart/form-data" method="post">
<h1>Upload Your Images</h1>
<div id="drop-zone" class="drop-zone flex">
<p class="td text-center">DRAG AND DROP IMAGES HERE</p>
<p class="td text-center" style="margin: 0">Or</p>
<p class="tl text-center select-files text-bold pointer">Select Files</p>
</div>
<input id="standard-upload-files" style="display:none" style="min-width: 100%" type="file" name="standard-upload-files[]" multiple>
<input type="submit" name="submit-images" id="submit-images" value="SUBMIT IMAGES">
<div id="show-selected-images"></div>
</form>
The problem is that Safari fires a new (trusted o.O) event when you set the .files property of your <input>. (BUG 180465)
Given they do fire that event synchronously, you could workaround that by using a simple flag:
let ignoreEvent = false; // our flag
const inp = document.querySelector("input");
inp.onchange = (evt) => {
if (ignoreEvent) {
console.log("this event should be ignored");
}
else {
console.log("A true change event");
}
};
const dT = new DataTransfer();
dT.items.add(new File(['foo'], 'programmatically_created.txt'));
// right before setting the .files
ignoreEvent = true;
inp.files = dT.files;
// the change event fired synchronously,
// lower the flag down
ignoreEvent = false;
<input type="file" id="inp">
Which in Safari will output "This event should be ignored".
Now, I can't help but remind that setting the FileList of an <input> like that is still a hack. You really shouldn't use this in production. (I did amend my answer to make it clearer.)
So, please go with a simple Array and a FormData for the upload. Moreover since you don't even show the original file picker <input>.
You apparently already did copy-paste a previous answer that you did not understand, so to avoid you falling in this (comprehensible) trap again, I'll just highlight the key points you will need for your implementation, and I won't give you a working example, on purpose.
So first you need to define an Array ([]) which will store the files your user did select and which is accessible to all of the following functions.
In both the drop and input's change events, you will update this Array with the Files newly selected (you can either append the new Files, or replace them, it's your call).
Then you will add a new event listener to the <form> element's submit event. This event will fire when the submit button is clicked.
Its default action would be to POST the <form>'s content to your server, but since we want to include our own Array and not the <input>'s file list (since it could actually be empty), we don't want this default action to occur, so we will call event.preventDefault() to avoid that.
Now, we still want to send something to the server, so we will build a new form, from scratch.
That's where we'll create a new FormData object, and use its append() method to store our files. The first argument of this method should be the field name, which is also the <input>'s name attribute value. The second one should be a single file, so we'll need to call this method as many times as we have items in our Array. Since we deal with File objects, we don't need the third argument.
Once our FormData is complete, we just have to upload it to the server.
To do so you, in modern browsers you can simply use the fetch() method, passing in the URL to the server, and an option bag looking roughly like
{
method: "POST",
body: formData // the FormData object we just created
}
If you need to support older browsers, you can also do the same with an XMLHttpRequest object, passing the FormData in its .send(formData) method.
Since OP apparently can't get out of this despite the step by step explanations, here is a jsfiddle.
I'm trying to create a website builder (drag and drop page builder) and was wondering where to store the styles when someone changes the styles of an element. For example, in WordPress you can type in your own custom CSS in Customizer (image example: https://i.imgur.com/qaUiVl6.png)
In other page builders like Wix or Google Chrome Inspect Element, you can click button to enable or disable styles.
While making current/live CSS edits to the page, where and how are these styles saved? (I'm not talking about a database as the code has not been saved yet. I'm talking about while making changes onsite changes, where do these "temporary/live" CSS styles get saved?)
You can use the CSSStyleSheet APIs to generate a stylesheet in memory then use insert and delete methods to add or remove rules from the stylesheet at will. When the user is done modifying you could then pass the generated stylesheet back server side to save perm.
Ref docs can be found here:
https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet#Methods
Compatability is IE9+ and all other modern browsers so it has good coverage.
Quick and dirty example below.
var style = (function() {
// Create the <style> tag
var style = document.createElement("style");
// Add the <style> element to the page
document.head.appendChild(style);
return style;
})();
function AddRule(){
//append rule from textbox to ss here
style.sheet.insertRule(document.getElementById("cssIn").value, 0);
document.getElementById("appliedRules").innerHTML = '';
var rules = style.sheet.cssRules;
for (var r in rules) {
if(rules[r].cssText){
document.getElementById("appliedRules").innerHTML += '<br>' + rules[r].cssText;
}
}
}
//enable this to see your special prize in the console
//console.log(style.sheet);
<div class="test"> here we go</div>
Add Rule: <input type="text" id="cssIn" value=".test {color:blue}">
<button type="button" onClick="AddRule();">Add</button>
<div id="appliedRules"></div>
Here is a simple proof-of-concept that demonstrates how this can be done using pure javascript. Just click the save button to see the CSS in the textarea get applied to the page. The CSS is just stored as the input value of the textarea element. You can also make it more complex by using localStorage and an iframe or shadow dom so you only affect a "preview" pane. But this is just a demonstration.
function saveStyles() {
document.querySelector('#style-container').innerHTML = document.querySelector('#style-input').value;
}
#style-input {
width: 100%;
box-sizing: border-box;
display: block;
margin-bottom: 8px;
}
<style id="style-container"></style>
<textarea id="style-input" rows="5">body{background:red;}</textarea>
<button onclick="saveStyles()">Save</button>
Here's an alternative that puts the stylesheet into memory and loads it via a blob URL.
This behaves a bit more like a real stylesheet than inline styles do in some edge cases, which may be desirable in some cases. It can also work on a webpage that blocks inline styles via a Content Security Policy (provided blob URL's are allowed).
(function() {
var styles = document.getElementById('styles');
var save = document.getElementById('save');
var link = null;
function getLink() {
if (!link) {
link = document.createElement('link');
link.rel = 'stylesheet';
document.head.appendChild(link);
}
return link;
}
save.addEventListener('click', function() {
var link = getLink();
if (link.href) {
URL.revokeObjectURL(link.href);
}
link.href = URL.createObjectURL(new Blob([styles.value], {type: 'text/css'}));
});
})();
#styles {
display: block;
width: 95%;
}
<textarea id="styles" rows="5">body {
background: green;
}
</textarea>
<button id="save">Save</button>
The answers here focus on the methods for building a stylesheet and adding css rules using common methods browsers provide as part of the DOM api. These are the underlying function calls that any UI framework on the web will use.
But when you ask, "Where is this stored?". In a sense, you are asking how is the "state" managed. If you look at original post jQuery/web app building frameworks, like Backbone.js -- its motto was, "Get your model out of the DOM". So generally the "elements" of the ui-building tools will themselves be represented as component/models.
If you look at view-centric frameworks, like React or Vue, more complex web apps will use a framework like Redux to handle "state", which is stored in single object store. This represents the current options of all the components on the page.
So ultimately, a non-toy WYSIWYG web editor will likely treat each "element" as a component that renders its styles based on an inputted state.
This coupled with a controlled and predictable way to change state, allows for the managing of complex UI. For instance, the click method would trigger an action that acts like the name of an event handler, triggering the functions (in redux world, reducers), which ultimately changes the state, in our example, color of the element.
The low-level calls to the DOM that facilitate this in a complex web-app/web-editor would be abstracted away.
Based on the discussion, I can suggest to use separate styles for each element id. Here is a sketch.
<script>
function setStyle(id, style_text)
{
var style_id = "style_" + id;
var style_forId = "#" + id + " " + style_text;
var styleDom = document.getElementById(style_id);
if(!styleDom)
{
styleDom = document.createElement('style');
styleDom.type = 'text/css';
styleDom.id = style_id;
styleDom.innerHTML = style_forId;
document.getElementsByTagName("head")[0].appendChild(styleDom);
}
else
{
styleDom.innerHTML = style_forId;
}
}
</script>
<button id="myButton1" type="button" >My Button 1</button>
<button id="myButton2" type="button" >My Button 2</button>
<br>
<button onclick="setStyle('myButton1', '{color:red}'); "> Set Red color for myButton1 </button>
<br>
<button onclick="setStyle('myButton2', '{color:red}'); "> Set Red color for myButton2 </button>
<br>
<button onclick="setStyle('myButton1', '{color:green}'); "> Set Green color for myButton1 </button>
<br>
<button onclick="setStyle('myButton2', '{color:green}'); "> Set Green color for myButton2 </button>
<br>
Great Answers already just putting a different viewpoint out there.
Simple Version
Using CSSStyleSheet
const style = document.createElement("style");
document.head.appendChild(style);
style.sheet.insertRule(`
header {
background: 'black'
}
`, 0);
Real World
I would take this simple idea and control it through a data structure like this.
// A JS Object to control and modify CSS styles on.
const css = {
header: {
background: 'black',
border: '2px green solid',
'font-size': '12px'
}
}
// Converts JS Object to CSS Text (This is not battle tested)
function objToCss(style) {
return Object.entries(style).reduce((styleString, [propName, propValue]) => {
propName = propName.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
if (typeof propValue === 'object') {
return `${styleString}${propName}{${objToCss(propValue)}}`;
} else {
return `${styleString}${propName}:${propValue};`;
}
}, '')
}
// Setup
const style = document.createElement("style");
document.head.appendChild(style);
style.sheet.insertRule(objToCss(css), 0);
// Updates
css.header.border = '1px blue solid';
style.sheet.replace(objToCss(css), 0);
I've made a mistake. I paired my functionality to .on('click', ...) events. My system installs certain items and each item is categorized. Currently, my categories are [post, image, widgets], each having its own process and they are represented on the front-end as a list. Here's how it looks:
Each one of these, as I said, is paired to a click event. When the user clicks Install a nice loader appears, the <li> itself has stylish changes and so on.
I also happen to have a button which should allow the user to install all the items:
That's neat. Except...there is absolutely no way to do this without emulating user clicks. That's fine, but then, how can I wait for each item to complete (or not) before proceeding with the next?
How can I signal to the outside world that the install process is done?
It feels that if I use new CustomEvent, this will start to become hard to understand.
Here's some code of what I'm trying to achieve:
const installComponent = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve();
}, 1500);
});
};
$('.item').on('click', (event) => {
installComponent().then(() => {
console.log('Done with item!');
});
});
$('#install-all').on('click', (event) => {
const items = $('.item');
items.each((index, element) => {
element.click();
});
});
ul,
ol {
list-style: none;
padding: 0;
margin: 0;
}
.items {
display: flex;
flex-direction: column;
width: 360px;
}
.item {
display: flex;
justify-content: space-between;
width: 100%;
padding: 12px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 0;
}
.item h3 {
width: 80%;
}
.install-component {
width: 20%;
}
#install-all {
width: 360px;
height: 48px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="items">
<li class="item" data-component-name="widgets">
<h3>Widgets</h3>
<button class="install-component">Install </button>
</li>
<li class="item" data-component-name="post">
<h3>Posts</h3>
<button class="install-component">Install </button>
</li>
<li class="item" data-component-name="images">
<h3>Images</h3>
<button class="install-component">Install </button>
</li>
</ul>
<button id="install-all">Install All</button>
As you can see, all clicks are launched at the same time. There's no way to wait for whatever a click triggered to finish.
This is simple architectural problems with your application that can be solved by looking into a pattern that falls into MVC, Flux, etc.
I recommend flux a lot because it’s easy to understand and you can solve your issues by separating out your events and UI via a store and Actions.
In this case you would fire an action when clicking any of these buttons. The action could immediately update your store to set the UI into a loading state that disables clicking anything else and show the loader. The action would then process the loader which can be monitored with promises and upon completion the action would finalize by setting the loading state in the store to false and the UI can resolve to being normal again. The cool thing about the proper separation is the actions would be simple JS methods you can invoke to cause all elements to install if you so desire. Essentially, decoupling things now will make your life easier for all things.
This can sound very complicated and verbose for something as simple as click load wait finish but that’s what react, angular, flux, redux, mobx, etc are all trying to solve for you.
In this case I highly recommend examining React and Mobx with modern ECMaScript async/await to quickly make this issue and future design decisions much easier.
What you should do is to declare a variable which will store the installation if it's in progress. And it will be checked when you are trying to install before one installation is complete.
var inProgress = false;
const installComponent = () => {
inProgress = true;
return new Promise((resolve, reject) => {
if(inProgress) return;
else{
setTimeout(() => {
inProgress = false;
return resolve();
}, 1500);
}
});
};
I'd be looking to implement something like this:
let $items = $('.items .item');
let promises = new Array($items.length);
// trigger installation of the i'th component, remembering the state of that
function startInstallOnce(i) {
if (!promises[i]) {
let component = $items.get(i).data('component-name');
promises[i] = installComponent(component);
}
return promises[i];
}
// used when a single item is clicked
$items.on('click', function(ev) {
let i = $(this).index();
startInstallOnce(i);
});
// install all (remaining) components in turn
$('#install-all').on('click', function(ev) {
(function loop(i) { // async pseudo-recursive loop
if (i === components.length) return; // all done
startInstallOnce(i).then(() => loop(i + 1));
})(0);
});
Basically, I have an appointment form which is broken down into panels.
Step 1 - if a user clicks london (#Store1) then hide Sunday and Monday from the calendar in panel 5.
Basically, I want to store this click so that when the user gets to the calendar panel, it will know not to show Sunday and Monday
$('#store1').click(function () {
var $store1 = $(this).data('clicked', true);
console.log("store 1 clicked");
$('.Sunday').hide();
$('.Monday').hide();
});
after I have captured this in a var I then want to run it when the calendar displays.
function ReloadPanel(panel) {
return new Promise(function (resolve, reject, Store1) {
console.log(panel);
console.log("finalpanel");
panel.nextAll('.panel').find('.panel-updater').empty();
panel.nextAll('.panel').find('.panel-title').addClass('collapsed');
panel.nextAll('.panel').find('.panel-collapse').removeClass('in');
var panelUpdater = $('.panel-updater:eq(0)', panel),
panelUrl = panelUpdater.data('url');
if (panelUpdater.length) {
var formData = panelUpdater.parents("form").serializeObject();
panelUpdater.addClass('panel-updater--loading');
panelUpdater.load(panelUrl, formData, function (response, status) {
panelUpdater.removeClass('panel-updater--loading');
if (status == "error") {
reject("Panel reload failed");
} else {
resolve("Panel reloaded");
}
});
} else {
resolve("no reloader");
}
});
}
I'm not sure if this is even written right, so any help or suggestions would be great
Thanks in advance
Don't think of it as "storing a click". Instead, consider your clickable elements as having some sort of data values and you store the selected value. From this value you can derive changes to the UI.
For example, consider some clickable elements with values:
<button type="button" class="store-button" data-store-id="1">London</button>
<button type="button" class="store-button" data-store-id="2">Paris</button>
<button type="button" class="store-button" data-store-id="3">Madrid</button>
You have multiple "store" buttons. Rather than bind a click event to each individually and customize the UI for each click event, create a single generic one which captures the clicked value. Something like:
let selectedStore = -1;
$('.store-button').on('click', function () {
selectedStore = $(this).data('store-id');
});
Now anywhere that you can access the selectedStore variable can know the currently selected store. Presumably you have some data structure which can then be used to determine what "days" to show/hide? For example, suppose you have a list of "stores" each with valid "days":
let stores = [
{ id: 1, name: 'London', days: [2,3,4,5,6] },
// etc.
];
And your "days" buttons have their corresponding day ID values:
<button type="button" class="day-button" data-day-id="1">Sunday</button>
<button type="button" class="day-button" data-day-id="2">Monday</button>
<!--- etc. --->
You can now use the data you have to derive which buttons to show/hide. Perhaps something like this:
$('.day-button').hide();
for (let i in stores) {
if (stores[i].id === selectedStore) {
for (let j in stores[i].days) {
$('.day-button[data-day-id="' + stores[i].days[j] + '"]').show();
}
break;
}
}
There are a variety of ways to do it, much of which may depend on the overall structure and flow of your UX. If you need to persist the data across multiple pages (your use of the word "panels" implies more of a single-page setup, but that may not necessarily be the case) then you can also use local storage to persist things like selectedStore between page contexts.
But ultimately it just comes down to structuring your data, associating your UI elements with that data, and performing logic based on that data to manipulate those UI elements. Basically, instead of manipulating UI elements based only on UI interactions, you should update your data (even if it's just in-memory variables) based on UI interactions and then update your UI based on your data.
you can use the local storage for that and then you can get your value from anywhere.
Set your value
localStorage.setItem("store1", JSON.stringify(true))
Get you value then you can use it anywhere:
JSON.parse(localStorage.getItem("store1"))
Example:
$('#store1').click(function() {
var $store1 = $(this).data('clicked', true);
localStorage.setItem("store1", JSON.stringify(true))
console.log("store 1 clicked");
$('.Sunday').hide();
$('.Monday').hide();
});
I currently have a page that displays data for different teams.
I have some data that the user can click on to make it an "on" or "off" state, showing a different icon for each. It's basically like a checklist, just without the physical checkboxes.
I would like to remember which of the "checkboxes" have been ticked, even after the user refreshes the page or closes the browser and returns later.
I have heard that localStorage is a good option, but I'm not sure how to use it in a situation like mine.
Currently I have this code:
team1 = {
"information1": {
"name": "tom",
"age": "34"
},
"information2": {
"name": "bob",
"age": "20"
},
};
team2 = {
"information1": {
"name": "betsy",
"age": "27"
},
"information2": {
"name": "brian",
"age": "10"
},
};
$(document).ready(function() {
$("#displayObject1").on("click", function() {
switchData(team1);
});
$("#displayObject2").on("click", function() {
switchData(team2);
});
$("#table").on("click", ".on", function() {
$(this).removeClass("on");
$(this).addClass("off");
});
$("#table").on("click", ".off", function() {
$(this).addClass("on");
$(this).removeClass("off");
});
});
function switchData(object) {
$("#table").contents("div").remove();
if (!('rows' in object)) {
var rows = [];
Object.keys(object).forEach(function (key) {
if (key != 'rows') {
rows.push($('<div class="row on">' + object[key].name + '</div>'));
}
});
object.rows = rows;
}
object.rows.forEach(function (row) {
$('#table').append(row);
});
}
This makes rows to represent a team. The rows are retained with their color highlighting when the user looks at different teams during a browser session.
This is my HTML:
<div id="displayObject1">
<span>Display object 1</span>
</div>
<div><hr></div>
<div id="displayObject2">
<span>Display object 2</span>
</div>
<div id="table">
</div>
And some CSS to show which list items are "on" and "off".
.on {
background-color: green;
}
.off {
background-color: red;
}
How can the page remember the color highlighting?
If you want to use local storage to make the state of the team listings persist across browser sessions, you have to maintain a logical representation of the state and save it as a string whenever the state changes.
JSON.stringify lets you encode a JavaScript object in a JSON string. For example, you can call a function like the following whenever you modify a global object named pageState:
function savePageState() {
localStorage.setItem('pageState', JSON.stringify(pageState));
}
To retrieve the page state on page load, you can do something like this:
pageState = JSON.parse(localStorage.getItem('pageState'));
if (pageState === null) {
pageState = {
teams: teams
};
savePageState();
} else {
teams = pageState.teams;
}
If pageState wasn't saved in a previous session, it is now created and saved to local storage. Otherwise, we consult pageState for data that we can use to restore the previous appearance of the team listings.
This code sample works on the assumption that the global object teams contains information relevant to the page state. You can add further properties to the page-state object to store more information. For example, to remember which team is currently displayed, you could do:
pageState.showTeam = teamName;
Then you can consult pageState.showTeam when you're initializing the page contents, perhaps like this:
if (teamName == pageState.showTeam) {
showTeam(teamName);
$(label).addClass('selected');
}
I have made a page that demonstrates this approach. I can't include it in my answer as a snippet because localStorage is sandboxed, but you can access the page here:
http://michaellaszlo.com/so/click-rows/
I've reorganized your team data to enable dynamic page initialization. Now each team object contains an array of person objects.
When the user clicks on a team member's name, the CSS class selected is toggled on the HTML element and the corresponding person object is updated by toggling its selected property:
function memberClick() {
$(this).toggleClass('selected');
this.person.selected = (this.person.selected ? false : true);
savePageState();
};
The showTeam function checks a person's selected property when it's building its HTML representation, and adds the CSS class selected if appropriate. This is what makes it possible to restore the visual appearance of the page from the last session.
you can set a flag in localStorage for each option using:
localStorage.setItem('item1',true) //set the value to the checkbox's value
and access it using:
flagValue = localStorage.item1
use these values similarly in your code once the document is ready to set the initial values of the checkboxes accordingly