How can I solve this circular dependency? (JavaScript) - javascript

I have the next problem: I had been creating a webpage for search GIFs using GIPHY API, everything was going OK until I had to implement a functionality to add GIFs to favorites, the favorites are stored in local storage and they are shown in a separate HTML file using CSS grid. I can add and delete favorites without problems outside /favorites.html, and I can also add favorites from trending GIFs inside this url, the problem is that when I need to remove a favorite and I am on /favorites.html I want the grid to be created from zero and therefore the user will see the gif disappear.
For that function I've created a GIFCard class which allow me to create all the HTML elements for a card and add all the classes they need to look fine using css styles, also this class handles the events so it automatically adds event listeners to all the buttons. I have a file called favoritesPage.js which defines a function called showAllUntilOffset() this function is used whenever the user press the favorite button for a GIF Card and is seeing favorites its work is to create again the GIF grid so it will be updated but I also need to import the GIFCard class in favoritesPage.js to create the Card object, add the html classes and add the event listener.
Here is an image of my webpage working correctly, it just stops working when I import "showAllUntilOffset" from favoritesPage.js because of the circular dependency (I guess).
Next is the code for my favoritesPage.js file and the GIFCard class, constructor and other methods that are not relevant to the problem are not shown here.
favoritesPage.js:
import { GIFCard as Card } from "../entities/GIFCard.js";
import { favoriteGIFs } from "../favorites.js";
import {
favoritesGrid,
favoritesEmpty,
favoritesShow,
seeMoreFavorites,
} from "../dom/favoritesSelectors.js";
import { displayComponent, hideComponent } from "../utils/utils.js";
function createAndAddFavGIFCards(start, end) {
for (let i = start; i < end; i += 1) {
const newCard = new Card(
favoriteGIFs[i].id,
favoriteGIFs[i].title,
favoriteGIFs[i].username,
favoriteGIFs[i].url,
favoriteGIFs[i].fixedHeightURL,
i,
"Favorite GIF"
);
if (window.innerWidth >= 1280) {
favoritesGrid.append(newCard.createCardDesktop());
} else {
favoritesGrid.append(newCard.createCardMobile());
}
}
}
function loadAndShowFavorites() {
favoritesGrid.innerHTML = "";
if (favoriteGIFs.length === 0) {
displayComponent(favoritesEmpty);
} else {
displayComponent(favoritesShow);
let limit = favoriteGIFs.length >= 12 ? 12 : favoriteGIFs.length;
if (favoriteGIFs.length > 12) {
displayComponent(seeMoreFavorites);
}
createAndAddFavGIFCards(0, limit);
}
}
export function showAllUntilOffset() {
favoritesGrid.innerHTML = "";
if (favoriteGIFs.length === 0) {
displayComponent(favoritesEmpty);
hideComponent(favoritesShow);
}
if (favoriteGIFs.length <= 12) {
hideComponent(seeMoreFavorites);
offset = favoriteGIFs.length;
} else if (favoriteGIFs.length < offset) {
offset -= 1;
}
createAndAddFavGIFCards(0, offset);
}
function getOffsetAndUpdateBtn() {
start = offset;
if (offset + 12 <= favoriteGIFs.length) {
offset += 12;
} else {
const excess = favoriteGIFs.length - offset;
offset += excess;
}
if (offset === favoriteGIFs.length) {
hideComponent(seeMoreFavorites);
}
}
let start;
let offset = 12;
loadAndShowFavorites();
seeMoreFavorites.addEventListener("click", () => {
getOffsetAndUpdateBtn();
createAndAddFavGIFCards(start, offset);
});
GIFCard Class:
import { showAllUntilOffset } from "../favorites/favoritesPage.js";
export class GIFCard {
createCardDesktop() {
const card = document.createElement("div");
card.classList.add("card");
const overlay = document.createElement("div");
overlay.classList.add("overlay");
const text = this.createCardInfo();
const favoriteBtn = this.createCardButton("favorite", "card-btn");
this.favoriteBtn = favoriteBtn;
const downloadBtn = this.createCardButton("download", "card-btn");
this.downloadBtn = downloadBtn;
const expandBtn = this.createCardButton("expand", "card-btn");
this.expandBtn = expandBtn;
const gifImg = this.createCardImg();
gifImg.classList.add("card-img");
card.append(overlay, text, favoriteBtn, downloadBtn, expandBtn, gifImg);
this.htmlDesktopCard = card;
this.checkAndUpdateFavBtn();
this.expandGIFDesktopEventListener();
this.addFavoriteEventListener();
return card;
}
favEventAction() {
if (!seeingFavorites()) {
if (!this.isInFavorites()) {
this.pushToFavorites();
} else {
this.removeFromFavorites();
}
} else {
if (!this.isInFavorites()) {
this.pushToFavorites();
if (this.type === "Trending GIF") {
hideComponent(favoritesEmpty);
displayComponent(favoritesShow);
if (window.innerWidth >= 1280) {
favoritesGrid.append(this.createCardDesktop());
} else {
favoritesGrid.append(this.createCardMobile());
}
}
} else {
this.removeFromFavorites();
if (this.type === "Favorite GIF") {
showAllUntilOffset();
}
}
}
}
addFavoriteEventListener() {
this.favoriteBtn.addEventListener("click", () => {
this.favEventAction();
});
}
}
Hope you can help me, I've tried everything and I just can't figure out a solution. This is my first JavaScript big project so if you can recommend me good practices I'd really appreciate it. Thank you so much.

Related

Star Rating Js + html with symfony

I'm working on a project with symfony 4. I want to implement a star rating system. I display 5 stars in each row in a table. I have two problems, one is more important than the other.
So the first issue is that I want to be able to retrieve the value of the star I selected. If I select 5 stars, I want to get that value back in my entity controller.
The second issue is that, currently, let's say I have 5 items in my table, so 5 rows. Currently, if I select 5 stars in one row it's selected for all rows and I can't select another value anymore. So, it's global or something.
Here is the javascript I'm using:
<script>
const stars = document.querySelectorAll('.star');
let check = false;
stars.forEach(star => {
star.addEventListener('mouseover', selectStars);
star.addEventListener('mouseleave', unselectStars);
star.addEventListener('click', activeSelect);
})
function selectStars(e) {
const data = e.target;
const etoiles = priviousSiblings(data);
if (!check) {
etoiles.forEach(etoile => {
etoile.classList.add('hover');
})
}
}
function unselectStars(e) {
const data = e.target;
const etoiles = priviousSiblings(data);
if (!check) {
etoiles.forEach(etoile => {
etoile.classList.remove('hover');
})
}
}
function activeSelect(e) {
if (!check) {
check = true;
document.querySelector('.note').innerHTML = 'Note ' + e.target.dataset.note;
}
}
function priviousSiblings(data) {
let values = [data];
while (data === data.previousSibling) {
if (data.nodeName === 'I') {
values.push(data);
}
}
return values;
}
</script>
And Here is the twig.html I'm displaying:
<td>
<i class="star" data-note="1">★</i>
<i class="star" data-note="2">★</i>
<i class="star" data-note="3">★</i>
<i class="star" data-note="4">★</i>
<i class="star" data-note="5">★</i>
<i class="note">Note:</i>
</td>
I want to be able to retrieve the value once I made a selection, and to have a different selection for each row I have.
The problem is with the "mouseover" and "mouseleave" event handlers - selectStars and unselectStars. In "selectStars", you are adding the class to only one star. And in "unselectStars", you were not resetting, or applying the "remove" class method to other stars.
Anyway, here is how I have achieved what you are trying to do:
const ratings = document.querySelectorAll('.rating');
ratings.forEach(rating =>
rating.addEventListener('mouseleave', ratingHandler)
);
const stars = document.querySelectorAll('.rating .star');
stars.forEach(star => {
star.addEventListener('mouseover', starSelection);
star.addEventListener('mouseleave', starSelection);
star.addEventListener('click', activeSelect);
});
function ratingHandler(e) {
const childStars = e.target.children;
for(let i = 0; i < childStars.length; i++) {
const star = childStars.item(i)
if (star.dataset.checked === "true") {
star.classList.add('hover');
}
else {
star.classList.remove('hover');
}
}
}
function starSelection(e) {
const parent = e.target.parentElement
const childStars = parent.children;
const dataset = e.target.dataset;
const note = +dataset.note; // Convert note (string) to note (number)
for (let i = 0; i < childStars.length; i++) {
const star = childStars.item(i)
if (+star.dataset.note > note) {
star.classList.remove('hover');
} else {
star.classList.add('hover');
}
}
}
function activeSelect(e) {
const parent = e.target.parentElement
const childStars = parent.children;
const dataset = e.target.dataset;
const note = +dataset.note; // Convert note (string) to note (number)
for (let i = 0; i < childStars.length; i++) {
const star = childStars.item(i)
if (+star.dataset.note > note) {
star.classList.remove('hover');
star.dataset.checked = "false";
} else {
star.classList.add('hover');
star.dataset.checked = "true";
}
}
const noteTextElement = parent.parentElement.lastElementChild.children.item(0)
noteTextElement.innerText = `Note: ${note}`;
}
You might notice I have a .rating class component. This is a div which I have created to hold all these "stars". Here is a link to a codepen I have created. Feel free to play around with it.
And as a note, please provide codepen (or any other) demos so that we can debug a bit better and faster.
I hope the codepen link would help you solve your problem.

Adding a simple left/right swipe gesture

I need to add a simple left/right swipe gesture so that the 'selected' image cycles when swiped on mobile, similar to clicking the buttons in the hero component, also similar to pressing the left/right arrow keys on a keyboard
I don't have the most experience with JavaScript so if anyone could tell me what exactly to write and where so that I can completely wrap up this project.
Here is a demo: http://nufaith.ca/justinatkins/
Code:
Vue.component('hero-bg', {
template: `
<div class="hero-bg">
<div class="hero">
<img id="pushed" :src="selected"/>
</div>
</div>
`,
props: ['selected']
});
Vue.component('hero-bg-empty', {
template: `
<div class="hero-bg">
<div class="hero">
<span style="display:block;height:100px;"></span>
</div>
</div>
`
});
Vue.component('hero', {
template: `
<div>
<topbar v-if="!gridEnabled"></topbar>
<topbar2 v-if="gridEnabled"></topbar2>
<hero-bg :selected="selectedItem.img" v-if="!gridEnabled"></hero-bg>
<hero-bg-empty v-if="gridEnabled"></hero-bg-empty>
<div class="hero-container" v-if="!gridEnabled">
<div class="hero">
<img :src="selectedItem.img" v-if="thing" alt=""/>
</div>
<div class="hero-desc">
<button class="control left" #click="previous">
<i class="zmdi zmdi-chevron-left"></i>
</button>
<span class="hero-desc-title" v-html="title"></span>
<button class="control right" #click="next">
<i class="zmdi zmdi-chevron-right"></i>
</button>
<br/>
<button class="view-all-button" #click="enableGrid">OVERVIEW</button>
</div>
</div>
</div>
`,
data() {
return {
gridEnabled: false,
selected: 0,
thing: true
};
},
computed: {
selectedItem() {
return info[this.selected];
},
title() {
const comma = this.selectedItem.title.indexOf(',');
const len = this.selectedItem.title.length;
const strBeginning = this.selectedItem.title.substring(comma, 0);
const strEnd = this.selectedItem.title.substring(comma, len);
if (this.selectedItem.title.includes(',')) {
return `<span>${strBeginning}<span class="font-regular font-muted">${strEnd}</span></span>`;
}
return this.selectedItem.title;
},
maxImages() {
return info.length - 1;
}
},
created() {
window.addEventListener('keydown', e => {
if (e.keyCode === 37) {
this.previous();
return;
}
if (e.keyCode === 39) {
this.next();
return;
}
});
Event.$on('updateImg', index => {
this.selected = index;
this.gridEnabled = !this.gridEnabled;
});
},
methods: {
next() {
this.selected === this.maxImages ? (this.selected = 0) : (this.selected += 1);
},
previous() {
this.selected === 0 ? (this.selected = this.maxImages) : (this.selected -= 1);
},
enableGrid() {
this.gridEnabled = !this.gridEnabled;
window.scroll(0, 0);
Event.$emit('enableGrid');
}
}
});
This is how I implemented a simple swipe gesture in one of my projects. You may check this out.
Code:
touchableElement.addEventListener('touchstart', function (event) {
touchstartX = event.changedTouches[0].screenX;
touchstartY = event.changedTouches[0].screenY;
}, false);
touchableElement.addEventListener('touchend', function (event) {
touchendX = event.changedTouches[0].screenX;
touchendY = event.changedTouches[0].screenY;
handleGesture();
}, false);
function handleGesture() {
if (touchendX < touchstartX) {
console.log('Swiped Left');
}
if (touchendX > touchstartX) {
console.log('Swiped Right');
}
if (touchendY < touchstartY) {
console.log('Swiped Up');
}
if (touchendY > touchstartY) {
console.log('Swiped Down');
}
if (touchendY === touchstartY) {
console.log('Tap');
}
}
Basically, touchableElement mentioned here, refers to the DOM Element that will receive the touch event. If you want to activate swipe options on your entire screen, then you may use your body tag as the touchable element. Or you may configure any specific div element as the touchable element, in case you just want the swipe gesture on that specific div.
On that touchableElement, we are adding 2 event-listeners here:
touchstart:
this is when user starts swiping. We take that initial coordinates (x,y) and
store them into touchstartX, touchstartY respectively.
touchend: this is when user stops swiping. We take that final coordinates (x, y) and store them into touchendX, touchendY respectively.
Keep in mind that, the origin of these coordinates is the top left corner of the screen. x-coordinate increases as you go from left to right and y-coordinate increases as you go from top to bottom.
Then, in handleGesture(), we just compare those 2 pair of coordinates (touchstartX, touchstartY) and (touchendX, touchendY), to detect different types of swipe gesture (up, down, left, right):
touchendX < touchstartX: says that, user started swiping at a higher X value & stopped swiping at a lower X value. That means, swiped from right to left (Swiped Left).
touchendX > touchstartX: says that, user started swiping at a lower X value & stopped swiping at a higher X value. That means, swiped from left to right (Swiped Right).
touchendY < touchstartY: says that, user started swiping at a higher Y value & stopped swiping at a lower Y value. That means, swiped from bottom to top (Swiped Up).
touchendY > touchstartY: says that, user started swiping at a lower Y value & stopped swiping at a higher Y value. That means, swiped from top to bottom (Swiped Down).
You may add the code for these 4 different events (Swipe Up/Down/Left/Right), on the corresponding if blocks, as shown on the code.
I took smmehrab’s answer, added some thresholds to avoid accidental swipes, and turned it into a little library. Might come in handy, so here it is:
export default class TouchEvent
{
static SWPIE_THRESHOLD = 50 // Minumum difference in pixels at which a swipe gesture is detected
static SWIPE_LEFT = 1
static SWIPE_RIGHT = 2
static SWIPE_UP = 3
static SWIPE_DOWN = 4
constructor(startEvent, endEvent)
{
this.startEvent = startEvent
this.endEvent = endEvent || null
}
isSwipeLeft()
{
return this.getSwipeDirection() == TouchEvent.SWIPE_LEFT
}
isSwipeRight()
{
return this.getSwipeDirection() == TouchEvent.SWIPE_RIGHT
}
isSwipeUp()
{
return this.getSwipeDirection() == TouchEvent.SWIPE_UP
}
isSwipeDown()
{
return this.getSwipeDirection() == TouchEvent.SWIPE_DOWN
}
getSwipeDirection()
{
let start = this.startEvent.changedTouches[0]
let end = this.endEvent.changedTouches[0]
if (!start || !end) {
return null
}
let horizontalDifference = start.screenX - end.screenX
let verticalDifference = start.screenY - end.screenY
// Horizontal difference dominates
if (Math.abs(horizontalDifference) > Math.abs(verticalDifference)) {
if (horizontalDifference >= TouchEvent.SWPIE_THRESHOLD) {
return TouchEvent.SWIPE_LEFT
} else if (horizontalDifference <= -TouchEvent.SWPIE_THRESHOLD) {
return TouchEvent.SWIPE_RIGHT
}
// Verical or no difference dominates
} else {
if (verticalDifference >= TouchEvent.SWPIE_THRESHOLD) {
return TouchEvent.SWIPE_UP
} else if (verticalDifference <= -TouchEvent.SWPIE_THRESHOLD) {
return TouchEvent.SWIPE_DOWN
}
}
return null
}
setEndEvent(endEvent)
{
this.endEvent = endEvent
}
}
How to use
Simply feed it the events from touchstart and touchend:
import TouchEvent from '#/TouchEvent'
let touchEvent = null;
document.addEventListener('touchstart', (event) => {
touchEvent = new TouchEvent(event);
});
document.addEventListener('touchend', handleSwipe);
function handleSwipe(event)
{
if (!touchEvent) {
return;
}
touchEvent.setEndEvent(event);
if (touchEvent.isSwipeRight()) {
// Do something
} else if (touchEvent.isSwipeLeft()) {
// Do something different
}
// Reset event for next touch
touchEvent = null;
}
This sounds like a job for Hammer.JS, unless you're trying to avoid dependencies. They have good documentation and examples for getting started
My Vue knowledge is next to nothing, so I'm wary of this becoming a blind leading the blind scenario, but the first thing you'll have to do is add the dependency using either npm or yarn - then add it to the top of your file using
import Hammer from 'hammerjs'
Try adding the below code right above this line: Event.$on('updateImg', index => {
const swipeableEl = document.getElementsByClassName('.hero')[0];
this.hammer = Hammer(swipeableEl)
this.hammer.on('swipeleft', () => this.next())
this.hammer.on('swiperight', () => this.previous())
If it doesn't work you'll have to check your developer tools / console log to see if it's logged any useful errors.
This codepen might be a useful resource too:
Good luck.
Using #smmehrab answer, I created a Vue 3 composable that also works for SSR builds.
import { onMounted, Ref } from 'vue'
export type SwipeCallback = (event: TouchEvent) => void;
export type SwipeOptions = {
directinoal_threshold?: number; // Pixels offset to trigger swipe
};
export const useSwipe = (touchableElement: HTMLElement = null, options: Ref<SwipeOptions> = ref({
directinoal_threshold: 10
})) => {
const touchStartX = ref(0);
const touchEndX = ref(0);
const touchStartY = ref(0);
const touchEndY = ref(0);
onMounted(() => {
if (!touchableElement)
touchableElement = document.body;
touchableElement.addEventListener('touchstart', (event) => {
touchStartX.value = event.changedTouches[0].screenX;
touchStartY.value = event.changedTouches[0].screenY;
}, false);
touchableElement.addEventListener('touchend', (event) => {
touchEndX.value = event.changedTouches[0].screenX;
touchEndY.value = event.changedTouches[0].screenY;
handleGesture(event);
}, false);
});
const onSwipeLeft: Array<SwipeCallback> = [];
const onSwipeRight: Array<SwipeCallback> = [];
const onSwipeUp: Array<SwipeCallback> = [];
const onSwipeDown: Array<SwipeCallback> = [];
const onTap: Array<SwipeCallback> = [];
const addEventListener = (arr: Array<SwipeCallback>, callback: SwipeCallback) => {
arr.push(callback);
};
const handleGesture = (event: TouchEvent) => {
if (touchEndX.value < touchStartX.value && (Math.max(touchStartY.value, touchEndY.value) - Math.min(touchStartY.value, touchEndY.value)) < options.value.directinoal_threshold) {
onSwipeLeft.forEach(callback => callback(event));
}
if (touchEndX.value > touchStartX.value && (Math.max(touchStartY.value, touchEndY.value) - Math.min(touchStartY.value, touchEndY.value)) < options.value.directinoal_threshold) {
onSwipeRight.forEach(callback => callback(event));
}
if (touchEndY.value < touchStartY.value && (Math.max(touchStartX.value, touchEndX.value) - Math.min(touchStartX.value, touchEndX.value)) < options.value.directinoal_threshold) {
onSwipeUp.forEach(callback => callback(event));
}
if (touchEndY.value > touchStartY.value && (Math.max(touchStartX.value, touchEndX.value) - Math.min(touchStartX.value, touchEndX.value)) < options.value.directinoal_threshold) {
onSwipeDown.forEach(callback => callback(event));
}
if (touchEndY.value === touchStartY.value) {
onTap.forEach(callback => callback(event));
}
}
return {
onSwipeLeft: (callback: SwipeCallback) => addEventListener(onSwipeLeft, callback),
onSwipeRight: (callback: SwipeCallback) => addEventListener(onSwipeRight, callback),
onSwipeUp: (callback: SwipeCallback) => addEventListener(onSwipeUp, callback),
onSwipeDown: (callback: SwipeCallback) => addEventListener(onSwipeDown, callback),
onTap: (callback: SwipeCallback) => addEventListener(onTap, callback)
}
}
Example usage:
const { onSwipeLeft, onSwipeRight } = useSwipe(document.body);
onSwipeLeft((e:TouchEvent) => {
//logic
});

Splicing only one element when creating a new element

I'm trying to make it where when a user creates a widget, and then reloads the page (it'll appear because it's saved in localStorage) and then once you create another widget, I want to be able to delete the old widget before the page refreshes but it deletes the widget that the user clicked and the new widget.
Each time a new widget it created, it gets assigned a property name 'id' and the value is determined based on what is already in localStorage and it finds the next available (or not in use) id number. The widgets array also gets sorted from smallest id to largest id before setting it back to localStorage.
I've tried attaching a click listener for the delete button on the widget both when it's created and when the document is loaded. But that wasn't working.
Now i'm thinking I have to call a function with the id as its param to add a click listener to all the widgets that are appended to the document and when a new widget is created.
app.js:
function addRemoveListener(id) {
let storageUi = localStorage.getItem('ui');
let localUi = JSON.parse(storageUi);
$(`#widget-${id} > span > .widget-clear`).click(() => {
for (let i = 0; i < localUi.widgets.length; i++) {
let thisWidget = `#widget-${id}`;
if (localUi.widgets[i].id == id) {
localUi.widgets.splice(i, 1)
}
$(thisWidget).remove();
console.log(localUi)
}
let newUi = JSON.stringify(localUi);
localStorage.setItem('ui', newUi);
})
}
widget.js:
static appendToDom(ui) {
let storageUi = localStorage.getItem('ui');
let localUi = JSON.parse(storageUi);
for (let i = 0; i < localUi.widgets.length; i++) {
let widget = localUi.widgets[i];
let query = () => {
if (widget.type == 'humidity') {
return `${Math.floor(ui.weather.currently.humidity * 100)}`
} else if (widget.type == 'eye') {
return `${Math.floor(ui.weather.currently.visibility)}`
} else if (widget.type == 'windsock') {
return `${Math.floor(ui.weather.currently.windSpeed)}`
} else if (widget.type == 'pressure') {
return `${Math.floor(ui.weather.currently.pressure)}`
} else if (widget.type == 'uv-index') {
return `${ui.weather.currently.uvIndex}`
}
}
$('nav').after(`<div class="widget widget-${widget.size}" id="widget-${widget.id}">
<span>
<i class="material-icons widget-clear">clear</i>
<i class="material-icons widget-lock-open">lock_open</i>
<i class="material-icons widget-lock">lock_outline</i>
</span>
<div class="data-container">
<img src=${widget.image}>
<h1> ${widget.type}: ${query()} ${widget.unit} </h1>
</div>
</div>`)
$(`#widget-${widget.id}`).delay(1000 * i).animate({ opacity: 1 }, 1000);
$(`#widget-${widget.id}`).css({ left: `${widget.left}`, top: `${widget.top}`, 'font-size': `${widget.dimensions[2]}` })
$(`.widget`).draggable();
$(`#widget-${widget.id}`).css({ width: `${widget.dimensions[0]}`, height: `${widget.dimensions[1]}` })
addRemoveListener(i);
}
// this function is called earlier in the script when the user has selected
// which kind of widget they want
let makeWidget = () => {
let newWidget = new Widget(this.size, this.id, this.image, this.type, this.unit, this.dimensions);
saveWidget(newWidget);
addRemoveListener(this.id)
}
I have no problems with this until I delete an existing widget after I create a new one, and before refreshing.
You might have a problem with the id that is passed to your addRemoveListener function. It could be passing the same id for any widget so the loop will delete the UI because thisWidget is in the for loop. Try adding some console logging.
function addRemoveListener(id) {
let storageUi = localStorage.getItem('ui');
let localUi = JSON.parse(storageUi);
$(`#widget-${id} > span > .widget-clear`).click(() => {
for (let i = 0; i < localUi.widgets.length; i++) {
let thisWidget = `#widget-${id}`;
if (localUi.widgets[i].id == id) {
localUi.widgets.splice(i, 1)
}
// Move this inside the if statement above.
$(thisWidget).remove();
console.log(localUi)
}
let newUi = JSON.stringify(localUi);
localStorage.setItem('ui', newUi);
})
}
or better yet, re-write it to continue if the id doesn't match
function addRemoveListener(id) {
let storageUi = localStorage.getItem('ui');
let localUi = JSON.parse(storageUi);
$(`#widget-${id} > span > .widget-clear`).click(() => {
for (let i = 0; i < localUi.widgets.length; i++) {
let thisWidget = `#widget-${id}`;
if (localUi.widgets[i].id !== id) {
continue;
}
localUi.widgets.splice(i, 1)
$(thisWidget).remove();
console.log(localUi)
}
let newUi = JSON.stringify(localUi);
localStorage.setItem('ui', newUi);
})
}

Auto Nested Tree Auto Expand not working in IE11 but is working in Chrome

I have a nested tree which is an html table with buttons on each elements to get the child table via AJAX. This works when done manually in IE and in chrome but I have a link to "Expand All" which selects all down arrow images on the page and .click()'s them. This happens on a 600 millisecond loop until all images are clicked and tables are opened. Here is the code for the auto click loop.
function autoClick(searchElement, clickElement) {
setTimeout(function () {
if ($(searchElement).length > 0) {
$(searchElement).each(function () {
$(this).click();
});
$(clickElement).click();
}
}, 600);
return ($(searchElement).length);
}
It doesn't even to click the first levels of the tree because when I watch for the ajax calls on the network monitor, none are fired. Here is the expand and collapse functions which utilize the autoclick loop.
function expandClick() {
if (!collapseExecuting) {
expandExecuting = true;
numOfExpandElements = autoClick('img[src="../Images/details_open.png"]', '#expandLink');
if (numOfExpandElements == 0) {
expandExecuting = false;
}
}
}
function collapseClick() {
if (!expandExecuting) {
collapseExecuting = true;
numOfCollapseElements = autoClick('img[src="../Images/details_close.png"]', '#collapseLink');
if (numOfCollapseElements == 0) {
collapseExecuting = false;
}
}
}
Here's the Click event handler:
$('#requestsTable tbody td img[data-description="toggle"]').live('click', function () { //alt="expand/collapse"
// var nTr = this.parentNode.parentNode;
var parentid = this.parentNode.parentNode.parentNode.parentNode.id;
var item = this;
if (parentid == "requestsTable") {
getChild("request", requestTable, item, "RequestCustomers?RequestID=" + $(this).data("request"));
}
else if (parentid == "customerTable") {
getChild("customer", requestTable, item, ".." + $(this).data('url') + "?RequestID=" + $(this).data("customer"));
}
else if (parentid == "accountTable") {
getChild("account", requestTable, item, ".." + $(this).data('url') + "?AccountNum=" + $(this).data("account") + "&RequestID=" + $(this).data("request"));
}
});
If you need to see get child here it is too:
function getChild(details, rTable, item, getCall) {
var row = item.parentNode.parentNode;
var parentid = item.parentNode.parentNode.parentNode.parentNode.id;
var rowClass = 'accountDetails';
if (item.src.match('details_close')) {
/* This row is already open - close it */
item.src = "../Images/details_open.png";
$(item).attr('title', 'Expand');
rTable.fnClose(row);
}
else {
/* Open this row */
item.src = "../Images/details_close.png";
$(item).attr('title', 'Collapse');
//set the class of the row based on the model being returned.
if (details == 'request') {
rowClass = 'requestDetails';
} else if(details == 'customer') {
rowClass = 'customerDetails';
}
$.get(getCall, function (response) {
rTable.fnOpen(row, response, rowClass);
if (response.BatchComplete) {
$('#batchStatus').value('Batch Completed. Click here to send batch.');
}
});
}
}
I found what was happening. In my ExpandClick and CollapseClick functions I was selecting the elements via relative URLs in the SRC attribute. Apparently in IE (at least IE 11) these relative URLs are mapped and then displayed in the source as absolute URLs. In chrome it keeps the relative URLs as is in the source so my selector was able to target in Chrome but not IE.
Changed my selectors that I pass to the autoClick function and it worked:
function expandClick() {
if (!collapseExecuting) {
expandExecuting = true;
numOfExpandElements = autoClick('img[title="Expand"]', '#expandLink');
if (numOfExpandElements == 0) {
expandExecuting = false;
}
}
}
function collapseClick() {
if (!expandExecuting) {
collapseExecuting = true;
numOfCollapseElements = autoClick('img[title="Collapse"]', '#collapseLink');
if (numOfCollapseElements == 0) {
collapseExecuting = false;
}
}
}

Illustrator JavaScript only works once - won't toggle

I'm writing a script which changes the colours of a stick-man's arms and legs. Once "he" is selected, it effectively just mirrors the colours:
//Declare and initialize variables
var msgType = "";
var app;
var docRef = app.activeDocument;
var testColor = docRef.swatches.getByName("CMYK Green");
var leftColor = docRef.swatches.getByName("LeftColor");
var rightColor = docRef.swatches.getByName("RightColor");
function sameColor(CMYKColor1, CMYKColor2) {
"use strict";
var isTheSameColor;
if ((CMYKColor1.cyan === CMYKColor2.cyan) && (CMYKColor1.magenta === CMYKColor2.magenta) && (CMYKColor1.yellow === CMYKColor2.yellow) && (CMYKColor1.black === CMYKColor2.black)) {
isTheSameColor = true;
} else {
isTheSameColor = false;
}
return isTheSameColor;
}
// check if a document is open in Illustrator.
if (app.documents.length > 0) {
var mySelection = app.activeDocument.selection;
var index;
// Loop through all selected objects
for (index = 0; index < mySelection.length; index += 1) {
// Switch left and right colours
if (sameColor(mySelection[index].strokeColor, leftColor.color)) {
mySelection[index].strokeColor = rightColor.color;
}
if (sameColor(mySelection[index].strokeColor, rightColor.color)) {
mySelection[index].strokeColor = leftColor.color;
}
if (sameColor(mySelection[index].fillColor, leftColor.color)) {
mySelection[index].fillColor = rightColor.color;
}
if (sameColor(mySelection[index].fillColor, rightColor.color)) {
mySelection[index].fillColor = leftColor.color;
}
}
}
It works, but it only works once (i.e. I can't toggle the change again). If I undo the change and try again it works again. Why is this?
After a lot of head-scratching / debugging it turns out that it was changing the CMYK values to be not quite the same (by a tiny fraction).
Changed the following:
if ((CMYKColor1.cyan === CMYKColor2.cyan) ...
to:
if ((Math.round(CMYKColor1.cyan) === Math.round(CMYKColor2.cyan)) ...
and it works fine now.

Categories