I have been racking my brain how I could include a toggle menu on my website, after some searching I found the below and have implemented it, which is great!
http://jsfiddle.net/hhcsz5cr/
<div>
<h1><button class="button" data-circle="travel">
<i class="fa fa-plus-square"></i>
</button> Travel</h1>
</div>
<div class="travel options">
<ul>
<li>Travel</li>
<li>Vehicles</li>
</ul>
</div>
var localStorageKey = "app_state";
// to preserve state, you first need to keep track of it
var default_state = {
biographies: false,
pictures: false,
poetry: false
}
var saved_state = localStorage.getItem(localStorageKey);
// ternary operator which means if `saved_state` is true we parse it and use that value for `state`; otherwise use `default_state`
var state = saved_state ? JSON.parse(saved_state) : default_state;
$(function() {
init();
$('.button').on('click', function() {
var circle = $(this).attr('data-circle');
toggleCircle(circle, !state[circle]);
$(this).find('i').toggleClass('fa-minus fa-plus');
});
});
function init() {
for(var key in state) {
var is_displayed = state[key];
if ( is_displayed ) {
$(this).find('i').toggleClass('fa-minus fa-plus');
} else {
$(this).find('i').toggleClass('fa-plus fa-plus');
}
console.log(is_displayed);
toggleCircle(key, is_displayed);
}
}
function toggleCircle(circle, is_displayed) {
if (is_displayed) {
$('.'+circle).show()
state[circle] = true;
} else {
$('.'+circle).hide()
state[circle] = false;
}
localStorage.setItem(localStorageKey, JSON.stringify(state));
}
But.. if you minimize a menu then refresh the icon shows a - even though its already minimize.
Is there any way I can change this?
I realise the code above is not my own and I can't find the person to credit! My jquery is terrible.
Any help would be appreicated.
Thanks
jsFiddle DEMO (since SO snippets do not allow localStorage from Iframe)
Use IDs, not classes. IDs are unique, not classes.
Store the entire ID as the object property i.e: "#pictures": false,
Store the entire selector inside data-* i.e: data-toggle="#biographies"
Use "is-*" classes as state CSS helpers: "is-hidden", "is-expanded"
You don't have to use .fa classes, just use CSS and font-family
Make use of Object.assign() to override your default values with the ones in Local Storage (if any).
Loop your object key value pairs using Object.entries() when initing your menu states.
// Override defaults with localStorage
const state = Object.assign({
"#biographies": false, // Feel free to change this default boolean
"#pictures": false,
"#poetry": false
}, JSON.parse(localStorage.state || "{}"));
const toggle = (k, v) => {
$(k).toggleClass('is-hidden', !v);
$(`[data-toggle="${k}"]`).toggleClass('is-expanded', v);
};
// On init
Object.entries(state).forEach(([k, v]) => toggle(k, v));
// On click
$("[data-toggle]").on("click", function() {
const id = this.dataset.toggle; // Get ID i.e: "#pictures"
state[id] = !state[id]; // Flip boolean
toggle(id, state[id]); // Trigger UI changes
localStorage.state = JSON.stringify(state); // Store into LS
});
.is-hidden {
display: none;
}
[data-toggle] i:before{
font-family: "FontAwesome";
font-style: normal;
content: "\f067"; /* Plus */
}
[data-toggle].is-expanded i:before{
content: "\f068"; /* Minus */
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css">
<div id="biographies" class="is-hidden">Biography</div>
<div id="pictures" class="is-hidden">Pictures</div>
<div id="poetry" class="is-hidden">Poetry</div>
<button type="button" class="button" data-toggle="#biographies">
<i></i> biographies
</button>
<button type="button" class="button" data-toggle="#pictures">
<i></i> pictures
</button>
<button type="button" class="button" data-toggle="#poetry">
<i></i> poetry
</button>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
Please try this..
$('.button').click(function(){
var whichbtn = $(this).attr('data-circle');
if($("."+whichbtn).hasClass("hidden")){
$(this).children("i").removeClass("fa-plus").addClass("fa-minus");
}else{
$(this).children("i").addClass("fa-plus").removeClass("fa-minus");
}
$("."+whichbtn).toggleClass("hidden");
});
.hidden{display:none;}
.button{
background:#00cc00;
padding:10px 20px;
margin-right:10px;
border:none;
color:white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet"/>
<div class="m-3">
<div class="biographies hidden mb-2 font-weight-bold">Biography</div>
<div class="pictures hidden mb-2 font-weight-bold">Pictures</div>
<div class="poetry hidden mb-2 font-weight-bold">Poetry</div>
<button class="button" data-circle="biographies">
<i class="fa fa-plus"></i> biographies
</button>
<button class="button" data-circle="pictures">
<i class="fa fa-plus"></i> pictures
</button>
<button class="button" data-circle="poetry">
<i class="fa fa-plus"></i> poetry
</button>
</div>
I add js for click event of button and get the attribute of data-circle of it's own for find which button clicked. And fa fa-plus icon changed to fa fa-minus also. Thats only.
toggleClass is used for toggle class when user click the button. First click add class hidden then second click remove class hidden.For more clarifications comment me.
Related
I have created 2 buttons, each button will replace content based on partial views, I could load the partial view on the page when I click the button, but this works only once, for instance I clicked button-1 and loaded data, now if I click on button 2 its not working, I needed to go back to main page to click again on button-2
<h3>
<a class="btn btn-warning" id="button1"> Partial View 1</a>
</h3>
<br/>
<h4>
<a class="btn btn-warning" id="buttton2"> Partial view 2</a>
</h4>
<br/> <br/>
<div id="testsim">
</div>
<script type="text/javascript">
$(function () {
$('#button1').click(function () {
$.get('#Url.Action("partialview1", "Home")', function (data1) {
if (data1) {
$('#testsim').replaceWith(data);
}
});
});
$('#button2').click(function () {
$.get('#Url.Action("partialview2", "Home")', function (data2) {
if (data2) {
$('#testsim').replaceWith(data2);
}
});
});
});
</script>
I'm trying to achieve button clicks to toggle between two buttons, everytime button click should replace the content in div tag. Any help would be appreciated.
I think the problem is because of replaceWith() which replaces the element itself i.e. outerHTML-
$(function() {
let $html, current;
$('#button1').click(function() {
/* $.get('#Url.Action("partialview1", "Home")', function(data1) {
if (data1) {
$('#testsim').replaceWith(data);
}
});*/
current = `button 1 was <em>clicked</em>`;
$html = `<div><strong>${current}</strong></div>`;
$('#testsim').replaceWith($html);
});
$('#button2').click(function() {
/*$.get('#Url.Action("partialview2", "Home")', function(data2) {
if (data2) {
$('#testsim').replaceWith(data2);
}
});*/
current = `button 2 was <strong>clicked</strong>`;
$html = `<div><em>${current}</em></div>`;
$('#testsim').replaceWith($html);
});
});
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h3>
<a class="btn btn-warning" id="button1"> Partial View 1</a>
</h3>
<br/>
<h4>
<a class="btn btn-warning" id="button2"> Partial view 2</a>
</h4>
<br/> <br/>
<div id="testsim" style="background: aquamarine; height: 200px">
</div>
As you can see that the styling of the element disappears after replacing. If you want to perform this operation then you should use html() which replaces only innerHTML-
$(function() {
let $html, current;
$('#button1').click(function() {
/* $.get('#Url.Action("partialview1", "Home")', function(data1) {
if (data1) {
$('#testsim').replaceWith(data);
}
});*/
current = `button 1 was <em>clicked</em>`;
$html = `<div><strong>${current}</strong></div>`;
$('#testsim').html($html);
});
$('#button2').click(function() {
/*$.get('#Url.Action("partialview2", "Home")', function(data2) {
if (data2) {
$('#testsim').replaceWith(data2);
}
});*/
current = `button 2 was <strong>clicked</strong>`;
$html = `<div><em>${current}</em></div>`;
$('#testsim').html($html);
});
});
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h3>
<a class="btn btn-warning" id="button1"> Partial View 1</a>
</h3>
<br/>
<h4>
<a class="btn btn-warning" id="button2"> Partial view 2</a>
</h4>
<br/> <br/>
<div id="testsim" style="background: aquamarine; height: 200px">
</div>
Hope this helps you.
I am using google's material design for web. And I would like to use their select component as an autocomplete component. What I am aiming for is the autocomplete you can see in react mui. I have removed the disabled and readonly input attributes but I still can't write anything to input field.
<div class="mdc-select demo-width-class">
<div class="mdc-select__anchor">
<span class="mdc-select__ripple"></span>
<input type="text" class="mdc-select__selected-text">
<i class="mdc-select__dropdown-icon"></i>
<span class="mdc-floating-label">Pick a Food Group</span>
<span class="mdc-line-ripple"></span>
</div>
<div class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
<ul class="mdc-list">
<li class="mdc-list-item mdc-list-item--selected" data-value="" aria-selected="true"></li>
<li class="mdc-list-item" data-value="grains">
<span class="mdc-list-item__text">
Bread, Cereal, Rice, and Pasta
</span>
</li>
<li class="mdc-list-item" data-value="vegetables">
<span class="mdc-list-item__text">
Vegetables
</span>
</li>
<li class="mdc-list-item" data-value="fruit">
<span class="mdc-list-item__text">
Fruit
</span>
</li>
</ul>
</div>
</div>
How can I enable the input field for writing into it, so that I can adapt and make an autocomplete input field?
since you have mentioned that you would like to see an answer without using react, thus in plain javascript. I have managed to do just that. I have combined the css and markup from #Sifat Haque's answer and the full working autocomplete logic from w3schools. Though this may seem simple, it was quite a hassle to make this work.
const select = new mdc.select.MDCSelect(document.querySelector('.mdc-select'));
function autocomplete(inp, arr) {
var currentFocus;
inp.addEventListener("input", autocomp);
inp.addEventListener("click", autocomp);
inp.addEventListener("focus", autocomp);
function autocomp(e) {
var a, b, i, val = this.value;
closeAllLists();
currentFocus = -1;
a = document.createElement("ul");
a.setAttribute("id", this.id + "autocomplete-list");
a.setAttribute("class", "autocomplete-items mdc-list");
document.getElementById("autocomp").appendChild(a);
for (i = 0; i < arr.length; i++) {
if (arr[i].substr(0, val.length).toUpperCase() == val.toUpperCase() || (val.trim()).length == 0) {
b = document.createElement("li");
b.setAttribute("class", "mdc-list-item")
b.innerHTML = "<span class='mdc-list-item__text'>" + arr[i] + "</span>";
b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>";
b.addEventListener("click", function(e) {
inp.value = this.getElementsByTagName("input")[0].value;
closeAllLists();
});
a.appendChild(b);
}
}
}
inp.addEventListener("keydown", function(e) {
var x = document.getElementById(this.id + "autocomplete-list");
if (x) x = x.getElementsByTagName("li");
if (e.keyCode == 40) {
currentFocus++;
addActive(x);
} else if (e.keyCode == 38) { //up
currentFocus--;
addActive(x);
} else if (e.keyCode == 13) {
e.preventDefault();
if (currentFocus > -1) {
if (x) x[currentFocus].click();
}
}
});
function addActive(x) {
if (!x) return false;
removeActive(x);
if (currentFocus >= x.length) currentFocus = 0;
if (currentFocus < 0) currentFocus = (x.length - 1);
x[currentFocus].classList.add("autocomplete-active");
x[currentFocus].classList.add("mdc-list-item--selected");
}
function removeActive(x) {
for (var i = 0; i < x.length; i++) {
x[i].classList.remove("autocomplete-active");
x[i].classList.remove("mdc-list-item--selected");
}
}
function closeAllLists(elmnt) {
var x = document.getElementsByClassName("autocomplete-items");
for (var i = 0; i < x.length; i++) {
if (elmnt != x[i] && elmnt != inp) {
x[i].parentNode.removeChild(x[i]);
}
}
}
}
/*An array containing all the foods :*/
var foods = ["fruit", "vegetables", "grains", "fries"];
/*initiate the autocomplete function on the "myInput" element, and pass along the foods array as possible autocomplete values:*/
autocomplete(document.getElementById("name-input"), foods);
function makeActive(element) {
document.getElementById("name-input").focus();
element.classList.add("mdc-select--focused");
element.classList.add("mdc-select--activated")
}
* {
box-sizing: border-box;
}
.autocomplete {
position: relative;
display: inline-block;
}
input {
border: 1px solid transparent;
background-color: #f1f1f1;
padding: 10px;
font-size: 16px;
}
input[type=text] {
background-color: transparent;
width: 100%;
margin-left: -200px;
margin-top: 30px;
z-index: -2;
}
input[type=text]:active {
border: none;
}
.autocomplete-items {
position: absolute;
border: 1px solid #d4d4d4;
border-bottom: none;
border-top: none;
z-index: 99;
/*position the autocomplete items to be the same width as the container:*/
top: 100%;
left: 0;
right: 0;
max-height: 200px;
/*overflow-y: scroll; */
}
.autocomplete-items li {
padding: 10px;
cursor: pointer;
background-color: #fff;
border-bottom: 1px solid #d4d4d4;
}
.mdc-select__menu {
margin-top: -30px;
z-index: 1;
height: 150px;
box-shadow: none;
background-color: transparent;
overflow-x: hidden !important;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<link href="https://unpkg.com/material-components-web#v4.0.0/dist/material-components-web.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<script src="https://unpkg.com/material-components-web#v4.0.0/dist/material-components-web.min.js"></script>
</head>
<body>
<h2>Autocomplete</h2>
<p>Start typing:</p>
<!--Make sure the form has the autocomplete function switched off:-->
<form autocomplete="off" action="" method="post">
<div class="mdc-select" onclick="makeActive(this)">
<div class="mdc-select__anchor demo-width-class">
<i class="mdc-select__dropdown-icon"></i>
<div class="mdc-select__selected-text"></div>
<span class="mdc-floating-label">Pick a Food Group</span>
<div class="mdc-line-ripple"></div>
<input type="text" id="name-input" name="selectione">
</div>
<div class="mdc-select__menu mdc-menu mdc-menu-surface">
<div class="autocomplete" id='autocomp' style="width:200px;">
</div>
</div>
</div>
<input type="submit">
</form>
Hope this helps!
edit: added standard selection option
I've created a demo and it somewhat does what you want. The two issues it has are-
User has to click twice(double click) to input values(I can't to make it work on a single click)
The position of the label overlaps the selected value(Even after giving another class or styling it dynamically)
If anybody has any idea about how to go through these issues, their ideas are
welcome. #Leff, you yourself look like a person with experience, can
you solve them? If yes, please do so and please also enlighten us(me).
Also, I've used jquery in this code if you're looking for only vanilla javascript, you might have to do it yourself or find a person who can as I'm no expert in that area but that's the logical part. Your main problem seemed to be of HTML which should be resolved.
Below is the demo, see if it helps you.
// initialize
const select = new mdc.select.MDCSelect(document.querySelector('.mdc-select'));
// stop the original propagation so that input field remains editable
$('#food').on('click', (event) => {
return false;
});
// Demo Data
const foodArr = ['Bread, Cereal, Rice, and Pasta', 'Vegetables', 'Fruit'];
// You'll have to use ajax here to get the data
$('#food').on('keyup', (event) => {
//console.clear();
let $this = $(event.currentTarget);
let currValue = $this.val();
let search = new RegExp(currValue, 'i'); // prepare a regex object // Your custom condition
let matchArr = foodArr.filter(item => search.test(item)); //store the result in an array
let $select = "";
// check if array is empty
if (matchArr.length > 0) {
// map the elements of the array and create option html
matchArr.forEach((item) => {
$select += `<li class="mdc-list-item" data-value="${item}"> ${item}</li>`;
})
} else { // if array is empty, display no match
$select += `<li class="mdc-list-item" id="no_match"> No match found!</li>`;
}
//console.log(matchArr);
// if the data was selected before, unselect it
$('.mdc-list-item--selected:first').attr({'data-value': ''});
$('.mdc-list-item--selected:first').text('');
$('.mdc-list-item--selected:not(:first)').removeClass('mdc-list-item--selected');
// remove all previous option elements
$('.mdc-list-item:not(.mdc-list-item--selected:first)').remove();
// add new option elements
$('.mdc-list-item--selected').after($select);
// start the click function, so that dropdown doesn't close
$this.click();
});
// When any option is selected, show it on input field
$(document).on('click', '.mdc-list-item:not(#no_match)', (event) => {
let $this = $(event.currentTarget);
$this.addClass('mdc-list-item--selected');
$('.mdc-floating-label').addClass('mdc-floating-label--float-above');
$('.mdc-select__anchor').addClass('demo-width-class mdc-ripple-upgraded')
$('.mdc-line-ripple').addClass('mdc-line-ripple--active mdc-line-ripple--deactivating')
$('.mdc-line-ripple').css({'transform-origin': '182px center'})
$('#food').val($this.attr('data-value'));
// return false;
// event.stopImmediatePropagation()
});
// if clicked on no match, value of input field should be empty, alternatively you can also make option disabled
$(document).on('click', '#no_match', (event) => {
$('#food').val('');
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>#sauhardnc</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link href="https://unpkg.com/material-components-web#v4.0.0/dist/material-components-web.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<script src="https://unpkg.com/material-components-web#v4.0.0/dist/material-components-web.min.js"></script>
</head>
<body>
<div class="mdc-select">
<div class="mdc-select__anchor demo-width-class" style="width: 100%">
<i class="mdc-select__dropdown-icon"></i>
<input type="text" class="mdc-select__selected-text" id="food"></input>
<!-- Give a unique id -->
<!--<div contenteditable="true" class="mdc-select__selected-text" id="fruit"></div>-->
<span class="mdc-floating-label">Pick a Food Group</span>
<div class="mdc-line-ripple"></div>
</div>
<div class="mdc-select__menu mdc-menu mdc-menu-surface demo-width-class" style="width: 100%">
<ul class="mdc-list">
<li class="mdc-list-item mdc-list-item--selected" data-value="" aria-selected="true"></li>
<li class="mdc-list-item" data-value="Bread, Cereal, Rice, and Pasta">
Bread, Cereal, Rice, and Pasta
</li>
<li class="mdc-list-item" data-value="Vegetables">
Vegetables
</li>
<li class="mdc-list-item" data-value="Fruit">
Fruit
</li>
</ul>
</div>
</div>
</body>
</html>
You need to combine the input fields along with the select to get an input field and then write some javascript to get the autocomplete functionality. You can check my solution.
console.clear();
const select = new mdc.select.MDCSelect(document.querySelector('.mdc-select'));
select.listen('MDCSelect:change', () => {
alert(`Selected option at index ${select.selectedIndex} with value "${select.value}"`);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<link href="https://unpkg.com/material-components-web#v4.0.0/dist/material-components-web.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<script src="https://unpkg.com/material-components-web#v4.0.0/dist/material-components-web.min.js"></script>
</head>
<body>
<div class="mdc-select">
<div class="mdc-select__anchor demo-width-class">
<i class="mdc-select__dropdown-icon"></i>
<div class="mdc-select__selected-text"></div>
<span class="mdc-floating-label">Pick a Food Group</span>
<div class="mdc-line-ripple"></div>
</div>
<div class="mdc-select__menu mdc-menu mdc-menu-surface demo-width-class">
<ul class="mdc-list">
<input type="text" class="mdc-list-item--selected mdc-text-field__input" id="name-input">
<label for="name-input" class="mdc-floating-label">search....</label>
<li class="mdc-list-item" data-value="grains">
Bread, Cereal, Rice, and Pasta
</li>
<li class="mdc-list-item" data-value="vegetables">
Vegetables
</li>
<li class="mdc-list-item" data-value="fruit">
Fruit
</li>
<input type="hidden" name="input_name" value="input_value" class="my_mdc-select__value" />
</ul>
</div>
</div>
</body>
</html>
This is what I could able to achieve, hope this helps in your implementation. I have not completely implemented the filter logic and all but was able to enter input and display the dropdown simultaneously.
const menuElement = document.querySelector(".mdc-menu");
// const menu = new mdc.menu.MDCMenu(menuElement);
const inputLabel = document.querySelector(".mdc-text-field");
const inputElem = inputLabel.querySelector("input");
const dropdownIcon = document.querySelector(".mdc-select__dropdown-icon");
let isMenuOpen = false;
inputLabel.addEventListener("click", () => {
inputElem.focus();
// menu.open = true;
if (!isMenuOpen) {
menuElement.classList.remove("list-menu-close");
menuElement.classList.add("list-menu-open");
dropdownIcon.classList.add("dropdown-icon-up");
} else {
menuElement.classList.remove("list-menu-open");
menuElement.classList.add("list-menu-close");
dropdownIcon.classList.remove("dropdown-icon-up");
}
isMenuOpen = !isMenuOpen;
});
inputElem.addEventListener("blur", () => {
menuElement.classList.remove("list-menu-open");
menuElement.classList.add("list-menu-close");
dropdownIcon.classList.remove("dropdown-icon-up");
isMenuOpen = false;
});
<div class="dropdown-container">
<label class="mdc-text-field mdc-text-field--filled">
<span class="mdc-text-field__ripple"></span>
<input
class="mdc-text-field__input"
type="text"
aria-labelledby="my-label-id"
/>
<i class="mdc-select__dropdown-icon"></i>
</label>
<div>
<div class="mdc-menu mdc-menu-surface list-menu">
<ul
class="mdc-list"
role="menu"
aria-hidden="true"
aria-orientation="vertical"
tabindex="-1"
>
<li class="mdc-list-item" role="menuitem">
<span class="mdc-list-item__text">A Menu Item</span>
</li>
<li class="mdc-list-item" role="menuitem">
<span class="mdc-list-item__text">Another Menu Item</span>
</li>
</ul>
</div>
</div>
</div>
.dropdown-container {
position: relative;
}
.list-menu {
position: absolute;
top: 60px;
}
.list-menu-open {
display: block;
opacity: 1;
}
.list-menu-close {
display: none;
opacity: 0;
}
.dropdown-icon-up {
transform: rotate(180deg) translateY(-5px);
}
Based on the answers here, I created my own function. Also the Material Design for Web Versions in the answers are quite old. Hope this helps for those looking for something like this.
At first, instead of using MDCSelect, I used MDCTextField and MDCMenu components. I thought everything would be easier this way. It took some time though, but I'm done.
The function that triggers the Menu to open when clicking on the TextField may seem a bit weird. #Zachary Haber explains this,
It appears that the inconsistency is due to a race condition. Clicking
on the menu causes the focus to leave the input which causes the menu
to close. And the menu closing causes focus to move back to the input
making it open again.
The issue is that the menu often closes before the menu has a chance
to send out the selected event.
To review the original answer for MDCMenu click issue: https://stackoverflow.com/a/61965646/5698079
Here is a working example;
const textFields = document.querySelectorAll('.mdc-text-field');
textFields.forEach(field => {
mdc.textField.MDCTextField.attachTo(field);
});
const menuS = document.querySelector("#menu");
const menu = new mdc.menu.MDCMenu(menuS);
const searchFieldS = document.querySelector("#food");
menu.setAnchorCorner(mdc.menuSurface.Corner.BOTTOM_LEFT);
document.querySelectorAll('#menu li').forEach(function(li) {
li.addEventListener('click', function() {
const selectedLi = this.getAttribute("data-value");
if (selectedLi != "notfound") {
// If you are going to post the text field data, I recommend you to get data-value.
searchFieldS.value = selectedLi;
searchFieldS.setAttribute("data-value", selectedLi);
}
});
});
// Open the menu when text field is focused.
(function() {
let menuFocused = false;
searchFieldS.addEventListener("focusin", () => {
if (!menuFocused) menu.open = true;
});
searchFieldS.addEventListener("click", () => {
menu.open = true;
});
menuS.addEventListener("focusin", () => {
menuFocused = true;
});
menuS.addEventListener("focusout", () => {
// This interval is to help make sure that input.focusIn doesn't re-open the menu
setTimeout(() => {
menuFocused = false;
}, 0);
});
searchFieldS.addEventListener("focusout", () => {
setTimeout(() => {
if (!menuFocused) menu.open = false;
}, 0);
});
})();
searchFieldS.addEventListener("keyup", function(e) {
const keyT = event.target.value.toUpperCase();
const menuList = document.querySelectorAll('.mdc-deprecated-list > li > .mdc-deprecated-list-item__text');
const menuLiss = document.querySelectorAll('.mdc-deprecated-list-item');
const noDataEl = document.querySelector("#menuNoData");
//
const arr = [];
menuList.forEach(function(searchItem) {
if (searchItem.parentElement.getAttribute('id') != "menuNoData") {
searchItem.parentElement.dataset.isfound = searchItem.textContent.toUpperCase().includes(keyT) ? "true" : "false";
arr.push(searchItem.parentElement.getAttribute("data-isfound"));
}
});
if (arr.filter(function(countArr) {
return countArr == "true"
}).length == 0) {
noDataEl.classList.remove("hide-none");
} else {
if (!noDataEl.classList.contains("hide-none")) {
noDataEl.classList.add("hide-none");
}
}
});
li[data-isfound="false"] {
display: none;
}
.hide-none {
display: none !important;
}
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://unpkg.com/material-components-web#latest/dist/material-components-web.min.js"></script>
<link href="https://unpkg.com/material-components-web#latest/dist/material-components-web.min.css" rel="stylesheet">
<div class="mdc-menu-surface--anchor">
<label class="mdc-text-field mdc-text-field--outlined">
<span class="mdc-notched-outline">
<span class="mdc-notched-outline__leading"></span>
<span class="mdc-notched-outline__notch">
<span class="mdc-floating-label" id="my-label-id">Select a fruit..</span>
</span>
<span class="mdc-notched-outline__trailing"></span>
</span>
<input id="food" type="text" class="mdc-text-field__input" aria-labelledby="my-label-id">
</label>
<div id="menu" class="mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
<ul class="mdc-deprecated-list" role="listbox" aria-label="Food picker listbox">
<li id="menuNoData" class="mdc-deprecated-list-item mdc-deprecated-list-item--disabled hide-none" aria-selected="false" aria-disabled="true" data-value="notfound" role="option" value="" data-value="">
<span class="mdc-deprecated-list-item__ripple"></span>
<span class="mdc-deprecated-list-item__text">No data found.</span>
</li>
<li class="mdc-deprecated-list-item" aria-selected="false" data-value="grains" role="option">
<span class="mdc-deprecated-list-item__ripple"></span>
<span class="mdc-deprecated-list-item__text">Bread, Cereal, Rice, and Pasta</span>
</li>
<li class="mdc-deprecated-list-item" aria-selected="false" data-value="vegetables" role="option">
<span class="mdc-deprecated-list-item__ripple"></span>
<span class="mdc-deprecated-list-item__text">Vegetables</span>
</li>
<li class="mdc-deprecated-list-item" aria-selected="false" data-value="fruit" role="option">
<span class="mdc-deprecated-list-item__ripple"></span>
<span class="mdc-deprecated-list-item__text">Fruit</span>
</li>
</ul>
</div>
</div>
CodePen: https://codepen.io/lastofdead/pen/bGMZPzO
So I am following this tutorial to extend the autodesk forge viewer. I have compelted all of the steps and no button is showing, I assume this is due to an error with the loading.
https://forge.autodesk.com/blog/extension-skeleton-toolbar-docking-panel
I have also tried this tutorial, with the same issue:
http://learnforge.autodesk.io/#/viewer/extensions/selection?id=conclusion
My issue is I am not getting an error, the extension just isn't showing... does anyone know why?
I'm assuming theres an error in either the viewer or the index.
Below is my code: (index & forge viewer)
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Autodesk Forge Tutorial</title>
<meta charset="utf-8" />
<!-- Common packages: jQuery, Bootstrap, jsTree -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
<script src="/js/MyAwesomeExtension.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
<!-- Autodesk Forge Viewer files -->
<link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/style.min.css?v=v6.0" type="text/css">
<script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/viewer3D.min.js?v=v6.0"></script>
<!-- this project files -->
<link href="css/main.css" rel="stylesheet" />
<script src="js/ForgeTree.js"></script>
<script src="js/ForgeViewer.js"></script>
</head>
<body>
<!-- Fixed navbar by Bootstrap: https://getbootstrap.com/examples/navbar-fixed-top/ -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<ul class="nav navbar-nav left">
<li>
<a href="http://developer.autodesk.com" target="_blank">
<img alt="Autodesk Forge" src="//developer.static.autodesk.com/images/logo_forge-2-line.png" height="20">
</a>
</li>
</ul>
</div>
</nav>
<!-- End of navbar -->
<div class="container-fluid fill">
<div class="row fill">
<div class="col-sm-4 fill">
<div class="panel panel-default fill">
<div class="panel-heading" data-toggle="tooltip">
Buckets & Objects
<span id="refreshBuckets" class="glyphicon glyphicon-refresh" style="cursor: pointer"></span>
<button class="btn btn-xs btn-info" style="float: right" id="showFormCreateBucket" data-toggle="modal" data-target="#createBucketModal">
<span class="glyphicon glyphicon-folder-close"></span> New bucket
</button>
</div>
<div id="appBuckets">
tree here
</div>
</div>
</div>
<div class="col-sm-8 fill">
<div id="forgeViewer"></div>
</div>
</div>
</div>
<form id="uploadFile" method='post' enctype="multipart/form-data">
<input id="hiddenUploadField" type="file" name="theFile" style="visibility:hidden" />
</form>
<!-- Modal Create Bucket -->
<div class="modal fade" id="createBucketModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Cancel">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="myModalLabel">Create new bucket</h4>
</div>
<div class="modal-body">
<input type="text" id="newBucketKey" class="form-control"> For demonstration purposes, objects (files) are
NOT automatically translated. After you upload, right click on
the object and select "Translate".
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="createNewBucket">Go ahead, create the bucket</button>
</div>
</div>
</div>
</div>
</body>
</html>
ForgeViewer.js:
var viewerApp;
function launchViewer(urn) {
var options = {
env: 'AutodeskProduction',
getAccessToken: getForgeToken
};
var documentId = 'urn:' + urn;
Autodesk.Viewing.Initializer(options, function onInitialized() {
viewerApp = new Autodesk.Viewing.ViewingApplication('forgeViewer');
viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, { extensions: ['MyAwesomeExtension'] });
viewerApp.loadDocument(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
function onDocumentLoadSuccess(doc) {
// We could still make use of Document.getSubItemsWithProperties()
// However, when using a ViewingApplication, we have access to the **bubble** attribute,
// which references the root node of a graph that wraps each object from the Manifest JSON.
var viewables = viewerApp.bubble.search({ 'type': 'geometry' });
if (viewables.length === 0) {
console.error('Document contains no viewables.');
return;
}
// Choose any of the available viewables
viewerApp.selectItem(viewables[0].data, onItemLoadSuccess, onItemLoadFail);
}
function onDocumentLoadFailure(viewerErrorCode) {
console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode);
}
function onItemLoadSuccess(viewer, item) {
// item loaded, any custom action?
}
function onItemLoadFail(errorCode) {
console.error('onItemLoadFail() - errorCode:' + errorCode);
}
function getForgeToken(callback) {
jQuery.ajax({
url: '/api/forge/oauth/token',
success: function (res) {
callback(res.access_token, res.expires_in)
}
});
}
MyAwesomeExtension.js:
// *******************************************
// My Awesome Extension
// *******************************************
function MyAwesomeExtension(viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
this.panel = null;
}
MyAwesomeExtension.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
MyAwesomeExtension.prototype.constructor = MyAwesomeExtension;
MyAwesomeExtension.prototype.load = function () {
if (this.viewer.toolbar) {
// Toolbar is already available, create the UI
this.createUI();
} else {
// Toolbar hasn't been created yet, wait until we get notification of its creation
this.onToolbarCreatedBinded = this.onToolbarCreated.bind(this);
this.viewer.addEventListener(av.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
}
return true;
};
MyAwesomeExtension.prototype.onToolbarCreated = function () {
this.viewer.removeEventListener(av.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
this.onToolbarCreatedBinded = null;
this.createUI();
};
MyAwesomeExtension.prototype.createUI = function () {
var viewer = this.viewer;
var panel = this.panel;
// button to show the docking panel
var toolbarButtonShowDockingPanel = new Autodesk.Viewing.UI.Button('showMyAwesomePanel');
toolbarButtonShowDockingPanel.onClick = function (e) {
// if null, create it
if (panel == null) {
panel = new MyAwesomePanel(viewer, viewer.container,
'awesomeExtensionPanel', 'My Awesome Extension');
}
// show/hide docking panel
panel.setVisible(!panel.isVisible());
};
// myAwesomeToolbarButton CSS class should be defined on your .css file
// you may include icons, below is a sample class:
/*
.myAwesomeToolbarButton {
background-image: url(/img/myAwesomeIcon.png);
background-size: 24px;
background-repeat: no-repeat;
background-position: center;
}*/
toolbarButtonShowDockingPanel.addClass('myAwesomeToolbarButton');
toolbarButtonShowDockingPanel.setToolTip('My Awesome extension');
// SubToolbar
this.subToolbar = new Autodesk.Viewing.UI.ControlGroup('MyAwesomeAppToolbar');
this.subToolbar.addControl(toolbarButtonShowDockingPanel);
viewer.toolbar.addControl(this.subToolbar);
};
MyAwesomeExtension.prototype.unload = function () {
this.viewer.toolbar.removeControl(this.subToolbar);
return true;
};
Autodesk.Viewing.theExtensionManager.registerExtension('MyAwesomeExtension', MyAwesomeExtension);
MyAwesomePanel:
// *******************************************
// My Awesome (Docking) Panel
// *******************************************
function MyAwesomePanel(viewer, container, id, title, options) {
this.viewer = viewer;
Autodesk.Viewing.UI.DockingPanel.call(this, container, id, title, options);
// the style of the docking panel
// use this built-in style to support Themes on Viewer 4+
this.container.classList.add('docking-panel-container-solid-color-a');
this.container.style.top = "10px";
this.container.style.left = "10px";
this.container.style.width = "auto";
this.container.style.height = "auto";
this.container.style.resize = "auto";
// this is where we should place the content of our panel
var div = document.createElement('div');
div.style.margin = '20px';
div.innerText = "My content here";
this.container.appendChild(div);
// and may also append child elements...
}
MyAwesomePanel.prototype = Object.create(Autodesk.Viewing.UI.DockingPanel.prototype);
MyAwesomePanel.prototype.constructor = MyAwesomePanel;
Yes, you are missing the CSS for the Buttons and also the reference to the JS files pertaining to the extensions in your HTML file.
<script src="your_folder/MyExtensionFileName.js"></script>
http://learnforge.autodesk.io/#/viewer/extensions/selection?id=toolbar-css
Check this for the CSS of your extension buttons.
Let's assume that I have Angular 5 project with routings. For instance /home, /landing-page, etc. Moreover, let's assume that in my landing page with url - localhost:4200. I want to create login panel. I have two fields - username and password, one button sign in and two other buttons forgot password? and Don't have an account?. My problem is that when user will click Forgot password? or Don't have an account? he will not be routed to another page with url like localhost:4200/sign-up but he will stay at the same page with url localhost:4200 and fields username, password, sign in, forgot password? and Don't have an account? will disappear and in their place will be displayed fields associated with registration. I am not sure whether you know what I mean. The good example what I wanna to achieve is https://www.instagram.com. No matter whether you click Sign up or Log in you are still on the same url and only one component changes. Do you know how can I achieve this? I am not sure whether I should use routes or maybe another way is more optimal to do this? Thanks in advance.
My code looks in this way. I added only the most important code from selected files.
index.html:
</head>
<body>
<app-root></app-root>
</body>
</html>
app.component.html:
<app-navbar></app-navbar>
<router-outlet></router-outlet>
<app-footer *ngIf="removeFooter()"></app-footer>
At the moment my home.component looks in this way:
home.component.html:
<div *ngIf="isSignIn()">
<app-sign-in></app-sign-in>
</div>
<div *ngIf="isSignUp()">
<app-sign-up></app-sign-up>
</div>
<div *ngIf="isForgotPassword()">
<app-forgot-password></app-forgot-password>
</div>
home.component.ts:
constructor() {
this.signin = true;
this.signup = false;
this.forgot = false;
}
isSignUp() {
if (this.signup === true) {
return true;
}
else {
return false;
}
}
isSignIn() {
if (this.signin === true) {
return true;
}
else {
return false;
}
}
isForgotPassword() {
if (this.forgot === true) {
return true;
}
else {
return false;
}
}
sign-in.component.html:
<div class="content-center">
<div class="container">
<div class="title-brand">
<div align="center">
<input style="background-color: black; border-color: white; color:white; width: 270px" type="text" value="" class="form-control" placeholder="USERNAME">
<br>
<input style="background-color: black; border-color: white; color:white; width: 270px" type="text" value="" class="form-control" placeholder="PASSWORD">
<div class="row">
<div class="col-md-8">
<br>
<button style="background-color: black; border-color: white; color:white; width: 270px" type="button" class="btn btn-danger">Log in</button>
</div>
</div>
<br>
<h6 style = "color: white" [routerLink]="['/main']" skipLocationChange class=pointer>Forgot Password?</h6>
<h6 style="color: white" [routerLink]="['/sign-up']" class=pointer>Don't have an account?</h6>
</div>
</div>
</div>
</div>
UPDATE
I added source code of sign-in.component.html to the question. Can you show me how I can switch the component sign-in.component.html after clicking Forgot Password? or Do not have an account? to the component forgot.component.html or sign-up.component.html
Use skipLocationChange from the route NavigationExtras. E.g.
<h6 style = "color: white" [routerLink]="['/main']" skipLocationChange>Forgot Password?</h6>
<h6 style="color: white" [routerLink]="['/sign-up']" skipLocationChange>Don't have an account?</h6>
Here is an example of using a boolean and buttons to allow you to switch between different components on a single page:
stackblitz example
This example could be improved but I hope it shows you how to easily swap the visible component.
You can view the code at this link
EDIT
You need to remove the responsibility of what component to show to a container component (i.e. a parent component). In the updated stackblitz example I've made the HomeComponent responsible for showing the correct component. This means the SignUp/SignIn/ForgotPassword components have no responsibility for switching between each other - that is the job for the HomeComponent (or whichever component you want to use for that job).
Hope that helps
home.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-home',
template:
`<div *ngIf="signin">
<app-sign-in></app-sign-in>
<button (click)="showSignUp()">Sign up</button>
<button (click)="showForgot()">Forgot password</button>
</div>
<div *ngIf="signup">
<app-sign-up></app-sign-up>
<button (click)="showSignIn()">Sign in</button>
<button (click)="showForgot()">Forgot password</button>
</div>
<div *ngIf="forgot">
<app-forgot-password></app-forgot-password>
<button (click)="showSignUp()">Sign up</button>
<button (click)="showSignIn()">Sign in</button>
</div>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HomeComponent {
public signup: boolean = true;
public signin: boolean = false;
public forgot: boolean = false;
constructor() {}
public showSignIn() {
this.signin = true;
this.signup = this.forgot = false;
}
public showSignUp() {
this.signup = true;
this.signin = this.forgot = false;
}
public showForgot() {
this.forgot = true;
this.signin = this.signup = false;
}
}
I am trying to get something like x-editable inline edit in reactjs. I am able to get the HTML form/control inside the bootstrap tooltip, however, not able to set or change value on the input control on the popover. Below is the code.
class Inline extends React.Component {
constructor(props, context) {
super(props, context);
this.state= {
displayText: props.displayText,
valueText : props.valueText
}
}
componentDidMount() {
$("[data-toggle=popover]").popover({
html: true,
content: function() {
return $('#popover-content').html();
}
});
$(document).on("click", ".popover .close" , function(){
$(this).parents(".popover").popover('hide');
});
}
onChange(e) {
console.log('on change : ' + e.target.value)
this.setState({ displayText: e.target.value });
}
handleClick (e) {
e.preventDefault();
console.log('The link was clicked.');
}
render(){
return (
<div className="container">
<ul className="list-unstyled inline-edit">
<li><a data-placement="bottom" data-toggle="popover" data-title="Inline Edit <a href='#' class='close' data-dismiss='alert'>×</a>" data-container="body" type="button" data-html="true" href="#" >{this.state.displayText}</a></li>
<div id="popover-content" className="hide">
<div className="form-group">
<span>{this.state.displayText}</span>
<input type="text" placeholder="Name" className="form-control"
value={this.state.displayText}
onChange={this.onChange}
></input>
<div className="form-group edit-control">
<a href="#" role="button" onClick={this.handleClick}><i className="fa fa-check-circle fa-2x green"/></a>
<a href="#" role="button" ><i className="fa fa-times-circle fa-2x red leftpad"/></a>
</div>
</div>
</div>
</ul>
</div>
);
}
};
Inline.propTypes = {
displayText: PropTypes.string.isRequired,
valueText: PropTypes.string.isRequired
};
export default Inline;
Screen shot
The issue is in the way your popover is created:
componentDidMount() {
$("[data-toggle=popover]").popover({
html: true,
content: function () {
return $('#popover-content').html();
}
});
}
You are using jquery html() function to duplicate a Html DOM element in the bootstrap popover. When duplicating an html element this way, the JavaScript React logic is not attached to the duplicated html code.
You can't only use the bootstrap tool tip to created the popover. You have to:
Either manually instantiate a react component in the popover (using the ReactDom render function).
Or (the better solution I think) use the dedicated react bootstrap lib that provide the popover bootstrapreact component