So I am still fairly early in my BED-Studies, and would apreciate any sugestions to be more code hevy than smart and eficient, this way i can learn.
Case:
I am creating a website that displays how the studies evolves my skills. And like any other page, it needs a nav-bar but it can get a little time consuming to retype every relevant link.
I have tried to google the question, but as far as i can find ther is a resounding "nope!", but then again, somthimes i com across people using a "file" function but i cant seem to find this function in jquery or js.
And thus back to my question: is ther a way to make JavaScript "see" the files in a location without hard-coding them in?
my folder structure is likethis:
/school:
./index.html
./css
./js
./programingFoundation:
./pfdindex.html
./module1
./module2
./...
./frontEndTech:
./fetIndex.html
./module1
./module2
,/..
Edit:
my attempt at navigation (mainNav.js):
function innsertMainNav() {
const mainNavTitles = [
{ button: 'Home', url: '/index.html', active: true },
{ button: 'JS Basic', url: '/programmingFoundations/pfindex.html', active: true },
{ button: 'Frontend', url: '/forntEndTechnologies/fetindex.html', active: true },
{ button: 'OOP', url: './programmingWithObjects/pwoindex.html', active: true },
{ button: 'Project Methodology', url: '/projectMethodology/pmindex.html', active: false },
{ button: 'Semester Project', url: '/semesterProject/smindex.html', active: false },
{ button: 'JS Servers', url: '/javaScriptServers/jssindex.html', active: false },
{ button: 'dataBases', url: '/databases/dbindex.html', active: false },
{ button: 'Rest Api', url: '/restApi/raindex.html', active: false },
{ button: 'Servers', url: '/serverDeployment/sdindex.html', active: false },
{ button: 'Exam', url: '/examProject/epindex.html', active: false }
];
//insert header
$('head').after('<header class="container-md bg-light"></header>');
// add title and logo line
$('header').append(`<div class="row"><h1>${document.title}</h1></div>`);
// add Navigation
$('header').append('<nav id="mainNav" class="row">\
<div id="outerDiv" class="container-fluid">\
<button\
class="navbar-toggler" type="button" data-bs-toggle="collapse"\
data-bs-target="#collapsing" aria-controls="collapsing"\
aria-expanded="false" aria-label="Togglenavigation">\
<span class="navbar-toggler-icon"></span>\
</button>\
<div id="collapsing" class="collapse navbar-collapse">\
<ul id="mainNavList" class="navbar-nav me-auto mb-2 mb-lg-0"></ul>\
</div></div></nav>');
// Populate main NAV
for (let i = 0; i < 10; i++) {
if (mainNavTitles[i].active) {
$('ul#mainNavList').append(`<li>\
<a class="col" href="..${mainNavTitles[i].url}">\
${mainNavTitles[i].button}\
</a></li>`);
}
else {
break;
}
}
$('#mainNav').addClass('navbar navbar-expand-lg navbar-light');
$('nav a').addClass('nav-link');
}
If you're only using HTML and vanilla JS, you could do it like this.
First, create a javascript file, and include it in every main html file you use on your website (for example, index.html. contact.html, portfolio.html, etc).
Let's call this file helpers.js.
Its contents would be something like this.
// Let's see if the HTML which is using helpers.js has its own navigation
var navigation = document.querySelectorAll("nav");
if(!navigation.length) {
var navigation = document.createElement("nav");
} else {
navigation = navigation[0];
}
// We'll make a centralized list of main links - the ones we want to see in the navigation
var linkList = {
"home":"index.html",
"portfolio":"portfolio.html",
"about me":"about.html",
"contact me":"contact.html"
};
// The function is here to help us populate the navigation with our links
function fillNavigation(elem, source) {
elem.innerHTML = "";
for(let description in source) {
let content = '' + description + '';
elem.innerHTML += content;
}
}
// We call the function, and let it work for us
fillNavigation(navigation,linkList);
One advantage of this is that you have one place where you change your main links list - you wouldn't have to visit each and every page of your site in order to make changes to your navigation. The helpers.js will do this for you.
A running example (JS fiddle here, in case you want to play around with this)
var navigation = document.querySelectorAll("nav");
if(!navigation.length) {
var navigation = document.createElement("nav");
} else {
navigation = navigation[0];
}
var linkList = {
"home":"index.html",
"portfolio":"portfolio.html",
"about me":"about.html",
"contact me":"contact.html"
};
function fillNavigation(elem, source) {
elem.innerHTML = "";
for(let description in source) {
let content = '' + description + '';
elem.innerHTML += content;
}
}
// The timeout is here for illustrative purposes only
setTimeout(function() {
fillNavigation(navigation,linkList);
},2000);
nav {
padding: 5px;
width: calc(100% - 10px);
text-align: center;
background-color: #333;
color: #fff;
}
nav a {
text-decoration: none;
padding: 3px;
}
nav a:hover {
color: red;
transition: 0.5s;
}
<nav>ok</nav>
<p>
lorem ipsum
sit dolor amet
etc lupus in fabula
homo homini lupus est
similis simili gaudet
bellum omnia contra omni
</p>
<hr>
<p>
some other stuff
</p>
EDIT For cases where you have multi-level menus, you could use something like this. As in the first example, a fiddle is available here
var navigation = document.querySelectorAll("nav");
if(!navigation.length) {
var navigation = document.createElement("nav");
} else {
navigation = navigation[0];
}
var linkList = {
"home":"index.html",
"portfolio":"portfolio.html",
"about me":"about.html",
"contact me":"contact.html",
"modules":{
"module1": {
"lorem":"lorem.html",
"ipsum":"ipsum.html"
},
"module2": {
"foo":"foo.html",
"bar":"bar.html"
}
}
};
function isObject(obj) {
return typeof obj === 'object';
}
function renderLinks(source,flagClose) {
let txt = "";
for(let description in source) {
if(isObject(source[description])) {
txt += '<li class="parent">' + description + '<ul class="sublevel none">';
txt += renderLinks(source[description],description);
} else {
let addition = flagClose ? (flagClose.trim() + '/') : ''
txt += '<li>' + description + '</li>';
}
}
if(flagClose) {
txt += '</ul></li>';
}
return txt;
}
function fillNavigation(elem, source) {
elem.innerHTML = "";
elem.innerHTML += renderLinks(source);
// show on hover
$("li.parent").mouseenter(function() {
$(this).find("> ul.sublevel").removeClass("none");
});
$("li.parent").mouseleave(function() {
$(this).find("> ul.sublevel").addClass("none");
});
}
fillNavigation(navigation,linkList);
.none {
display: none !important;
}
nav {
padding: 5px;
width: calc(100% - 10px);
text-align: left;
background-color: #333;
color: #fff;
}
nav a {
text-decoration: none;
padding: 3px;
}
nav a:hover {
color: red;
transition: 0.5s;
}
li {
list-style: none;
}
li.parent {
position: relative;
padding-right: 10px;
padding-left: 5px;
}
li.parent * {
display: block;
}
ul.sublevel {
position: absolute;
top: -1px; /* because of the border */
left: 100%;
background-color: #333;
margin-top: 0px;
margin-bottom: 0px;
padding-left: 10px;
min-width: 100px;
border: 1px solid #aaa;
text-align: left;
}
li.parent, ul.sublevel {
cursor: pointer;
}
nav li {
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav>ok</nav>
<p>lorem ipsum sit dolor amet etc lupus in fabula homo homini lupus est similis simili gaudet bellum omnia contra omni </p>
<hr>
<p>some other stuff</p>
You will notice a few differences from the first case (one-level menu):
the original linkList has been modified, in order to show the case when there's a multi-level menu (modules/module1/link1.html, modules/module1/link2.html, etc)
the function fillNavigation has been changed, as well - it now contains a call to a new function, renderLinks, which creates all the innerHTML
renderLinks is a recursive function, which makes it handy for cases where you decide to change the depth of your navigation. Please note that it has been tested on one and two-level menus. It should work for more levels than that, but you would have to adjust the CSS accordingly (or change the function a bit, so that you may have specific CSS classes, connected with a specific level)
the function renderLinks relies on a helper function, isObject. The purpose of this function is to test whether the item we're currently processing is actually a sub-menu. Since we control the original linkList and its structure, we can do it as shown in the code. For more complex cases (for example, where we have no, or little control on how the link list is formed, this solution would most likely be lacking)
hover effect and showing of sub-level menus is handled via jQuery. There is no special reason for this, other than that I found it simpler to achieve (less to type)
since we can have more than one menu element with children, mouseenter and mouseleave target only the first sub-level within the one we're hovering on. This also lets us travel (visually) along the hovered route, without closing the original top-level menu item which contains the one's we're currently traversing
The question here seems to be, how do I read a local filesystem from a web browser. Browsers have a FileAPI but it does not offer the ability to pass a list of the files and folders on the local filesystem without user input.
Related
I'm building a website and am trying to display a message at the top of the page (inside the header) so it only appears once on every visit/session. An example of this the 'Book an appointment' green bar at the top of this website:
https://www.tiffany.co.uk
My website is here: https://vitrify.tempurl.host/
I've got as far as having a message appear (orange panel at top of page) but currently it appears every time a page is loaded. I just want it to appear once, just like a cookie consent.
I've spent hours looking for a solution but, as I'm not a programmer, I'm struggling. Any help would be much appreciated.
Here's the HTML:
function myFunction() {
var x = document.getElementById("topDIV");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
.topDIV {
color: #000000;
font-weight: 300;
font-size: 15px;
}
.topDIV a:link, .topDIV a:visited {
color: #000000!important;
font-weight: 500;
letter-spacing: -0.3px;
line-height: 1.2 ;
}
span.topDIV {
}
.topDIV a:hover {
border-bottom: 1px solid rgba(0,0,0,0.50) !important;
display: inline-block;
}
.button-x {
position: relative;
float: right;
top: -5px;
background:none;
border:none;
color:rgb(0,0,0) ;
cursor: pointer;
vertical-align: 0px;
}
.button-x:before {
font-family: 'Times';
content: "X";
font-size:30px;
vertical-align:0px;
opacity:0.5;
}
.button-x:hover {
opacity:1!important;
}
<span class = "topDIV">Welcome to <em>Vitrify</em>. Following in the finest traditions of vitreous enamelled jewellery. Find out more.</span><button class = "button-x" onclick="myFunction()"></button>
As mentioned above you need to use localStorage to solve your problem.
Need to know when page is loaded
Get value from the localStorage
Add event to the button (i removed the onclick event from the html for a cleaner solution)
After click set value for localStorage and hide item
If localStorage have value, hide the element
working example
Javascript
document.addEventListener('DOMContentLoaded', function () {
const topDiv = document.querySelector('.topDIV');
const buttonX = document.querySelector('.button-x');
// Get value from localStorage when open the page
const lS = localStorage.getItem('first-visit');
// Add button 'click' event
buttonX.addEventListener('click', () => {
// Set value to the localStorage
localStorage.setItem('first-visit', false);
// hide DOM element
topDiv.style.display = 'none';
});
// This does not check on the first visit to the page
// If localStorage have value, hide DOM element
if (lS) topDiv.style.display = 'none';
});
Html
<span class="topDIV"
>Welcome to <em>Vitrify</em>.Following in the finest traditions of
vitreous enamelled jewellery.
Find out more.
</span>
<button class="button-x"></button>
You can add a flag "showTopMessage:true" in your local storage or session storage based on one time msg to user or every time he visits. respectively.
On Cross/close icon click set the flag "showTopMessage:false".
const showTopBar = "SHOW_TOP_BAR";
const[showTopBar,setShowTopBar] = useState(true);
useEffect(()=>{
const saveState = localstorage.getItems(showTopBar);
if(saveState) setShowTopBar(saveState);
else localstorage.setItem(showTopBar, true);
},[]);
const handleTopBarClose = () => {
setShowTopBar(false);
localstorage.setItem(showTopBar, false);
}
return(
<div>
{
showTopBar && <TopBarComponent onClose={handleTopBarClose}/>
}
</div>
)
I am trying to make a grid where the different boxes will blink based off of a binary value defined within my HTML document. I have created a grid in HTML, where the background colour is automatically green and what I'm trying to achieve is that if my value changes to from 0 to 1 for each of the grid items it will then change the colour to red and blink respectively.
I have managed to get the first one working and thought I could just repeat the code with different variables assigned, however this hasn't worked. The weird thing is, if I remove the code for the first box the second box will start working.
Do I need to add some extra code in JS to separate the if statments?
CSS'
.grid-container {
display: grid;
grid-gap: 50px;
grid-template-columns: auto auto auto;
background-color: grey;
padding: 10px;
}
.grid-item {
background-color: green;
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 50px;
font-size: 30px;
text-align: center;
}
HTML
<div class="grid-container">
<div class="grid-item" id = "blink1">A</div>
<div class="grid-item" id = "blink2">B</div>
</div>
<div class = "values">
<div id = "$box1value"> 1 </div>
<div id = "$box2value"> 1 </div>
</div>
JS
var $box1 = document.getElementById("$box1value").innerHTML;
if ($box1 > 0) {
document.getElementById("blink1").style.backgroundColor = '#ff0000';
// blink "on" state
function show() {
if (document.getElementById)
document.getElementById("blink1").style.visibility = "visible";
}
// blink "off" state
function hide() {
if (document.getElementById)
document.getElementById("blink1").style.visibility = "hidden";
}
for (var i = 900; i < 99999999999; i = i + 900) {
setTimeout("hide()", i);
setTimeout("show()", i + 450);
}
} else {
document.getElementById("blink1").style.backgroundColor = '#098700';
}
/////////////////////next box/////////////////////////////
var $box2 = document.getElementById("$box2value").innerHTML;
if ($box2 > 0) {
document.getElementById("blink2").style.backgroundColor = '#ff0000';// blink "on" state
function show() {
if (document.getElementById)
document.getElementById("blink2").style.visibility = "visible";
}
// blink "off" state
function hide() {
if (document.getElementById)
document.getElementById("blink2").style.visibility = "hidden";
}
for (var i = 900; i < 99999999999999999; i = i + 900) {
setTimeout("hide()", i);
setTimeout("show()", i + 450);
}
} else {
document.getElementById("blink2").style.backgroundColor = '#098700';
}
2 different solutions (all JS vs. mostly CSS)
Keeping the core functionality in JS
Leveraging CSS for core functionality
I see what you're trying to achieve here, and I see a couple of different ways to accomplish this. Both of the solutions below allow your code to dynamically loop through any number of box items— no need to write a separate block for each item.
The first example below is modeled more similar to yours, based on
your code but rewritten to work more dynamically. The second solution
further down greatly simplifies things by moving all initialization
scripting into CSS, leaving JS responsible for only boolean switching
if you need to make any real-time state switches.
#1. Keeping the core functionality in JS
This solution modifies your original code to dynamically read the values for however many values there are, and then looping through them. In order to perform the repeated blinking in JS, I would suggest using setInterval. You'll also need to move that outside the rest of the code when using a loop or you'll end up with a conflict between the loop's iterator and the setInterval's and setTimeout's timing. More on that here. You can see the working example below:
function blink(el) {
if (el.style) {
setInterval(function() {
el.style.visibility = "visible";
setTimeout(function() {
el.style.visibility = "hidden";
}, 450);
}, 900);
}
}
const $boxes = document.querySelectorAll('[id^="blink"]');
for (const $box of $boxes) {
var boxId = $box.id.match(/\d+/)[0]; // store the ID #
if (document.getElementById('$box' + boxId + 'value')) {
var boxValue = parseInt(document.getElementById('$box' + boxId + 'value').innerHTML);
if (boxValue) {
$box.style.backgroundColor = '#ff0000';
blink($box);
} else {
$box.style.backgroundColor = '#098700';
}
}
}
.grid-container {
display: grid;
grid-gap: 50px;
grid-template-columns: auto auto auto;
background-color: grey;
padding: 10px;
}
.grid-item {
background-color: #098700;
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 50px;
font-size: 30px;
text-align: center;
}
.values {
display: none;
}
<div class="grid-container">
<div class="grid-item" id="blink1">A</div>
<div class="grid-item" id="blink2">B</div>
<div class="grid-item" id="blink3">C</div>
</div>
<div class="values">
<div id="$box1value">1</div>
<div id="$box2value">0</div>
<div id="$box3value">1</div>
</div>
CodePen: https://codepen.io/brandonmcconnell/pen/ecc954bad5552962574c080631700932
#2. Leveraging CSS for core functionality
This solution moves all of your JS code (color and animation) to the CSS, moving the binary boolean switch 0/1 to data-attributes on the grid-items themselves instead of separate items and then trigger any boolean switches on those containers using JS by targeting them by another attribute such as ID, or as I used in my example below, another data-attribute I called data-blink-id. This is my recommended solution if you're able to move all of this logic into CSS. It'll be much easier to maintain and to manipulate in real-time, as all it requires to change state is a simple boolean switch.
.grid-container {
display: grid;
grid-gap: 50px;
grid-template-columns: auto auto auto;
background-color: grey;
padding: 10px;
}
.grid-item {
background-color: #098700;
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 50px;
font-size: 30px;
text-align: center;
}
.grid-item[data-blink-status="1"] {
background-color: #f00;
animation: blink 900ms linear infinite forwards;
}
#keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
<div class="grid-container">
<div class="grid-item" data-blink-id="1" data-blink-status="1">A</div>
<div class="grid-item" data-blink-id="2" data-blink-status="0">B</div>
<div class="grid-item" data-blink-id="3" data-blink-status="1">C</div>
</div>
CodePen: https://codepen.io/brandonmcconnell/pen/5b4f3090b3590902b11d50af43361758
To trigger the binary boolean switch on an item (turn ON/OFF), use the below JS command. I've commented this out in the CodePen example linked above. Un-comment this JS line to activate it and switch ON the block with data-blink-id=2
document.querySelector('[data-blink-id="2"]').setAttribute('data-blink-status', 1);
Even though your functions are declared inside if statements, they are still global.
So, you essentially redeclare the show and hide functions, and they stop working.
To make those functions local to the if statement, you'll have to use one of the ES6 block scope declarations, let or const, like this:
const show = function(){ ... }
const hide = function(){ ... }
To do this, you should also replace setTimeout's first argument with a reference to the function (actually, you should always do that):
setTimeout(hide, i)
setTimeout(show, i + 450)
Other improvements you can make:
Avoid that loop that sets timeouts. It's ugly, takes long to execute, and doesn't work forever. Instead, replace setTimeouts with setIntervals.
Remove the if (document.getElementById) part. You can count on it to be defined (it has been around for a loooong time...)
So, you get to:
var $box1 = document.getElementById("$box1value").innerHTML;
if ($box1 > 0) {
document.getElementById("blink1").style.backgroundColor = '#ff0000';// blink "on" state
const show = function () {
document.getElementById("blink1").style.visibility = "visible";
}
// blink "off" state
const hide = function () {
document.getElementById("blink1").style.visibility = "hidden";
}
let flag = false //This is needed to keep track if the element is visible
setInterval(function(){
if(flag = !flag)
hide()
else
show()
}, 450);
} else {
document.getElementById("blink1").style.backgroundColor = '#098700';
}
/////////////////////next box/////////////////////////////
var $box2 = document.getElementById("$box2value").innerHTML;
if ($box2 > 0) {
document.getElementById("blink2").style.backgroundColor = '#ff0000';// blink "on" state
const show = function () {
document.getElementById("blink2").style.visibility = "visible";
}
// blink "off" state
const hide = function () {
document.getElementById("blink2").style.visibility = "hidden";
}
let flag = false //This is needed to keep track if the element is visible
setInterval(function(){
if(flag = !flag)
hide()
else
show()
}, 450);
} else {
document.getElementById("blink2").style.backgroundColor = '#098700';
}
I am currently using a combination of fancybox thumbnails and buttons (only the toggle size button). By default, the buttons appear in the centre of the viewport, I would like to move this to the bottom right hand corner of the image, essentially appearing attached in the same way the close button is or just below the right corner would also be fine.
I have tried space it relative to the viewport width but it doesn't work. Is there a way to position it relative to the image?
I apologise for the abundance of code - I have no idea what to include and what to disregard, so I've put it all in.
If you look at my website (unfinished but I've uploaded it to help) you can see the issue on gallery one.
Shereewalker.com
Any help would be very much appreciated.
Here is my css
#fancybox-buttons {
position: fixed;
left: 0;
width: 100%;
z-index: 8050;}
#fancybox-buttons.top {top: 10px;}
#fancybox-buttons.bottom {bottom: 10px;}
.fancybox-close {
position: absolute;
top: -18px;
right: -18px;
width: 36px;
height: 36px;
cursor: pointer;
z-index: 8040; }
#fancybox-buttons ul {
width: 36px;
height: 36px;
cursor: pointer;
margin: 0 auto;
list-style: none;}
#fancybox-buttons ul li {
float: left;
margin: 0;
padding: 0;}
And my javascript
jQuery(document).ready(function ($) {
$(".fancybox").fancybox({
prevEffect : 'none',
nextEffect : 'none',
// API options
helpers : {
title : {
type: 'outside'
},
buttons: {},
thumbs: {
width : 10,
height : 10
}
}
}); // fancybox
}); // ready
And even more javascript
(function ($) {
//Shortcut for fancyBox object
var F = $.fancybox;
//Add helper object
F.helpers.buttons = {
defaults : {
skipSingle : false, // disables if gallery contains single image
position : 'top', // 'top' or 'bottom'
tpl : '<div id="fancybox-buttons"><ul><li><a class="btnToggle" title="Toggle size" href="javascript:;"></a></li></ul></div>'
},
list : null,
buttons: null,
beforeLoad: function (opts, obj) {
//Remove self if gallery do not have at least two items
if (opts.skipSingle && obj.group.length < 2) {
obj.helpers.buttons = false;
obj.closeBtn = true;
return;
}
//Increase top margin to give space for buttons
obj.margin[ opts.position === 'bottom' ? 2 : 0 ] += 30;
},
onPlayStart: function () {
if (this.buttons) {
this.buttons.play.attr('title', 'Pause slideshow').addClass('btnPlayOn');
}
},
onPlayEnd: function () {
if (this.buttons) {
this.buttons.play.attr('title', 'Start slideshow').removeClass('btnPlayOn');
}
},
afterShow: function (opts, obj) {
var buttons = this.buttons;
if (!buttons) {
this.list = $(opts.tpl).addClass(opts.position).appendTo('body');
buttons = {
prev : this.list.find('.btnPrev').click( F.prev ),
next : this.list.find('.btnNext').click( F.next ),
play : this.list.find('.btnPlay').click( F.play ),
toggle : this.list.find('.btnToggle').click( F.toggle ),
close : this.list.find('.btnClose').click( F.close )
}
}
//Prev
if (obj.index > 0 || obj.loop) {
buttons.prev.removeClass('btnDisabled');
} else {
buttons.prev.addClass('btnDisabled');
}
//Next / Play
if (obj.loop || obj.index < obj.group.length - 1) {
buttons.next.removeClass('btnDisabled');
buttons.play.removeClass('btnDisabled');
} else {
buttons.next.addClass('btnDisabled');
buttons.play.addClass('btnDisabled');
}
this.buttons = buttons;
this.onUpdate(opts, obj);
},
onUpdate: function (opts, obj) {
var toggle;
if (!this.buttons) {
return;
}
toggle = this.buttons.toggle.removeClass('btnDisabled btnToggleOn');
//Size toggle button
if (obj.canShrink) {
toggle.addClass('btnToggleOn');
} else if (!obj.canExpand) {
toggle.addClass('btnDisabled');
}
},
beforeClose: function () {
if (this.list) {
this.list.remove();
}
this.list = null;
this.buttons = null;
}
};
}(jQuery));
If you want to move the toggle button only, you actually need to clone it rather than moving it.
Based on this answer https://stackoverflow.com/a/17888534/1055987, we could tweak it into this :
CSS
/* hide the actual buttons helper */
#fancybox-buttons {
display: none;
}
/* position the clone : notice the class "clone" */
.clone {
position: absolute;
top: 5px;
right: 0;
}
.btnToggle.clone {
background-position: 3px -60px;
border-left: 1px solid #111;
border-right: 1px solid #3e3e3e;
width: 35px;
height: 35px;
background-image: url("{your path}/helpers/fancybox_buttons.png");
background-color: #333;
}
.clone.btnToggleOn {
background-position: -27px -60px;
}
Then the jQuery code :
jQuery(document).ready(function ($) {
$(".fancybox").fancybox({
helpers: {
title: {
type: 'inside'
},
buttons: {} // we need this to clone
},
afterLoad: function () {
// make sure we have a title
this.title = this.title ? this.title : " ";
},
afterShow: function () {
// wait for the buttons helper
setTimeout(function () {
$("#fancybox-buttons")
.find(".btnToggle")
.clone(true, true) // clone with data and events
.addClass("clone") // add class "clone"
.appendTo(".fancybox-title") // append it to the title
.fadeIn(); // show it nicely
}, 100); //setTimeout
}, // afterShow
onUpdate: function () {
var toggle = $(".btnToggle.clone").removeClass('btnDisabled btnToggleOn');
if (this.canShrink) {
toggle.addClass('btnToggleOn');
} else if (!this.canExpand) {
toggle.addClass('btnDisabled');
}
}
}); // fancybox
}); // ready
See JSFIDDLE
The toggle size button (and thumbnails) and hard to align because they are in separate full-width containers. This markup is generated by FancyBox for the gallery slideshow.
It looks like this:
<body>
<div class="fancybox-wrap">
<div class="fancybox-overlay">
<div id="fancybox-buttons">
<div id="fancybox-thumbs">
The .fancybox-wrap contains the gallery image and #fancybox-buttons contains the toggle size button.
Luckily, the OP was creating a template (tpl) for the toggle size button... but was injecting it into the <body> so it was unaware of the content of the .fancybox-wrap.
To fix this just change this line:
this.list = $(opts.tpl).addClass(opts.position).appendTo('body');
To this:
this.list = $(opts.tpl).addClass(opts.position).appendTo('.fancybox-wrap');
This just puts the buttons inside the same container as the gallery image (which is what we want to align too).
I am pulling some information from an external database using javascript. However, I am not sure how to generate a like unlike toggle button for users to click below each information.
I place a toggle link within the appendeddata variable just to test if it would work. The link did show up below each data but the toggle effect did not work. I place the code within the same script and in its own script however no luck.
I want to also id the user that clicks the link. how can I do this?
Thank you.
here is my javascript code.
var appendedhtml = "";
var address = "";
function getDomes() {
$.ajax({
type: "GET",
url: "https://api.google.com/d/domes/explore?
ll="+lat+","+lng+"&client_id= client_id&query="+$("#query").val()+"",
success: function(data) {
$("#domes").show();
var dataobj = data.response.groups[0].items;
$("#domes").html("");
$.each( dataobj, function() {
if (this.dome.categories[0]) {
str = this.dome.categories[0].icon.prefix;
newstr = str.substring(0, str.length - 1);
icon = newstr+this.dome.categories
[0].icon.suffix;
} else {
icon = "";
}
if (this.dome.location.address) {
address = '<p
class="subinfo">'+this.dome.location.address+'<br>';
} else {
address = "";
}
appendedtml = '<div
class="venue"><h2><span>'+address'</span></h2> <hr>';
$("#domes").append(appendedhtml);
});
}
});
}
I place the code that would give the toggle effect within the main script and by itself
with no luck.
<script>
$(document).ready(function(){
$('a.toggler').click(function(){
$(this).toggleClass('off');
});
});
this is the css for the toggle button. I know the button works indecently just cannot get it
to work with the data output from the main script.
a.toggler {
background: green;
cursor: pointer;
border: 2px solid black;
border-right-width: 15px;
padding: 0 5px;
border-radius: 5px;
text-decoration: none;
transition: all .5s ease;
}
a.toggler.off {
background: red;
border-right-width: 2px;
border-left-width: 15px;
}
try jQuery's on method instead of click method.
$( "a.toggler" ).on( "click", function() {
$(this).toggleClass('off');
});
on method listens to events from elements that were created dynamically.
I am looking for a Javascript autocomplete implementation which includes the following:
Can be used in a HTML textarea
Allows for typing regular text without invoking autocomplete
Detects the # character and starts autocomplete when it is typed
Loads list of options through AJAX
I believe that this is similar to what Twitter is doing when tagging in a tweet, but I can't find a nice, reusable implementation.
A solution with jQuery would be perfect.
Thanks.
Another great library which solves this problem At.js (deprecated)
Source
Demo
They are now suggesting the Tribute library
https://github.com/zurb/tribute
Example
I'm sure your problem is long since solved, but jquery-textcomplete looks like it would do the job.
Have you tried this
GITHUB: https://github.com/podio/jquery-mentions-input
DEMO/CONFIG: http://podio.github.io/jquery-mentions-input/
It is pretty simple to implement.
I've created a Meteor package for this purpose. Meteor's data model allows for fast multi-rule searching with custom rendered lists. If you're not using Meteor for your web app, (I believe) you unfortunately won't find anything this awesome for autocompletion.
Autocompleting users with #, where online users are shown in green:
In the same line, autocompleting something else with metadata and bootstrap icons:
Fork, pull, and improve:
https://github.com/mizzao/meteor-autocomplete
Try this:
(function($){
$.widget("ui.tagging", {
// default options
options: {
source: [],
maxItemDisplay: 3,
autosize: true,
animateResize: false,
animateDuration: 50
},
_create: function() {
var self = this;
this.activeSearch = false;
this.searchTerm = "";
this.beginFrom = 0;
this.wrapper = $("<div>")
.addClass("ui-tagging-wrap");
this.highlight = $("<div></div>");
this.highlightWrapper = $("<span></span>")
.addClass("ui-corner-all");
this.highlightContainer = $("<div>")
.addClass("ui-tagging-highlight")
.append(this.highlight);
this.meta = $("<input>")
.attr("type", "hidden")
.addClass("ui-tagging-meta");
this.container = $("<div></div>")
.width(this.element.width())
.insertBefore(this.element)
.addClass("ui-tagging")
.append(
this.highlightContainer,
this.element.wrap(this.wrapper).parent(),
this.meta
);
var initialHeight = this.element.height();
this.element.height(this.element.css('lineHeight'));
this.element.keypress(function(e) {
// activate on #
if (e.which == 64 && !self.activeSearch) {
self.activeSearch = true;
self.beginFrom = e.target.selectionStart + 1;
}
// deactivate on space
if (e.which == 32 && self.activeSearch) {
self.activeSearch = false;
}
}).bind("expand keyup keydown change", function(e) {
var cur = self.highlight.find("span"),
val = self.element.val(),
prevHeight = self.element.height(),
rowHeight = self.element.css('lineHeight'),
newHeight = 0;
cur.each(function(i) {
var s = $(this);
val = val.replace(s.text(), $("<div>").append(s).html());
});
self.highlight.html(val);
newHeight = self.element.height(rowHeight)[0].scrollHeight;
self.element.height(prevHeight);
if (newHeight < initialHeight) {
newHeight = initialHeight;
}
if (!$.browser.mozilla) {
if (self.element.css('paddingBottom') || self.element.css('paddingTop')) {
var padInt =
parseInt(self.element.css('paddingBottom').replace('px', '')) +
parseInt(self.element.css('paddingTop').replace('px', ''));
newHeight -= padInt;
}
}
self.options.animateResize ?
self.element.stop(true, true).animate({
height: newHeight
}, self.options.animateDuration) :
self.element.height(newHeight);
var widget = self.element.autocomplete("widget");
widget.position({
my: "left top",
at: "left bottom",
of: self.container
}).width(self.container.width()-4);
}).autocomplete({
minLength: 0,
delay: 0,
maxDisplay: this.options.maxItemDisplay,
open: function(event, ui) {
var widget = $(this).autocomplete("widget");
widget.position({
my: "left top",
at: "left bottom",
of: self.container
}).width(self.container.width()-4);
},
source: function(request, response) {
if (self.activeSearch) {
self.searchTerm = request.term.substring(self.beginFrom);
if (request.term.substring(self.beginFrom - 1, self.beginFrom) != "#") {
self.activeSearch = false;
self.beginFrom = 0;
self.searchTerm = "";
}
if (self.searchTerm != "") {
if ($.type(self.options.source) == "function") {
self.options.source(request, response);
} else {
var re = new RegExp("^" + escape(self.searchTerm) + ".+", "i");
var matches = [];
$.each(self.options.source, function() {
if (this.label.match(re)) {
matches.push(this);
}
});
response(matches);
}
}
}
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
self.activeSearch = false;
//console.log("#"+searchTerm, ui.item.label);
this.value = this.value.replace("#" + self.searchTerm, ui.item.label) + ' ';
self.highlight.html(
self.highlight.html()
.replace("#" + self.searchTerm,
$("<div>").append(
self.highlightWrapper
.text(ui.item.label)
.clone()
).html()+' ')
);
self.meta.val((self.meta.val() + " #[" + ui.item.value + ":]").trim());
return false;
}
});
}
});
body, html {
font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
}
.ui-tagging {
position: relative;
border: 1px solid #B4BBCD;
height: auto;
}
.ui-tagging .ui-tagging-highlight {
position: absolute;
padding: 5px;
overflow: hidden;
}
.ui-tagging .ui-tagging-highlight div {
color: transparent;
font-size: 13px;
line-height: 18px;
white-space: pre-wrap;
}
.ui-tagging .ui-tagging-wrap {
position: relative;
padding: 5px;
overflow: hidden;
zoom: 1;
border: 0;
}
.ui-tagging div > span {
background-color: #D8DFEA;
font-weight: normal !important;
}
.ui-tagging textarea {
display: block;
font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
background: transparent;
border-width: 0;
font-size: 13px;
height: 18px;
outline: none;
resize: none;
vertical-align: top;
width: 100%;
line-height: 18px;
overflow: hidden;
}
.ui-autocomplete {
font-size: 13px;
background-color: white;
border: 1px solid black;
margin-bottom: -5px;
width: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea></textarea>
http://jsfiddle.net/mekwall/mcWnL/52/
This link will help you
I could not find any solution that matched my requirements perfectly, so I ended up with the following:
I use the jQuery keypress() event to check for the user pressing the # character.
If this is the case, a modal dialog is shown using jQuery UI. This dialog contains an autocomplete text field (many options can be used here, but I recommmend jQuery Tokeninput)
When the user selects an option in the dialog, a tag is added to the text field and the dialog is closed.
This is not the most elegant solution, but it works and it does not require extra keypresses compared to my original design.
Edit
So basically, we have our large text box where the user can enter text. He should be able to "tag" a user (this just means inserting #<userid> in the text). I attach to the jQuery keyup event and detect the # character using (e.which == 64) to show a modal with a text field for selecting the users to tag.
The meat of the solution is simply this modal dialog with a jQuery Tokeninput text box. As the user types here, the list of users is loaded through AJAX. See the examples on the website for how to use it properly. When the user closes the dialog, I insert the selected IDs into the large text box.
Recently i had to face this problem and this is how i nailed down...
Get the string index at the cursor position in the textarea by using selectionStart
slice the string from index 0 to the cursor position
Insert it into a span (since span has multiple border boxes)
Get the dimensions of the border box using element.getClientRects() relative to the view port. (here is the MDN Reference)
Calculate the top and left and feed it to the dropdown
This works in all latest browsers. haven't tested at old ones
Here is Working bin
Another plugin which provides similar functionality:
AutoSuggest
You can use it with custom triggers or you can use it without any triggers. Works with input fields, textareas and contenteditables. And jQuery is not a dependency.
I would recommend the textcomplete plugin. No jQuery dependency. You may need bootstrap.css to refer, but I recommend to write your own CSS, lighter and simple.
Follow the below steps to give it a try
npm install #textcomplete/core #textcomplete/textarea
Bind it to your input element
const editor = new TextareaEditor(inputEl);
const textcomplete = new Textcomplete(editor, strategy, options);
Set strategy(how to fetch suggestion list) and options(settings to configure the suggestions) according to your need.
JS version
Angular Version
This small extension seems to be the closest at least in presentation to what was asked. Since it's small, it can be easily understood and modified. http://lookapanda.github.io/jquery-hashtags/
THIS should work. With regards to the # kicking off the search, just add (dynamically or not) the symbol to the beginning of each possible search term.