Using CSS content attribute vs JavaScript - javascript

I'm creating input that shows different notes based on content. First, I tried using content attribute of CSS, but it only works for :before and :after selectors.
So, I created another div element, using :before on it to display the note text. Should I switch to using JavaScript to do this?
JS Fiddle
HTML:
<div class="link-input-container">
<input class="link-input" placeholder="Zalijepi link ovdje..." type="text" class="url_input" name="url_input">
</div>
<div class="link-note no-input">
<div class="link-note-content">
</div>
</div>
CSS:
.link-input-container {
position: relative;
display: inline-block;
}
.link-input-container::after {
display: block;
content: "";
width: 14px;
height: 14px;
background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACT0lEQVRIS8WVS2sTURTHf2N8JMG2iK/WUt+LWpSilFIV9BPoByh059qNghsxtRtBcevCreAHqJ9AQQ0hVIqvitpqk2mrzUOrJK2JuXKGudNk5s60m+DAZTJzz/n/zuOeiUWbL6vN+vxfgE3yUi+VJ1FZbmQTmoE4WjCp4HIYZDM2RoB27Dl/lsXnLzFBPJuxURYfPTbaSOYBgHbsPjfiVWbpRbpFoFmcchlVKuO30c4BwAJJ1T0yHCj7UjrjQNyoJnvGRqFUQhVL64GkMxyg0qJpymDcgtT+4aEA5Fsm67wTcVUsogpFz0b2FNzupTLe7BjWAweyb+hMABI70Y8qFGgsF7y979kpo7ixB9rLJulA9p4ejJzF5VfToeKRANnUkD2Dp4yQwvTrSPENAWIgTd99csAIKL55F2iq3zDyUyHi0tC17FRomUrvZyIhoQAtXrcXqNu2A1CmwQHKHz6GQowALV7L29RyeS/6n58+O7+7jh8LZCR7/hkw9sATz+WpzefWxWfnnIa6Tqmuo0eCkNm5zQ9a5+FDnsDKl68tp0Wfrigb7Rw5aB0H+/g1n+MvTPRRcaLXV45kKga3tI1piv0lEtgWYCuwbYbEzU6sGz9Q9wao3nf72xyQekvi2i6s6yuou/1U7wB1oObeG3Iumh1EfDuwE+gA4s+IX73A6kMg5sLFXpYcKFmNp8SvXGT1gStcBX6764/s+wESfQJIAjvcbES8GaCr5ABAKugsiX4NqAACkucWgC6ZZKKXLpuO3H9yvEzcjASkwXJv/5/+P/1hAyh9doPuAAAAAElFTkSuQmCC') no-repeat;
background-size: contain;
background-position: center;
position: absolute;
top: 50%;
right: -5px;
transform: translateY(-50%) translateX(100%);
}
.link-input-container.valid::after {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACHklEQVRIS+2Vv27TQBzHv2fT4PhPUjEg0Y6deACmdiviBViZoM/A2padN6Bdu/EASNnIwBMwdQApiCFVqGM7dl370PfqS8+xm0RU3TjpJzun8+fzu9/v7Ag88BAPzMd/garw2y+7W8LGSBbYPn01/GWW/d4l0vCd5zs4/36ORcm9BCY8yzOkeYrxj3FN8s+CNrguDSUn+0PFbgiW1VMDTHh6larMhbhBES4gjj/tfz1qCFbV02woaz7LZkizFLCgBBc/L2rwmkDDN59tout04Tpuo2lmAsksQZImCiwsgclo0oDXBO8Gu5JwKSVkKeF2XXiuN5eoxTZGzDyOY8RJrMCMy9+XrfCa4GCwdyQhD3tPeyjLUkl8z4fv+0rCQXg0jTCNp/PMo3F0J7zRAy3xnnhKwAiCAEEvUIIwDDENb+HJn2QpvPUUaYnTc+aSfr8PCYlwEt401BLIomwlvFXASS3puB3IQqq+6MGm5lm+FvxOgSmxN2xAQkkIL66LteFLBaaEJZlLjJdonf+SVZ8KcTDYO+TpUjCJDycvh8cLYF2/2zoaC0wB7y0AjwBsVFebc28+v3hfSEucvf720XiWQEZZRQHgGkBeXTkvTQHhHQA+AJ5LpxJRokTVt4vPmHCCGQTPAERVXFG8KGD2XQAugMfGLkyB3oTOXguYfQYgqUT8XRPopjNTHbpsvLb1y9wJ7ynTYtWTVU1e56AsXfMXq50xKBJ0RMMAAAAASUVORK5CYII=')
}
.link-input {
font-family: Courier New;
font-size: 12px;
}
.link-input-container.valid .link-input {
color: green;
}
.link-input-container:not(valid) .link-input {
color: red;
}
.link-note {
font-size: 12px;
}
.link-note.no-input {
color: rgb(193, 162, 0);
}
.link-note.no-input .link-note-content:before {
content: "*Link nije unesen";
}
.link-note.valid {
color: green;
}
.link-note.valid .link-note-content:before {
content: "*Stisni enter da dodas ovaj link";
}
.link-note.invalid {
color: red;
}
.link-note.invalid .link-note-content:before {
content: "*Link nije ispravan";
}
JS:
function toggleClass(elem, class_name, class_on) {
if (class_on) {
elem.classList.add(class_name);
} else {
elem.classList.remove(class_name);
}
}
function switchClass(elem, class_list, on_class) {
for (var i = 0; i < class_list.length; i++) {
elem.classList.remove(class_list[i]);
}
elem.classList.add(on_class);
}
function LinkInput() {
LinkInput = null;
var self = {
container_elem: null,
input_elem: null,
note_elem: null,
init: () => {
var container_elem = self.container_elem = document.querySelector(
'.link-input-container'
);
var input_elem = self.input_elem = document.querySelector(
'.link-input'
);
input_elem.size = 23;
var note_elem = self.note_elem = document.querySelector(
'.link-note'
);
input_elem.addEventListener('input', (ev) => {
var new_val = input_elem.value;
var new_size = new_val.length;
input_elem.size = Math.max(new_size + 2, 23);
if (new_val.length > 5) {
switchClass(note_elem, [
"no-input", "invalid", "valid"
], "valid");
toggleClass(container_elem, "valid", true);
} else {
if (new_val === "") {
switchClass(note_elem, [
"no-input", "invalid", "valid"
], "no-input");
} else {
switchClass(note_elem, [
"no-input", "invalid", "valid"
], "invalid");
}
toggleClass(container_elem, "valid", false);
}
});
input_elem.addEventListener('keyup', (ev) => {
if (ev.keyCode == 13) {
input_elem.value = "";
input_elem.blur();
// Submit
return false;
}
});
},
};
self.init();
return self;
}
var link_input = LinkInput();

It's up to you. I think notifications should be driven by javascript as it's not part of the site at the moment it loads. Im thinking some SEO here, not that the search engines should save notifications like that anyways. Well that's my opinion :)
Cheers!

Related

Why is my eventlistener being removed after an element has been dropped using drag / drop API

Below I have a simplified version of my code where I can click a button to add new elements. These element should all be draggable so they can swap places and they should also have functionality to be deleted when clicking somewhere on them. I have implemented this successfully as you can see by running my snippet ... except for one thing ...
If you try to click on the button area in the middle before swapping it with anything it works just like I want it to.
But if you try to click on it after you have swapped it, it no longer works. Please help me fix this!
const btn_add_element = document.querySelector('.btn_add_element');
const my_draggable_elements = document.querySelector('.my_draggable_elements');
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragEnd(e) {
this.style.opacity = '1'; my_draggable_elements.querySelectorAll('.container').forEach(elm => {
elm.classList.remove('dragged_over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('dragged_over');
}
function handleDragLeave(e) {
this.classList.remove('dragged_over');
}
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
function getRandomColor() { return '#'+Math.floor(Math.random()*16777215).toString(16); }
function add_element() {
// Create Container & Make it draggable
const new_container = document.createElement('div');
new_container.classList.add('container');
new_container.setAttribute('draggable', true);
new_container.addEventListener('dragstart', handleDragStart);
new_container.addEventListener('dragover', handleDragOver);
new_container.addEventListener('dragenter', handleDragEnter);
new_container.addEventListener('dragleave', handleDragLeave);
new_container.addEventListener('dragend', handleDragEnd);
new_container.addEventListener('drop', handleDrop);
// Create Content
const new_content = document.createElement('div');
new_content.classList.add('content');
new_content.style.color = getRandomColor();
new_content.innerText = 'Click to Delete';
new_content.addEventListener('click', () => {
new_container.remove();
});
new_container.appendChild(new_content);
my_draggable_elements.appendChild(new_container);
}
btn_add_element.addEventListener('click', () => add_element());
btn_add_element.click();
btn_add_element.click();
btn_add_element.click();
.my_draggable_elements {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.container {
padding: 2rem;
border: 0.1rem solid black;
cursor: grab;
}
.container.dragged_over {
border: 0.1rem dashed black;
}
.container > .content {
background-color: #ddd;
padding: 0.25rem;
cursor: pointer;
}
.btn_add_element {
margin-top: 2rem;
width: 100%;
text-align: center;
padding: 0.5rem;
}
<div class='my_draggable_elements'></div>
<button class='btn_add_element'>Add Element</button>
In handleDrop() the element get's transfered from event.dataTransfer.getData('text/html') to the innerHTML of the element it is being dropped on. In that, the eventListener is lost. You have to add it again, as demonstrated in the modified snippet below.
const btn_add_element = document.querySelector('.btn_add_element');
const my_draggable_elements = document.querySelector('.my_draggable_elements');
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragEnd(e) {
this.style.opacity = '1'; my_draggable_elements.querySelectorAll('.container').forEach(elm => {
elm.classList.remove('dragged_over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('dragged_over');
}
function handleDragLeave(e) {
this.classList.remove('dragged_over');
}
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
console.log(this.innerHTML);
this.innerHTML = e.dataTransfer.getData('text/html');
this.addEventListener('click', () => {
this.remove();
});
}
return false;
}
function getRandomColor() { return '#'+Math.floor(Math.random()*16777215).toString(16); }
function add_element() {
// Create Container & Make it draggable
const new_container = document.createElement('div');
new_container.classList.add('container');
new_container.setAttribute('draggable', true);
new_container.addEventListener('dragstart', handleDragStart);
new_container.addEventListener('dragover', handleDragOver);
new_container.addEventListener('dragenter', handleDragEnter);
new_container.addEventListener('dragleave', handleDragLeave);
new_container.addEventListener('dragend', handleDragEnd);
new_container.addEventListener('drop', handleDrop);
// Create Content
const new_content = document.createElement('div');
new_content.classList.add('content');
new_content.style.color = getRandomColor();
new_content.innerText = 'Click to Delete';
new_content.addEventListener('click', () => {
new_container.remove();
});
new_container.appendChild(new_content);
my_draggable_elements.appendChild(new_container);
}
btn_add_element.addEventListener('click', () => add_element());
btn_add_element.click();
btn_add_element.click();
btn_add_element.click();
.my_draggable_elements {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.container {
padding: 2rem;
border: 0.1rem solid black;
cursor: grab;
}
.container.dragged_over {
border: 0.1rem dashed black;
}
.container > .content {
background-color: #ddd;
padding: 0.25rem;
cursor: pointer;
}
.btn_add_element {
margin-top: 2rem;
width: 100%;
text-align: center;
padding: 0.5rem;
}
<div class='my_draggable_elements'></div>
<button class='btn_add_element'>Add Element</button>
With the help of #anarchist912, I finally got the desired result.
As he stated in his answer, the eventlistener for removing the element gets lost inside the handleDrop function. To fix this we had to manually add the eventlistener back again.
this.addEventListener('click', () => {
this.remove();
});
However this was apparently not enough.
I accidentally solved this by writing a remove_element() function for my personal use-case and getting it to work, then realising it was not working here when I tested it in the snippet using the arrow function like above. So here are the changes that made it work:
function remove_element(e) {
e.target.closest('.container').remove();
}
// and inside handleDrop()
dragSrcEl.querySelector('.content').addEventListener('click', remove_element);
this.querySelector('.content').addEventListener('click', remove_element);
const btn_add_element = document.querySelector('.btn_add_element');
const my_draggable_elements = document.querySelector('.my_draggable_elements');
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragEnd(e) {
this.style.opacity = '1'; my_draggable_elements.querySelectorAll('.container').forEach(elm => {
elm.classList.remove('dragged_over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('dragged_over');
}
function handleDragLeave(e) {
this.classList.remove('dragged_over');
}
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
dragSrcEl.querySelector('.content').addEventListener('click', remove_element);
this.innerHTML = e.dataTransfer.getData('text/html');
this.querySelector('.content').addEventListener('click', remove_element);
}
return false;
}
function getRandomColor() { return '#'+Math.floor(Math.random()*16777215).toString(16); }
function remove_element(e) {
e.target.closest('.container').remove();
}
function add_element() {
// Create Container & Make it draggable
const new_container = document.createElement('div');
new_container.classList.add('container');
new_container.setAttribute('draggable', true);
new_container.addEventListener('dragstart', handleDragStart);
new_container.addEventListener('dragover', handleDragOver);
new_container.addEventListener('dragenter', handleDragEnter);
new_container.addEventListener('dragleave', handleDragLeave);
new_container.addEventListener('dragend', handleDragEnd);
new_container.addEventListener('drop', handleDrop);
// Create Content
const new_content = document.createElement('div');
new_content.classList.add('content');
new_content.style.color = getRandomColor();
new_content.innerText = 'Click to Delete';
new_content.addEventListener('click', remove_element);
new_container.appendChild(new_content);
my_draggable_elements.appendChild(new_container);
}
btn_add_element.addEventListener('click', () => add_element());
btn_add_element.click();
btn_add_element.click();
btn_add_element.click();
.my_draggable_elements {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.container {
padding: 2rem;
border: 0.1rem solid black;
cursor: grab;
}
.container.dragged_over {
border: 0.1rem dashed black;
}
.container > .content {
background-color: #ddd;
padding: 0.25rem;
cursor: pointer;
}
.btn_add_element {
margin-top: 2rem;
width: 100%;
text-align: center;
padding: 0.5rem;
}
<div class='my_draggable_elements'></div>
<button class='btn_add_element'>Add Element</button>

Iterate HTML classes to create class objects with javascript

class CVP {
constructor(obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
// player
this.player = document.querySelector(P);
// video el
this.video = document.querySelector(V);
// player controller
this.playerController = document.querySelector(PC);
// play controlers
this.bigPlayBtn = document.querySelector(BPB);
this.miniPlayBtn = document.querySelector(MPB);
// volume controllers
this.volumeRangeController = document.querySelector(VRC);
this.volumeBtnController = document.querySelector(VBC);
// fullscreen
this.fullscreen = document.querySelector(FS);
// progress bar
this.progress = document.querySelector(PROG);
this.progressCurr = document.querySelector(PROGC);
// time displayers
this.timeWhole = document.querySelector(TW);
this.timeCurr = document.querySelector(TC);
// bool vars
this.isMousedown = false;
this.isFullScreen = false;
// timer vars
this.timer = '';
this.timerTime = 1000;
}
setIsMouseDown = () => {
this.isMousedown = !this.isMousedown;
};
bigPlayBtnHandler = () => {
this.bigPlayBtn.classList.toggle('HideBigPlayBtn');
};
showHideControlls = () => {
this.player.classList.toggle('paused');
};
static changeIcon(target, remove, add) {
target.classList.remove(remove);
target.classList.add(add);
}
changePlayIcon() {
console.log(this.video.src);
const target = this.miniPlayBtn;
let icon = this.video.paused ? 'play' : 'pause';
if (icon === 'pause') {
CVP.changeIcon(target, 'play', 'pause');
} else {
CVP.changeIcon(target, 'pause', 'play');
}
}
changeVolumeIcon(volElIconID) { // not in use
const target = document.querySelector(volElIconID);
if (this.video.muted) {
CVP.changeIcon(target, 'max', 'mute');
} else {
CVP.changeIcon(target, 'mute', 'max');
}
}
handleVideoTime(time) {
// minutes and seconds So Far
let hours;
let min = Math.floor(time / 60);
let sec = Math.floor(time % 60);
let output = '';
sec = (sec < 10) ? sec = `0${sec}` : sec;
min = (min < 10) ? min = `0${min}` : min;
output = `${min}:${sec}`;
return output;
}
rangeVolumeController() {
cvp.video[this.name] = this.value;
if (cvp.volumeRangeController.value == 0) {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
} else {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
}
}
btnVolumeController() {
if (cvp.video.muted) {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
cvp.volumeRangeController.value = 1;
cvp.video.volume = 1;
} else {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
cvp.volumeRangeController.value = 0;
}
}
hideWhenFullScreen() {
if (this.isFullScreen) {
this.playerController.hidden = false; // or this.playerController.style.bottom = '0px';
document.body.style.cursor = 'auto';
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.playerController.hidden = true; // or this.playerController.style.bottom = '-50px';
document.body.style.cursor = 'none';
}, this.timerTime);
} else {
this.playerController.hidden = false; // or this.playerController.style.bottom = '0px';
document.body.style.cursor = 'auto';
}
}
toggleFullScreen() {
if (this.isFullScreen) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else {
console.error('Unable to find a fullscreen exit method.');
}
} else {
if (this.player.requestFullscreen) {
this.player.requestFullscreen();
} // standard
else if (this.player.webkitRequestFullscreen) {
this.player.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (this.player.mozRequestFullScreen) {
this.player.mozRequestFullScreen();
} else if (this.player.msRequestFullscreen) {
this.player.msRequestFullscreen();
} else {
console.error('Unable to find a fullscreen request method');
}
}
}
toggleFullScreenHelper() {
this.player.classList.toggle('isFullScreen');
this.isFullScreen = !this.isFullScreen;
}
togglePlay() {
const action = this.video.paused ? 'play' : 'pause';
this.video[action]();
this.playerController.style.bottom = '0px';
this.changePlayIcon();
this.showHideControlls();
}
showVideoTime() {
const {
currentTime,
duration
} = cvp.video;
const pct = (currentTime / duration) * 100;
cvp.progressCurr.style.width = pct + '%';
cvp.timeCurr.innerText = cvp.handleVideoTime(currentTime.toFixed(0));
cvp.timeWhole.innerText = cvp.handleVideoTime(duration);
}
scrub(e) {
console.log(e);
console.log('e', e, 'this', this);
let seconds = (e.offsetX / this.progress.offsetWidth) * this.video.duration;
this.video.currentTime = seconds;
}
loadVideo() {
// il metodo è da applciare assieme a un loader e ad altre gestioni degli eventi video
// esempi di loader: https://www.w3schools.com/howto/howto_css_loader.asp
// metodi video: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadeddata_event
// per i tooltips: https://www.w3schools.com/howto/howto_css_tooltip.asp
console.log(this.video.src);
}
init() {
// Hook up the Event Listeners
// toggle Play
this.video.addEventListener('click', this.togglePlay.bind(this));
this.miniPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.bigPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.video.addEventListener('loadeddata', this.loadVideo.bind(this));
// bigPlayBtn show/hide handler
this.video.addEventListener('click', this.bigPlayBtnHandler);
this.bigPlayBtn.addEventListener('click', this.bigPlayBtnHandler);
this.miniPlayBtn.addEventListener('click', this.bigPlayBtnHandler);
// time update
this.video.addEventListener('timeupdate', this.showVideoTime);
// progress bar events
this.progress.addEventListener('click', this.scrub.bind(this));
this.progress.addEventListener('mousemove', (e) => this.isMousedown && this.scrub(e));
this.progress.addEventListener('mousedown', this.setIsMouseDown);
this.progress.addEventListener('mouseup', this.setIsMouseDown);
// fullscreen
this.fullscreen.addEventListener('click', this.toggleFullScreen.bind(this));
// volume controllers
this.volumeBtnController.addEventListener('click', this.btnVolumeController);
this.volumeRangeController.addEventListener('change', this.rangeVolumeController);
this.volumeRangeController.addEventListener('mousemove', this.rangeVolumeController);
// cross-browser fullscreen hanlder
const browsersFullScreen = [
'fullscreenchange',
'mozfullscreenchange',
'webkitfullscreenchange',
'msfullscreenchange',
];
browsersFullScreen.forEach(browser => document.addEventListener(browser, this.toggleFullScreenHelper.bind(this)));
// player show/hide controlls on mousemove event
this.player.addEventListener('mousemove', this.hideWhenFullScreen.bind(this));
}
}
const initializeCVP = {
P: '.CVP',
V: '.video',
PC: '.playerController',
BPB: '.bigPlayBtn',
MPB: '.miniPlayBtn',
VRC: '.volumeRC',
VBC: '.volumeBC',
FS: '.fullscreen',
PROG: '.progress',
PROGC: '.progressCurr',
TW: '.timeWhole',
TC: '.timeCurr',
};
let cvp = new CVP(initializeCVP);
cvp.init();
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
height: 100vh;
background-image: linear-gradient(to top, #dad4ec 0%, #dad4ec 1%, #f3e7e9 100%);
/* background-image: linear-gradient(-20deg, #f794a4 0%, #fdd6bd 100%); */
}
[hidden] {
display: none !important;
}
.player {
display: flex;
justify-content: center;
border-radius: 10px;
width: 700px;
max-height: 100%;
position: relative;
font-size: 16px;
overflow: hidden;
text-align: center;
background: #000;
color: #fff;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.4), 0 10px 20px -3px rgba(0, 0, 0, 0.2);
margin-bottom: 5vh;
}
/* This css is only applied when fullscreen is active. */
.player.isFullScreen {
max-width: none;
max-height: none;
width: 100%;
height: 100%;
background: #000;
}
.player.isFullScreen video {
width: 100%;
height: auto;
}
.video {
max-width: 100%;
max-height: 100%;
-o-object-fit: fill;
object-fit: fill;
/* These are the actual dimensions of the video. Avoid the awkward element resize after the video loads. */
/* width: 640px;
height: 358px; */
}
.playerController {
display: flex;
align-items: center;
justify-content: space-evenly;
width: 98%;
max-width: 1000px;
/* limiting in fullscreen mode */
position: absolute;
background: rgba(51, 51, 51, 0.8);
color: #fff;
border-radius: 10px;
margin-bottom: 1%;
z-index: 2147483649;
transform: translateY(150px);
transition: .2s;
bottom: -50px;
/* 0 to show in preview */
}
.playerController * {
color: rgba(255, 255, 255, 0.9);
}
.playerController button {
background: none;
border: 0;
outline: 0;
text-align: center;
cursor: pointer;
}
.icon {
font-size: 16px;
font-family: 'Font Awesome 5 Free';
font-weight: 900;
}
.player:hover .playerController,
.player.paused .playerController {
transform: translateY(0);
}
.player__btn {
max-width: 50px;
}
.play::before {
content: '\f04b';
}
.pause::before {
content: '\f04c';
}
.progress {
/* position: relative; */
width: 50%;
cursor: pointer;
user-select: none;
height: 10px;
/* or 15px */
background: rgba(160, 154, 154, 0.8);
overflow: hidden;
border-radius: 50px;
}
.progressCurr {
height: 100%;
width: 0;
background: rgba(255, 255, 255, 0.9);
/* flex: 0;
flex-basis: 0%; */
/* old styles yellow progressCurr bg flex-basic insted of width*/
}
.fullscreen::before {
font-family: 'FontAwesome';
content: '\f065';
font-size: 16px
}
.player.isFullScreen .playerController .fullscreen::before {
content: '\f066';
}
.timeCurr,
.timeWhole {
font-size: 14px;
user-select: none;
}
.volumeContainer {
display: flex;
}
.volumeBC {
cursor: pointer;
padding-right: 5px;
}
.volumeRC {
width: 10px;
height: 30px;
}
.max::before {
content: '\f028'
}
.mute::before {
content: '\f6a9'
}
.bigPlayBtn {
font-size: 80px;
position: absolute;
top: 50%;
left: 50%;
cursor: pointer;
box-sizing: border-box;
transform: translate(-50%, -50%);
transition: .5s;
}
.bigPlayBtn::before {
content: '\f04b';
}
.HideBigPlayBtn {
cursor: default;
opacity: 0;
transform: translate(-50%, -50%) scale(1.2);
}
<div class='player paused CVP'>
<video class='video' src='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' width='700' height='350' poster='https://i.ytimg.com/vi/A1sVB8XBNDo/maxresdefault.jpg'>
</video>
<div class='bigPlayBtn icon'></div>
<div class='playerController'>
<button class='player__btn miniPlayBtn icon' title='Play/Pause'></button>
<div class='timeCurr'>00:00</div>
<div class='progress'>
<span id='buffered'>
<div class='progressCurr'></div>
</span>
</div>
<div class='timeWhole'>--:--</div>
<div class='volumeContainer'>
<button class='player__btn max volumeBC icon'></button>
<input type='range' name='volume' class='volume volumeRC' min='0' max='1' step='0.01' value='1' title='Volume' />
</div>
<button class='player__btn fullscreen icon' title='Full Screen'></button>
</div>
</div>
<div class='player paused CVP'>
<video class='video' src='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4' width='700' height='350' poster='https://i.ytimg.com/vi/A1sVB8XBNDo/maxresdefault.jpg'>
</video>
<div class='bigPlayBtn icon'></div>
<div class='playerController'>
<button class='player__btn miniPlayBtn icon' title='Play/Pause'></button>
<div class='timeCurr'>00:00</div>
<div class='progress'>
<span id='buffered'>
<div class='progressCurr'></div>
</span>
</div>
<div class='timeWhole'>--:--</div>
<div class='volumeContainer'>
<button class='player__btn max volumeBC icon'></button>
<input type='range' name='volume' class='volume volumeRC' min='0' max='1' step='0.01' value='1' title='Volume' />
</div>
<button class='player__btn fullscreen icon' title='Full Screen'></button>
</div>
</div>
Sorry if I'm disturbing you for something that will probably appear you very simple, but I can't find a way to solve the problem on my own.
And, maybe, the title is not appropriated...
Well, I have a class with a constructor that works with HTML tag VIDEO, named CVP. It creates methods to act on video controls. The initial method consist in hooking the event listeners to HTML controls'classes.
Anyway, with a single VIDEO tag in the HTML code, it works correctly, but I don't know how to iterate the class on any VIDEO when there are many of them. This is an examples of the VIDEO tag:
<div class='player paused CVP'>
<video class='video' src="../../video/pietro.mp4">
</video>
etc...
This is the beginnig of the JS CLASS:
class CVP {
constructor (obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
this.player = document.querySelector(P);
this.video = document.querySelector(V);
etc..
The class ends this way:
(many other methods...)
init () {
// Hook up the Event Listeners
// toggle Play
this.video.addEventListener('click', this.togglePlay.bind(this));
this.miniPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.bigPlayBtn.addEventListener('click', this.togglePlay.bind(this));
etc...
}
}
Then, there's a inizialization's variable:
const initializeCVP = {
P: '.CVP',
V: '.video',
PC: '.playerController',
etc...
};
To add a single VIDEO tag:
const cvp = new CVP(initializeCVP);
cvp.init();
In this case it works perfectly. But, How can I assign the JS class to many VIDEO tags?
I tried this way, but it doesn't work:
window.onload = function () {
const CVPs = document.querySelectorAll('.CVP');
CVPs.forEach((cvp) => {
cvp = new CVP(initializeCVP);
cvp.init();
});
};
Is it clear? I hope so. I'm quite a beginner, so plase, don't complain...
Change your constructor so it takes elements rather than selectors. The caller can call document.querySelector() or document.querySelectorAll() when creating the initialization object.
class CVP {
constructor (obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
this.player = P;
this.video = V;
etc..
}
}
window.onload = function() {
const players = document.querySelectorAll('.player');
players.forEach(player => {
const initializeCVP = {
P: player,
V: player.querySelector('.video'),
PC: player.querySelector('.playerController'),
BPB: player.querySelector('.bigPlayBtn'),
MPB: player.querySelector('.miniPlayBtn'),
VRC: player.querySelector('.volumeRC'),
VBC: player.querySelector('.volumeBC'),
FS: player.querySelector('.fullscreen'),
PROG: player.querySelector('.progress'),
PROGC: player.querySelector('.progressCurr'),
TW: player.querySelector('.timeWhole'),
TC: player.querySelector('.timeCurr'),
};
const cvp = new CVP(initializeCVP);
cvp.init();
});
};
I found a way to fix what #Barman and #RandyCasburn pointed out regarding the single 'cvp' istance connected to class methods.
First, I applied #Barman suggestion, so to iterate all players has in the previous answer.
Then, I put a global array to collect the single 'cvp', with a couple of other new variables:
let cvpItems = [];
let cvpItem;
let currentCVP;
Then, I pushed all player istances in the HTML, applying to anyone an addEventListener. The window.onload code is changed in this way:
window.onload = function () {
const players = document.querySelectorAll('.player');
players.forEach(player => {
const initializeCVP = {
P: player,
V: player.querySelector('.video'),
PC: player.querySelector('.playerController'),
BPB: player.querySelector('.bigPlayBtn'),
MPB: player.querySelector('.miniPlayBtn'),
VRC: player.querySelector('.volumeRC'),
VBC: player.querySelector('.volumeBC'),
FS: player.querySelector('.fullscreen'),
PROG: player.querySelector('.progress'),
PROGC: player.querySelector('.progressCurr'),
TW: player.querySelector('.timeWhole'),
TC: player.querySelector('.timeCurr'),
};
cvpItem = new CVP(initializeCVP);
cvpItems.push(cvpItem);
cvpItem.init();
player.addEventListener('click', currentItem, false);
});
};
With the currentItem function I find the current player iteration using the title attribute in the HTML video tags:
let currentItem = function () {
for (var i = 0; i < cvpItems.length; i++) {
if (cvpItems[i].video.title == this.querySelectorAll('.video')[0].title) {
currentCVP = i;
break;
}
}
};
So, now I can identify the specific player in the JS class. For examples, the previous rangeVolumeController method changes from:
rangeVolumeController () {
cvp.video[this.name] = this.value;
if (cvp.volumeRangeController.value == 0) {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
} else {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
}
}
to
rangeVolumeController () {
console.log(CVP, this, event.target);
cvpItems[currentCVP].video[this.name] = this.value;
if (cvpItems[currentCVP].volumeRangeController.value == 0) {
cvpItems[currentCVP].video.muted = true;
CVP.changeIcon(cvpItems[currentCVP].volumeBtnController, 'max', 'mute');
} else {
cvpItems[currentCVP].video.muted = false;
CVP.changeIcon(cvpItems[currentCVP].volumeBtnController, 'mute', 'max');
}
}
It works! But, I know, it's not an elegant way for coding. If someone wants to propose a better or more "elegant" solution, I'll be grateful.

create simple toast notifications and chain them in a row from top to bottom [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I am trying to create toast notifications, for the most part, my code is working fine enough. However, I am struggling to position my toasts on top of everything else in the page, so that toast #1 appears on top of everything in the top of the page, toast #2 appears on top of everything, but will be stacked under toast #1, etc.
Here is my code:
$(document).ready(function() {
$("#btnSuccess").click(function() {
createSuccessToast("everything is fine");
});
$("#btnError").click(function() {
createErrorToast("something went wrong");
});
});
function createSuccessToast(toastMessage) {
createToast(true, toastMessage);
}
function createErrorToast(toastMessage) {
createToast(false, toastMessage);
}
function createToast(isSuccess, toastMessage) {
var toastContainer = createToastContainer(isSuccess);
createToastHeader(toastContainer, isSuccess);
createToastContent(toastContainer, toastMessage);
initToast(toastContainer);
destroyToast(toastContainer);
}
function createToastContainer(isSuccess) {
var toastContainer = $("<div></div>");
toastContainer.addClass("toastContainer");
if (isSuccess) {
toastContainer.addClass("toastContainerSuccess");
} else {
toastContainer.addClass("toastContainerError");
}
return toastContainer;
}
function createToastHeader(toastContainer, isSuccess) {
var toastHeader = $("<div></div>");
toastHeader.addClass("toastHeader");
toastHeader.html(isSuccess ? "Success" : "Error");
toastContainer.append(toastHeader);
}
function createToastContent(toastContainer, toastMessage) {
var toastContent = $("<div></div>");
toastContent.addClass("toastContent");
toastContent.html(toastMessage);
toastContainer.append(toastContent);
}
function initToast(toastContainer) {
toastContainer.hide(function() {
$(document.body).append(toastContainer);
toastContainer.fadeIn(500);
});
}
function destroyToast(toastContainer) {
setTimeout(function() {
toastContainer.fadeOut(500, function() {
toastContainer.remove();
});
}, 5000);
}
.toastContainer {
margin-top: 10px;
border-radius: 5px;
font-weight: bold;
padding: 10px;
color: #ffffff;
}
.toastContainerSuccess {
background-color: #99ff33;
}
.toastContainerError {
background-color: #ff1a1a;
}
.toastHeader {}
.toastContent {
margin-top: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="btnSuccess">
Success
</button>
<button id="btnError">
Error
</button>
To achieve my desired results, I added 2 CSS properties to .toastContainer:
position: absolute;
z-index: 1;
But then, all toasts would spawn at the exact same location on the page, so if there were multiple toasts, only the most recent would be visible.
You should append the toasts to a container element which is position: absolute; z-index: 1 instead (because if multiple elements are position: absolute, and they have the same top/left positions, they WILL appear on top of another, see this):
$(document).ready(function() {
$("#btnSuccess").click(function() {
createSuccessToast("everything is fine");
});
$("#btnError").click(function() {
createErrorToast("something went wrong");
});
});
function createSuccessToast(toastMessage) {
createToast(true, toastMessage);
}
function createErrorToast(toastMessage) {
createToast(false, toastMessage);
}
function createToast(isSuccess, toastMessage) {
var toastContainer = createToastContainer(isSuccess);
createToastHeader(toastContainer, isSuccess);
createToastContent(toastContainer, toastMessage);
initToast(toastContainer);
destroyToast(toastContainer);
}
function createToastContainer(isSuccess) {
var toastContainer = $("<div></div>");
toastContainer.addClass("toastContainer");
if (isSuccess) {
toastContainer.addClass("toastContainerSuccess");
} else {
toastContainer.addClass("toastContainerError");
}
return toastContainer;
}
function createToastHeader(toastContainer, isSuccess) {
var toastHeader = $("<div></div>");
toastHeader.addClass("toastHeader");
toastHeader.html(isSuccess ? "Success" : "Error");
toastContainer.append(toastHeader);
}
function createToastContent(toastContainer, toastMessage) {
var toastContent = $("<div></div>");
toastContent.addClass("toastContent");
toastContent.html(toastMessage);
toastContainer.append(toastContent);
}
function initToast(toastContainer) {
toastContainer.hide(function() {
$("#toastsContainer").append(toastContainer);
toastContainer.fadeIn(500);
});
}
function destroyToast(toastContainer) {
setTimeout(function() {
toastContainer.fadeOut(500, function() {
toastContainer.remove();
});
}, 5000);
}
#toastsContainer {
position: absolute;
top: 0;
z-index: 1;
width: 100%;
}
.toastContainer {
margin-top: 10px;
border-radius: 5px;
font-weight: bold;
padding: 10px;
color: #ffffff;
position: relative;
z-index: 1;
}
.toastContainerSuccess {
background-color: #99ff33;
}
.toastContainerError {
background-color: #ff1a1a;
}
.toastHeader {}
.toastContent {
margin-top: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="btnSuccess">
Success
</button>
<button id="btnError">
Error
</button>
<div id="toastsContainer"></div>
Please try the below code, I have used the global variable pos, and accordingly set the position of the buttons dynamically after the button click, and after the fade out the buttons will move back to the previous position again.
var pos = 80;
$(document).ready(function() {
$("#btnSuccess").click(function() {
document.getElementById("btnSuccess").style.top= ""+pos+"px";
document.getElementById("btnError").style.top= ""+pos+"px";
pos = pos + 80;
createSuccessToast("everything is fine");
});
$("#btnError").click(function() {
document.getElementById("btnSuccess").style.top= ""+pos+"px";
document.getElementById("btnError").style.top= ""+pos+"px";
pos = pos + 80;
createErrorToast("something went wrong");
});
});
function createSuccessToast(toastMessage) {
createToast(true, toastMessage);
}
function createErrorToast(toastMessage) {
createToast(false, toastMessage);
}
function createToast(isSuccess, toastMessage) {
var toastContainer = createToastContainer(isSuccess);
createToastHeader(toastContainer, isSuccess);
createToastContent(toastContainer, toastMessage);
initToast(toastContainer);
destroyToast(toastContainer);
}
function createToastContainer(isSuccess) {
var toastContainer = $("<div></div>");
toastContainer.addClass("toastContainer");
if (isSuccess) {
toastContainer.addClass("toastContainerSuccess");
} else {
toastContainer.addClass("toastContainerError");
}
return toastContainer;
}
function createToastHeader(toastContainer, isSuccess) {
var toastHeader = $("<div></div>");
toastHeader.addClass("toastHeader");
toastHeader.html(isSuccess ? "Success" : "Error");
toastContainer.append(toastHeader);
}
function createToastContent(toastContainer, toastMessage) {
var toastContent = $("<div></div>");
toastContent.addClass("toastContent");
toastContent.html(toastMessage);
toastContainer.append(toastContent);
}
function initToast(toastContainer) {
toastContainer.hide(function() {
$(document.body).append(toastContainer);
toastContainer.fadeIn(500);
});
}
function destroyToast(toastContainer) {
setTimeout(function() {
pos = pos - 160;
toastContainer.fadeOut(500, function() {
document.getElementById("btnSuccess").style.top= ""+pos+"px";
document.getElementById("btnError").style.top= ""+pos+"px";
toastContainer.remove();
pos = pos + 80;
});
}, 5000);
}
.toastContainer {
bottom:20px;
position:relative;
z-index:1;
margin-top: 10px;
border-radius: 5px;
font-weight: bold;
padding: 10px;
color: #ffffff;
}
#btnSuccess{
position:relative;
}
#btnError{
position:relative;
}
.toastContainerSuccess {
background-color: #99ff33;
}
.toastContainerError {
background-color: #ff1a1a;
}
.toastHeader {}
.toastContent {
margin-top: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="btnSuccess">
Success
</button>
<button id="btnError">
Error
</button>

jQuery: Turn button filters into select menu options?

I'm using Lever's job listing API, but in its current form, it uses buttons to filter the jobs by category. How would I be able to turn the buttons into a complete select menu?
In the Fiddle, the options are "Business Dev", "Customer Success", so those would be examples of options in the select menu I want to create.
Fiddle.
HTML:
<section>
<div class="container" id="jobs-container">
<h1>Open jobs</h1>
<div class="jobs-teams">
</div>
<div class="jobs-list">
</div>
</div>
</section>
JS:
url = 'https://api.lever.co/v0/postings/leverdemo?group=team&mode=json'
//Functions for checking if the variable is unspecified
function cleanString(string) {
if (string) {
var cleanString = string.replace(/\s+/ig, "");
return cleanString;
}
else {
return "Uncategorized";
}
}
function nullCheck(string) {
if (!string) {
var result = 'Uncategorized'
return result;
}
else {
return string;
}
}
function createJobs(_data) {
for(i = 0; i < _data.length; i++) {
var team = nullCheck(_data[i].title)
var teamCleanString = cleanString(team);
$('#jobs-container .jobs-teams').append(
''+team+''
);
}
for(i = 0; i < _data.length; i++) {
for (j = 0; j < _data[i].postings.length; j ++) {
var posting = _data[i].postings[j]
var title = posting.text
var description = posting.description
//Making each job description shorter than 250 characters
var shortDescription = $.trim(description).substring(0, 250)
.replace('\n', ' ') + "...";
var location = nullCheck(posting.categories.location);
var locationCleanString = cleanString(location);
var commitment = nullCheck(posting.categories.commitment);
var commitmentCleanString = cleanString(commitment);
var team = nullCheck(posting.categories.team);
var teamCleanString = cleanString(team);
var link = posting.hostedUrl;
$('#jobs-container .jobs-list').append(
'<div class="job '+teamCleanString+' '+locationCleanString+' '+commitmentCleanString+'">' +
'<a class="job-title" href="'+link+'"">'+title+'</a>' +
'<p class="tags"><span>'+team+'</span><span>'+location+'</span><span>'+commitment+'</span></p>' +
'<p class="description">'+shortDescription+'</p>' +
'<a class="btn" href="'+link+'">Learn more</a>' +
'</div>'
);
}
}
}
//Creating filter buttons for sorting your jobs
function activateButtons(_data){
$('.jobs-teams').on("click", "a", function(e) {
e.preventDefault();
for(i = 0; i < _data.length; i++) {
var teamRaw = _data[i].title;
var team = cleanString(teamRaw);
var jobs = $(".jobs-list");if ($(this).hasClass(team)) {
if ($(this).hasClass("active")) {
$(this).removeClass("active");
jobs.find(".job").fadeIn("fast");
}
else {
$(".jobs-teams").find("a").removeClass("active");
$(this).addClass("active");
jobs.find("."+team).fadeIn("fast");
jobs.find(".job").not("."+team).fadeOut("fast");
}
}
}
})
}
//Fetching job postings from Lever's postings API
$.ajax({
dataType: "json",
url: url,
success: function(data){
createJobs(data);
activateButtons(data);
}
});
Updates I made are the following.
Added select tag on jobs-team
On the createJob()
//get select element on the element with jobs-team css class
$('#jobs-container .jobs-teams select').append(
//append option.
'<option value="" class=' + teamCleanString + '>' + team + '</option>'
);}
On the activateButtons()
//get selected option.
if($(this).find(":selected").hasClass(team))
// Replace "leverdemo" with your own company name
url = 'https://api.lever.co/v0/postings/leverdemo?group=team&mode=json'
//Functions for checking if the variable is unspecified
function cleanString(string) {
if (string) {
var cleanString = string.replace(/\s+/ig, "");
return cleanString;
}
else {
return "Uncategorized";
}
}
function nullCheck(string) {
if (!string) {
var result = 'Uncategorized'
return result;
}
else {
return string;
}
}
function createJobs(_data) {
for(i = 0; i < _data.length; i++) {
var team = nullCheck(_data[i].title)
var teamCleanString = cleanString(team);
$('#jobs-container .jobs-teams select').append(
'<option value="" class=' + teamCleanString + '>' + team + '</option>'
);
}
//''++''
for(i = 0; i < _data.length; i++) {
for (j = 0; j < _data[i].postings.length; j ++) {
var posting = _data[i].postings[j]
var title = posting.text
var description = posting.description
//Making each job description shorter than 250 characters
var shortDescription = $.trim(description).substring(0, 250)
.replace('\n', ' ') + "...";
var location = nullCheck(posting.categories.location);
var locationCleanString = cleanString(location);
var commitment = nullCheck(posting.categories.commitment);
var commitmentCleanString = cleanString(commitment);
var team = nullCheck(posting.categories.team);
var teamCleanString = cleanString(team);
var link = posting.hostedUrl;
$('#jobs-container .jobs-list').append(
'<div class="job '+teamCleanString+' '+locationCleanString+' '+commitmentCleanString+'">' +
'<a class="job-title" href="'+link+'"">'+title+'</a>' +
'<p class="tags"><span>'+team+'</span><span>'+location+'</span><span>'+commitment+'</span></p>' +
'<p class="description">'+shortDescription+'</p>' +
'<a class="btn" href="'+link+'">Learn more</a>' +
'</div>'
);
}
}
}
//Creating filter buttons for sorting your jobs
function activateButtons(_data){
$('.jobs-teams select').on("change", function(e) {
e.preventDefault();
for(i = 0; i < _data.length; i++) {
var teamRaw = _data[i].title;
var team = cleanString(teamRaw);
var jobs = $(".jobs-list");
if ($(this).find(":selected").hasClass(team)) {
if ($(this).hasClass("active")) {
$(this).removeClass("active");
jobs.find(".job").fadeIn("fast");
}
else {
$(".jobs-teams").find("a").removeClass("active");
$(this).addClass("active");
jobs.find("."+team).fadeIn("fast");
jobs.find(".job").not("."+team).fadeOut("fast");
}
}
}
})
}
//Fetching job postings from Lever's postings API
$.ajax({
dataType: "json",
url: url,
success: function(data){
createJobs(data);
activateButtons(data);
}
});
body {
font-family: 'Lato', sans-serif;
overflow-y: scroll;
}
p {
margin: 0 0 1em 0;
line-height: 1.4em;
}
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
section {
position: relative;
padding: 30px;
}
.container {
max-width: 960px;
margin: 0 auto;
}
.job {
display: inline-block;
vertical-align: top;
width: 50%;
padding: 40px 30px;
}
h1 {
font-size: 48px;
color: #454545;
padding: 0 30px;
}
.job-title {
font-size: 24px;
text-decoration: none;
color: #454545;
}
.job-title:hover {
color: #00A0DF;
}
.tags span {
color: #999;
font-size: 12px;
color: grayMediumDark;
}
.tags span:after {
content: ', ';
}
.tags span:last-of-type:after {
content: '';
}
.description {
color: #999;
}
.btn {
display: inline-block;
padding: 7px 15px;
text-decoration: none;
font-weight: normal;
color: #999;
border: 2px solid #ebebeb;
-webkit-border-radius: 4px;
border-radius: 4px;
background: #f9f9f9;
}
.btn:hover {
background: #ebebeb;
color: #555;
}
.btn.active {
background: #454545;
border-color: #454545;
color: #fff;
}
.jobs-teams {
margin-bottom: 40px;
padding: 0 30px
}
.jobs-teams .btn {
margin: 0 8px 8px 0;
}
.jobs-teams .btn:first-of-type {
margin-left: 0;
}
.jobs-teams .btn:last-of-type {
margin-right: 0;
}
<section>
<div class="container" id="jobs-container">
<h1>Open jobs</h1>
<div class="jobs-teams">
<select></select>
</div>
<div class="jobs-list">
</div>
</div>
</section>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
I see you got your answer, but figured I'd throw my 2 cents in and show you how you can, not only consolidate this code (¡A LOT!) but also make a lot better use of some jQuery methods you may not be aware of. No criticism intended, simply hope to show you another way that may help you work with jQuery better in the future.
/** debug()
* Simply for writing console messages as needed for testing.
*/
function debug() {
try {
window['console'] && (console.log(Array(100).join("=")), console.log.apply(console, arguments), console.log(Array(100).join("=")));
} catch (a) {}
};
/** ajaxSuccess(data, status, xhr)
* Easily seperated, and therefor easy to read/write method to use for success callback
*/
function ajaxSuccess(data, status, xhr) {
debug("Success:\t", $([data, status, xhr]));
var sorted = sortData(data);
debug("Sorted Data:\t", sorted);
prepHTMLContainers(); // ensure are HTML space is ready
$.each(sorted, function(title, postings) {
debug("Postings for ["+title+"]:\t", postings);
// add select option for each category
$('<option />', { text: postings[0].category, value: title }).appendTo('#jobsTeams select');
$.each(postings, function(i, post) {
// notice here, i do away with all that silly string building
// jQuery has a lot of nice options for building HTML,
// below is simply one of many ways
var container = $('<div />').addClass('job').data('desc', { short: post.shortDescription, full: post.description }),
aTitle = $('<a />', { href: post.url, text: post.title }).addClass('job-title').appendTo(container),
pLocCom = $('<p />').addClass('tags').appendTo(container),
pDesc = $('<p />', { text: post.shortDescription }).addClass('description').appendTo(container),
aMore = $('<a />', { "class": 'btn', href: post.url, text: 'Learn more' }).appendTo(container)
pLocCom
.append($('<span />', { text: post.location }))
.append($('<span />', { text: post.commitment }))
container
.addClass(post.teamClean.toLowerCase())
.addClass(post.locationClean.toLowerCase())
.addClass(post.commitmentClean.toLowerCase())
$('#jobsList').append(container);
});
});
$('#jobsTeams select').show();
}
/** prepHTMLContainers()
* As stated, clean and clear HTML containers and ready them for new listings of data.
*/
function prepHTMLContainers() { $('#jobsTeams select, #jobsList').empty(); $('#jobsTeams select').hide().append($('<option />', { text: ' - Select a Category - ', value: '' })); }
/** sortData(data)
* Sort data in a manner that makes creating the HTML easier
*/
function sortData(data) {
var ret = {};
$.each(data, function(index, item) {
var catClass = trimString(item.title, true).toLowerCase();
if (!ret[catClass]) ret[catClass] = [];
$.each(item.postings, function(i, post) {
ret[catClass].push({
category: trimString(item.title),
title: trimString(post.text),
description: $(trimString(post.description)),
shortDescription: $(trimString(post.description)).text().substring(0, 250).replace('\n', ' ') + '...',
"location": trimString(post.categories.location),
locationClean: trimString(post.categories.location, true),
commitment: trimString(post.categories.commitment),
commitmentClean: trimString(post.categories.commitment, true),
team: trimString(post.categories.team),
teamClean: trimString(post.categories.team, true),
url: post.hostedUrl
});
});
})
return ret;
}
/** trimString(str, cln)
* Simple and clean cut way to trim each string. Second param provides way to remove space if desired.
*/
function trimString(str, cln) { return !str ? 'Uncategorized' : !cln ? $.trim(str) : $.trim(str).replace(/\s+/ig, ""); }
// The first variable, "myAjax", will be used to ensure there's only ever one call to our ajax'd link
// The second is simply the options we'll use in it
var myAjax, myAjaxOpts = {
data: "group=team&mode=json",
dataType: "json",
url: "https://api.lever.co/v0/postings/leverdemo",
beforeSend: prepHTMLContainers,
success: ajaxSuccess
};
// jQuery shorthand for document.ready
$(function() {
$('#fetchData').on('click', function(e) {
// by doing it like this (there's many ways to go about this,
// you don't have to use a button), we ensure that the
// connection is broken before trying to retrieve the same
// data over and over
if (myAjax) myAjax.abort();
myAjax = $.ajax(myAjaxOpts);
/** my general use of this doesn't rely on a global variable, but rather a local one
* i usually just assign it to the element object itself, such as:
*
* if (this.ajx) this.ajx.abort();
* this.ajx = $.ajax(ajaxOpts);
*
* */
});
// see how quick and easy jQuery can make things?!
// the following line uses a "static parent" to assign an event to a "child"
// by doing it this way, the child can then be "dynamic" (loaded after
// page and js has been loaded) and still use the same event without
// need to "re-asign"
$('#jobsTeams').on('change', 'select', function(e) {
if (!this.value) $(this).children('option:eq(0)').text(' - Select a Category - '), $('#jobsList .job').fadeIn();
else {
var val = this.value;
$(this).children('option:eq(0)').text(' - See All - ');
$('#jobsList .job').fadeOut().filter(function(i) { return $(this).hasClass(val); }).fadeIn();
}
});
// allows us to now toggle full descript
// using .on to a static parent makes this work for all dynamically created children
$('#jobsList').on('click', '.job', function(e) {
var descShort = $(this).data('desc').short,
descFull = $(this).data('desc').full,
pDesc = $(this).find('.description');
pDesc.html() == descShort ? pDesc.html(descFull) : pDesc.html(descShort);
});
// I moved the initial first call to here, as it appears
// the connection to OP's site has slowed up since first
// posting this answer.
// JS is single threaded, but by using
// timers we can "temporarily emulate" multithreading and
// keep the browser from locking up on one piece of code
setTimeout(function() { $('#fetchData').trigger('click'); }, 10);
})
/* Expected data return
[
{
postings: [
additional: String,
applyUrl: String,
categories: {
commitment: Sting,
location: Stirng,
team: String
},
createdAt: timestamp,
description: String(HTML),
descriptionPlain: Sting,
hostedUrl: String,
id: String,
list: [
{
content: Sting(HTML),
text: String
}
],
text: String
],
title: String
}
]
*/
body { font-family: 'Lato', sans-serif; overflow-y: scroll; }
p { margin: 0 0 1em 0; line-height: 1.4em; }
* { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
section { position: relative; padding: 30px; }
.container { max-width: 960px; margin: 0 auto; }
.job { display: inline-block; vertical-align: top; width: 50%; padding: 40px 30px; }
h1 { font-size: 48px; color: #454545; padding: 0 30px; }
.job-title { font-size: 24px; text-decoration: none; color: #454545; }
.job-title:hover { color: #00A0DF; }
.tags span { color: #999; font-size: 12px; color: grayMediumDark; }
.tags span:after { content: ', '; }
.tags span:last-of-type:after { content: ''; }
.description { color: #999; cursor: pointer; }
.btn { display: inline-block; padding: 7px 15px; text-decoration: none; font-weight: normal; color: #999; border: 2px solid #ebebeb; -webkit-border-radius: 4px; border-radius: 4px; background: #f9f9f9; }
.btn:hover { background: #ebebeb; color: #555; }
.btn.active { background: #454545; border-color: #454545; color: #fff; }
.jobs-teams { margin-bottom: 40px; padding: 0 30px; }
.jobs-teams select { display: none; }
.jobs-teams .btn { margin: 0 8px 8px 0; }
.jobs-teams .btn:first-of-type { margin-left: 0; }
.jobs-teams .btn:last-of-type { margin-right: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<button id="fetchData">Fetch Data</button>
<section>
<div id="jobsContainer" class="container">
<h1>Open jobs</h1>
<div id="jobsTeams" class="jobs-teams"><select></select></div>
<div id="jobsList" class="jobs-list"></div>
</div>
</section>
Enjoy!

Links in Top-Fixed Navigation Bar Not Responding

I am using a little Javascript navigation bar for my single-page site. All of my text links work just fine, but the outbound social media links on the right side are not responding (unless you secondary-click and open it from there). Now, I am just barely knowledgable in JQuery and Javascript...I can understand it and how it works but when it comes to errors I can't figure it out. Thank you for helping! :)
Here is my CSS:
.single-page-nav {
background: rgb(255, 255, 255);
background: rgba(255, 255, 255, .9);
padding: 1.25em 0;
box-shadow: 0px 2px 8px #555;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index:100000;
}
.single-page-nav ul {
list-style: none;
padding: 0;
margin: 0 auto;
margin-left: -30px;
width: 80%;
overflow: hidden;
}
.single-page-nav li {
float: left;
width: 16%;
text-align: center;
}
.single-page-nav a {
display: block;
color: #000;
font-family: 'Calibri', Helvetica, Sans-Serif;
text-decoration: none;
font-size: 18px;
font-weight: bold;
line-height:1.5em;
}
.single-page-nav a:hover,
.single-page-nav .current {
color: #F92F2C;
}
Here is my HTML
<nav id="menu" role="navigation">
<div class="single-page-nav">
<ul>
<li>Page Top</li>
<li>Watch the Video</li>
<li>Kickstarter</li>
<li>About the Project</li>
<li>Meet the Team</li>
<li>What Are We Doing?</li>
</ul>
<span id="socialtop1">
<img src="/wp-content/images/emailg.png" alt="Email" />
</span>
<span id="socialtop2">
<img src="/wp-content/images/ytg.png" alt="YouTube" />
</span>
<span id="socialtop3">
<img src="/wp-content/images/vmg.png" alt="Vimeo" />
</span>
<span id="socialtop4">
<img src="/wp-content/images/instag.png" alt="Instagram" />
</span>
<span id="socialtop5">
<img src="/wp-content/images/twg.png" alt="Twitter" />
</span>
<span id="socialtop6">
<img src="/wp-content/images/fbg.png" alt="Facebook" />
</span>
</div>
</nav>
And, last but not least, here is the JQuery/Javascript. I didn't write most of it, it's from a tutorial I used.
// Utility
if (typeof Object.create !== 'function') {
Object.create = function(obj) {
function F() {}
F.prototype = obj;
return new F();
};
}
(function($, window, document, undefined) {
"use strict";
var SinglePageNav = {
init: function(options, container) {
this.options = $.extend({}, $.fn.singlePageNav.defaults, options);
this.container = container;
this.$container = $(container);
this.$links = this.$container.find('a');
if (this.options.filter !== '') {
this.$links = this.$links.filter(this.options.filter);
}
this.$window = $(window);
this.$htmlbody = $('html, body');
this.$links.on('click.singlePageNav', $.proxy(this.handleClick, this));
this.didScroll = false;
this.checkPosition();
this.setTimer();
},
handleClick: function(e) {
var self = this,
link = e.currentTarget,
$elem = $(link.hash);
e.preventDefault();
if ($elem.length) { // Make sure the target elem exists
// Prevent active link from cycling during the scroll
self.clearTimer();
// Before scrolling starts
if (typeof self.options.beforeStart === 'function') {
self.options.beforeStart();
}
self.setActiveLink(link.hash);
self.scrollTo($elem, function() {
if (self.options.updateHash) {
document.location.hash = link.hash;
}
self.setTimer();
// After scrolling ends
if (typeof self.options.onComplete === 'function') {
self.options.onComplete();
}
});
}
},
scrollTo: function($elem, callback) {
var self = this;
var target = self.getCoords($elem).top;
var called = false;
self.$htmlbody.stop().animate(
{scrollTop: target},
{
duration: self.options.speed,
complete: function() {
if (typeof callback === 'function' && !called) {
callback();
}
called = true;
}
}
);
},
setTimer: function() {
var self = this;
self.$window.on('scroll.singlePageNav', function() {
self.didScroll = true;
});
self.timer = setInterval(function() {
if (self.didScroll) {
self.didScroll = false;
self.checkPosition();
}
}, 250);
},
clearTimer: function() {
clearInterval(this.timer);
this.$window.off('scroll.singlePageNav');
this.didScroll = false;
},
// Check the scroll position and set the active section
checkPosition: function() {
var scrollPos = this.$window.scrollTop();
var currentSection = this.getCurrentSection(scrollPos);
this.setActiveLink(currentSection);
},
getCoords: function($elem) {
return {
top: Math.round($elem.offset().top) - this.options.offset
};
},
setActiveLink: function(href) {
var $activeLink = this.$container.find("a[href='" + href + "']");
if (!$activeLink.hasClass(this.options.currentClass)) {
this.$links.removeClass(this.options.currentClass);
$activeLink.addClass(this.options.currentClass);
}
},
getCurrentSection: function(scrollPos) {
var i, hash, coords, section;
for (i = 0; i < this.$links.length; i++) {
hash = this.$links[i].hash;
if ($(hash).length) {
coords = this.getCoords($(hash));
if (scrollPos >= coords.top - this.options.threshold) {
section = hash;
}
}
}
// The current section or the first link
return section || this.$links[0].hash;
}
};
$.fn.singlePageNav = function(options) {
return this.each(function() {
var singlePageNav = Object.create(SinglePageNav);
singlePageNav.init(options, this);
});
};
$.fn.singlePageNav.defaults = {
offset: 0,
threshold: 120,
speed: 400,
currentClass: 'current',
updateHash: false,
filter: '',
onComplete: false,
beforeStart: false
};
})(jQuery, window, document);
The problem is that your javascript drastically changes the default link behavior inside of the single-page-nav container. It is applying this javascript functionality to your external links as well, which you do NOT want to have in order for them to work properly.
This bit e.preventDefault(); in your javascript prevents the browser from handling the click event normally.
To fix your problem I would apply the class single-page-nav to the ul (since your social links are outside of this ul) and tweak your CSS a bit.
Personally, I would change the CSS something like so:
.topnav { ... }
.single-page-nav { ... }
.single-page-nav li { ... }
.topnav a { ... }
.topnav a:hover, .topnav .current { ... }

Categories

Resources