I'm trying to figure out why my function that renders bootstrap 5 cards fails but only after first clicking a pagination button then selecting a drop down menu option. The pagination buttons work fine it seems and the dropdown menu also works fine but only if a pagination button is never clicked.
The problem seems to be in my displayList() function. After clicking a pagination button and clicking a dropdown option the correct array is passed into displayList() but let paginatedItems = items.slice(start, end); returns an empty array. Any help at all would be greatly appreciated.
I've my stripped down code so that hopefully it's easier to read and understand:
const setPosters = [{id:'1',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'Sam Smith',category:'treatments'},{id:'2',img:'libs/images/pdfFile.jpg',title:'Treatments',author:'Dave Smith',category:'illness'},{id:'3',img:'libs/images/pdfFile.jpg',title:'Pain',author:'Sam Smith',category:'illness'},{id:'4',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'Bob Burke',category:'illness'},{id:'5',img:'libs/images/pdfFile.jpg',title:'Pain Treatments',author:'James Frank',category:'cures'},{id:'6',img:'libs/images/pdfFile.jpg',title:'Sinus Treatments',author:'Ted Reed',category:'illness'},{id:'7',img:'libs/images/pdfFile.jpg',title:'Migrain Treatments',author:'Ted Reed',category:'remedy'},{id:'8',img:'libs/images/pdfFile.jpg',title:'Flu Treatments',author:'Ted Reed',category:'remedy'},{id:'9',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'James Frank',category:'remedy'},{id:'10',img:'libs/images/pdfFile.jpg',title:'Flu Treatments',author:'Ralph Barnes ',category:'remedy'},{id:'11',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'Thomas Smith',category:'cures'},{id:'12',img:'libs/images/pdfFile.jpg',title:'Pain Treatments',author:'Ralph Barnes',category:'remedy'},{id:'13',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'Sam Smith',category:'treatments'},{id:'14',img:'libs/images/pdfFile.jpg',title:'Treatments',author:'Dave Smith',category:'illness'},{id:'15',img:'libs/images/pdfFile.jpg',title:'Pain',author:'Sam Smith',category:'illness'},]
// this continues for a total of about 80 cards in my local version
let posters;
const cardElement = $('#cards');
const paginationElement = $('#pagination');
let currentPage = 1;
let rows = 10;
let page_count;
///////////\\\\\\\\\\\
// **** Functions ***\\
//////////\\\\\\\\\\\\\\
function displayList(items, wrapper, rows_per_page, page) {
wrapper.html("");
page--;
console.log('displayList', items);
let start = rows_per_page * page;
let end = start + rows_per_page;
let paginatedItems = items.slice(start, end);
console.log('paginatedItems:', paginatedItems);
for (let i = 0; i < paginatedItems.length; i++) {
wrapper.append(`
<div class="col gx-2">
<div class="card">
<img
src=${paginatedItems[i].img}
class="card-img-top"
alt="..."
/>
<div class="card-body p-1">
</div>
<div class="card-footer">
Title: ${paginatedItems[i].title}
Category: <strong>${paginatedItems[i].category}</strong><br>
Author: ${paginatedItems[i].author}<br>
id: ${paginatedItems[i].id}<br>
</div>
</div>
</div>
`)
};
};
function setupPagination(items, wrapper, rows_per_page) {
wrapper.html("");
page_count = Math.ceil(items.length / rows_per_page) + 1;
for (let i = 1; i < page_count; i++) {
wrapper.append(`<li class="page-item"><a class="page-link" href="#">${i}</a></li>`);
};
};
/////////////////////\\\\\\\\\\\\\\\\\\\\
// **** Posters By Category Dropdown ***\\
////////////////////\\\\\\\\\\\\\\\\\\\\\\
//Adds Categories to Dropdown Select Options
uniqCatergoriesArray = uniqCatergories(setPosters, it => it.category);
function uniqCatergories(data, key) {
return [...new Map(data.map(x => [key(x), x])).values()]
}
for (var i = 0; i < uniqCatergoriesArray.length; i++) {
$('#selPosterCat').append($('<option>', {
value: uniqCatergoriesArray[i].category,
text: uniqCatergoriesArray[i].category,
}));
}
//Displays Posters By Category
$('#selPosterCat').on('change', function() {
console.log($('#selPosterCat').val());
$('#textSearchResult').html(' ');
$('#inlineFormInput').val('');
if ($('#selPosterCat').val() === 'all') {
posters = setPosters.filter(item => item);
displayList(posters, cardElement, rows, currentPage);
setupPagination(posters, paginationElement, rows);
} else {
posters = setPosters.filter(item => item.category === $('#selPosterCat').val());
console.log('dropdown', posters);
displayList(posters, cardElement, rows, currentPage);
setupPagination(posters, paginationElement, rows);
}
});
/////////////////\\\\\\\\\\\\\
// **** Pagination Click ***\\
/////////////////\\\\\\\\\\\\\\
$(document).on("click", "ul.pagination li a:not(.static)", function(e) {
currentPage = parseInt($(this).text());
posters = setPosters.filter(item => item);
displayList(posters, cardElement, rows, currentPage);
});
posters = setPosters.filter(item => item)
displayList(posters, cardElement, rows, currentPage);
setupPagination(posters, paginationElement, rows);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- *** Main Content Container *** -->
<div class="container-fluid" id="mainContainer">
<div id="mainCardContainer">
<!-- *** Search Bar *** -->
<div class="container-fluid">
<div class="row">
<div class="col">
<form id="form-id">
<div class="mb-3 d-flex justify-content-center" style="margin-top: 5vh;">
<select id="selPosterCat" class="form-select-sm w-25" aria-label="Default select example" style="border-right: none; border-width: 1px; height: 2rem;">
<option value="all" selected>All Categories</option>
<!-- other options added here by javaScript -->
</select>
</div>
</form>
</div>
</div>
<div class="row row-cols-1 row-cols-lg-5 row-cols-sm-2 g-4" id="cards">
<!-- individual cards added here by javaScript -->
</div>
<!-- *** Pagination Bar *** -->
<div class="container-fluid mt-3 pe-0 me-0">
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-end">
<li class="page-item" id="previousBtn">
<a class="page-link static" href="#" tabindex="-1" aria-disabled="true" id="">Previous</a>
</li>
<div class="d-flex" id="pagination">
<!-- page numbers added here by javaScript -->
</div>
<li class="page-item" id="nextBtn">
<a class="page-link static" id="" href="#">Next</a>
</li>
</ul>
</nav>
</div>
Just change this code:
item => item.category === $('#selPosterCat').val()
With this:
item => item.category = $('#selPosterCat').val()
and be sure from adding these links respectively:
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/css/bootstrap.min.css
https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/js/bootstrap.bundle.min.js
http://jsfiddle.net/rewan_95/107zxr3g is the fiddle I tested on.
Note: don't forget to add the above links for testing on jsfiddle
Sorry for the long post, but I tried explaining things in as much detail as possible.
So as I dive deeper into JavaScript and start learning more and more about AJAX requests and other components, I've stumbled across something that I can't seem to figure out.
So below, I will explain what I'm doing and what I would like to do, and see if someone has some guidance for me.
So here is my Vue.js app:
new Vue({
name: 'o365-edit-modal',
el: '#o365-modal-edit',
data: function() {
return {
list: {},
}
},
created() {
this.fetchApplicationsMenu();
},
methods: {
fetchApplicationsMenu() {
var self = this;
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'GET',
}).then(menu => self.list = menu.data);
},
changed() {
const selected = this.$data.list.selected;
function get_ids(list, field) {
const output = [];
for (let i=0; i < list.length ; ++i)
output.push(list[i][field]);
return output;
}
const result = get_ids(selected, "id");
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'PUT',
data: {
ids: result,
},
}).then((post) => {
return post;
},
(error) => {
console.log(error);
});
},
add(x) {
this.$data.list.selected.push(...this.$data.list.available.splice(x, 1));
this.changed();
},
remove(x) {
this.$data.list.available.push(...this.$data.list.selected.splice(x, 1));
this.changed();
},
},
});
Then here is the HTML portion that I'm using to render the two columns:
<div class="column is-half-desktop is-full-mobile buttons">
<nav class="level is-mobile mb-0">
<div class="level-left">
<div class="level-item is-size-5 has-text-left">Selected</div>
</div>
<div class="level-right">
<div class="level-item">
<i class="fas fa-sort-alpha-up is-clickable"></i>
</div>
</div>
</nav>
<hr class="mt-1 mb-3">
<draggable class="list-group"
v-model="list.selected"
v-bind="dragOptions"
:list="list.selected"
:move="onMove"
#change="changed">
<button class="button is-fullwidth is-flex list-group-item o365_app_handle level is-mobile" v-for="(app, index) in list.selected" :key="app.id">
<div class="level-left">
<span class="icon" aria-hidden="true">
<img :src="app.icon_url" />
</span>
<span>{{app.name}}</span>
</div>
<div class="level-right">
<span class="icon has-text-danger is-clickable" #click="remove(index)">
<i class="fas fa-times"></i>
</span>
</div>
</button>
</draggable>
</div>
<div class="column is-half-desktop is-full-mobile buttons">
<div class="is-size-5 has-text-left">Available</div>
<hr class="mt-1 mb-3">
<draggable class="list-group"
v-model="list.available"
v-bind="dragOptions"
:list="list.available"
:move="onMove">
<button class="button is-fullwidth is-flex list-group-item o365_app_handle level is-mobile" v-for="(app, index) in list.available" :key="app.id">
<div class="level-left">
<span class="icon" aria-hidden="true">
<img :src="app.icon_url" />
</span>
<span>{{app.name}}</span>
</div>
<div class="level-right">
<span class="icon has-text-primary is-clickable" #click="add(index)">
<i class="fas fa-plus"></i>
</span>
</div>
</button>
</draggable>
</div>
That outputs the following items, and all works great. See the video display below of each component working as needed. This all works great! I'm calling the changed() method on add and remove which grabs all the IDs and stores them in the DB via an endpoint.
The Problem:
Now I have the following dropdown menu, which depends on the fh/v1/menus/applications endpoint to pull in all the items as shown below:
As you can see below, when I open the dropdown, it has three apps, when I open the cog wheel and remove one of the apps and it saves it but the dropdown doesn't get automatically updated, I have to refresh the page and then I will see the updates.
Does anyone know how to fetch the new items without a refresh?
Here is the HTML and the JS for the dropdown piece:
HTML: As you can see in there, I have data-source="applications" which pulls in the items inside the init_menu as shown in the JS.
<div class="dropdown-menu" id="dropdown-o365" role="menu">
<div class="dropdown-content">
<div class="container is-fluid px-4 pb-4">
<?php if ($application = Applications::init()): ?>
<div class="columns">
<div class="dropdown-item column is-full has-text-centered is-size-6">
<div class="level is-mobile">
<div class="level-left">
<?= $application->get_name() ?>
</div>
<div class="level-right">
<a class="navbar-item modal-element icon" id="o365-apps-cogwheel" data-target="o365-modal-edit" aria-haspopup="true">
<i class="fa fa-cog"></i>
</a>
</div>
</div>
</div>
</div>
<div class="columns is-multiline" data-source="applications"></div>
<?php else: ?>
<div class="columns">
<div class="column is-full">
No applications present.
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
Then here is the JavaScript. I initilize the method inside DOMContentLoaded using init_menu('applications');:
function init_menu(paths)
{
paths.forEach(path => {
const target = document.querySelector('[data-source=' + path + ']');
if (target) {
wp.api.loadPromise.done(function () {
const Menus = wp.api.models.Post.extend({
url: wpApiSettings.root + 'fh/v1/menus/' + path,
});
const menus = new Menus();
menus.fetch().then(posts => {
// This returns the data object.
const data = posts.data;
let post_list;
// Check if it's an array and see if selected is empty otherwise show available.
if (Array.isArray(data.selected) && data.selected.length !== 0) {
post_list = data.selected;
} else {
post_list = data.available;
}
post_list.forEach(function (post) {
switch(path) {
case 'applications':
target.appendChild(create_apps_dom_tree(post));
break;
default:
console.log('Path route is invalid.');
break;
}
})
})
})
}
});
}
function create_apps_dom_tree(post) {
const {
icon_url,
url,
name,
} = post
const container = document.createElement('div');
container.className = 'column is-one-third is-flex py-0';
const anchor = document.createElement('a');
anchor.href = url;
anchor.className = 'dropdown-item px-2 is-flex is-align-items-center';
const figure = document.createElement('figure');
figure.className = 'image is-32x32 is-flex';
const img = document.createElement('img');
img.src = icon_url;
const span = document.createElement('span');
span.className = 'pl-2';
span.textContent = name;
figure.appendChild(img);
anchor.append(figure, span);
container.appendChild(anchor);
return container;
}
If anyone has some guidance or an answer on how to pull in live data from the database on the fly, that would be amazing.
Basically, I need my data-source: to automatically grab the items when my vue/db request is sent so I don't have to refresh the page.
Inside my Vue app, I have the following method:
fetchApplicationsMenu() {
var self = this;
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'GET',
}).then(menu => self.list = menu.data);
},
which calls a GET request and then stores the data inside the return { list: {} }.
A quick fix might be to just invoke init_menu() from the component's beforeDestroy() hook, called when the dialog closes. You might choose to do it from changed() instead if the dropdown is still accessible with this dialog open.
new Vue({
// option 1:
beforeDestroy() {
init_menu('applications');
},
// option 2:
methods: {
changed() {
init_menu('applications');
}
}
})
Alternative: You already know what the final application list is in changed(), so you could update the dropdown with the new list from that method.
function update_menu(path, post_list) {
const target = document.querySelector('[data-source=' + path + ']');
// remove all existing children
Array.from(target.childNodes).forEach(x => x.remove());
post_list.forEach(post => target.appendChild(create_apps_dom_tree(post)))
}
new Vue({
methods: {
changed() {
update_menu('applications', this.$data.available);
}
}
})
I'm trying to develop a Load-More button using Javascript calling an API done with PHP.
So far, so good. I can load the new objects in a range i += i + 4 (I load three new comments everytime I press the button).
This is my load-more button:
$(document).ready(function () {
$(".load-more").on('click', function () {
var tab = $(this).data('tab');
var next_page = $(this).data('next-page');
console.log(next_page);
console.log(tab);
$.get($(this).data('url') + '?tab=' + tab + '&page=' + next_page, function (data) {
addNewQuestions($.parseJSON(data));
});
});
});
And for every object loaded I want to print each of these html blocks.
<div class="question-summary narrow">
<div class="col-md-12">
<div class="votes">
<div class="mini-counts"><span title="7 votes">
{if $question['votes_count']}
{$question['votes_count']}
{else}
0
{/if}
</span></div>
<div>votes</div>
</div>
<div {if $question['solved_date']}
class="status answered-accepted"
{else}
class="status answer-selected"
{/if}
title="one of the answers was accepted as the correct answer">
<div class="mini-counts"><span title="1 answer">{$question['answers_count']}</span></div>
<div>answer</div>
</div>
<div class="views">
<div class="mini-counts"><span title="140 views">{$question['views_counter']}</span></div>
<div>views</div>
</div>
<div class="summary">
<h3>
<a href="{questionUrl($question['publicationid'])}" class="question-title" style="font-size: 15px; line-height: 1.4; margin-bottom: .5em;">
{$question['title']}
</a>
</h3>
</div>
<div class = "statistics col-sm-12 text-right" style="padding-top: 8px">
<span>
<i class = "glyphicon glyphicon-time"></i>
<span class="question-updated-at">{$question['creation_date']}</span>
</span>
<span>
<i class = "glyphicon glyphicon-comment"></i>
<span class="question-answers">{$question['answers_count']}</span>
</span>
</div>
</div>
</div>
The problem is that I have several conditions, as {if$question['votes_count']} and I'm struggling because I don't know how get those variables when rendering the html.
Then I found something but I can't figure out how to adapt to my case
On that addNewQuestions I think that a good approach would be:
function addNewQuestions(objects) {
$.each(objects, function (i, object) {
console.log(object);
var lastItem = $('div.active[role="tabpanel"] .question-line:last');
var newLine = lastItem.clone(true);
var newObject = newLine.find('.question-col:eq(' + i + ')');
newObject.find('.question-info-container').attr('data-id', object.publicationid);
newObject.find('.vote-count').html(object.votes);
updateTitleAndLink(newObject.find('.question-title'), object);
lastItem.after(newLine);
});
}
function updateTitleAndLink(questionTitle, object) {
questionTitle.attr('href', questionTitle.data('base-question-url') + object.publicationid);
questionTitle.html(object.title);
}
But nothing happens and I can't figure out why.
Any idea or suggestion?
Kind regards
I'm trying to change the default text of the of custom-file-input to show it in Spanish.
<div class="card">
<div class="card-header">
<h2>12. Carga de ficheros customizado</h2>
</div>
<div class="card-block">
<form>
<div class="form-group">
<label class="custom-file">
<input type="file" id="fileCustom" class="custom-file-input">
<span class="custom-file-control"></span>
</label>
<small id="fileCustom" class="form-text text-muted">Para hacerlo personal
hay que envolver en una etiqueta el elemento input.
</small>
</div>
</form>
</div>
</div>
The JavaScript that I'm using is the suggested in Translating or customizing the strings (bottom of the page)
$fileCustom: (
placeholder: (
en: "Choose file...",
es: "Seleccionar archivo..."
),
button-label: (
en: "Browse",
es: "Navegar"
)
);
I've already changed the language of my document <html lang="es"> and probably the mistake is in the relation between the javascript and the HTML code but I couldn't find a solution within the internet.
I suggest you to use image that you wanted to view "Seleccionar archivo..." and es: "Navegar"
You can use below line of codes:
var W3CDOM = (document.createElement && document.getElementsByTagName);
function initFileUploads() {
if (!W3CDOM) return;
var fakeFileUpload = document.createElement('div');
fakeFileUpload.className = 'fakefile';
fakeFileUpload.appendChild(document.createElement('input'));
var image = document.createElement('img');
image.src='pix/button_select.gif';
fakeFileUpload.appendChild(image);
var x = document.getElementsByTagName('input');
for (var i=0;i<x.length;i++) {
if (x[i].type != 'file') continue;
if (x[i].parentNode.className != 'fileinputs') continue;
x[i].className = 'file hidden';
var clone = fakeFileUpload.cloneNode(true);
x[i].parentNode.appendChild(clone);
x[i].relatedElement = clone.getElementsByTagName('input')[0];
x[i].onchange = x[i].onmouseout = function () {
this.relatedElement.value = this.value;
}
}
}
N:B: You should change the image url from above codes.
(function ($) {
$.fn.fCycle = function () {
var x;
for (x in arguments) {
$(arguments[x]).close();
}
$(this).collapse("show");
};
$(".btn-next").on('click', function() {
var form = [$("#name"), $("#surname"), $("#student_number"), $("#cellphone"), $("#email"), $("#course"), $("#year")],
i = 0,
a = "#" + $(this).attr("data-to"),
b = "#" + $(this).attr("data-from");
if ($(this).hasClass("to_course")) {
for (i; i < 5; i++) {
console.log(form[i].val());
if (form[i].val() === undefined) {
form[i].addClass("has-danger")
$(a).fCycle(b);
} else if (form[i].hasClass("has-danger") && form[i].length > 0) {
form[i].removeClass("has-danger")
}
}
$(a).fCycle(b);
}
});
}(jquery));
$(".btn-next").on('click', function () {
var form = [$("#name")],
i = 0,
a = "#" + $(this).attr("data-to"),
b = "#" + $(this).attr("data-from");
if ($(this).hasClass("to_course")) {
for (i; i < form.length; i++) {
if (form[i].val() === undefined) {
form[i].addClass("has-danger")
$(a).fCycle(b);
}
else if (form[i].hasClass("has-danger") && form[i].length > 0) {
form[i].removeClass("has-danger");
}
}
$(a).fCycle(b);
}
});
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.2/css/bootstrap.min.css" rel="stylesheet"/>
<form method="post" id="apply">
<div id="personal" class="collapse in">
<fieldset class="col-xs-10 col-xs-offset-1 form-group">
<div class="row">
<label for="names">Name(s)</label>
<input class="form-control" type="text" name="names" id="names" placeholder="Enter your full name here">
</div>
<hr>
<div class="row">
<nav>
<ul class="pager">
<li>
<button class="btn btn-danger btn-cancel" type="button">cancel</button>
</li>
<li class="pager-next">
<button class="btn btn-next btn-success to_course" type="button" data-to="course" data-from="personal">next</button>
</li>
</ul>
</nav>
</div>
</fieldset>
</div>
<div id="course" class="collapse">
<p>course</p>
</div>
</form>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.2/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
The above snippet doesn't seem to work at all - in the case of the Original (http://alpha.msauwc.co.za -> if you click on the Join the MSA button,
You'll find that the form does work. The issue is that the next button should not be working when the input fields have not been filled.
This has to be prevented using jquery.
Try defining your array with just selector strings, and then wrapping your form[i] objects like:
$(form[i]).val(), $(form[i]).addClass('has-danger'), etc
You can use these methods to disable & activate the button:
$(".btn-next").addClass('disabled') // when you want to disable it
$(".btn-next").removeClass('disabled') // when you want to activate it