Saving many persistents configurations values with javascript - javascript

I have many dropdown lists which I created and work properly .When I click on its list title element (here on City ) it opens or shows its dropdown menu below .
Below is an illustration to let you know how all my dropdown list are made .
<div class="dropdown-container">
<div class="title">City </div>
<ul class="dropdown-menu" >
<li class="glist_item">Calgary</li>
<li class="glist_item">Miisssauga</li>
<li class="glist_item">Winnipeg</li>
<li class="glist_item">Vancouver</li>
<li class="glist_item">Surrey</li>
</ul>
</div>
My problem is how to save the state (opened or closed) of each dropdown list after the page reloads so that those who were opened or closed keep their respectives state before reload.
NB: I tried localStorage or sessionStorage before but these cannot store complex data or data with many records since the number of list is undefined .
So I need a persistent data storage capability as well as an ability to store data like array does .
CAN YOU HELP ME ?

You can do it in this way. Just create an array includes information of lists states.
let states = {
list1: "opened",
list2: "opened",
list3: "closed",
list4: "opened",
list5: "opened",
}
Here, I think you can change listx to list id.
You can save this to localStorage by converting it to string.
Then, you can get that string from localStorage and parse it to js object and do what you are going to do.

Related

Retain state of multi-level navbar/sidebar on new page

I'm very new to programming & working on creating a website for a work project.
In it, there will be a multi-level (w/sub-menus) vertical sidebar on each page.
The problem I'm facing is that every time a user clicks on one link, the sidebar resets to its original state & will have to redo the same thing & not very UX friendly.
I took the template of the accordian sidebar from here.
I've looked at various search results on both stack overflow & google, but can't seem to understand how to get it working to retain the state of the sidebar, regardless of how many levels are opened.
Can someone please help me with the JS code to get it working?
UPDATE:
Nathan, thanks for writing mate! I really appreciate the help.
So based on your suggestion, I've written the following (shoddy) code that injects the 'checked' attribute to the input element.
But it isn't transferring over to the new/redirected html page when a user clicks on one of the sub-menus. What am I missing here?
var menuIndex = -1;
//extract all the input elements
var inputs = document.querySelectorAll('.parent-menu');
//Find index of the element from the array that has "checked == true"
function indexFinder() {
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].checked == true) {
menuIndex = i;
console.log(menuIndex);
}
};
}
//Function to set/inject the attribute
function attributeSetter() {
inputs[menuIndex].setAttribute('checked', 'checked')
}
//When a user clicks literally anywhere, it'll run the indexFinder function
//to check if any of the input elements were expanded (i.e. checked == true)
window.addEventListener('click', () => {
indexFinder();
});
//Run the attributeSetter function when a page loads
window.addEventListener('DOMContentLoaded', () => {
attributeSetter();
});
Welcome to the world of programming! Hopefully I can help you out a little!
So what you're asking is something that can easily get a little complicated.
In order to achieve what you're trying to do you need to specify how you want your menu to look on each individual page!
Allow me to present a few menu options for an imaginary site:
Home
Contact
Email
Mail
About
The Company
Our Owner
I've indented the page names based on how we want them to show up in our menu.
So for example you may click on "Contact" and it drops down with the Email and Mail options.
Well, if you take your regular code from that webpage and copy and paste it everywhere. Any time you reload a page (or travel to another page with the same code) it's gonna reset the code! Thus "closing" the menu. Think of it as some sort of multi-dimentional sci-fi. When you load a webpage, you are accessing the main flow of time, any time you make an update to that page it takes you to an alternate reality with that change! but once you reload the webpage you jump back to the main timeline as if you never made that change (when you get into more advanced web dev, this analogy will break down but it should work to help your understanding for now.)
So let's say I click on the Contact > Email option and it takes me to the Email page. Well, in order to make it seem like my changes to the menu bar (clicking "Contact" to expand the dropdown) are still active. I need to hardcode the change into the Email page!
Here's some sample code:
<nav class="nav">
<a class="navOption">Home<a>
<a class="navOption">Contact<a>
<div class="navDropdown">
<a class="navOption">Email<a>
<a class="navOption">Mail<a>
</div>
<a class="navOption">About<a>
<div class="navDropdown">
<a class="navOption">The Company<a>
<a class="navOption">Our Owner<a>
</div>
<nav>
By default the .navDropdown will be closed. However when we add a class to them .active they will expand! If this is my base menu, then how should I make it so that the "About" dropdown is expanded when you are on one of the About pages?
Simply by adding .active to that dropdown!
<nav class="nav">
<a class="navOption">Home<a>
<a class="navOption">Contact<a>
<div class="navDropdown">
<a class="navOption">Email<a>
<a class="navOption">Mail<a>
</div>
<a class="navOption active">About<a>
<div class="navDropdown">
<a class="navOption">The Company<a>
<a class="navOption">Our Owner<a>
</div>
<nav>
Now, my example is different from yours because it's meant more for JavaScript. However, you can use the same concept in your code too.
For you, instead of having a .active class to expand a dropdown menu. You are using a checkbox element! In your codem you have CSS which is checking to see if the checkbox is checked and then it is opening the dropdown menu if it is:
<input class="cd-accordion__input" type="checkbox" name ="group-1" id="group-1">
So, if we use this method on our example webpage. We could set it to be open by setting the checkbox to start out being checked. Like so:
<input class="cd-accordion__input" type="checkbox" name ="group-1" id="group-1" checked>
It's important to note that as you get better and better at web development (eventually learning JavaScript and a server side language such as PHP). You will be able to piece together more advanced methods to doing what we're trying to accomplish! But for now, I hope I was able to break this down for you!

How to change element attributes in Jest testing?

I have a filter bar written with React which has basically 3 dropdowns in it. My goal is to test whether clicking one of the dropdowns and selecting an item changes the redux state.
At the start of my app, I make an API request to pull all items which will be filtered by filter bar.
And to prevent any user apply a filter before all items loaded, I made all dropdowns disabled
Communication between dropdowns and listed items are provided by redux.
The problem is since I cannot fake redux, I try to grab the dropdowns and make them enabled by changing their properties, but changing properties doesn't make dropdowns clickable at all.
How can I achieve the behavior I want?
I've already tried to mock API request. But because the API call is fired from different component, mocking it has no use
it("selecting a filter updates redux store", async () => {
const { getByText, getByLabelText } = setup(FilterNav);
const countrySelect = getByText("Country Filter");
// Dropdown disabled
// <a aria-haspopup="true" aria-expanded="false" href="#"
class="dropdown-toggle nav-link disabled" role="button"
tabindex="-1" aria-disabled="true">Country Filter</a>
countrySelect.removeAttribute("aria-disabled");
countrySelect.classList.remove("disabled");
// Enabled
// <a aria-haspopup="true" aria-expanded="false" href="#"
class="dropdown-toggle nav-link" role="button">Company</a>
fireEvent.click(countrySelect);
// countrySelect is disabled at the beginning
// updating state some how doesn't update the disabled attribute
// so click becomes useless
const targetCountry = await waitForElement(() =>
getByLabelText("Example Country")
);
});
Start by breaking up the components in a better way. First, separate the data fetching (api call) from the list of dropdowns. Then, have the dropdown list component accept the data that is needed. This will allow you to easily mock the data when creating a unit test.
Once the first dropdown has its data and is enabled, this can be tested by:
expect(dropdown1.prop('disabled')).toBeTruthy()
expect(dropdown2.prop('disabled')).toBeFalsy()
expect(dropdown3.prop('disabled')).toBeFalsy()
I don't think you need to update the disabled attribute. However, if this is the case you should be able to use something like .setProp()
If you're not using jest/enzyme just determine an equivalent for prop() and setProp()

Intel appframework ui: how to pass data in a simple list-detail app

I try to learn the Intel Appframework UI.
I want to build a simple offline app with just two panels.
One panel contains a list with items (links), the second panel should display details to the selected item. The details in the second panel should be filled with javascript.
How do I pass data (for example a numeric item_id) to the second panel, so that my javascript can access it and fill it into the details page?
Can I use a part of the url of the links in the list, like this:
<li>
Item 3
</li>
If yes, how do I get the information back in the details page? Or do I need to use javacript-links?
In the intel appframework forum is an answer to my question:
https://forums.html5dev-software.intel.com/viewtopic.php?f=26&t=4478
So the solution is to use urls like like this:
<li>
Item 3
</li>
We than attach a handler to the target panel, which gets the url from the browser. The framework has set the url to the new value when the callback is called:
$("#detailpanel").bind("loadpanel",function(evt){
//url would be #detailpanel/N
params = document.location.hash.split('/');
if(params.length > 1){
showDetails(params[1]);
}
else {
console.log('on_load_detailpanel: detailnr missing');
}
})
I'm not an Appframework expert, but here's the simplest solution I came up with:
Since you're on a single-page app, you can store the currently selected item in a global data structure:
<script type="text/javascript">
var dialogData = {
"currentItemNo": -1,
// possibly other dialog data
};
</script>
Then in your HTML you can set it like this:
<li>
Item 3
<li>
and access it when the user navigates to the page:
<script type="text/javascript">
$("#detailpanel").bind("loadpanel", function () {
console.log("You clicked " + dialogData.currentItemNo);
});
</script>
Note that both the click handler and AF's anchor handler get called (the click handler first).
In case your list is generated from a template you might want to use a more elegant solution to access the current item number:
<li>
Item 3
<li>

localStorage save content of multiple list items by id on click and get saved content on different page

I'm trying to have functionality where you can click on the, "MAKE IT MY STORE," link and it saves the content of the li based on the id and will pull the content of the li onto another page.
This code:
<li><xsl:attribute name="id">store-<xsl:value-of select="#id"/></xsl:attribute></li>
outputs to
<li id="store-1075"></li>
and the number 1075 represented by, "#id," changes based on each store location and there are multiple locations.
The li in this section auto-generates any store locations added in the backend. So on the frontend it currently displays more than one list item store location.
<ul id="storeList">
<xsl:for-each select="$currentPage/* [#isDoc]">
<xsl:sort select="#nodeName" order="ascending" />
<li>
<xsl:attribute name="id">store-<xsl:value-of select="#id"/></xsl:attribute>
<a href="{umbraco.library:NiceUrl(#id)}">
<img class="location-image" alt="">
<xsl:attribute name="src">
<xsl:value-of select="storeImage"/>
</xsl:attribute>
</img>
</a>
<a href="{umbraco.library:NiceUrl(#id)}">
<xsl:value-of select="location"/>
</a>
<p><xsl:value-of select="./address"/></p>
<p><xsl:value-of select="./addressCity"/></p>
<div class="makeMyStoreWrapper"><a class="makeMyStore" href="#"><xsl:attribute name="id">store-<xsl:value-of select="#id"/></xsl:attribute>MAKE IT MY STORE</a></div>
</li>
</xsl:for-each>
</ul>
Right now I have it setup to save the state of the, ul id="storeList" when clicked using localStorage.setItem. And then any time the id of #storeList appears in the code it will pull in whatever value was saved to localStorage as seen below:
$(function() {
var save = $('#storeList').get(0);
$(save).click(function() {
localStorage.setItem('saved', this.innerHTML);
});
if ( localStorage.getItem('saved') ) {
save.innerHTML = localStorage.getItem('saved');
}
Obviously this is not what I want it to do. Instead I want it to save the content of each individual li clicked. I want the code to be something like this:
var id = any of the store ids or the specific ids, whatever works better.
var save = document.getElementById('store'+id);
or maybe var id equals an array of all the possible store id numbers.
The main purpose is a store locator page where you can click on one or more of the stores and save them to your, "My Store," page. I almost have it working but I've hit a wall. Any help would be awesome and let me know if there is anything I need to clarify. I don't ever work with ASP so this is tricky for me and this is my first time doing any jQuery or javascript from scratch. I originally tried using it with cookies but decided to use localStorage instead since there is no user login.
I just finished a similar type project, hopefully this will get you in the right direction
Inside the document ready function:
$('.store').on('click', function () { // something with the class store clicked
var storeStorageKey = $(this).attr('id'); // store id of the item
$(this).data('value', 1); // use of html5 data values on the html line helped alot, just settin static right now
//set in local storage so refreshing the page remembers what youve clicked
localStorage.setItem(storeStorageKey, $(this).data('value'));
});
Now you can have a collection you can access via store ids, although it might be better to store the entire local storage value as a JSON object to iterate thru, whichever you prefer
Heres a link to my project javascript that used local storage if you would like reference https://github.com/Jrizzi1/ND-mSummit-bingo/blob/master/js/script.js

Data storage/access/writing/editing and ajax calling

I've come up with a way to maintain and combine two lists, however I am a little puzzled exactly how I can display the result without the page being stored. I thought .data() might be the solution - but this is new to me.
Basically I have 2 lists:
<ul id="choice_1">
<li><img id="c1_1" src="image_path">Option 1</li>
<li><img id="c1_2" src="image_path">Option 2</li>
<li><img id="c1_3" src="image_path">Option 3</li>
</ul>
<ul id="choice_2">
<li><img id="c2_1" src="image_path">Option A</li>
<li><img id="c2_2" src="image_path">Option B</li>
<li><img id="c2_3" src="image_path">Option C</li>
</ul>
Then with jQuery I've made a script where you can click the image to select the choice(s) from the left and right column, where the image changes onclick and stores the ID numbers in two hidden inputs - #hidden_1 and #hidden_2 (this part works perfectly, and stores just the number from the image ID). I've also made a button #transfer which, when clicked, gets the values of the hidden text inputs and resets the choices as if nothing has been selected.
I thought first that I should pass the hidden data via ajax to #groups, however since I don't want to save the data in MySQL until someone hits the save button, and I also want to process the data (so that if someone were to select Option 1 with Options A+B, then next selecting Option 2 with A+B, I would process the data in PHP to show the result being Option 1+2, A+B - since they effectively go together. Then I would use the ID values to display the name of the Options. Then I could either delete or edit (by sending the data back to the lists) the groups.
This made me think that somehow .data() could be used to store the data, however I'm unsure how I can access it from #groups since it has been called via ajax. I also thought that would be handy, especially if I later came to edit the groups after they were stored in MySQL. My ajax call is below:
var data_array = {};
data_array['id'] = $("#hidden_id").val(); // only used when editing
data_array['c1'] = $("#hidden_1").val();
data_array['c2'] = $("#hidden_2").val();
$.ajax({
url: "processor.php",
type: "POST",
data: data_array,
success: function(response){
$("#groups").html(response);
},
error: function(){
$("#groups").html("<p>Could not process choices</p>");
}
});
In processor.php I thought that I could access .data() simply by calling it $("data").data();, but it seems to just return null - though I then thought that even if I could call it, how can I allow PHP to process the data, so I guess I'd have to pass that through ajax anyway. But then, how could I delete something from #groups so it doesn't get passed back later and get displayed? Or how to I overwrite the data with the updated edit? If I could access .data() in processor.php after ajax call, write to it, then make it available to the rest of the page, then that would be the goal.
I hope someone can help me figure this out - I am a bit new to .data(), especially accessing/writing from inside some called by ajax.
Seems I need to do something like this is the parent:
$.ds = "";
$.ds = $("#data").data(JSON.parse()); // json_encode the existing php array (if there is one)
Then I can access/edit/update it within processor.php using the below:
$.ds.data();
Fingers crossed that I can put it all together working the way I want - works so far just using test data above, still - if anyone has a better way, best let me know :)

Categories

Resources