I'm going to create a hierarchical template html tags with knockoutjs :
Json Data :
{
"BenchmarkGroups": [{
"Id": 43,
"Title": "Display",
"PersianTitle": "Display",
"ObjectId": 12,
"ParentId": 0,
"OrderNumber": 0,
"ImagePath": "/File/Get/14971.jpg.ashx",
"Attachments": [],
"ChildrenBenchmarkGroup": [{
"Id": 44,
"Title": "Screen measurements",
"PersianTitle": "Screen measurements",
"ObjectId": 12,
"ParentId": 43,
"OrderNumber": 0,
"ImagePath": "",
"Attachments": [],
"ChildrenBenchmarkGroup": [],
"ParentBenchmarkGroup": null,
"Object": null,
"BenchmarkItems": []
},
{
"Id": 45,
"Title": "Viewing angles",
"PersianTitle": "Viewing angles",
"ObjectId": 12,
"ParentId": 43,
"OrderNumber": 0,
"ImagePath": "",
"Attachments": [],
"ChildrenBenchmarkGroup": [],
"ParentBenchmarkGroup": null,
"Object": null,
"BenchmarkItems": []
},
{
"Id": 46,
"Title": "Color charts",
"PersianTitle": "چارت رنگ ها",
"ObjectId": 12,
"ParentId": 43,
"OrderNumber": 0,
"ImagePath": "",
"Attachments": [],
"ChildrenBenchmarkGroup": [],
"ParentBenchmarkGroup": null,
"Object": null,
"BenchmarkItems": []
}],
"ParentBenchmarkGroup": null,
"Object": null,
"BenchmarkItems": []
}]
}
Html :
<script id="BenchmarkGroups-template" type="text/html">
<li>
<!-- ko if: $index() > 0 -->
<hr style="width: 98%; margin: 10px auto;" />
<!-- /ko -->
<div data-name="benchmarkgroup-header" style="background: #E4E45B; padding: 10px; margin: 0 10px;" data-bind="attr: { 'data-groupId': Id }">
<div style="float: right; margin: 0 20px 0 0;">
<h3 style="direction: rtl; margin: 0; cursor: pointer;" data-bind="html: PersianTitle, event: { click: edit }" title="عنوان گروه بنچمارک به فارسی"> </h3>
</div>
<!-- ko if: ImagePath() != '' -->
<img data-bind="attr: { src : ImagePath() + '?maxwidth=50&maxheight=50' }" src="#" alt="" style="max-width: 50px; max-height: 50px; float: left; margin: 0 10px 0 0;" />
<!-- /ko -->
<div style="float: left; margin: 0 0 0 20px;">
<h3 style="direction: ltr; margin: 0; cursor: pointer;" data-language="en" data-bind="html: Title, event: { click: edit }" title="عنوان گروه بنچمارک به انگلیسی"> </h3>
</div>
<div class="clear-fix"></div>
</div>
//`ReferenceError: edit is not defined` at leaf
<ul style="width: 100%;" data-bind="template: { name: 'BenchmarkGroups-template', foreach: ChildrenBenchmarkGroup }"></ul>
</li>
</script>
<div style="width: 70%; margin: 10px auto;" id="BenchmarkGroupsDiv" data-bind="visible: BenchmarkGroups().length > 0">
<fieldset>
<legend>#Html.DisplayNameFor(model => model.BenchmarkGroups)</legend>
<ul style="width: 100%;" data-bind="template: { name: 'BenchmarkGroups-template', foreach: BenchmarkGroups }"></ul>
</fieldset>
</div>
It will be OK if I remove the following line from template but it doesn't show ChildrenBenchmarkGroup :
<ul style="width: 100%;" data-bind="template: { name: 'BenchmarkGroups-template', foreach: ChildrenBenchmarkGroup }"></ul>
But with above line knockoutjs throw an error at the leaf objects at the line.
I've found the problem:
I wrote a mapper option as the following :
var mappingOptionsBenchmarkGroups = {
create: function(options) {
return (new (function() {
var self = this,
$target = undefined,
$acceptButton = $('<input type="button" title="ثبت" style="margin:0 5px 0 0; width:auto; height:auto; border:1px solid #4651D8; background: auto; border-radius:0; background-image:none; line-height:20px; box-shadow:none;" value="✓" />'),
$rejectButton = $('<input type="button" title="انصراف" style="margin:0 5px 0 0; width:auto; height:auto; border:1px solid #4651D8; background: auto; border-radius:0; background-image:none; line-height:20px; box-shadow:none;" value="×" />'),
$textBox = $('<input type="text" style="width:150px; box-shadow: none; padding: 2px; border-radius: 0;" />');
$acceptButton.click(function() {
hideEdit();
});
$rejectButton.click(function() {
hideEdit();
});
$textBox.keyup(function(e) {
if (e.keyCode == 27) {
hideEdit();
}
});
self.edit = function(arg1, arg2) {
if ($target) hideEdit();
$target = $(arg2.target);
if ($target.attr('data-language') == 'en') {
$textBox.css('direction', 'ltr');
$textBox.attr('placeholder', 'نام انگلیسی');
} else {
$textBox.css('direction', 'rtl');
$textBox.attr('placeholder', 'نام فارسی');
}
showEdit();
};
function showEdit() {
$textBox.val($target.text());
$target.hide()
.before($textBox.show())
.before($acceptButton.show())
.before($rejectButton.show());
$textBox.show().focus();
}
function hideEdit() {
$target.show();
$textBox.hide();
$acceptButton.hide();
$rejectButton.hide();
$target = undefined;
}
ko.mapping.fromJS(options.data, {}, this);
})());
}
};
it applies to the root and I had to use $root.edit instead of edit.
Related
I want to display percent instead of an integer.
Now it simply shows the value as integer when hovering on the data point of the chart (the tooltip).
I think I need to set in the formatter formula like this:
let test = value / SUM(all_values)
return test.toFixed(0) + '%'
But, I could not find it in their documentation, the best I found is this one which always gives me 100%, per every data point:
tooltip: {
y: {
formatter: function(value, opts) {
let percent = opts.w.globals.seriesPercent[opts.seriesIndex][opts.dataPointIndex];
return percent.toFixed(0) + '%'
}
}
}
You're right that you'll need to use the tooltip formatter, but the array you're using to get the percentage values isn't correct.
If you log the values opts.w.globals.seriesPercent you'll find that it's all 100's:
[[100, 100, 100, ..., 100]]
(I suspect this might be because it's intended to be used with other chart types.)
You can still get the percentage though, it'll just need to be worked out.
Using your same method to format the tooltip, but getting the values from opts.series instead:
tooltip: {
y: {
formatter: function(value, opts) {
const sum = opts.series[0].reduce((a, b) => a + b, 0);
const percent = (value / sum) * 100;
return percent.toFixed(0) + '%'
},
},
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="app/app.js"></script>
<script>
let apiLabels = [];
let apiData = [];
let currentTimestamp = (Math.floor(Date.now() / 1000)).toString();
//use this URL for daily fetch
//let url = "https://aave-api-v2.aave.com/data/rates-history?reserveId=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480xb53c1a33016b2dc2ff3653530bff1848a515c8c5&resolutionInHours=24&from="+currentTimestamp;
let url = "https://aave-api-v2.aave.com/data/rates-history?reserveId=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480xb53c1a33016b2dc2ff3653530bff1848a515c8c5&resolutionInHours=24&from=1639813032";
// months list
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
// fetch from api
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
let myData = JSON.parse(this.responseText);
console.log(myData);
for (let i = 0; i < myData.length; i++) {
//let formedDate = (myData[i]['x']['year']+'-'+myData[i]['x']['month']+'-'+myData[i]['x']['date']).toString();
let formedDate = monthNames[myData[i]['x']['month']] + ' ' + myData[i]['x']['date'];
console.log(formedDate);
let formedLiquidData = (myData[i]['liquidityRate_avg'] * 100).toFixed(2);
console.log(formedLiquidData+"%");
apiLabels.push(formedDate);
apiData.push(formedLiquidData);
}
const data = {
labels: apiLabels,
datasets: [
{
label: "USDC",
borderColor: "rgb(180,11,107)",
data: apiData
}
]
};
const config = {
type: "line",
data: data,
options:{
scales: {
y: {
ticks: {
callback: function (value, index, values) {
return value + '%';
}
}
},
},
},
};
const myChart = new Chart(document.getElementById("myChart"), config);
}
});
xhr.open(
"GET",
url
);
xhr.send();
</script>
</div>
<div id="app"></div>
<title>brew</title>
<style>
body {
background-color: white;
}
.center-form {
width: 80%;
margin: auto;
}
#title {
color: teal;
text-align: center;
}
.waitlist {
position: relative;
z-index: 3;
width: 146px;
height: 30px;
margin-top: 60px;
border-style: solid;
border-width: 1px;
border-color: #081852;
background-color: transparent;
box-shadow: -10px 10px 0 0 #081852;
color: #081852;
font-size: 12px;
line-height: 30px;
font-weight: 400;
text-align: center;
letter-spacing: 0.21px;
position: fixed;
right: 3%;
top: -5%;
}
.waitlist-button {
display: inline-block;
padding: 9px 15px;
background-color: #3898EC;
color: white;
border: 0;
line-height: inherit;
text-decoration: none;
cursor: pointer;
border-radius: 0;
}
.box-prices {
padding: 20px;
width: 200px;
height: auto;
color: #000;
/* background-color: #fff; */
border: 4px solid #1c1c53;
border-radius: 2px;
}
.dropbtn {
background-color: navy;
color: white;
padding: 16px;
font-size: 16px;
border: none;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
}
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
.dropdown-content a:hover {
background-color: #ddd;
}
.dropdown:hover .dropdown-content {
display: block;
}
.dropdown:hover .dropbtn {
background-color: #0b95f1;
}
footer {
display: flex;
justify-content: space-between;
}
.myChart {
width: 50%;
height: 40px;
}
/* Decorative */
a {
text-decoration: none;
color: #555;
}
footer {
background-color: #82cdf8;
padding: 20px 40px;
}
.right {
float: left;
text-align: center;
}
.article {
color: white;
}
</style>
</head>
<body>
<img src="https://uploads-ssl.webflow.com/61768faf04d20bf3487b5a2a/6176a6363e64b8d84e32d0b7_brew-icon-256px-v2.png"
loading="eager" width="55" height="55" alt="Brew" class="img-logo" position="absolute" top="-3%" left="7;">
<!-------------- title------------------------>
<div class="container">
<div class="row">
<div class="col-md-12 mt-2">
<h1 style="color: #1c98">
Stablecoin Deposits on Aave
</h1>
<a data-w-id="14f1dfb2-926f-581f-7faa-3f62171b1db3" href="" class="waitlist waitlist-button">Join the
waitlist</a>
</div>
</div>
</div>
<!------------------------AAVE PRICE ------------>
<div class="container">
<div class="row mt-5">
<div class="col-md-6">
<div class="aave-prices">
<p>AAVE PRICE</p>
<p>$172.8</p>
<p>16%</p>
</div>
</div>
<!------------------------AAVE DESCRIPTION ------------>
<div class="col-md-6">
<h1>What is AAVE ?</h1>
<p>Aave is an open source and non-custodial liquidity protocol for earning
interest on deposits and borrowing assets.</p>
</div>
</div>
<!------------------------AAVe CHAIN ------------>
<div class="row">
<div class="col-md-12 mt-3">
<button class="dropbtn">CHAIN</button>
<div class="dropdown-content">
Polygon
Ethereum
Avalance
</div>
</div>
</div>
</div>
<!------graph----->
<div>
<canvas id="myChart" height="50" weidth="50"></canvas>
</div>
<!-- calculator -->
<div class="container mt-5" style="border: 1px solid black; border-radius: 5px;">
<div class="row">
<div class="col-md-12">
<h1 id="title">How Much You Can Earn ?</h1>
</div>
</div>
<br>
<div class="row">
<div class="col-md-6">
<form>
<div class="form-group mt-2">
<label class="form-group">Amount</label>
<input class="form-control mt-2" type="text" id="principal" placeholder="$">
</div>
<div class="form-group mt-2">
<label class="form-group">Interest rate: 4%</label>
</div>
<div class="form-group mt-2">
<label class="form-group">Compound Freequency: Daily</label>
</div>
<div class="form-group mt-2">
<label class="form-group">Payout Freequency: Monthly</label>
</div>
<button class="btn btn-block btn-info mt-3" type="button" onclick="calculate()">Calculate</button>
</form>
</div>
<div class="col-md-6">
<form style="padding-top: 70px;">
<div class="form-group mt-2">
<label class="form-group">Interest rate: 5%</label>
</div>
<div class="form-group mt-2">
<label class="form-group">Compound Freequency: Daily</label>
</div>
<div class="form-group mt-2">
<label class="form-group">Payout Freequency: Monthly</label>
</div>
</form>
</div>
</div>
<br>
<div class="row">
<div class="col-md-6">
<h5 class="total">Result USD</h5>
<h1 id="USD"></h1>
</div>
<div class="col-md-6">
<h5 class="total">Result USDT</h5>
<h1 id="USDC"></h1>
</div>
</div>
</div>
<!-- calculator end -->
<div>
<canvas id="myChart_subbagain" height="50" weidth="50" top="80%"></canvas>
</div>
<!----footer-->
<div class="col-md-12 mt-5">
<footer>
<div class="col-md-6">
<img src="/dawnload.png" alt="" width="150">
</div>
<div class="col-md-6">
<section class="right">
Join Brew Today
<article>USDC is not a legal tender recognised by US or any other government. Unlike a bank account, your
deposit is not insured. While Brew will make every effort to ensure that your deposit earn the best interest
rates in the most secure way possible, please note that any investment entails risk. Interest Rates are
subject to change anytime as per the market conditions.</article>
</section>
</div>
</footer>
</div>
</body>
<script type="text/javascript">
function calculate() {
var principle = 0;
var interest = 5 / 100;
var numberOfPeriod = 12;
var time = 10;
var CI = 0;
principle = document.getElementById("principal").value;
// CI = ((p * (1 + i) ^ n) - p);
CI = principle * (1 + interest / numberOfPeriod) ^ (numberOfPeriod * time);
document.getElementById("USD").innerHTML = CI;
document.getElementById("USDC").innerHTML = CI;
}
</script>
<!------cal graph-->
</html>
This is the array
[
{
"TO":"test#gmail.com",
"FROM":"nathanoluwaseyi#gmail.com",
"SUBJECT":"subject 1",
"NAME":"Oluwaseyi Oluwapelumi",
"MESSAGE-DATE":[
[
"Hey eniayomi heeyyy",
"2019-12-03 20:49:07"
]
]
},
{
"TO":"test#gmail.com",
"FROM":"pelz#gmail.com",
"SUBJECT":"Thanks for contacting R",
"NAME":"",
"MESSAGE-DATE":[
[
"Thanks for contacting me! Once i check my email, i shall definitely get back.",
"2019-08-18 19:48:10"
],
[
"will check it.",
"2019-08-18 19:48:10"
]
]
}
]
i need it to display on the angular frontend.
this is the mail.component.html file
<div class="card-body p-0">
<div class="float-left" style="width: 330px; height: 430px; border-right: 1px solid #dad9d9; overflow-x: hidden; overflow-y: auto;">
<div class="p-2 profile-card" style="width: 315px; height: 100px; border-bottom: 1px solid #dad9d9;" *ngFor="let mail of dataservice.data; let i = index;" (click)="viewMail(mail.MESSAGE_DATE,mail.FROM,mail.NAME,mail.DATE,i)" [ngClass]="{'highlight': selectedIndex === i}">
<div class="row">
<div class="col-md-3 pt-2">
<div class="rounded-circle shadow" style="background-image: url('images/avt.jpg'); background-repeat: round; height: 70px; width: 70px;">
<div style="height: 20px; width: 20px; border: 3px solid white;" class="rounded-circle bg-success"></div>
</div>
</div>
<div class="col-md-7 p-0 pl-3 pt-4" style="line-height: 12px;">
<p style="font-size:18px;"><b>{{mail.FROM}}</b></p>
<p style="font-size:13px;">{{mail.NAME }}.</p>
</div>
<div class="col-md-2 p-0 pt-3" style="line-height:11px;">
<p class="text-secondary" style="font-size:12px;">20m <i class="fa fa-star fa-md" aria-hidden="true"></i></p>
</div>
</div>
</div>
this is the data.service.ts file
mail_det() {
this.message = 'Welcome!';
console.log(this.message);
this.staff_email=sessionStorage.getItem('email');
console.log(this.staff_email)
this.http.get(this.domain_protocol + this.f_domain_name+'/api/v1.0/get_user_detail/?id='+this.staff_email)
.subscribe((res) => {
this.data = res
console.log(this.data)
})
}
this is the mail.component.ts file
viewMail(mail, mailer, mailee, user_date, _index: number) {
this.router.navigate(['mail/'+ mailer])
console.log(mail)
console.log(mailer)
console.log(user_date)
this.message = ''
sessionStorage.setItem('mailer', mailer)
sessionStorage.setItem('mailee', mailee);
sessionStorage.setItem('user_date', user_date)
console.log(sessionStorage.getItem('mailer'))
this.user_message = mail;
this.mailee = mailee;
this.user_date = user_date;
this.selectedIndex = _index;
}
i am doing something wrong. The only thing i get to show is the mail.FROM and mail.SUBJECT. I know this is because of the array in the mesage part. I dont know how to go about that.
In Data.service.ts
mail_det() {
this.message = 'Welcome!';
console.log(this.message);
this.staff_email=sessionStorage.getItem('email');
console.log(this.staff_email)
this.http.get(this.domain_protocol + this.f_domain_name+'/api/v1.0/get_user_detail/?id='+this.staff_email);
}
in mail.component.ts
public data: Array<any> = [];
constructor(public dataSrv: DataService <-- this is the class name of the data service you created; import it)
ngOnInit(){
this.dataSrv.mail_det().subscribe(result =>{
console.log(result); <-- your api response;
this.data = result;
}, error => {console.log(error);
});
}
in mail.component.html
<div class="card-body p-0">
<div class="float-left" style="width: 330px; height: 430px; border-right: 1px solid #dad9d9; overflow-x: hidden; overflow-y: auto;">
<div class="p-2 profile-card" style="width: 315px; height: 100px; border-bottom: 1px solid #dad9d9;" *ngFor="let mail of data; let i = index;" (click)="viewMail(mail.MESSAGE_DATE,mail.FROM,mail.NAME,mail.DATE,i)" [ngClass]="{'highlight': selectedIndex === i}">
<div class="row">
<div class="col-md-3 pt-2">
<div class="rounded-circle shadow" style="background-image: url('images/avt.jpg'); background-repeat: round; height: 70px; width: 70px;">
<div style="height: 20px; width: 20px; border: 3px solid white;" class="rounded-circle bg-success"></div>
</div>
</div>
<div class="col-md-7 p-0 pl-3 pt-4" style="line-height: 12px;">
<p style="font-size:18px;"><b>{{mail.FROM}}</b></p>
<p style="font-size:13px;">{{mail.NAME }}.</p>
</div>
<div class="col-md-2 p-0 pt-3" style="line-height:11px;">
<p class="text-secondary" style="font-size:12px;">20m <i class="fa fa-star fa-md" aria-hidden="true"></i></p>
</div>
</div>
</div>
Still working on my menu and struggling with a new problem.
I want the user to be able to the LI submenus when there is a click on the UL.
The problem is that I don't see how to aim only at the linked LI elements. When I click on any UL, it opens all the LI.
An easy way could be to create different UL in HTML, but I would like to keep this short generated with a loop menu.
How can I aim at the precise UL with the #click event, to open only its child LI?
new Vue({
el: "#app",
data: {
categories: {
Atoms: ['Buttons', 'Icons'],
Molecules: [],
Organisms: [],
Templates: [],
Utilities: ['Grid']
},
openSubCategories: false,
},
})
.doc_nav {
display: flex;
justify-content: around;
}
.doc_nav__ul {
margin: 0 30px;
}
.doc_nav__li {
text-align: center;
}
.doc_nav__li:first-child {
margin-top: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<header class="doc_header">
<nav class="doc_nav">
<ul #click="openSubCategories = !openSubCategories" class="doc_nav__ul" v-for="[ category, subCategories ] in Object.entries(categories)" :key="category"> {{category}}
<template v-if="openSubCategories == true" >
<li class="doc_nav__li" v-for="subCategory in subCategories" :key="subCategory">
{{ subCategory }}
<!-- <router-link :to="subCategory"> {{ subCategory }} </router-link> -->
</li>
</template>
</ul>
</nav>
</header>
</div>
Use CSS to hide li.
I think you can handle it.
new Vue({
el: "#app",
data: {
categories: {
Atoms: ['Buttons', 'Icons'],
Molecules: [],
Organisms: [],
Templates: [],
Utilities: ['Grid']
},
currentActiveCategory: null,
},
method: {
changeClickUl(category) {
if (category == this.currentActiveCategory) this.currentActiveCategory = null
else this.currentActiveCategory = category
}
}
})
.doc_nav {
display: flex;
justify-content: around;
}
.doc_nav__ul {
margin: 0 30px;
}
.doc_nav__ul:not(visible) {
display: none;
}
.doc_nav__li {
text-align: center;
}
.doc_nav__li:first-child {
margin-top: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<header class="doc_header">
<nav class="doc_nav">
<ul #click="changeClickUl(category)" :class="{visible:currentActiveCategory==category}" class="doc_nav__ul" v-for="[ category, subCategories ] in Object.entries(categories)" :key="category"> {{category}}
<li class="doc_nav__li" v-for="subCategory in subCategories" :key="subCategory">
{{ subCategory }}
<!-- <router-link :to="subCategory"> {{ subCategory }} </router-link> -->
</li>
</ul>
</nav>
</header>
</div>
Here is the corrected and working answer of gao.xiangyang
It is using css.
A solution without css: v-if="currentActiveCategory==category"
new Vue({
el: "#app",
data: {
categories: {
Atoms: ['Buttons', 'Icons'],
Molecules: [],
Organisms: [],
Templates: [],
Utilities: ['Grid']
},
currentActiveCategory: null,
},
methods: {
displaySubCategories(category) {
if (category == this.currentActiveCategory) {
this.currentActiveCategory = null
}
else this.currentActiveCategory = category
}
}
})
.doc_nav {
display: flex;
justify-content: around;
}
.doc_nav__ul {
margin: 0 30px;
}
.doc_nav__li:not(visible) {
display: none;
}
.doc_nav__li--visible {
display: block !important;
}
.doc_nav__li {
text-align: center;
}
.doc_nav__li:first-child {
margin-top: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<header class="doc_header">
<nav class="doc_nav">
<ul #click="displaySubCategories(category)" class="doc_nav__ul" v-for="[ category, subCategories ] in Object.entries(categories)" :key="category"> {{category}}
<li :class="{'doc_nav__li--visible' : currentActiveCategory==category}" class="doc_nav__li" v-for="subCategory in subCategories" :key="subCategory">
{{ subCategory }}
<!-- <router-link :to="subCategory"> {{ subCategory }} </router-link> -->
</li>
</ul>
</nav>
</header>
</div>
I have a counter component - pretty straight forward.
<div> + </div>
<input type="text" v-model="countData" />
<div> - </div>
Detailed code is here - https://github.com/Shreerang/Vue-Nuggets/blob/master/src/components/QuantitySelector/QuantitySelector.vue
I am trying to unit test this component.
it('Renders a default quantity selector with the max count set to 6', () => {
const wrapper = shallowMount(QuantitySelector);
wrapper.find('input[type="text"]').setValue('1');
expect(wrapper.find('input[type="text"]').element.value).toBe('1'); // notice here that the value is a String, whereas I expect it to be a Number
wrapper.findAll('div').at(2).trigger('click');
expect(wrapper.vm.countData).toBe('2'); // This fails as countData becomes "11" instead of doing a 1 + 1 = 2 and then becoming Number 2.
expect(wrapper.find('input[type="text"]').element.value).toBe(2); // You can notice the same thing as above.
wrapper.find('input[type="text"]').setValue(wrapper.vm.countData); // Do I have to do this? This does not seem right to me :frowning:
});
I am not able to get this unit test to work! Any help with this is appreciated!
Text fields contain text values. Note that you even specified a text value: setValue('1'). If you change the value in the input manually, (say, to 3) and press the increment button, it becomes 31. Your test is telling you the truth.
You need to change your functions to convert to number. [Update] As your comment informed me, Vue has a .number modifier for v-model for this very purpose
new Vue({
el: '#app',
name: 'quantity-selector',
props: {
count: {
type: Number,
default: 1,
}, // Makes sense to have default product count value
maxCount: {
type: Number,
default: 6,
}, // maxCount makes sense when you have a restriction on the max quantity for a product
iconDimensions: {
type: Number,
default: 15,
},
minusIconFillColor: {
type: String,
default: '#000',
},
plusIconFillColor: {
type: String,
default: '#000',
},
isCountEditable: {
type: Boolean,
default: true,
},
},
data() {
return {
countData: this.count,
};
},
computed: {
minusIconColor: function() {
return this.countData === this.count ? '#CCC' : this.minusIconFillColor;
},
plusIconColor: function() {
return this.countData === this.maxCount ? '#CCC' : this.plusIconFillColor;
},
},
methods: {
decrement: function() {
if (this.countData > this.count) {
this.countData -= 1;
}
},
increment: function() {
if (this.countData < this.maxCount) {
this.countData += 1;
}
},
adjustCount: function() {
if (this.countData > this.maxCount) {
this.countData = this.maxCount;
} else if (this.countData < this.count) {
this.countData = this.count;
} else {
if (isNaN(Number(this.countData))) {
this.countData = this.count;
}
}
},
}
});
.nugget-quantity-counter {
display: inline-flex;
}
.nugget-quantity-counter div:first-child {
border: solid 1px #ccc;
border-radius: 5px 0px 0px 5px;
}
.nugget-quantity-counter div:nth-child(2) {
border-top: solid 1px #ccc;
border-bottom: solid 1px #ccc;
display: flex;
flex-direction: column;
justify-content: center;
}
.nugget-quantity-counter input[type='text'] {
border-top: solid 1px #ccc;
border-bottom: solid 1px #ccc;
border-left: none;
border-right: none;
text-align: center;
width: 20px;
padding: 12px;
margin: 0;
font-size: 16px;
}
.nugget-quantity-counter div:last-child {
border: solid 1px #ccc;
border-radius: 0px 5px 5px 0px;
}
.nugget-quantity-counter > div {
cursor: pointer;
padding: 12px;
width: 20px;
text-align: center;
}
.nugget-quantity-counter > div > svg {
height: 100%;
}
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div #click="decrement">
<svg viewBox="0 0 24 24" :width="iconDimensions" :height="iconDimensions">
<g>
<path d='M64 0 M2 11 L2 13 L22 13 L22 11 Z' :fill="minusIconColor" />
</g>
</svg>
</div>
<input v-if="isCountEditable" type="text" v-model.number="countData" #blur="adjustCount" />
<div v-else>{{countData}}</div>
<div #click="increment">
<svg viewBox="0 0 24 24" :width="iconDimensions" :height="iconDimensions">
<g>
<path d="M 11 2 L 11 11 L 2 11 L 2 13 L 11 13 L 11 22 L 13 22 L 13 13 L 22 13 L 22 11 L 13 11 L 13 2 Z" :fill="plusIconColor" />
</g>
</svg>
</div>
</div>
Looking through http://foundation.zurb.com/docs/components/section.html, is there anyway I can add horizontal scroll for Section headers ( Tabs) . I am looking something like http://www.seyfertdesign.com/jquery/ui.tabs.paging.html in foundation sections with horizontal scroll and continue to use accordion in small screen
I found a solution for those interested : https://codepen.io/gdyrrahitis/pen/BKyKGe
.tabs {
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
.tabs-title {
float: none;
display: inline-block;
}
}
if someone needs an angularjs with jquery implementation, below code can help you, for pure jquery replace angularjs directive method with a native js method with respective attributes.
I tried to search for similar implementation but found nothing, so I have written a simple angular directive which can transform a foundation CSS tabs to scrollable tabs
angular.module("app.directives.scrollingTabs", [])
.directive("scrollingTabs", ScrollingTabsDirective);
//#ngInject
function ScrollingTabsDirective($timeout, $window) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if(attr.scrollingTabs == "true"){
element.addClass('scrolling-tabs-container');
element.find('.nav-buttons').remove();
element.append('<div class="scrolling-tabs nav-buttons nav-buttons-left"></div>');
element.append('<div class="scrolling-tabs nav-buttons nav-buttons-right"></div>');
let scrolledDiv = $(element).find('.tabs');
let scrolled;
let scrolling;
let scrollFn = (step, animationTime, cb) => {
scrolled = Math.max(scrolled + step, 0);
scrolledDiv.animate({
scrollLeft: scrolled
}, animationTime, ()=>{
if (scrolling) {
scrollFn(step, animationTime, cb)
}else{
if(cb){cb()}
}
});
};
let checkActiveNavButtonsClasses = () => {
scrolled = scrolledDiv.scrollLeft();
let scrollWidth = scrolledDiv.get(0).scrollWidth;
let scrolledDivWidth = scrolledDiv.get(0).clientWidth;
if(scrollWidth > scrolledDivWidth){
element.addClass('nav-active');
scrollWidth = scrolledDiv.get(0).scrollWidth;
if(scrolled == 0){
element.removeClass('nav-active-left').addClass('nav-active-right')
}else if(scrolled > 0 && scrolled + scrollWidth < scrolledDivWidth){
element.addClass('nav-active-left').addClass('nav-active-right');
}else if(scrolled > 0 && scrolled + scrollWidth >= scrolledDivWidth){
element.addClass('nav-active-left').removeClass('nav-active-right');
}else{
element.removeClass('nav-active-left').removeClass('nav-active-right')
}
}else{
element.removeClass('nav-active-left').removeClass('nav-active-right').removeClass('nav-active');
}
};
let scrollToActiveTab = () => {
let activeDD = scrolledDiv.find('dd.active');
let tabsOffset = scrolledDiv.offset();
let activeTaboffset = activeDD.offset();
let activeTabwidth = activeDD.width();
let scrolledStep = activeTaboffset.left - tabsOffset.left - scrolledDiv.width() + activeTabwidth;
scrollFn(scrolledStep, 100, checkActiveNavButtonsClasses);
};
element.find(".nav-buttons.nav-buttons-left")
.off("click.scrolling")
.on("click.scrolling", (event)=>{
event.preventDefault();
scrolling = false;
scrollFn(-100, 100, checkActiveNavButtonsClasses);
})
.off("mouseover.scrolling")
.on("mouseover.scrolling", function (event) {
scrolling = true;
scrollFn(-2, 1, checkActiveNavButtonsClasses);
})
.off("mouseout.scrolling")
.on("mouseout.scrolling", function (event) {
scrolling = false;
});
element.find(".nav-buttons.nav-buttons-right")
.off("click.scrolling")
.on("click.scrolling", (event)=>{
event.preventDefault();
scrolling = false;
scrollFn(100, 100, checkActiveNavButtonsClasses);
})
.off("mouseover.scrolling")
.on("mouseover.scrolling", function (event) {
scrolling = true;
scrollFn(2, 1, checkActiveNavButtonsClasses);
})
.off("mouseout.scrolling")
.on("mouseout.scrolling", function (event) {
scrolling = false;
});
$timeout(()=>{
checkActiveNavButtonsClasses();
scrollToActiveTab()
},1000);
$($window).off('resize.scrolling').on('resize.scrolling', _.debounce(()=> {
checkActiveNavButtonsClasses();
}, 500));
scope.$on('$destroy', function() {
$($window).off('resize.scrolling');
});
}
}
}}
css:
.scrolling-tabs-container {
position: relative;
.tabs {
overflow-x: hidden;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
display: block;
margin-right: 18px;
dd {
display: inline-block;
float: none;
margin: 0px -3px 0px 0px;
}
.tabs-title {
float: none;
display: inline-block;
}
}
.scrolling-tabs {
&.nav-buttons {
display: none;
position: absolute;
width: 19px;
height: 38px;
border: 1px solid #c1c1c1;
top: 1px;
background-color: rgba(255, 255, 255, 0.5);
opacity: 0.4;
cursor: pointer;
&:hover {
opacity: 1;
&:before {
color: #444;
}
}
&:before {
position: absolute;
left: 7px;
top: 8px;
color: #777;
}
&.nav-buttons-left {
left: 0;
&:before {
content: '<';
}
}
&.nav-buttons-right {
right: 18px;
&:before {
content: '>';
}
}
}
}
&.nav-active{
.tabs{
margin-right: 36px;
margin-left: 18px;
}
.scrolling-tabs {
&.nav-buttons {
display: inline-block !important;
}
}
}
&.nav-active-left{
.scrolling-tabs{
&.nav-buttons-left{
opacity: 0.8;
}
}
}
&.nav-active-right{
.scrolling-tabs{
&.nav-buttons-right{
opacity: 0.8;
}
}}}
HTML: Foundation Tabs template.
<tabset class="list-tabs" scrolling-tabs="true">
<tab heading="tab1"></tab>
<tab heading="tab2"></tab>
<tab heading="tab2"></tab>
</tabset>
Before you start you'll want to verify that both jQuery (or Zepto) and foundation.js are available on your page. These come with foundation package so just uncomment them in your footer or include them accordingly.
<div class="section-container auto" data-section>
<section class="active">
<p class="title" data-section-title>Section 1</p>
<div class="content" data-section-content>
<p>Content of section 1.</p>
</div>
</section>
<section>
<p class="title" data-section-title>Section 2</p>
<div class="content" data-section-content>
<p>Content of section 2.</p>
</div>
</section>
</div>
The foundation documentation has all of the information for this :
http://foundation.zurb.com/docs/components/section.html#panel2
This will get you your section tabular headers. You then want to manage the content to be scrollable.
<div class="content" data-section-content>
<p>Content of section 1.</p>
</div>
This content here will be the area to work on, try adding a new class called .scrollable
Within this class use something like:
.scrollable{
overflow:scroll;
}
You may want to add some more to this however this will get you started. Your HTML should now look like this :
<div class="content scrollable" data-section-content>
<p>Content of section 1. This content will be scrollable when the content has exceeded that of the div size. </p>
</div>
This this is what you are looking for.