Automated id increment for bootstrap collapse component - javascript

Bootstrap's Collapse components require sections (or cards) with unique id to enable collapsing / decollapsing each section independently. I am trying to create multiple sections equal to the amount of data points using "v-for" from vue.js. However, I don't know how to automatically generate unique ids for the cards. this is what I tried:
<div id = "story-content" class="content">
<div v-for="(subchapter, chapter) in story" class="card">
<div class="card-header">
<a href = "'collapse' + generateNewId" class="card-link" data-toggle="collapse" >
[[chapter]]
</a>
</div>
<div id = "'collapse' + getCurrentId" class="collapse show" data-parent="#story-content" >
<div class="card-body">
[[subchapter]]
</div>
</div>
</div>
</div>
<script>
var story = {{story | safe}};
var app = new Vue({
delimiters: [ '[[', ']]'],
el: '#portofolio',
data: {
story: story,
uniqueId: 0,
},
methods: {
generateNewId: function() {
return uniqueId + 1;
},
getCurrentId: function() {
return uniqueId;
}
}
})
</script>
Example of the story data structure:
{
"Chapter1": {
"Subchapter1": {
"side_note": "January - June 2019",
"desc": "description here"
},
"Subchapter2": {
"side_note": "January - June 2019",
"desc": "description here"
}
},
"Chapter2": {
"Subchapter1": {
"side_note": "",
"desc": ""
}
}
}
P.S I am not using bootstrap-vue since I did not know its existence until in the middle of my learning. I'm a beginner in web development. Hopefully there is a way to solve this issue without the need of bootstrap-vue as I will need to modify other components as well.

Bootstrap 4 uses jQuery for the Collapse component. So, one way to solve it is to simply use the Chapter keys as the unique id's...
<div id="story-content" class="content">
<div v-for="(subchapter, chapter) in story" class="card">
<div class="card-header">
<a :href="'#collapse' + chapter" :data-target="'#collapse' + chapter" class="card-link"
data-toggle="collapse"> [[chapter]] </a>
</div>
<div :id="'collapse' + chapter"
class="collapse show" data-parent="#story-content">
<div class="card-body"> [[subchapter]] </div>
</div>
</div>
</div>
Demo: https://codeply.com/p/0DEYVY4HUA
However, using jQuery with Vue isn't desirable, so a better way would be to recreate the collapse behavior in Vue as explained here

Related

Is it a good idea to use images local URL in array?

I want to make a list of projects and I made an array and using .map() method to display them, and I'm adding that list in the HTML element using .insertAdjacentHTML() method.
While inspecting the elements I can see the image path is coming fine but image is not showing in UI. What would be the reason of it?
const projectData = [
{
id: 01,
title: "Project 1",
url: "https://react-projects-1/",
imageUrl: "src/img/projects/project-1.png", //is it wrong using like this
description: "This is project 1 desc",
},
{
id: 02,
title: "Project 2",
url: "https://react-projects-2/",
imageUrl: "src/img/projects/project-2.png", //is it wrong using like this
description: "This is project 2 desc",
},
];
Mapping them and using the HTML:-
const projectHTML = projectData.map((el) => {
return `<div class="row align-items-center project_row">
<div class="col-md-5 col-12 order-md-2">
<a href="${el.url}" class="thumb">
<img
src="${el.imageUrl}"
class="w-100"
alt="${el.title}"
target="_blank"
/>
</a>
</div>
<div class="col-md-7 col-12 order-md-1 mt-4 mt-md-0">
<h3><span>${el.id}.</span>${el.title}</h3>
<p>
${el.description}
</p>
<ul class="buildTech">
<li><i class="fa-brands fa-react"></i> React</li>
</ul>
View
</div>
</div>`;
});
workWrapper.insertAdjacentHTML("afterbegin", projectHTML);
Inspect:-

In Knockout how do you go about nesting custom components when using an html binding?

I'm a bit new to knockout. I'm trying to get a custom component to dynamically load another custom component. I have a variable called location_board that contains html and that html has a custom component in it. . When I use the data-bind="html: location_board" it put the line for the in the dom but it doesn't run the custom component to fill out that node. Note: If I add the npc-widget directly to the template it works. It just doesn't work when it is added though the html binding. From my research I think this means I need to applyBindings on it? I'm not sure how to go about that in this situation though.
Any help is apricated.
Here is my full code for the custom component.
import {Database} from './database.js'
let database = new Database();
import {ResourceManager} from "./resource-manager.js";
let resourceManager = new ResourceManager();
let locationRegister = {
fog_forest: {
name: "The Ghostly Woodland",
image: "url('img/foggy_forest.jpeg')",
description: `
Place holder
`,
location_board: `
<npc-widget id="john-npc" params="id: 1, tree: 'shopkeep', speed: 50"></npc-widget>
<div>In</div>
`
}
};
ko.components.register('location-widget', {
viewModel: function (params) {
let self = this;
self.function = function () {
console.log("Functions!")
}
for(let k in locationRegister[params.id]) {
console.log(k)
this[k] = locationRegister[params.id][k];
}
console.log(this.name)
//return { controlsDescendantBindings: true };
},
template:
`
<div class="row">
<div class="col-lg-12">
<h2 id="title" class="tm-welcome-text" data-bind="html: name"></h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div data-bind="style: { 'background-image': image}" class="location-picture mx-auto d-block">
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="location-description mx-auto d-block" data-bind="html: description"></div>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-12">
<div id="location_board" data-bind="html: location_board">
</div>
</div>
</div>
`
});

How combine enable popovers everywhere AND use container: 'body' option?

I noticed my Bootstrap 5 popovers are missing the arrow and I believe it's caused by the parent element interfearing somehow.
The docs are kind enough to offer help by showing me how to use the container: 'body' option - on ONE item. I need to apply that to all at once. How would I do that?
I'm using codeKit3 to bundle everything. The import statement looks like this (working):
// Bootstrap components
import '#popperjs/core/lib/index.js'
import bootstrap from 'bootstrap/dist/js/bootstrap.min.js'
This is the html I'm using:
<div class="card">
<div class="card-header">
<i class="fas fa-calendar-day"></i> 24-h
</div>
<div class="card-body">
<div class="row">
<div class="col-6">
högsta temp
</div>
<div class="col-3">
<span class="card-value" id="min-rh-24h">24</span><span class="card-value">°C</span>
</div>
<div class="col-3">
<a href="#" data-bs-toggle="popover" data-bs-placement="top"
title="Ytterligare information"
data-bs-content="Some sample text right here"><i
class="fas fa-info" id="maxCelsius24hInfo"></i></a>
</div>
</div>
<div class="row">
<div class="col-6">
lägsta temp
</div>
<div class="col-3">
<span class="card-value" id="min-rh-24h">12</span><span class="card-value">°C</span>
</div>
<div class="col-3">
<a href="#" data-bs-toggle="popover" data-bs-placement="top" data-container="body"
title="Ytterligare information"
data-bs-content="Some other sample data right here"><i
class="fas fa-info" id="minCelsius24hInfo"></i></a>
</div>
</div>
</div>
</div>
Code snippet from Bootstrap 5 docs:
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'))
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl)
})
The only way the documentation shows how to implement the container option is like this:
var popover = new bootstrap.Popover(document.querySelector('.example-popover'), {
container: 'body'
})
I need to use the container: 'body' option in the first example (the one with map()). How would I do that?
Thank you!
var popover = new bootstrap.Popover(document.querySelector('.example-popover'), {
container: 'body'
})
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl)
})
This does the same basic method call new bootstrap.Popover(...) in both places - so just add the second parameter in the second version, exactly the same as it was used in the first one:
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl, { container: 'body' })
})

ng-repeat on bootstrap tab-content/tab-pane

I am having trouble getting my tab content to appear properly on page load. What I have is a factory of Objects called apiList, I then use two ng-repeats, one which hits each object in the apiList, then the next one which iterates through that object and puts all of its data on the page. I use #tab{{$index}} to keep my data-toggles aligned between the buttons and tab-panes.
The problem seems to be with how I use the active attribute on my tab-pane.
I have three known test cases so far.
I can only get information from the first Object by using the condition ng-class="{ 'active': $index == 0}" as in..
<div ng-repeat="(key, data) in apiList[0]" class="tab-pane active" id="tab{{$index}}" ng-class="{ 'active': $index == 0}">
If I remove the conditional from the line then nothing appears
And if I just add active to the pane value (with no condition)
(i.e. <div ng-repeat="(key, data) in apiList[0]" class="tab-pane active" id="tab{{$index}}"> They all appear.
So my problem seems to be with how I need to turn on/off my active for the ng-repeat and I am just getting lost.
Also, clicking on accounts/customers does not update the tabs. They both have the right data-toggle ID's (0 and 1) in respect to the tab-panes so I assume it is part of the active issue. And whenever I click on my tab panes, they do expand just fine.
Here is my Factory, ng-repeat(s), and the code that was my original setup (functional).
Factory
.factory('APIMethodService', [function() {
var Head = "api.verifyvalid";
return {
apis: [
{
accounts: [
{
parameters : [
{
name : "Accounts",
version : "1.0"
}
],
uri: Head + "/v1/accounts/account_number",
methods : [
{
name: "Account Number",
desc: "Returns the account number."
}, {
name: "Account Money",
desc: "Returns the monetary amount within the account."
}
]
},
{
parameters : [
{
name : "Accounts",
version : "2.0"
}
],
uri: Head + "/v2/accounts/account_number",
methods: [
{
name: "Account Number",
desc: "Returns the account number."
}, {
name: "Account Money",
desc: "Returns the monetary amount within the account."
}, {
name: "Account Token",
desc: "Returns the account's token."
}
]
}
],
customers:[
{
parameters : [
{
name : "Customers",
version : "1.0"
}
],
uri: Head + "/v1/customers/customer_number",
methods : [
{
name: "Customer Name",
desc: "Returns the customer's name."
}, {
name: "Customer ID",
desc: "Returns the customer's ID."
}
]
},
{
parameters : [
{
name : "Customers",
version : "2.0"
}
],
uri : Head + "/v2/customers/customer_number",
methods: [
{
name: "Customer Name",
desc: "Returns the customer's name."
}, {
name: "Customer ID",
desc: "Returns the customer's ID."
}, {
name: "Customer Email",
desc: "Returns the customer's email."
}
]
}
]
}
]
};
ng-repeat (just the right-hand tabs)
<div class="col-md-9">
<div class="tab-content">
<div ng-repeat="(key, data) in apiList[0]" class="tab-pane active" id="tab{{$index}}">
<div ng-repeat="api in apiList[0][key]">
<div class="panel panel-info" id="panel{{$index}}">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-target="#collapse{{key}}{{$index}}" class="collapsed">
{{api.uri}}<i class="newTab" ng-click="apiTab(api)">(Open in new tab)</i>
</a>
</h4>
</div>
<div id="collapse{{key}}{{$index}}" class="panel-collapse collapse">
<div class="panel-body">
<table class="table">
<tr ng-repeat="method in api.methods">
<td>{{method.name}}</td>
<td>{{method.desc}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The following code is what I originally had, before trying to crunch it down more
<div class="col-md-9" style="display:none">
<div class="tab-content">
<!-- Accounts -->
<div class="tab-pane active" id="tab0">
<div ng-repeat="api in apiList[0].accounts">
<div class="panel panel-info" id="panel0">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-target="#collapseAccountsV{{$index}}" class="collapsed">
{{api.uri}}
</a>
<i class="newTab" ng-click="apiTab(api)">(Open in new tab)</i>
</h4>
</div>
<div id="collapseAccountsV{{$index}}" class="panel-collapse collapse">
<div class="panel-body">
<table class="table">
<tr ng-repeat="method in api.methods">
<td>{{method.name}}</td>
<td>{{method.desc}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Customers -->
<div class="tab-pane" id="tab1">
<div ng-repeat="api in apiList[0].customers">
<div class="panel panel-info" id="panel1">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-target="#collapseCustomersV{{$index}}" class="collapsed">
{{api.uri}}<i class="newTab" ng-click="apiTab(api)">(Open in new tab)</i>
</a>
</h4>
</div>
<div id="collapseCustomersV{{$index}}" class="panel-collapse collapse">
<div class="panel-body">
<table class="table">
<tr ng-repeat="method in api.methods">
<td>{{method.name}}</td>
<td>{{method.desc}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Other -->
<div class="tab-pane fade" id="tab3">
<!-- TBA -->
</div>
</div>
</div>
Noob mistake, everything works fine! I was using my old code as the base outline on the same page and had set that old code to style="display:hidden" but this still meant that the ID's were apart of the DOM, hence why I couldn't change my tab's properly (duplicate/conflicting ID's).

Bootstrap radio tabs not working with Knockout

I tried to use Bootstrap radio button data toggle for tabs, my render of DOM for this happens through Knockout. the tab does not seem to switch between active and inactive tab correctly.
Here is a working example: http://jsfiddle.net/msurguy/x8GvJ/
Here is how to recreate the problem (JSFiddle):
<div id="tabtest" data-bind="foreach: arrayItems">
<div data-bind="attr:{'id':'itemtab' + $index()}" class="btn-group " data-toggle="buttons-radio">
<a data-bind="attr:{href:'#entitysearchitem' + $index()}" class="btn " style="background-color: #f5f5f5" data-toggle="tab">Item</a>
<a data-bind="attr:{href:'#entitymemberssearch' + $index(),'data-index':$index}" style="background-color: #f5f5f5" class="btn " data-toggle="tab">Member</a>
</div>
<div class="tab-content">
<div class="tab-pane fade in " data-bind="attr:{id:'entitysearchitem' + $index()}">
<div class="row padding3centt">
<div class="col-xs-12">
<div class="row">
<div class="col-xs-1"></div>
<div class="col-xs-11">
<ul data-bind="foreach:interests" class="list-styled">
<li>
<span class="fontsize80" data-bind="text:Description">
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" data-bind="attr:{id:'entitymemberssearch' + $index()}">
Hello i am entitymemberssearch
</div>
</div>
<br/>
</div>
JS:
var myObject = [{
"first": "John",
"interests": [ {"Description": "Reading"}, {"Description":"Mountain Biking"}, {"Description":"Hacking"} ]
},
{
"first": "Boy",
"interests": [ {"Description":"Reading"}, {"Description":"Mountain Biking"}, {"Description":"Hacking"} ]
}]
var koData ={
arrayItems :ko.observableArray()
}
ko.mapping.fromJS(myObject, {}, koData.arrayItems);
ko.applyBindings(koData, $("#tabtest").get(0));
I have described the problem in the picture below as well. Any ideas on how to correctly get the bootstrap radio tabs to work?
You need to reconsider the approach you are trying to use : Here's a beautiful solution by Ryan Niemeyer which you can refer to..
http://jsfiddle.net/rniemeyer/cGMTV/
You need to rewrite your code in following format.
var Section = function (name, selected) {
this.name = name;
this.isSelected = ko.computed(function() {
return this === selected();
}, this);
}
var ViewModel = function () {
var self = this;
self.selectedSection = ko.observable();
self.sections = ko.observableArray([
new Section('Section 1', self.selectedSection),
new Section('Section 2', self.selectedSection),
new Section('Section 3', self.selectedSection)
]);
//inialize to the first section
self.selectedSection(self.sections()[0]);
}
ko.applyBindings(new ViewModel());

Categories