Material design for web - how to make an autocomplete input - html
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
Related
When I switch to monthly/yearly billing, I get "NaN" as a text content.Otherwise, my JS code is running well.My code snippets are attached below
Can not fix the "Nan" issue.I wish to display real "$12" format for example, instead of to be displaying isNan.Any hint how to get this working? I am not really sure which direction should I go. Already tried changing strings like "$8.00" to "8" as a textContent property. Any suggestion is appreciated. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- displays site properly based on user's device --> <link rel="icon" type="image/png" sizes="32x32" href="./images/favicon-32x32.png"> <link rel="stylesheet" href="style.css"> <title>Frontend Mentor | Interactive pricing component</title> </head> <body> <section class="main-section"> <div class="title-section"> <h1 class="main-title">Simple, traffic-based pricing</h1> <span class="title-span-1">Sign up for our 30-day trial. No credit card required.</span> </span> <img src="./images/pattern-circles.svg" alt="background image pattern" class="img-pattern"> </div> <div class="card-section"> <h2 class="views-title"><span>100K</span> Pageviews</h2> <div class="slider-container"> <input type="range" min="1" max="5" step="1" value="3" class="slider-range"> </div> <div class="amount-container"> <span class="price">$16.00</span> <span class="month"> /month</span> </div> <div class="billing-container"> <div class="billing-month"> <span class="monthly-billing-text">Monthly Billing</span> <label class="switch"> <input type="checkbox" class="input-range"> <span class="slider round"></span> </label> </div> <div class="billing-year"> <span class="yearly-billing-text">Yearly Billing</span> <span class="yearly-discount">-25%</span> </div> </div> <hr> <div class="billing-benefits"> <ul> <li>Unlimited websites</li> <li>100% data ownership</li> <li>Email reports</li> </ul> <button class="button-trial"> Start my trial </button> </div> </div> </section> <script src="app.js"></script> </body> </html> JS code: const range = document.querySelector('.slider-range'); const pageViews = document.querySelector('.views-title span'); const price = document.querySelector('.price'); const switcher = document.querySelector('.switch'); const checkbox = document.querySelector('.switch input'); const period = document.querySelector('.month'); switcher.addEventListener('click', () => { if (checkbox.checked == true) { let priceInt = parseInt(price.textContent); price.textContent = `${(priceInt - (priceInt * 0.25)) * 12}`; period.textContent = `/year`; } else { period.textContent = `/month`; } }) range.addEventListener("input", updatePrice); function updatePrice() { if (range.value == 1) { if (checkbox.checked == true) { price.textContent = `${(8 - (8 * 0.25)) * 12}`; } else { price.textContent = "$8.00"; } pageViews.textContent = "10K"; } if (range.value == 2) { if (checkbox.checked == true) { price.textContent = "$108.00"; } else { price.textContent = "$12.00"; } pageViews.textContent = "50K"; } if (range.value == 3) { if (checkbox.checked == true) { price.textContent = "$144.00"; } else { price.textContent = "$16.00"; } pageViews.textContent = "100K"; } if (range.value == 4) { if (checkbox.checked == true) { price.textContent = "$216.00"; } else { price.textContent = "$24.00"; } pageViews.textContent = "500K"; } if (range.value == 5) { if (checkbox.checked == true) { price.textContent = "$324.00"; } else { price.textContent = "$36.00"; } pageViews.textContent = "1M"; } }
Toggle menu with jQuery
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.
HTML View doesn't display updated model
I have an HTML file which displays 2 lists using AngularJS file with 2 controllers and a service. The lists are arrays which are being correctly updated in the model, as evidenced by the console.log output. But the HTML doesn't display the updated list2 (data stored in the angularJS service). Can someone tell where I am going wrong? Tried looking at the API, angular directives, Controller As syntax and inheritance concepts. index.html <!DOCTYPE html> <html lang="en" ng-app="ShoppingListCheckOff"> <head> <title>Shopping List Check Off</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="styles/bootstrap.min.css" /> <script src="angular.min.js"></script> <script src="app.js"></script> <style> .emptyMessage { font-weight: bold; color: red; font-size: 1.2em; } li { margin-bottom: 7px; font-size: 1.2em; } li > button { margin-left: 6px; } button > span { color: green; } </style> </head> <body> <div class="container"> <h1>Shopping List Check Off</h1> <div class="row"> <!-- To Buy List --> <div class="col-md-6" ng-controller="ToBuyController as toBuy"> <h2>To Buy:</h2> <ul> <li ng-repeat="item in toBuy.list"> Buy {{ item.name }} {{ item.quantity }} <button ng-click="toBuy.bought($index)" class="btn btn-default"> <span class="glyphicon glyphicon-ok"></span> Bought </button> </li> </ul> <div ng-if="!toBuy.list.length" class="emptyMessage">Everything is bought!</div> </div> <!-- Already Bought List --> <div class="col-md-6"> <h2>Already Bought:</h2> <ul> <li ng-repeat="item in bought.list">Bought {{ item.quantity }} {{ item.name }}</li> </ul> <div ng-if="!bought.list.length" class="emptyMessage">Nothing bought yet.</div> </div> </div> </div> </body> </html> App.js (function() { 'use strict'; angular .module('ShoppingListCheckOff', []) .controller('ToBuyController', ToBuyController) .controller('AlreadyBoughtController', AlreadyBoughtController) .service('ShoppingListCheckOffService', ShoppingListCheckOffService); ToBuyController.$inject = ['ShoppingListCheckOffService']; function ToBuyController(ShoppingListCheckOffService) { var toBuy = this; toBuy.list = ShoppingListCheckOffService.getList(1); toBuy.bought = function(itemIndex) { ShoppingListCheckOffService.transfer(itemIndex); }; } AlreadyBoughtController.$inject = ['ShoppingListCheckOffService']; function AlreadyBoughtController(ShoppingListCheckOffService) { var bought = this; bought.list = ShoppingListCheckOffService.getList(2); } function ShoppingListCheckOffService() { var service = this; // List of shopping items var list1 = [ { name: 'Cookies', quantity: 10 }, { name: 'Bananas', quantity: 100 }, { name: 'Toys', quantity: 6 }, { name: 'Dildos', quantity: 300 }, { name: 'Yaakovs', quantity: 1 } ]; var list2 = []; service.transfer = function(itemIndex) { list2 = list2.concat(list1.splice(itemIndex, 1)); console.log('List 1', list1); console.log('List 2', list2); }; service.getList = function(num) { if (num == 1) { return list1; } if (num == 2) { return list2; } }; } })();
The issue is that concat does not change the original array. It creates a new array. When you do list2 = list2.concat(list1.splice(itemIndex, 1)); you are setting list2 to a new array but bought.list is still set to the old array so it doesn't change. One solution would be to replace list2 = list2.concat(list1.splice(itemIndex, 1)); with list2.push(list1.splice(itemIndex, 1)[0]);
Footer covers my content when using w3.css
I am trying out w3.css for styling, along with knockout, and when I use a footer, it covers the content near the bottom of the page. I have a button at the bottom of my content. When the page resizes or is small enough, the footer covers the button. See codepen, or the code below function setting(color) { this.color = ko.observable(color); this.colorClassName = ko.computed(function() { return "w3-hover-" + this.color(); }, this); } function myInput() { this.data = ko.observable(""); this.nameValid = ko.computed(function() { return !(this.data() == null || this.data().length == 0); }, this); this.error = ko.computed(function() { //if (this.data() == null || this.data().length == 0) if (this.nameValid() == false) { return "Enter name"; } else { return ""; } }, this); this.display = ko.computed(function() { if (this.nameValid() == false) { return "block"; } else { return "none"; } }, this); this.ageData = ko.observable(); this.ageValid = ko.computed(function() { var age = this.ageData() + ""; var patt = new RegExp(/^[0-9]+$/g); /// ^-from start, $-to end, [0-9] - 0 to 9 numbers only var res = patt.test(age); if (isNaN(age) == true || res == false) { return false; } else { return true; } }, this); this.ageError = ko.computed(function() { if (this.ageValid() == false) { return "Enter a valid age"; } else { return ""; } }, this); this.ageDisplay = ko.computed(function() { if (this.ageValid() == true) { return "none"; } else { return "block"; } }, this); this.phone = ko.observable('http://digitalbush.com/projects/masked-input-plugin/'); this.allValid = ko.computed(function() { return this.ageValid() && this.nameValid(); }, this); } function myModel() { this.name = "Ice-Cream"; this.items = [{ name: "Chocolate", price: 10 }, { name: "Vanilla", price: 12 }]; this.style = new setting('pale-green'); this.input = new myInput(); this.changeColor = function() { if (this.style.color().indexOf('blue') == -1) { this.style.color('pale-blue'); } else { this.style.color('pale-green'); } }; } ko.applyBindings(new myModel()); <script type='text/javascript' src='http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.3.0.js'></script> <link href="http://www.w3schools.com/lib/w3.css" rel="stylesheet" type="text/css"> <body class="w3-content w3-pale-blue" style="max-width:100%"> <header class="w3-container w3-pale-green w3-border"> <h1>Hello</h1> </header> <div class="w3-container w3-pale-yellow w3-border w3-padding-hor-16 w3-content" style="max-width:100%"> W3.CSS <p> The item is <span data-bind="text: name"></span> today. <br />Available flavours are: </p> <div class="w3-container"> <ul data-bind="foreach: items" class="w3-ul w3-left w3-border w3-border-red"> <li class="w3-ul w3-hoverable w3-border-red " data-bind="css: $parent.style.colorClassName()"> <span data-bind="text: name"></span> is $<span data-bind="text:price" /> </li> </ul> </div> <label class="w3-label w3-text-blue w3-xlarge">Name</label> <input class="w3-input w3-border" type="text" data-bind="textInput: input.data"> <label class="w3-label w3-text-red w3-large" data-bind="text: input.error(), style: { display: input.display()}"></label> <br /> <label class="w3-label w3-text-blue w3-xlarge">Age</label> <input class="w3-input w3-border" type="text" data-bind="textInput: input.ageData"> <label class="w3-label w3-text-red w3-large" data-bind="text: input.ageError(), style: { display: input.ageDisplay()}"></label> <br /> <label class="w3-label w3-text-blue w3-xlarge">Phone</label> <input class="w3-input w3-border" type="text" data-bind="textInput: input.phone"> <!--<label class="w3-label w3-text-red w3-large" data-bind="text: input.phoneError(), style: { display: input.phoneDisplay()}"></label>--> <br /> <button class="w3-btn w3-border w3-border-teal w3-round w3-teal" data-bind="click: changeColor, enable: input.allValid()">Test</button> </div> <footer class="w3-light-grey w3-bottom"> <div class="w3-container"> <p>This is my footer</p> </div> </footer>
My solution was to add another div element with the same content as my footer, but make it invisible. This way it will fill the space behind the real footer. In the code i above i will add the following <div class="w3-container" style="opacity:0"> <p>This is my footer</p> </div> The updated codepen shows the solution.
The elements .w3-top, .w3-bottom have a position of :fixed, so they're always going to stick to the page. Remove this from their stylesheet or add an alternative to your own. E.g. .w3-bottom { position: static; }
The class of your footer is w3-bottom, and by defaut, its position is fixed, so you need to change it to relative: .w3-bottom { position: relative !important; }
Another way could be to place the "button" in its own "div" to better control its attributes, remove the "div" from the footer and the bottom class. Something like: <div class="w3-container w3-padding-bottom-32"> <button class="w3-btn w3-border w3-border-teal w3-round w3-teal" data-bind="click: changeColor, enable: input.allValid()">Test</button> </div> <footer class="w3-container w3-light-grey"> <p>This is my footer</p> </footer> Please let me know if it works for you. Kindly, Edwin
Try to use the styles from Sticky footer example from the bootstrap. But this method have one disadvantage: footer have fixed height :( Example: /* Sticky footer styles -------------------------------------------------- */ html { position: relative; min-height: 100%; } body { /* Margin bottom by footer height */ margin-bottom: 80px; } .footer { position: absolute; bottom: 0; width: 100%; overflow: hidden; /* Set the fixed height of the footer here */ height: 80px; background-color: #f5f5f5; } <!DOCTYPE html> <html> <title>W3.CSS</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css"> <body> <div class="w3-container w3-teal"> <h1>Header</h1> </div> <div class="w3-container"> <p>The w3-container class can be used to display headers.</p> </div> <div class="footer w3-container w3-teal"> <h5>Footer</h5> <p>Footer information goes here</p> </div> </body> </html>
Change default text in input type="file"?
I want to change default text on button that is "Choose File" when we use input="file". How can I do this? Also as you can see in image button is on left side of text. How can I put it on right side of text?
Use the for attribute of label for input. <div> <label for="files" class="btn">Select Image</label> <input id="files" style="visibility:hidden;" type="file"> </div> Below is the code to fetch name of the uploaded file $("#files").change(function() { filename = this.files[0].name; console.log(filename); }); <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div> <label for="files" class="btn">Select Image</label> <input id="files" style="visibility:hidden;" type="file"> </div>
I think this is what you want: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <button style="display:block;width:120px; height:30px;" onclick="document.getElementById('getFile').click()">Your text here</button> <input type='file' id="getFile" style="display:none"> </body> </html>
Each browser has it's own rendition of the control and as such you can't change either the text or the orientation of the control. There are some "kind of" hacks you may want to try if you want an html/css solution rather than a Flash or silverlightsolution. http://www.quirksmode.org/dom/inputfile.html http://www.shauninman.com/archive/2007/09/10/styling_file_inputs_with_css_and_the_dom Personally, because most users stick to their browser of choice, and therefore are probably used to seeing the control in the default rendition, they'd probably get confused if they saw something different (depending on the types of users you're dealing with).
This might help someone in the future, you can style the label for the input as you like and put anything you want inside it and hide the input with display none. It works perfectly on cordova with iOS <link href="https://cdnjs.cloudflare.com/ajax/libs/ratchet/2.0.2/css/ratchet.css" rel="stylesheet"/> <label for="imageUpload" class="btn btn-primary btn-block btn-outlined">Seleccionar imagenes</label> <input type="file" id="imageUpload" accept="image/*" style="display: none">
To achieve this, the default input button must be hidden using display:none CSS property and a new button element is added to replace it, so we can customize as we wish. With Bootstrap <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"> Optional text here <label for="img" class="btn btn-info">Try me</label> <input type="file" id="img" style="display:none"> With jQuery In this case the onclick attribute added to the button element is indicating to JavaScript to click on the hidden default input button whenever the visible button is clicked. <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> Optional text here <button style="cursor:pointer" onclick="$('#input').click()">Click me</button> <input type="file" id="input" style="display:none"> Plain JavaScript with event listener document.getElementById('btn').addEventListener('click', () => { document.getElementById('input').click(); }) Optional text here <button style="cursor:pointer" id="btn">Click me</button> <input type="file" id="input" style="display:none">
It is not possible. Otherwise you may need to use Silverlight or Flash upload control.
$(document).ready(function () { $('#choose-file').change(function () { var i = $(this).prev('label').clone(); var file = $('#choose-file')[0].files[0].name; $(this).prev('label').text(file); }); }); .custom-file-upload{ background: #f7f7f7; padding: 8px; border: 1px solid #e3e3e3; border-radius: 5px; border: 1px solid #ccc; display: inline-block; padding: 6px 12px; cursor: pointer; } <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> can you try this <label for="choose-file" class="custom-file-upload" id="choose-file-label"> Upload Document </label> <input name="uploadDocument" type="file" id="choose-file" accept=".jpg,.jpeg,.pdf,doc,docx,application/msword,.png" style="display: none;" />
The trick is to trigger a click event on click of the file input and manage the visibility of the default input file via CSS. Here's how you can do it: jQuery: $(function() { $("#labelfile").click(function() { $("#imageupl").trigger('click'); }); }) css .file { position: absolute; clip: rect(0px, 0px, 0px, 0px); display: block; } .labelfile { color: #333; background-color: #fff; display: inline-block; margin-bottom: 0; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; background-image: none; white-space: nowrap; padding: 6px 8px; font-size: 14px; line-height: 1.42857143; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } HTML code: <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <div> <input name="imageupl" type="file" id="imageupl" class="file" /> <label class="labelfile" id="labelfile"><i class="icon-download-alt"></i> Browse File</label> </div>
I made a script and published it at GitHub: get selectFile.js Easy to use, feel free to clone. HTML <input type=file hidden id=choose name=choose> <input type=button onClick=getFile.simulate() value=getFile> <label id=selected>Nothing selected</label> JS var getFile = new selectFile; getFile.targets('choose','selected'); DEMO jsfiddle.net/Thielicious/4oxmsy49/
Update 2017: I have done research on how this could be achieved. And the best explanation/tutorial is here: https://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/ I'll write summary here just in case it becomes unavailable. So you should have HTML: <input type="file" name="file" id="file" class="inputfile" /> <label for="file">Choose a file</label> Then hide the input with CSS: .inputfile { width: 0.1px; height: 0.1px; opacity: 0; overflow: hidden; position: absolute; z-index: -1;} Then style the label: .inputfile + label { font-size: 1.25em; font-weight: 700; color: white; background-color: black; display: inline-block; } Then optionally you can add JS to display the name of the file: var inputs = document.querySelectorAll( '.inputfile' ); Array.prototype.forEach.call( inputs, function( input ) { var label = input.nextElementSibling, labelVal = label.innerHTML; input.addEventListener( 'change', function( e ) { var fileName = ''; if( this.files && this.files.length > 1 ) fileName = ( this.getAttribute( 'data-multiple-caption' ) || '' ).replace( '{count}', this.files.length ); else fileName = e.target.value.split( '\\' ).pop(); if( fileName ) label.querySelector( 'span' ).innerHTML = fileName; else label.innerHTML = labelVal; }); }); But really just read the tutorial and download the demo, it's really good.
This should work: input.*className*::-webkit-file-upload-button { *style content..* }
Here is how its done with bootstrap, only u should put the original input somewhere...idk in head and delete the < br > if you have it, because its only hidden and its taking space anyway :) <head> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> </head> <label for="file" button type="file" name="image" class="btn btn-secondary">Secondary</button> </label> <input type="file" id="file" name="image" value="Prebrskaj" style="visibility:hidden;"> <footer> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> </footer>
Using Bootstrap you can do this thing like the below code. <!DOCTYPE html> <html lang="en"> <head> <style> .btn-file { position: relative; overflow: hidden; } .btn-file input[type=file] { position: absolute; top: 0; right: 0; min-width: 100%; min-height: 100%; font-size: 100px; text-align: right; filter: alpha(opacity=0); opacity: 0; outline: none; background: white; cursor: inherit; display: block; } </style> <title>Bootstrap Example</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <span class="btn btn-file">Upload image from here<input type="file"> </body> </html>
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <button style="display:block;width:120px; height:30px;" onclick="document.getElementById('getFile').click()">Your text here</button> <input type='file' id="getFile" style="display:none"> </body> </html>
I'd use a button to trigger the input: <button onclick="document.getElementById('fileUpload').click()">Open from File...</button> <input type="file" id="fileUpload" name="files" style="display:none" /> Quick and clean.
You can use this approach, it works even if a lot of files inputs. const fileBlocks = document.querySelectorAll('.file-block') const buttons = document.querySelectorAll('.btn-select-file') ;[...buttons].forEach(function (btn) { btn.onclick = function () { btn.parentElement.querySelector('input[type="file"]').click() } }) ;[...fileBlocks].forEach(function (block) { block.querySelector('input[type="file"]').onchange = function () { const filename = this.files[0].name block.querySelector('.btn-select-file').textContent = 'File selected: ' + filename } }) .btn-select-file { border-radius: 20px; } input[type="file"] { display: none; } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="file-block"> <button class="btn-select-file">Select Image 1</button> <input type="file"> </div> <br> <div class="file-block"> <button class="btn-select-file">Select Image 2</button> <input type="file"> </div>
My solution... HTML : <input type="file" id="uploadImages" style="display:none;" multiple> <input type="button" id="callUploadImages" value="Select"> <input type="button" id="uploadImagesInfo" value="0 file(s)." disabled> <input type="button" id="uploadProductImages" value="Upload"> Jquery: $('#callUploadImages').click(function(){ $('#uploadImages').click(); }); $('#uploadImages').change(function(){ var uploadImages = $(this); $('#uploadImagesInfo').val(uploadImages[0].files.length+" file(s)."); }); This is just evil :D
Ok so very simple pure css way of creating your custom input file. Use labels, but as you know from previous answers, label doesn't invoke onclick function in firefox, may be a bug but doesn't matter with the following. <label for="file" class="custom-file-input"><input type="file" name="file" class="custom-file-input"></input></label> What you do is style the label to look how you want it to .custom-file-input { color: transparent;/* This is to take away the browser text for file uploading*/ /* Carry on with the style you want */ background: url(../img/doc-o.png); background-size: 100%; position: absolute; width: 200px; height: 200px; cursor: pointer; top: 10%; right: 15%; } now simply hide the actual input button, but you cant set it to to visability: hidden So make in invisible by setting opacity: 0; input.custom-file-input { opacity: 0; position: absolute;/*set position to be exactly over your input*/ left: 0; top: 0; } now as you might have noticed i have the same class on my label as i do my input field, that is because i want the to both have the same styling, thus where ever you click on the label, you are actually clicking on the invisible input field.
I build a script that can be easier to do that. For example: <input data-com="fileBtn" placeholder="Select Image"> basically, my script is very similar to this link Code Pure javascript, no dependencies needed <!-- bootstrap.min.css not necessary --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.0/css/bootstrap.min.css"> <input data-com="fileBtn" placeholder="Select Image"> <!-- com: components --> <input data-com="fileBtn" placeholder="Select File"> <div class="mt-2"> <input id="build-by-myself" placeholder="Select Video" accept="video/mp4, video/webm"> <div> <script> // 👇 Test (()=>{ window.onload = () =>{ // FileButton.className ="btn btn-danger" FileButton.BuildAll() // auto build all data-com="fileBtn" // or you can specify the target that you wanted. new FileButton(document.getElementById("build-by-myself"), "btn btn-danger") } })() // 👇 script begin class FileButton { static className = "btn btn-primary" static BuildAll() { document.querySelectorAll(`input[data-com="fileBtn"]`).forEach(input=>{ new FileButton(input, FileButton.className) }) } /** * #param {HTMLInputElement} input * #param {string} btnClsName * */ constructor(input, btnClsName) { input.style.display = "none" // [display is better than visibility](https://stackoverflow.com/a/48495293/9935654) input.type = "file" const frag = document.createRange().createContextualFragment(`<button class="${btnClsName}">${input.placeholder}</button>`) const button = frag.querySelector(`button`) input.parentNode.insertBefore(frag, input) button.onclick = ()=>{ input.click() } input.addEventListener(`change`, (e)=>{ // create a textNode to show the file name. const file = input.files[0] if (file === undefined) { return } const textNode = document.createTextNode(file.name) if (button.textNode) { // create a new attribute to record previous data. button.textNode.remove() } button.textNode = textNode button.parentNode.insertBefore(textNode, input) }) } } </script> Reference What is the difference between visibility:hidden and display:none? data-*
Below is an example of a stylized upload button that will read an image, compress it, and download the resulting image. It works by hiding the actual input element, and then through some trickery we make it so that when you click on our fake file uploader it uses the actual input element to pop up the window for choosing a file. By using this method we get 100% control over how the file uploader looks since we are using our own element instead of styling the file upload menu. It also makes it easy to add drag and drop functionality in the future if we ever want to do that. And then I actually created a series of blog posts about this file upload button. 'use strict' var AMOUNT = 10 var WIDTH = 600 var HEIGHT = 400 var canvas = document.getElementById('canvas') canvas.width = WIDTH canvas.height = HEIGHT //here's how I created the clickable area //user clicks the clickable area > we send a click event //to the file opener > the file opener clicks on the open //file button > the open file dialogue pops up function clickableAreaListener(e){ let clickEvent = new CustomEvent("click",{"from":"fileOpenerHandler"}); document.getElementById("fileOpener").dispatchEvent(clickEvent); } function fileOpenerListener(e) { document.getElementById("file-btn").click(); e.preventDefault(); } function fileSelectedListener(e){ readFiles(e.target.files); } document.getElementById('file-btn').addEventListener('change', fileSelectedListener); document.getElementById("clickable-area").addEventListener('click', clickableAreaListener); document.getElementById("fileOpener").addEventListener("click", fileOpenerListener); function readFiles(files){ files = [].slice.call(files); //turning files into a normal array for (var file of files){ var reader = new FileReader(); reader.onload = createOnLoadHandler(file); reader.onerror = fileErrorHandler; //there are also reader.onloadstart, reader.onprogress, and reader.onloadend handlers reader.readAsDataURL(file); } } function fileErrorHandler(e) { switch(e.target.error.code) { case e.target.error.NOT_FOUND_ERR: throw 'Image not found'; break; case e.target.error.NOT_READABLE_ERR: throw 'Image is not readable'; break; case e.target.error.ABORT_ERR: break; default: throw 'An error occurred while reading the Image'; }; } function createOnLoadHandler(file){ console.log('reading ' + file.name + ' of type ' + file.type) //file.type will be either image/jpeg or image/png function onLoad(e){ var data = e.target.result display(data); var compressedData = compressCanvas(AMOUNT) download(compressedData) } return onLoad } function display(data){ var img = document.createElement('img'); img.src = data; var context = canvas.getContext('2d') context.clearRect(0, 0, WIDTH, HEIGHT); context.drawImage(img, 0, 0, WIDTH, HEIGHT); } function compressCanvas(){ return canvas.toDataURL('image/jpeg', AMOUNT / 100); } function download(data) { function b64toBlob(b64Data, contentType, sliceSize) { contentType = contentType || ''; sliceSize = sliceSize || 512; var byteCharacters = atob(b64Data); var byteArrays = []; for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { var slice = byteCharacters.slice(offset, offset + sliceSize); var byteNumbers = new Array(slice.length); for (var i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } var byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } var blob = new Blob(byteArrays, {type: contentType}); return blob; } var chromeApp = Boolean(chrome && chrome.permissions) if (chromeApp){ chrome.fileSystem.chooseEntry({type:'openDirectory'}, function(entry) { chrome.fileSystem.getWritableEntry(entry, function(entry) { entry.getFile('example.jpg', {create:true}, function(entry) { entry.createWriter(function(writer){ writer.write(b64toBlob(data.slice(23), 'image/jpg')) }) }) }) }) } else { let a = document.createElement("a"); a.href = data; a.download = 'downloadExample.jpg' document.body.appendChild(a) a.click(); window.URL.revokeObjectURL(a.href); a.remove() } } .fileInput { display: none; position: absolute; top: 0; right: 0; font-size: 100px; } #clickable-area{ background: #ccc; width: 500px; display: flex; margin-bottom: 50px; } #clickable-area-text{ margin: auto; } .yellow-button { cursor: pointer; color: white; background: #f1c40f; height: 30px; width: 120px; padding: 30px; font-size: 22px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); } <div id="clickable-area"> <a id='fileOpener'> </a> <input type="file" class="fileInput" id="file-btn" accept="image/*" multiple/> <div class="yellow-button"><span>Shrink Image</span> </div><p id="clickable-area-text">( you can click anywhere in here ) </p> </div> <canvas id="canvas"></canvas> Stack Overflow limitations seem to prevent the code snippet from actually compressing and downloading the file. The exact same code here shows that the full upload/compress/download process does actually work as intended.
With answers from this question, I fixed what many in coments said doesn¨t work for them which is that it's not showing how many files user chose. <label for="uploadedFiles" class="btn btn-sm btn-outline-primary">Choose files</label> <input type="file" name="postedFiles" id="uploadedFiles" multiple="multiple" hidden onchange="javascript:updateList()" /> <input class="btn btn-primary mt-2 btn-action" type="submit" value="Send" formmethod="post" formaction="#Url.Action("Create")" /><br /> <span id="selected-count">Selected files: 0</span> <script> updateList = function () { var input = document.getElementById('uploadedFiles');//list of files user uploaded var output = document.getElementById('selected-count');//element displaying count output.innerHTML = 'Selected files: ' + input.files.length; } </script> You can easily improve it by showing names of files instead or whatever you wish to do but all I wanted was to inform user that they have already picked files.
You can use a simple button and hide input file using jquery and bootstrap : HTML code <button class="btn btn-white" id="btn-file" type="button"><i class="fa fa-file-pdf"></i> Anexar Documento</button> <input name="shutdown" id="input-file" type="file" class="form-control hidden" accept="application/pdf, image/png, image/jpeg"> CSS : .hidden{display:none} JS : $("#btn-file").click(function () { $("#input-file").trigger('click'); }); $("#input-file").change(function () { var file = $(this)[0].files[0].name; $("#btn-file").html('<i class="fa fa-file-pdf"></i> ' + file); });