Next/Prev buttons to step through div contents - javascript

First of all a disclaimer, I'm not a dev. I'm halfway through The Odin Project and have covered some HTML and CSS, but, have not yet started on JS. In order to help with my learning I've created my own blog. My aim is for each blog post to have its own stylesheet (so with each new post I learn a little more about CSS).
Anyway, I plan to write a post about the benefits of using an eReader, specifically the Kindle. I've styled the page to look like a Kindle Oasis, and I'd like the reader to be able to step through the article contents via the Kindle's next/prev buttons, but, as I'm not a dev, this is where I'm stuck. Via Stack overflow I've managed to add some JS that will display page 1, 2 and 3 via dedicated buttons for each dive element, but, what I really need is to step through x number of pages via the prev/next buttons.
Here's what I have so far: https://codepen.io/dbssticky/pen/yLVoORO. Any help would be much appreciated. What I should do of course is finish The Odin Project and come up with a solution on my own, but, I'd really like to get this Kindle article published sooner rather than later. Hence my rather cheeky request for assistance.
Here's the JS I'm currently using:
function swapContent(id) {
const main = document.getElementById("main_place");
const div = document.getElementById(id);
const clone = div.cloneNode(true);
while (main.firstChild) main.firstChild.remove();
main.appendChild(clone);
}

You have the right idea and it just needs a few adjustments to get the previous/next functionality.
Currently your div IDs are following the format operation1, operation2, and so on. Since you want the previous/next functionality you'll need to change your 'swapping' function, which currently takes the full ID, to use the numeric portion only.
Add a new function which appends the number to 'operation' instead of using the whole thing:
function goToPage(pageNumber){
const main = document.getElementById("main_place");
const div = document.getElementById("operation" + pageNumber);
const clone = div.cloneNode(true);
while (main.firstChild) main.firstChild.remove();
main.appendChild(clone);
}
And then change your Page 1/2/3 buttons to use goToPage(1), goToPage(2) and so on.
Now for the previous/next functionality you'll need a way to track which page you're on, so that you can figure out which page to load.
Add a variable at the top (outside functions)
var currentPage = 0;
Then add a line in your goToPage function to track the page you're on.
currentPage = pageNumber;
Now that you're tracking you can add a previous and next function.
function goNextPage(){
goToPage(currentPage-1);
}
function goPreviousPage(){
goToPage(currentPage+1);
}
Then call it from the previous and next buttons.
<button onClick="goNextPage()" class="next-button"></button>
<button onClick="goPreviousPage()" class="previous-button"></button>
Here's a codepen: https://codepen.io/srirachapen/pen/WNZOXQZ
It's barebones and you may have to handle things like non existent div IDs.

HTML
<button class="next-button" onclick="nextContent()"></button>
<button class="previous-button" onclick="prevContent()"></button>
JS
var pageid = 0;
var maxpage = 3;
function nextContent() {
if(pageid == maxpage) return
pageid++
swapContent(`operation${pageid}`)
}
function prevContent() {
if(pageid == 1) return
pageid--
swapContent(`operation${pageid}`)
}
you can try this to switch between pages. But you may need to edit the "swapContent" method more sensibly.

Track the Current Page
Whatever solution you use to render pages & links (manual hardcoded links & content vs externally-stored & auto-generated), one thing is unavoidable: You need to track the current page!
var currentPage = 0
Then, any time there's a page change event, you update that variable.
With the current page being tracked, you can now perform operations relative to it (e.g. +1 or -1)
I'd suggest making a goToPage(page) function that does high-level paging logic, and keep your swapContent() function specifically for the literal act of swapping div content. In the future, you may find you'd want to use swapContent() for non-page content, like showing a "Welcome" or "Help" screen.
Example:
function goToPage(page) {
// Update `currentPage`
currentPage = page
// ... other logic, like a tracking event or anything else you want you occur when pages change
// Do the actual content swap, which could be your existing swapContent()
swapContent('operation'+page)
}
You'd invoke the function like so:
goToPage(3) // Jump to a specific page
goToPage(currentPage + 1) // Go to the next page
goToPage(currentPage - 1) // Go to the prev page
You can make separate helper functions like "goToNextPage()" if you desire, but for sure you start with a fundamental page-change function first.

Related

Switch between multiple (5-7) images on click and loop

I want to have a really simple gallery of around 5-7 images, where you can click on the image and it switches to the next one, and by the end it circles back to the beginning. My code right now only allows me to have 3 images, where it just loops between image 2 and image 3.
function swaparrows(obj, i1, i2, i3) {
var src = obj.getAttribute('src');
if (src.match(i1))
obj.setAttribute('src', i2);
else
obj.setAttribute('src', i1);
}
And for the HTML:
<img src="https://minecraft-statistic.net/en/og/player/Druio.png"
onclick="swaparrows(this, 'https://camblab.info/wp/wp-content/uploads/2017/02/pool-water.jpg', 'https://i2-prod.mirror.co.uk/incoming/article13348246.ece/ALTERNATES/s615/0_SupernumeraryRainbows_Entwistle_13621-610x859.jp', 'https://minecraft-statistic.net/en/og/player/Druio.png')" />
You can use an attribute on the element to figure out what the current index is. Then, increment that index by one and cap it off at the end of the array via the modulus operator so that it cycles circularly. Also, please do not use onclick for such long code snippets because it makes code very unmaintainable. Also, if you are not using jQuery, then apply to the whole page so that you can easily and quickly add new interactive content. If you are using jQuery, then do not do this because your page will get very laggy very quickly because jQuery embodies and promotes poor performance.
🢔 Because it appears as though you are new to Stackoverflow, I shall explain this checkmark here. After reading and reviewing my answer, if (and only if) you are thoroughly satisfied with the answer I have posted here, then you can reward me by clicking this checkmark to accept this answer as the best answer. If someone else posts a better answer, then click their checkmark. Clicking a checkmark is not permanent: if someone later on post a better answer, then you can click their checkmark and switch the best answer over to the new answer posted.
<img src="https://minecraft-statistic.net/en/og/player/Druio.png"
data-swap-index="2"
data-swap-0="https://camblab.info/wp/wp-content/uploads/2017/02/pool-water.jpg"
data-swap-1="https://i2-prod.mirror.co.uk/incoming/article13348246.ece/ALTERNATES/s615/0_SupernumeraryRainbows_Entwistle_13621-610x859.jpg"
data-swap-2="https://minecraft-statistic.net/en/og/player/Druio.png"
style="max-height:80vh" decoding="async" />
<img src="https://www.minecraft.net/content/dam/archive/og-image/minecraft-hero-og.jpg"
data-swap-index="0"
data-swap-0="https://www.minecraft.net/content/dam/archive/og-image/minecraft-hero-og.jpg"
data-swap-1="https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/10/29/2f/10292fd0-d87a-6856-7523-f67fa8051df7/AppIcon-0-1x_U007emarketing-0-85-220-9.png/246x0w.jpg"
style="max-height:80vh" decoding="async" />
<script>
addEventListener("click", function(evt){
// when anywhere on the page is clicked, this will fire
var eventObj = evt || window.event;
var target = eventObj.target;
var swapIndex = target.getAttribute("data-swap-index");
if (swapIndex !== null) {
var newSwapIndex = (swapIndex|0) + 1|0;
if (!target.hasAttribute("data-swap-" + newSwapIndex)) {
// cycle back to the first image
newSwapIndex = 0;
}
// now apply the new swap index
target.setAttribute(
"src",
target.getAttribute("data-swap-" + newSwapIndex)
);
target.setAttribute("data-swap-index", newSwapIndex);
eventObj.preventDefault(); // prevent link from opening or something
} // else the element being clicked on is not swappable
});
</script>
Notice how I prefix the attributes with data. All attributes prefixed with data- have been set aside for special custom use purposes by the WhatWG with datasets, thus it is always a good idea to prefix custom attributes with data- in order to ensure that in the future the attribute the you use is not added to the specification with special effects based upon its value.

TinyMCE retrieving elements using DomQuery

I'm dealing with TinyMCE to create a WYSIWYG editor, but there is a problem and now I'm stuck on it.
I need to create a system where users are allowed to create specific documents, each devided into sections i.e. a wrapper. Inside every section there are textual and block elements (p, table, img and so on).
Now, the problem is: when a new section needs to be created I'm using the following code
function insertRawSection () {
// Close the current section and open the next one
Editor.execCommand('mceInsertRawHtml',false,`</section><section><h1>${ZERO_SPACE}</h1>`)
}
This code works, but the real problem comes out when I need to move the cursor at the start of the new h1 element.
I can't retrieve the new heading because if I look for it with DomQuery it doesn't appear.
The code I use to lookup the h1 element is the following
function insertRawSection () {
// Close the current section and open the next one
Editor.execCommand('mceInsertRawHtml',false,`</section><section><h1 data-pointer>${ZERO_SPACE}</h1>`)
// Lookup the last inserted heading
console.log($('[data-pointer]'))
}
Note: The variable $ is not JQuery but is TinyMCE.DomQuery (everything is correctly setted up)
The log print only the previous existent headings, but not the last one. Probably there is somethings like a refresh to execute, but what i have to do in order to "communicate" between this command and the DomQuery APIs?
Instead of using mceInsertRawHtml same can be achieved using dom methods of tinymce.
var ed = tinymce.activeEditor;
var currentNode = ed.selection.getNode();
var newEle = ed.dom.create('section', {}, '<h1></h1>');
ed.dom.insertAfter(newEle, currentNode);
ed.selection.select(newEle.firstChild.firstChild);
ed.selection.collapse(false);
ed.focus();
Once the element is created same can be selected using dom methods to place the cursor at the begining/ending of the element.

jquery performance issues by enable drag & drop for new elements

I've a small webapplication that works with some drag / drop functionality. Just imagine a small order system in which you can say order 1 (draggable) will be done by employee 2 (droppable). That works fine. BUT:
Every 20sec. I ask the database via AJAX for new orders. These new orders will also be draggable. In case that another college has given an order to en employee the list of orders for every employee is also no loaded. To enable drag / drop after the ajax request I had to run:
$('.order').draggable({
..
});
and
$('.employee').dropable({
..
});
So the jquery function walks every 20 secunds through the hole DOM. After 10-15 minitues the app becomes very slow. Do you have an idea how to ingrease that process? Is it possible to give an absolute statement that every .order class is draggable even if this element will be create after the first registration?
I think the problem is that the elements that are already on the page become draggable over and over again.
I think a solution would be to assign a class to those that already have it:
$('.order').each(function() {
var element = $(this);
if ( !element.hasClass('event-already-attached')) {
element.addClass('event-already-attached').draggable({
})
}
});
Thanks # Jonas your great idea did it!
You can use the classes ui-droppable and ui-draggable for that job
$('.order').each(function() {
var element = $(this);
if ( !element.hasClass('ui-droppable')) {
element.droppable({
...
}
});

Do not reload entire template

I'm working with the Meteor Wordplay example right now. The project I have going is at https://github.com/ajcrites/meteor-wordplay
One feature that I wanted to add was not showing duplicate words and highlighting the duplicated word in red (animating it). I got this working via
Meteor.call('score_word', word_id, function (error, result) {
if (result !== undefined) {
var bg = $("#word_" + result.id).css('background-color');
$("#word_" + result.id).css('background-color', 'red');
//Otherwise transition takes effect *before* BG color is applied
setTimeout(function () {
$("#word_" + result.id).css('transition', 'all 2s')
.css('background-color', bg);
}, 10);
}
});
The server will send back a duplicated word ID if there is one.
This works fine, but the problem is that any time a word is added it seems like the entire > words template gets redrawn. I thought it was because the HTML was changing because of the animation that's going on, but I also tried doing this using CSS to do the animation instead, and even without duplicating words I can see in the console that the entire template gets redrawn.
I found a question on Meteor earlier that said the answer is to use a Meteor Collection to return from the template instead of some other abstract collection, but as far as I can tell I am:
Template.words.words = function () {
return Words.find({game_id: game() && game()._id,
player_id: this._id});
};
How can I stop the entire > words template from being redrawn each time? Is there a way to only add new words to the template?
Regardless of the answer to #1, is there a way that I can animate the red BG on a duplicated word and have it go through the full animation even if the > words template is updated?
I'll try to answer that hard question, please don't downvote me if I'm mistaken:
I think you can't nowadays. Maybe on the next render system they are building.
Yes, but I think it's not trivial with the current system. I have a game that I need to rotate a card when users choose one. What I've done was duplicating the div. One receives the last card and the other one receives the current card. So with jQuery I .show() or .hide() them and .addClass() that does the animation. In the class I have transitions and other things that rotate the card.
.
{{#with player_next_card}}
<div id="player-next-card" class="inner-card" style="display: none;">
<!--- stuff here --->
</div>
{{/with}}
{{#with player_last_card}}
<div id="player-last-card" class="inner-card">
<!--- same stuff with other info here --->
</div>
{{/with}}

Tree Menu? In JS?

I need a tree menu. But instead of a listview where you expand/collapse i need a dropdown box with the list and when you click on a element i need the box to update (with the first entry being 'Back') so the menu stays in a neat little dialog.
Does this menu have a name? Does anyone know where i can get code to do this?
I can think of several jQuery plugins which would soot your purposes. However, I would recommend jQuery iPod Style Drilldown Menu (Newer Version), which is exactly what it sounds like. The dropdown box updates in place, uses a cool sideways slide animation, and includes a "Back" button (as you desired). Finally, if you don't want any animation, you can try tweaking the plugin's many options. Setting crossSpeed to 0 may work, for example.
Adam is right, jQuery offers an assortment of menu's which you could use. Really though, this is a somewhat trivial problem, the code to write it would take up about 1/10th the space that jQuery's code will. So if possible I would say write it without jQuery.
The most effective method would be to do it JS OOP (Javascript Object-Oriented), but understandably this is a confusing topic.
Basically you just want something like:
function drillDown(){
//Any code that multiple drilldowns
// might need on the same page goes here
//Every instance of a drillDown will
// instantiate a new set of all functions/variables
// which are contained here
//A reference to the parent node the dropdown is placed in
this.parent;
//A reference to the div the dropdown is incased in
this.object;
//Returns a reference to this object so it can be
// stored/referenced from a variable in it's
// superclass
return this;
}
//Prototype Functions
//prototypes are shared by all
// instances so as to not double up code
//this function will build the dropdown
drillDown.prototype.build = function(parent){
//Too lazy to write all this, but build a div and your select box
// Add the select box to the div,
// Add the div to the parent (which is in your document somewhere)
var divEle = document.createElement('div');
var inputBox = document.createElement('input');
//code code code
divEle.appendChild(inputBox);
parent.appendChild(divEle);
}
//this function loads the newest dataset of
drillDown.prototype.loadNewDataSet = function(data){
//first clear out the old list
// remember we have a reference to both the
// 'object' and 'parent' by using
// this.object and this.parent
//load the data, we are going to use the text from
// the select boxes to load each new dataset, woo eval();
// If you didn't know, eval() turns a string into JS code,
// in this case referencing an array somewhere
var dataSet = eval(data);
//then loop through your list adding each new item
for(item in dataSet){
//add item to the list
//change the .onClick() of each one to load the next data set
// a la ->
selectItem.onClick = function(){this.loadNewDataSet(item);};
//if you name your datasets intelligently,
// say a bunch of arrays named for their respective selectors,
// this is mad easy
}
}
//Then you can just build it
var drillDownBox = new drillDown();
drillDownBox.build(document.getElementsByTagName('body')[0]);
drillDownBox.loadNewDataSet("start");
//assuming your first dataset array is named "start",
// it should just go
And by the way, Adam also said it, but wasn't explicit, this is refered to as a drill-down.

Categories

Resources