PageSourceSearch

https://somersetnlv.org/apps-shared/js/en_card_slider.js

js somersetnlv.org collected 2026-10-06 06:46:20 UTC 11,472 bytes, 272 lines download raw bytes

1function ENCardSlider(params) {
2
3    this.init = function(params) {
4        let me = this;
5        this.container = params.container;
6        if (params.maxColumnGap >= 0) {
7            this.maxColumnGap = params.maxColumnGap;
8        } else if (params.maxColumnGap && params.maxColumnGap.endsWith("px") && parseInt(params.maxColumnGap) >= 0) {
9            this.maxColumnGap = parseInt(params.maxColumnGap);
10        }
11
12        this.slideSingleCard = params.slideSingleCard;
13
14        this.allowManualPadding = params.allowManualPadding == 1 || params.allowManualPadding === true;
15        this.align = (params.align == "left" || params.align == "right") ? params.align : "center";
16        this.showHiddenElements = params.showHiddenElements == 1 || params.showHiddenElements === true;
17
18        if (!this.container) {
19            throw("The <DIV> element for the container is not provided. (container=null)");
20        }
21
22        if (this.container.nodeName != "DIV") {
23            throw("ENCardSlider's container must be a DIV element. " + this.container.nodeName + " is invalid.");
24        }
25
26        let i = this.container.getElementsByClassName("en-card-slider-inner-container");
27        if (i.length == 0) {
28            throw("Inner container doesn't exist.");
29        } else if (i.length > 1) {
30            throw(i.length + " inner containers found. Only 1 is expected.");
31        } else if (i[0].parentNode != this.container) {
32            throw("Inner container's parentNode is not the main container.");
33        }
34        this.innerContainer = i[0];
35
36        let contStyles = new Map();
37        contStyles.set("overflow-x", "hidden");
38        contStyles.set("padding", "0px");
39
40        let innerContStyles = new Map();
41        innerContStyles.set("position", "relative");
42        innerContStyles.set("display", "flex");
43        innerContStyles.set("flex-direction", "row");
44        innerContStyles.set("border", "none");
45        innerContStyles.set("padding", "0px");
46        innerContStyles.set("margin-left", "0px");
47        innerContStyles.set("margin-right", "0px");
48
49        let childStyles = new Map();
50        childStyles.set("overflow", "hidden");
51        childStyles.set("flex-shrink", "0");
52        childStyles.set("margin-left", "0px");
53        childStyles.set("margin-right", "0px");
54        childStyles.set("min-width", "auto");
55        childStyles.set("max-width", "none");
56
57        this.enforceStyles(this.container, contStyles);
58        this.enforceStyles(this.innerContainer, innerContStyles);
59
60        let innerContCompStyles = window.getComputedStyle(this.innerContainer);
61
62        let gap = innerContCompStyles.getPropertyValue("column-gap");
63        if (gap.endsWith("px")) {
64            this.minColumnGap = parseInt(gap);
65        } else {
66            this.minColumnGap = 8;
67        }
68
69        let cardWidth;
70
71        if (this.innerContainer.children.length == 0) {
72            console.log("ENCardSlider Warning: No child element (card) exists in the inner container.");
73        }
74
75        for (let x = 0; x < this.innerContainer.children.length; x++) {
76            let c = this.innerContainer.children[x];
77            this.enforceStyles(c, childStyles);
78            if (x == 0) {
79                cardWidth = c.offsetWidth;
80                if (cardWidth > this.innerContainer.clientWidth) {
81                    throw("First card's width is smaller than the container's width. Container: " + this.innerContainer.clientWidth + "px - Card: " + cardWidth + "px");
82                }
83            } else {
84                if (cardWidth != c.offsetWidth) {
85                    console.log("ENCardSlider Warning: All cards must have the equal width. Width of the card #" + x + " is " + c.offsetWidth + "px. Expected: " + cardWidth + "px");
86                }
87            }
88
89            try {
90                let childResizeObserver = new ResizeObserver(entries => { this.adjust(); });
91                childResizeObserver.observe(c);
92            } catch (e) {
93                console.log("ENCardSlider Warning: Resize observer is not available. It's OK.");
94            }
95        }
96
97        let transition = innerContCompStyles.getPropertyValue("transition");
98        if (!transition.includes("transform")) {
99            this.innerContainer.style['transition'] = "transform 0.6s ease-out";
100        }
101
102        this.innerContainer.style["transform"] = "translateX(0px)";
103        this.curIndex = 0;
104
105        try {
106            let resizeObserver = new ResizeObserver(entries => { this.adjust(); });
107            resizeObserver.observe(this.container);
108        } catch (e) {
109            console.log("ENCardSlider Warning: Resize observer is not available. It's OK.");
110        }
111
112
113        if (params.prevButton) {
114            this.prevButton = params.prevButton;
115            this.prevButton.addEventListener("click", () => {
116                me.prev();
117            });
118        }
119        if (params.nextButton) {
120            this.nextButton = params.nextButton;
121            this.nextButton.addEventListener("click", () => {
122                me.next();
123            });
124        }
125        this.adjust();
126    }
127
128    this.enforceStyles = function(element, styles) {
129        let computedStyles = window.getComputedStyle(element);
130        for (const [key, value] of styles.entries()) {
131            if (computedStyles.getPropertyValue(key) != value) {
132                this.setImportantStyle(element, key, value);
133            }
134        }
135    }
136
137    this.setImportantStyle = function(element, attribute, value) {
138        element.style[attribute] = value;
139        element.setAttribute("style", element.getAttribute("style").replace((attribute + ": " + value + ";"), (attribute + ": " + value + " !important;")));
140    }
141
142    this.move = function(index) {
143        index = Math.max(0, Math.min(index, this.getMaxIndex()));
144        if (isNaN(index)) {
145            index = 0;
146        }
147        let cardWidth = this.innerContainer.children[0].offsetWidth;
148        let curGap = parseInt(window.getComputedStyle(this.innerContainer).getPropertyValue("column-gap"));
149        let x = index * (cardWidth + curGap);
150        if (!this.paddingOnSides) {
151            x += curGap;
152        }
153        this.innerContainer.style["transform"] = "translateX(-" + x + "px)";
154        this.curIndex = index;
155        if (this.prevButton) {
156            if (this.curIndex == 0) {
157                this.prevButton.classList.add("en-card-slider-button-disabled");
158            } else {
159                this.prevButton.classList.remove("en-card-slider-button-disabled");
160            }
161        }
162        if (this.nextButton) {
163            if (this.curIndex >= this.getMaxIndex()) {
164                this.nextButton.classList.add("en-card-slider-button-disabled");
165            } else {
166                this.nextButton.classList.remove("en-card-slider-button-disabled");
167            }
168        }
169        // The leftover space travels with the visible window (as a margin on the first/last
170        // visible card, per the align setting) so the neighboring off-screen cards can never
171        // flow into it. In the stretch mode the margins stay on the overall first/last card,
172        // and with showHiddenElements the space is deliberately left open so the neighboring
173        // cards stay partially visible in it (styled via the hidden-card class in CSS).
174        let windowed = this.paddingOnSides && !this.showHiddenElements;
175        let marginLeftIndex = windowed ? this.curIndex : 0;
176        let marginRightIndex = windowed ? (this.curIndex + this.numberVisibleCards - 1) : (this.innerContainer.children.length - 1);
177        for (let i = 0; i < this.innerContainer.children.length; i++) {
178            let c = this.innerContainer.children[i];
179            this.setImportantStyle(c, "margin-left", (i == marginLeftIndex ? this.windowLeftPad : 0) + "px");
180            this.setImportantStyle(c, "margin-right", (i == marginRightIndex ? this.windowRightPad : 0) + "px");
181            if (i < this.curIndex || i >= this.curIndex + this.numberVisibleCards) {
182                c.classList.add("en-card-slider-card-hidden");
183            } else {
184                c.classList.remove("en-card-slider-card-hidden");
185            }
186        }
187    }
188
189    this.prev = function() {
190        this.curIndex = Math.max(0, this.curIndex - this.numberCardsToSlide);
191        this.move(this.curIndex);
192    }
193
194    this.next = function() {
195        this.curIndex = Math.min(this.getMaxIndex(), this.curIndex + this.numberCardsToSlide);
196        this.move(this.curIndex);
197    }
198
199    this.getFitGap = function() {
200        if (this.allowManualPadding) {
201            let gap = parseInt(window.getComputedStyle(this.innerContainer).getPropertyValue("column-gap"));
202            return isNaN(gap) ? 0 : gap;
203        }
204        return this.minColumnGap;
205    }
206
207    this.getNumberVisibleCards = function() {
208        let availableWidth = this.container.clientWidth;
209        let cardWidth = this.innerContainer.children[0].offsetWidth;
210        let fitGap = this.getFitGap();
211        return Math.max(1, Math.min(this.innerContainer.children.length, Math.round((availableWidth + fitGap) / (cardWidth + fitGap) - 0.5)));
212    }
213
214    this.getMaxIndex = function() {
215        return Math.max(0, this.innerContainer.children.length - this.getNumberVisibleCards());
216    }
217
218    this.adjust = function() {
219        let numberCards = this.getNumberVisibleCards();
220        this.numberVisibleCards = numberCards;
221        this.numberCardsToSlide = this.slideSingleCard ? 1 : numberCards;
222
223        let availableWidth = this.container.clientWidth;
224        let cardWidth = this.innerContainer.children[0].offsetWidth;
225        let remaining = availableWidth - numberCards * cardWidth;
226
227        let newGap;
228        if (this.allowManualPadding) {
229            newGap = this.getFitGap();
230            this.paddingOnSides = true;
231        } else if (numberCards > 1) {
232            newGap = Math.round(remaining / (numberCards - 1) - 0.5);
233            if (this.maxColumnGap && newGap >
233 this.maxColumnGap) {
234                // Round up: rounding down leaves leftover pixels that let the edge of the
235                // neighboring off-screen card peek into the container.
236                newGap = (this.align == "center" && !this.showHiddenElements) ? Math.ceil(remaining / (numberCards + 1)) : this.maxColumnGap;
237                this.paddingOnSides = true;
238            } else {
239                this.paddingOnSides = false;
240            }
241        } else {
242            newGap = this.showHiddenElements ? this.getFitGap() : remaining;
243            this.paddingOnSides = true;
244        }
245
246        if (this.paddingOnSides) {
247            let leftover = remaining - (numberCards - 1) * newGap;
248            if (this.align == "left") {
249                this.windowLeftPad = 0;
250            } else if (this.align == "right") {
251                this.windowLeftPad = leftover;
252            } else {
253                this.windowLeftPad = Math.round(leftover / 2 - 0.5);
254            }
255            this.windowRightPad = this.showHiddenElements ? 0 : (leftover - this.windowLeftPad);
256        } else {
257            this.windowLeftPad = newGap;
258            this.windowRightPad = newGap;
259        }
260
261        if (!this.allowManualPadding) {
262            this.setImportantStyle(this.innerContainer, "column-gap", newGap + "px");
263        }
264        this.move(this.curIndex);
265    }
266
267    try {
268        this.init(params);
269    } catch (e) {
270        console.error("Could not initialize ENCardSlider. Error: " + e);
271    }
272}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.