I am new to vuejs and I am trying to implement a javascript shopping cart,so I have this method,the output is a html table with some v-on:click actions that do not work,what is wrong
<div v-html="str"></div>
<div v-html="total"></div>
<div v-html="count"></div>
var vm = new Vue({
el: '#vue_det',
data: {
str:"",
total:0,
count:0
},
methods: {
displaycart: function ()
{
var cartArray = shoppingCart.listCart();
var output = "<table>";
for (var i in cartArray) {
output += "<tr>"
+ "<td>" + cartArray[i].name + "</td>"
+ "<td>(" + cartArray[i].price + ")</td>"
+ "<td><div class='input-group'><button v-on:click=minusitem('" + cartArray[i].name + "') class='minus-item input-group-addon btn btn-primary' >-</button>"
+ "<input type='number' class='item-count form-control' data-name='" + cartArray[i].name + "' value='" + cartArray[i].count + "'>"
+ "<button v-on:click=plusitem('" + cartArray[i].name + "') class='plus-item btn btn-primary input-group-addon' >+</button></div></td>"
+ "<td><button v-on:click=deleteitem('" + cartArray[i].name + "') class='delete-item btn btn-danger' >X</button></td>"
+ "<td>" + cartArray[i].total + "</td>"
+ "</tr>";
}
output +="</table>";
this.str=output;
this.total=shoppingCart.totalCart()
this.count=shoppingCart.totalCount()
// $('.show-cart').html(output);
// $('.total-cart').html(shoppingCart.totalCart());
// $('.total-count').html(shoppingCart.totalCount());
}
},
I make this code for my shopping cart
<div id="vue_det">
<div id="products">
<p>
Orange,pricε:20 Add
</p>
<p>
Paok,pricε:22 Add
</p>
</div>
<div v-for="entry in listCart()" class="entry">{{ entry.name }}
<button v-on:click=removeItemFromCart(entry.name) >-</button>
<input type='number' v-model.number="entry.count" >
<button v-on:click=addItemToCart(entry.name)>+</button>
<button v-on:click=removeItemFromCartAll(entry.name) >X</button>
{{entry.total}}
</div>
<div>Total:{{totalCart()}}</div>
<div>Totalcount:{{totalCount()}}</div>
</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#vue_det',
data: {
item:
{
name: "",
price: 0,
count: 0
},
cart: []
},
methods: {
addItemToCart: function (name, price, count) {
for (var item in this.cart) {
if (this.cart[item].name === name) {
this.cart[item].count++;
this.saveCart();
return;
}
}
var item = {};
item.count = count;
item.price = price;
item.name = name;
this.cart.push(item);
this.saveCart();
},
saveCart: function ()
{
sessionStorage.setItem('shoppingCart', JSON.stringify(this.cart));
return;
},
// Load cart
loadCart: function () {
this.cart = JSON.parse(sessionStorage.getItem('shoppingCart'));
},
setCountForItem : function (name, count) {
for (var i in this.cart) {
if (this.cart[i].name === name) {
this.cart[i].count = count;
this.saveCart();
break;
}
}
},
removeItemFromCart : function (name) {
for (var item in this.cart) {
if (this.cart[item].name === name) {
this.cart[item].count--;
if (this.cart[item].count === 0) {
this.cart.splice(item, 1);
}
this.saveCart();
break;
}
}
this.saveCart();
},
removeItemFromCartAll :function (name) {
for (var item in this.cart) {
if (this.cart[item].name === name) {
this.cart.splice(item, 1);
this.saveCart();
break;
}
}
//this.saveCart();
},
clearCart : function () {
cart = [];
this.saveCart();
},
totalCount : function () {
var totalCount = 0;
for (var item in this.cart) {
totalCount += this.cart[item].count;
}
return totalCount;
},
totalCart : function () {
var totalCart = 0;
for (var item in this.cart) {
totalCart += this.cart[item].price * this.cart[item].count;
}
return Number(totalCart.toFixed(2));
},
listCart : function () {
var cartCopy = [];
for (i in this.cart) {
//item =cart[i];
itemCopy = {};
for (p in this.cart[i]) {
// itemCopy[p] = this.cart[i][p];
itemCopy[p] = this.cart[i][p];
}
itemCopy.total = Number(this.cart[i].price * this.cart[i].count).toFixed(2);
cartCopy.push(itemCopy)
}
return cartCopy;
},
},
created: function () {
if (sessionStorage.getItem("shoppingCart") != null) {
this.loadCart();
}
},
})
</script>
Related
In my ticket processing application I currently have a back and forward button contained in my TicketRunner.vue Component, I would like to change it so that these buttons only appear if I have an associated case file, for which I've used V-If:
TicketRunner.Vue
<div class="level nav-btns" v-if='!currentTicketCaseFiles.length'>
<div class="buttons has-addons level-left">
<b-button
#click.prevent="navGoPrev()"
:disabled="currentStepIndex === 0 || navWaiting"
size="is-medium"
>
</div>
export default {
name: 'TicketRunner',
mixins: [NavStepsByIndexMixin()],
components: {
StagePresenter,
CaseFilesStage,
ParticipantsStage,
AttachmentsStage,
CaseFilesRunner,
TicketContextButtons,
},
data: function() {
return {
firstComponentsInitialization: true,
loadingConfirm: false,
confirmationModalActive: false,
confirmationSucceeded: undefined
}
},
props: {
ticketId: {
type: Number,
required: true,
},
},
provide() {
return {
contextButtons: {
capture: (name, callback, title) => this.$refs['contextButtons'].captureButton(name, callback, title),
release: (name) => this.$refs['contextButtons'].releaseButton(name),
enable: (name) => this.$refs['contextButtons'].enableButton(name),
disable: (name) => this.$refs['contextButtons'].disableButton(name),
},
};
},
computed: {
...mapGetters(['currentTicket', 'ticketCaseFiles', 'allCurrentTicketAttachments', 'currentTicketCaseFileNotAssociated',
'currentRequesterType', 'currentTicketStage', 'lastCaseFile']),
caseFiles() {
return this.ticketCaseFiles(this.ticketId);
},
ticketHasAttachments() {
return this.allCurrentTicketAttachments.length > 0;
},
isTicketAssociatedWithCaseFile() {
return !this.currentTicketCaseFileNotAssociated;
},
isFirstNavInitializationInProgress() {
return !this.navReady && this.firstComponentsInitialization;
},
isShowAttachmentsStep() {
return this.ticketHasAttachments && this.currentRequesterType !== 'unknown' &&
(this.isFirstNavInitializationInProgress || this.isTicketAssociatedWithCaseFile)
},
isCurrentTicketResolved() {
return this.currentTicket.status === 'resolved';
},
islastStep() {
return this.navLastStep() && this.lastCaseFile;
}
},
watch: {
ticketId(){
this.navigator.reset();
},
navReady() {
this.moveForwardIfReady();
this.firstComponentsInitialization = false;
}
},
methods: {
...mapActions(['confirmTicket']),
moveForwardIfReady() {
if (this.navigator.currentIndex === 0 && this.firstComponentsInitialization) {
let steps = 0
const step_names = ['case_files_stage']
for(const [_idx, name] of step_names.entries()) {
const ref_name = `step[${name}]`;
if (this.$refs.hasOwnProperty(ref_name) && this.$refs[ref_name].navReady) {
steps += 1
} else {
break
}
}
this.navigator.currentIndex += steps
}
},
confirm() {
this.$buefy.dialog.confirm({
message: this.t('tickets.stages.confirmation.simplified_confirm_reply'),
onConfirm: () => this.confirmStep()
})
},
async confirmStep() {
this.loadingConfirm = true;
const promise = this.confirmTicket(this.ticketId);
return promise.then((response) => {
this.confirmationModalActive = true;
this.confirmationSucceeded = true;
return true; // true is correct here. for goNext it makes parent to stay on on the current step
}).catch(() => {
this.confirmationModalActive = true;
this.confirmationSucceeded = false;
return true; // true is correct here. for goNext it makes parent to stay on on the current step
}).finally(() => this.loadingConfirm = false);
},
},
};
I then receive the following Console Error:
[Vue warn]: Property or method "currentTicketCaseFiles" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I know that "!currentTicketCaseFiles.length" works successfully in the Component CaseFilesStage.vue, which makes me believe I should somehow connect the two? But importing it doesn't seem right to me either. I'm not quite sure how to tackle this issue as I'm quite new at VueJS, and would be happy for any pointers. I'll attach the CaseFilesStage.vue Component below.
CaseFilesStage.vue
<template>
<div class="hero">
<div class="block">
<template v-if="!currentTicket.spamTicket">
<b-field>
<b-input
v-model="filter"
:loading="loading"
:placeholder="t('tickets.stages.case_files.search.tooltip')"
v-on:keyup.enter.native="searchCaseFiles"
type="search"
icon="search"
:class="{ 'preview-enabled': showAttachmentsPreview}"
/>
</b-field>
<template v-if="foundCaseFiles.length">
<h4 class="title is-4 table-title">{{ t('tickets.stages.case_files.search.table_title') }}</h4>
<CaseFilesSearchTable
:case-files="foundCaseFilxes"
:found-by-data-points="foundCaseFilesByParticipant"
:show-header="true"
v-slot="cf">
<b-checkbox v-if="cfBelongsToCurrentTicket(cf.id)" :disabled="true" :value="true"></b-checkbox>
<b-checkbox v-else #input="onFoundCaseFile(cf.id, $event)"></b-checkbox>
</CaseFilesSearchTable>
</template>
<div v-else-if="lookupStatus === 'notFound'">
{{ t('tickets.stages.case_files.search.not_found') }}
<!-- display button here if above is activated -->
</div>
</template>
</div>
<template v-if='currentTicketCaseFiles.length'>
<h4 class="title is-4 table-title">{{ t('tickets.stages.case_files.table_title') }}</h4>
<CaseFilesTable :case-files="currentTicketCaseFiles" :show-header="true" v-slot="cf">
<DeleteButton
:model-id="cf.id"
modelName="CaseFile" >
</DeleteButton>
</CaseFilesTable>
</template>
</div>
</template>
<script>
import CaseFilesTable from '../tables/CaseFilesTable';
import CaseFilesSearchTable from '../tables/CaseFilesSearchTable';
import DeleteButton from '../../../../shared/components/controls/DeleteButton';
import { mapGetters, mapActions } from 'vuex';
import { mapServerActions } from "../../../../../../_frontend_infrastructure/javascript/lib/crudvuex_new";
export default {
name: 'CaseFilesStage',
data() {
return {
lookupStatus: 'waitingInput',
filter: '',
waiting: {},
foundCaseFiles: [],
foundCaseFilesByParticipant: {}
};
},
components: {
CaseFilesTable,
CaseFilesSearchTable,
DeleteButton
},
computed: {
...mapGetters(
['currentTicketCaseFiles', 'currentTicketCaseFileNotAssociated', 'currentTicket', 'showAttachmentsPreview']
),
loading() {
return this.lookupStatus === 'waitingServer';
},
allCaseFilesMix(){
this.currentTicketCaseFiles + this.foundCaseFiles
},
foundCaseFilesEx() {
return this.foundCaseFiles.filter((x) => !this.cfBelongsToCurrentTicket(x.id))
},
checkboxValue() {
if(!this.currentTicketCaseFileNotAssociated) {
return null;
}
return true;
},
navReady() {
return this.currentTicket.spamTicket || this.currentTicketCaseFiles.length > 0 || this.checkboxValue;
},
markSpam: {
get: function() {
return this.currentTicket.spamTicket
},
set: function(val) {
return this.updateTicket([this.currentTicket.id, { spam_ticket: val }]);
},
}
},
methods: {
...mapActions(['updateTicket']),
...mapServerActions(['createCaseFile', 'deleteCaseFile']),
cfBelongsToCurrentTicket(id){
return this.currentTicketCaseFiles.map((x) => x.caseFileId).includes(id);
},
cantAssignCaseFileCheckbox(isChecked){
if(isChecked) {
this.createCaseFile({ isCfNotAssociated: true });
} else {
this.deleteCaseFile(this.currentTicketCaseFileNotAssociated);
}
},
onFoundCaseFile(id, useIt){
console.log("onFoundCaseFile: ", id, useIt);
if(useIt) {
this.createCaseFile({ caseFileId: id });
} else {
this.deleteCaseFile(this.currentTicketCaseFiles.find({ caseFileId: id }));
}
},
searchCaseFiles() {
const newData = this.filter;
if (newData.length < 3) { // TODO: some smarter condition here
this.foundCaseFiles = [];
this.lookupStatus = 'waitingInput';
return;
}
this.lookupStatus = 'waitingServer';
this.$axios.get('case_files', { params: { "case_files.filter" : newData } })
.then((response) => {
this.foundCaseFiles = response.data.caseFilesSearchResult.caseFiles;
this.foundCaseFilesByParticipant = response.data.caseFilesSearchResult.foundByPrivateInfo;
if(this.foundCaseFiles.length > 0) {
this.lookupStatus = 'success';
} else {
this.lookupStatus = 'notFound';
}
}).catch(() => this.lookupStatus = 'error');
}
},
};
</script>
</style>
Add this to your TicketRunner.vue Component script:
computed: {
...mapGetters(['currentTicketCaseFiles'])
}
I am having a video site. Where users can upload YouTube videos. The teasers of the videos should be playing on top of the same thumbnail. However they are playing on the thumbnail to the right behind the next thumbnail. and the last one is playing outside the container. Below is my code
kYoutube.vue
<script>
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
import VueYoutube from 'vue-youtube';
export default {
extends: VueYoutube,
methods : {
playerStateChange(e) {
log.info ("PSC on " + e.data);
if (e.data !== null && e.data !== -1) {
this.$emit(this.events[e.data], e.target)
}
}
}
}
</script>
PreVideo.vue
<template>
<div class="vid-cont">
<div class="thumb-place">
<PreImage :post="post" :sizes="sizes" #loaded="handleLoad"></PreImage>
</div>
<label v-if="post.proof_img == null">
<div #mouseover="startVideo" #click="emitClick" v-bind:class="thumbZ">
<img ref="pimage" :src="post.post_image.image"
:srcset="post.post_image.thumbs"
:sizes="sizes" style="object-fit: cover;"/>
<img v-if="playImg" class="pre-play-button" :src="playImg" :key="playImg"/>
</div>
<div v-if="iReady" class="player-cont" :key="iReady">
<youtube :video-id="videoId" ref="youtube"
#playing="playing" #ended="ended" #cued="ended" #paused="paused"
:width="width" :height="height"
:player-vars="playerVars" :fitParent="true"></youtube>
</div>
<div class="player-cont player-trans" #mouseout="pausePreview" #click="emitClick">
</div>
</label>
</div>
</template>
<script>
import PreImage from './PreImage';
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
export default {
name: 'Video',
props: [
'post',
'sizes',
],
components: {
PreImage
//VueYoutube
},
data() {
return {
'first': this.post.post_image,
'videoId': this.post.video_id,
'width': 0,
'height': 0,
'iReady': false,
'playerVars': {
'autoplay': 0,
'rel': 0,
'enablejsapi': 1,
'controls': 0,
'disablekb': 1,
'fs': 0,
'modestbranding': 1,
'cc_load_policy': 0,
'origin': document.location.origin
},
'playImg': null,
'pReload': false,
'rates': [],
'preview': false,
'duration': 0,
'count': 1,
'userState': -1,
'numSkips': 3,
};
},
computed: {
thumbZ: function() {
return {
'thumb-cont': true,
'thumb-top': (this.userState < 3)
};
},
},
mounted() {
var that = this;
this.api.map_prom.then(() => {
that.playImg = that.api.mkstatic("youtube_red.png");
that.pReload = !that.pReload;
});
},
updated() {
var that = this;
this.$nextTick(function() {
if (that.iReady && that.userState < 0) {
that.$refs.youtube.player.mute().then(() => {
that.toState(0);
that.$refs.youtube.player.playVideo().then(() => {
});
});
}
});
},
methods: {
emitClick() {
this.$emit("clicked");
},
pausePreview() {
this.toState(1);
this.$refs.youtube.player.stopVideo();
},
delayedPause() {
var that = this;
this.toState(2);
that.$refs.youtube.player.pauseVideo().then(function(){
//log.info ("Paused ?? ");
that.$refs.youtube.player.getPlayerState();/*.then(data => {
//log.info ("STATE is " + data);
});*/
});
},
jump() {
if (this.userState <= 2)
return;
var that = this;
var ct = parseInt(this.duration * this.count / this.numSkips);
//log.info ("Jump call " + that.count);
if (this.count <= this.numSkips) {
this.$refs.youtube.player.seekTo(ct, true).then(function(){
//that.$refs.youtube.player.getPlayerState().then(data => {
//log.info ("STATE " + data + " Jumped to " + ct + " at " + (new Date()).getTime());
that.count++;
that.$refs.youtube.player.pauseVideo().then(function(){
/*that.$refs.youtube.player.playVideo().then(function(){
//setTimeout(that.jump.bind(that), 10000);
});*/
});
//});
});
}
else {
this.$refs.youtube.player.stopVideo();
}
},
toState(state) {
//log.info ("From " + this.userState + " to " + state);
this.userState = state;
},
/* If you are paused, play */
paused() {
switch (this.userState) {
case 2:
//log.info ("Paused, playback " + this.count);
if (this.count <= this.numSkips) {
this.toState(3);
this.$refs.youtube.player.playVideo();
}
else {
this.$refs.youtube.player.stopVideo();
}
break;
case 3:
case 4:
var that = this;
that.$refs.youtube.player.playVideo().then(function(){
setTimeout(that.jump.bind(that), 2000);
});
break;
}
},
/* If you're playing, pause in a sec */
playing() {
//log.info ("Playing called " + this.userState);
switch (this.userState) {
case 0:
var that = this;
that.$refs.youtube.player.pauseVideo().then(() => {
if (!that.duration) {
that.$refs.youtube.player.mute().then(() => {
that.$refs.youtube.player.getDuration().then(data => {
that.duration = data;
//log.info ("Duration " + that.duration);
that.$refs.youtube.player.getAvailablePlaybackRates().then (data => {
//log.info ("Data is " + data);
that.rates = data;
let rate = Math.max.apply(null, that.rates);
that.$refs.youtube.player.setPlaybackRate(rate).then(() => {
//log.info ("Suggested is " + rate);
this.toState(1);
});
});
});
});
}
else {
this.toState(1);
}
})
break;
case 3:
this.toState(4);
setTimeout(this.jump.bind(that), 2000);
}
},
startVideo() {
if (this.userState == 1) {
this.toState(2);
this.paused();
}
},
handleLoad(ev, post) {
this.post = post;
if (!this.$parent.modalOpen) {
//this.post.post_image = post_image;
this.height = ev.target.clientHeight;
this.width = ev.target.clientWidth;
log.info ("PREvid handle load** " + this.width);
this.iReady = true;
}
},
ended() {
//log.info ("ENDED indeed");
this.count = 1;
this.toState(0);
this.$refs.youtube.player.playVideo();
},
}
}
</script>
<style>
</style>
PostList.vue
<template>
<div>
<div ref="me" :class="postListClass" :style="`top: -${scrollOff}px`">
<Loading :loading="loading == 'L'" :loaderror="loading =='E'"></Loading>
<table class="table">
<tbody>
<tr v-for="row in posts" :key="row">
<td v-for="post in row" :key="post">
<div class="post-grid" v-if="post.post_image != null" ref="postgrid" :key="post">
<PreVideo v-if="post.video_id" :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreVideo>
<PreVideoFile v-else-if="post.video_file" :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreVideoFile>
<PreImage v-else :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreImage>
<div class="post-title" #click="selectPost(post.pk)">
<KTruncate :inhtml="post.title" :stripped="post.title" :nomore="true" length="30"></KTruncate>
</div>
<div class="post-user">
by <a :href="post.user.profile.url">{{post.user.full_name}}</a>
</div>
<div class="post-synopsis">
<KTruncate :inhtml="post.message" :stripped="post.stripped" :nomore="true" length="60">
</KTruncate>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<div class="way-container" v-if="nextPageURL !== '' || loading == 'E'">
<Loading :loading="'L'" :key="loading" :loaderror="loading == 'E'" class="way-div" v-waypoint="{ active: true, callback: onWaypoint, options: intersectionOptions }" id="More">
</Loading>
</div>
</div>
<PostModal v-if="selectedPost > 0" ref="modal" :pk="selectedPost"
#closed="onHideDetail" #modalOut="onModalOut" :key="selectedPost"></PostModal>
</div>
</template>
<script>
/* eslint-disable no-console */
import Loading from './Loading';
import PostModal from './PostModal';
import KTruncate from './KTruncate';
import PreVideo from './PreVideo';
import PreVideoFile from './PreVideoFile';
import PreImage from './PreImage';
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
const getOffsetTop = element => {
let offsetTop = 0;
while(element) {
offsetTop += element.offsetTop;
element = element.offsetParent;
}
return offsetTop;
}
export default {
name: 'PostList',
components: {
Loading,
PostModal,
KTruncate,
PreVideo,
PreVideoFile,
PreImage
},
data() {
return {
scrollOff: 0,
origTop: null,
postListClass: "post-scroll",
selectedPost:0,
posts: [],
numberOfPages:0,
pages : [],
numberOfPosts:0,
loading: null, //false,
nextPageURL:'',
previousPageURL:'',
size:'',
winWidth: window.innerWidth,
intersectionOptions: {
root: null,
//rootMargin: '0px 0px 0px 0px',
threshold: [0.25, 0.75] // [0.25, 0.75] if you want a 25% offset!
},
divisor: 4,
modalOpen: false,
};
},
watch: {
pk: function (newpk, oldpk) {
log.info ("Change from " + oldpk + " to " + newpk);
}
},
methods: {
onWaypoint ({ going, direction }) {
if (!this.loading) {
// going: in, out
// direction: top, right, bottom, left
if (going === this.$waypointMap.GOING_IN &&
direction === this.$waypointMap.DIRECTION_TOP &&
this.nextPageURL != '') {
log.info ("Waypoint " + this.nextPageURL + " ??");
this.loading = 'L'; //true;
this.api.getItemsByURL(this.nextPageURL).then((page) => {
this.posts = this.posts.concat(this.breakupPosts(page.data));// this.posts = page.data;
this.nextPageURL = page.nextlink;
this.previousPageURL = page.prevlink;
this.loading = null; //false;
}).catch (err => {
log.info ("Waypoint err " + err.code + " " + err.message);
this.loading = 'E';
});
}
}
},
breakupPosts(data) {
let posts = [];
for (let i = 0; i < data.length; i++) {
let row = parseInt(i / this.divisor);
if (row >= posts.length)
posts.push([]);
posts[parseInt(i / this.divisor)].push(data[i]);
}
return posts;
/*this.posts = page.data.reduce((acc, k) => {
acc[parseInt(k/3)].push(page.data[k])
return acc;
}, {});*/
},
getPosts(){
if (this.origTop == null) {
this.origTop = getOffsetTop(this.$refs.me);
}
this.loading = 'L'; //true;
this.api.getFeedPosts().then((page) => {
this.setPage(page);
this.numberOfPosts = page.count;
this.numberOfPages = page.numpages;
if(this.numberOfPages) {
for(var i = 1 ; i <= this.numberOfPages ; i++) {
const link = `/api/posts/?page=${i}`;
this.pages.push({pageNumber: i , link: link})
}
}
}).catch (err => {
log.info ("Final status* " + err.code + " " + err.message);
this.loading = 'E';
});
},
getPage(link){
this.loading = 'L'; //true;
this.api.getItemsByURL(link).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("GetPage err " + err.code + " " + err.message);
this.loading = 'E';
});
},
getNextPage(){
this.loading = 'L'; // true;
this.api.getItemsByURL(this.nextPageURL).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("GetNextPage err " + err.code + " " + err.message);
this.loading = 'E';
});
},
setPage(page) {
this.posts = this.breakupPosts(page.data);//this.posts = page.data;
this.nextPageURL = page.nextlink;
this.previousPageURL = page.prevlink;
this.sizes = page.sizes;
this.loading = null; //false;
},
getPreviousPage(){
this.loading = 'L'; // true;
this.api.getItemsByURL(this.previousPageURL).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("Get Prev err " + err.code + " " + err.message);
this.loading = 'E';
});
},
onHideDetail() {
this.postListClass = "post-scroll";
let sides = document.getElementsByClassName ("tofix");
for (let i = 0; i < sides.length; i++) {
sides[i].classList.remove ("postbg-fixed");
}
log.info ("On hide detail " + this.selectedPost);
this.selectedPost = 0;
},
onModalOut() {
setTimeout(this.scroller.bind(this), 100);
},
scroller() {
document.documentElement.scrollTop = document.body.scrollTop = this.scrollOff + this.origTop; //document.body.scrollTop = this.scrollOff;
this.scrollOff = 0;
},
selectPost(post) {
log.info ("Select post called " + post );
this.selectedPost = post;
this.modalOpen = true;
this.scrollOff = (window.pageYOffset || document.documentElement.scrollTop) - this.origTop;
this.postListClass = "post-fixed";
let sides = document.querySelectorAll ("tofix");
for (let i = 0; i < sides.length; i++) {
sides[i].classList.add ("postbg-fixed");
}
log.info ("Select post done " + this.selectedPost);
},
getDivisor() {
let u600 = window.matchMedia('(min-width: 600px)');
let u900 = window.matchMedia('(min-width: 900px)');
this.divisor = u600.matches ? (u900.matches ? 4 : 3) : 2;
},
handleResize() {
if (window.innerWidth != this.winWidth) {
this.winWidth = window.innerWidth;
this.getDivisor();
this.posts = this.breakupPosts(this.posts.flat());
}
},
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
},
updated() {
},
mounted() {
this.getPosts();
this.getDivisor();
window.addEventListener('resize', this.handleResize);
},
}
</script>
<style scoped>
.table>tbody>tr>td {
border-top: transparent;
}
.post-fixed {
position: fixed;
z-index: -1;
opacity: 0;
}
#More {
-height: 100px;
}
</style>
I have a problem about dynamite vue component beyond my ability, here is the question:
1.click icons
icons
2.put components into an array and render components dynamitically
click icons and render components
3.click tabs, tabs will be removed and, components relatived with tabs will be removed too
close tabs and windows
when I close tabs or windows from right, everything is ok, but when I close tabs or windows which index less then active window, the active window will refresh。I tried a lot of ways to resolve it, but it doesnt work.
Is there someone else had the same problem, or give me some suggestions, thanks.
Router:
var router = [{
path: '',
redirect: '/layout'
}, {
path: '/',
component: App,
redirect: '/layout',
children: [{
path: '/login',
component: login,
beforeEnter: redirectToHome
},
{
path: '/layout',
component: layout
},
{
path: '/getbackPwd',
component: getbackPwd
},
{
path: '/*',
component: notfound
}]
}]
Layout:
<div class="mu-layout">
<LayoutHead>
<div class="mu-layout-head-left">
<a class="mu-layout-showdesk" #click="toggleShowDesk"></a>
<a class="mu-layout-toggle-sidebar" #click="toggleSidebar">
<i class="el-icon-menu"></i>
</a>
<layout-tab v-for="(tab, i) in iconTabs"
:key="i"
:title="tab.title"
:component="tab.componentName"
:img="tab.img"
:minisize="tab.showMinisize"
:current="tab.current"
#close="closeTab"
#choose="chooseTab"
:tabIdx="i"
></layout-tab>
</div>
<div class="mu-layout-head-right">
<a class="mu-layout-head-link">
<i class="el-icon-search"></i>
</a>
<a class="mu-layout-head-link">
<i class="el-icon-warning" title="通知"></i>
</a>
<a class="mu-layout-head-link">
<i class="iconfont icon-act7"></i>
<span class="head-user-wrap">
{{loginUser && loginUser.username}}
</span>
</a>
<el-popover placement="bottom"
width="160"
trigger="hover"
popper-class="rect-popper"
>
<ul class="mu-layout-list mu-setting-list">
<li class="mu-layout-item">
<a class="mu-layout-item-inner" #click="openSetting"><i class="el-icon-edit-outline"></i>修改密码</a>
<a class="mu-layout-item-inner" #click="logout"><i class="el-icon-logout"></i>注销</a>
</li>
</ul>
<a class="mu-layout-head-link" slot="reference"><i class="icon-more"></i></a>
</el-popover>
</div>
</LayoutHead>
<div class="mu-layout-content">
<div class="swiper-container swiper-no-swiping">
<div class="swiper-wrapper">
<div class="swiper-slide">
<layout-iconmenu-panel>
<layout-icon-menu :img="imenu.img"
:component="imenu.component"
:title="imenu.title"
:menu-id="imenu.menu_id"
v-for="(imenu, i) in iconMenus"
:key="i"
#click="clickIconMenu"
></layout-icon-menu>
</layout-iconmenu-panel>
</div>
<div class="swiper-slide">Slide 2</div>
<div class="swiper-slide">Slide 3</div>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-next"></div>
<div class="swiper-button-prev"></div>
</div>
</div>
<win-panel :title="comp.title"
:tool-title="`${comp.title}(${comp.componentName})`"
:minisize-event="minisize"
#close-event="close"
:component="comp.componentName"
:ref="comp.componentName"
#choose="chooseWin"
:current="comp.current"
v-for="(comp, i) in wpanels"
:key="i"
v-model="comp.show"
:resizable="comp.resizable"
:win-width="comp.width"
:win-height="comp.height"
:is-sys="comp.isSys"
:clazz="comp.classname"
#monitor-win="monitorWin"
:rect="{drag_x: comp.drag_x, drag_y: comp.drag_y , resize_x: comp.resize_x, resize_y: comp.resize_y}"
>
<component :is="comp.comp" ></component>
</win-panel>
<layout-sidebar v-model="sidebarModel"></layout-sidebar>
</div>
import $ from 'jquery'
import LayoutHead from './head'
import LayoutFoot from './foot'
import LayoutLaunch from './button'
import LayoutIconMenu from './iconmenu'
import LayoutIconmenuPanel from './iconmenu-panel'
import LayoutTab from './tabs'
import LayoutPost from './post'
import LayoutSidebar from './sidebar'
import WinPanel from './winpanel'
import * as Components from '../getComponent'
import {
setActivePanel,
getActivePanel,
removeActivePanel,
logout,
getLoginUser,
getLoguserMenus,
setMenuId,
getMenuId
} from '../utils/auth'
import { stopPropagation } from '../utils'
import Swiper from 'swiper'
export default {
components: {
LayoutTab,
LayoutHead,
LayoutPost,
LayoutFoot,
LayoutLaunch,
LayoutSidebar,
LayoutIconMenu,
LayoutIconmenuPanel,
WinPanel,
},
data() {
var _userMenus = getLoguserMenus()
var $menus = _userMenus ? JSON.parse(_userMenus).map(e => {
return {
img: e.menu_icon,
component: e.menu_component,
title: e.menu_name,
menu_id: e.menu_id
}
}).filter(e => e.component!= 'login') : [] //排除登录组件
return {
Components,
sidebarModel: false,
loginUser: getLoginUser(),
iconMenus: $menus,
iconTabs: [],
wpanels: [],
showDeskModel: false,
activeIdx: 0, //当前激活的索引
}
},
methods: {
/*
* 根据传入的arrs进行map 操作,在回调中传入遍历的参数
*/
filterArrs(comName, cb, arrs) {
arrs = arrs ? arrs : this.iconTabs
arrs = arrs.map(e => {
cb(e)
return e
})
},
/*
* 切换sidebar事件
*/
toggleSidebar(e) {
stopPropagation(e)
this.sidebarModel = !this.sidebarModel
},
/*
* 选择窗口
*/
chooseWin(comName) {
this.toggleTab( comName )
},
/*
* 点击tab的操作事件
*/
chooseTab(comName, minisize, current) {
this.setMenuids(comName)
if(current) {
this.filterArrs(comName, e => {
if(e.componentName == comName) {
e.current = false
}
})
this.filterArrs(comName, e => {
if(e.componentName == comName ) {
e.show = false
}
}, this.wpanels)
}
else {
this.filterArrs(comName, e => {
if(e.componentName == comName ) {
e.current = true
} else {
e.current = false
}
})
this.filterArrs(comName, e => {
if(e.componentName == comName ) {
e.show = true
e.current = true
}
}, this.wpanels)
}
},
/*
* 切换不同的tab
*/
toggleTab(comName) {
this.filterArrs(comName, e => {
if(e.componentName == comName) {
e.current = true
} else {
e.current = false
}
})
this.showDeskModel = !this.showDeskModel
setActivePanel(comName)
this.setMenuids(comName)
},
/*
* 点击icon 菜单
*/
clickIconMenu(componentName, title, img, menuId, config) {
var filters = this.iconTabs.filter(e => e.componentName == componentName)
if(filters.length == 0) {
var _config = {
title,
componentName,
img,
showMinisize: false,
current: true,
comp: Components[componentName],
show: true,
menuId
}
if(componentName == 'sysSetting') {
_config.isSys = true
}
if(config) {
_config = Object.assign(_config, config)
}
this.iconTabs.push(_config)
this.wpanels = this.iconTabs.slice(0)
} else {
this.wpanels = this.wpanels.map(function(e) {
if(e.componentName == componentName) {
e.current = true
e.show = true
} else {
e.current = false
}
return e
})
this.iconTabs = this.iconTabs.map(function(e) {
if(e.componentName == componentName) {
e.current =true
} else {
e.current = false
}
return e
})
}
this.toggleTab( componentName )
this.filterArrs(componentName, e => {
if(e.componentName == componentName) {
e.current = true
} else {
e.current = false
}
}, this.wpanels)
},
/*
* 自动选择最后一个tab 和 窗口
*/
autoChooseLast() {
if(this.iconTabs.length > 0) {
var $fs = this.iconTabs.filter(e => e.current)
if($fs.length == 0) {
setTimeout(_ => {
this.iconTabs.forEach((e, i) => {
if(i == this.iconTabs.length - 1) {
e.current = true
this.wpanels[i].current = true
this.wpanels[i].show = true
setActivePanel(e.componentName)
}
})
})
}
} else {
removeActivePanel()
}
},
/*
* 窗口最小化
*/
minisize(id, component, e) {
// stopPropagation(e)
this.filterArrs(component, el => {
if(el.componentName == component) {
el.current = false
}
})
this.filterArrs(component, el => {
if(el.componentName == component) {
el.show = false
el.current = false
}
}, this.wpanels)
// this.autoChooseLast()
},
/*
* 关闭窗口事件
* 和关闭tab 相同,需要阻止冒泡,否则最外层的事件会冲突
*/
close(component, e) {
stopPropagation(e)
this.closeTab(component, e)
},
/*
* 关闭tab,同时关闭相关联的窗口
*/
closeTab(comName, e) {
stopPropagation(e)
this.iconTabs = this.iconTabs.filter(el => el.componentName != comName)
this.wpanels = this.wpanels.filter(e => e.componentName != comName)
this.autoChooseLast()
},
/*
* 切换显示菜单
*/
toggleShowDesk() {
if(this.iconTabs.length == 0 || this.wpanels.length == 0) {
return
}
if(!this.showDeskModel) {
this.iconTabs = this.iconTabs.map(function(e) {
e.current = false
return e
})
this.wpanels = this.wpanels.map(function(e) {
e.show = false
return e
})
} else {
this.wpanels = this.wpanels.map(function(e) {
e.show = true
return e
})
this.autoChooseLast()
}
this.showDeskModel = !this.showDeskModel
}
},
}
Actrually, most of my components dont relate with router, but dynamitecally component rendering.
The website is just like an web os, every icon has its own page, they are all in layout.vue page, the router path always is '/layout'
I am using NuxtJs in my project, I a have list of checkboxes, on click of each checkbox I am sending an array of checkboxes to a my POST api which return data.
Here, when I check the first checkbox it returns the data. But when I check the second checkbox it does not does return the data.
I mean it only returns the data on single checkbox checked.
Its working with normal vuejs but not in nuxtjs
My Code:
<script>
import axios from "axios";
import uniq from "lodash/uniq";
export default {
async asyncData({ req, params }) {
let [storeInfo, feedsInfo] = await Promise.all([
axios.get(
process.env.apiURL +
"/stores/findOne?filter[where][store_name]" +
"=" +
params.id
),
axios.post(process.env.apiURL + "feeds/feedsByStores", {
stores: [params.id]
})
]);
return {
stores: storeInfo.data,
feeds: feedsInfo.data,
categories: uniq(feedsInfo.data.map(p => p.feed_category))
};
},
data() {
return {
checkedCategories: [],
checkedCategory: false,
selectedCategories: []
};
},
methods: {
feedsByCategories: function(categories) {
console.log(categories);
axios.post(process.env.apiURL + "feeds/feedsByCategories", {
categories: [categories]
}).then((res) => {
console.log(res);
})
},
categoryChecked: function(category, checked) {
this.display = "inline";
if (checked) {
this.selectedCategories.push(category);
console.log(this.selectedCategories);
this.feedsByCategories(this.selectedCategories);
} else if (!checked) {
const index = this.selectedCategories.indexOf(category);
this.selectedCategories.splice(index, 1);
this.feedsByCategories(this.selectedCategories);
if (this.selectedCategories == "") {
this.display = "none";
this.getFeeds();
}
}
if (!checked && this.selectedCategories.length === 0) {
this.getFeeds();
}
},
uncheckCategory: function(checkedCategory) {
this.checkedCategories = this.checkedCategories.filter(
name => name !== checkedCategory
);
const index = this.selectedCategories.indexOf(checkedCategory);
this.selectedCategories.splice(index, 1);
this.feedsByCategories(this.selectedCategories);
if (this.checkedCategories == "") {
this.display = "none";
this.getFeeds();
}
},
uncheckallCategories: function(event) {
this.checkedCategories = [];
this.display = "none";
this.search = "";
this.Search = "";
this.filteredCategories;
},
getFeeds() {
return this.feeds;
}
}
};
</script>
<template>
<v-layout>
<ul class="list-unstyled scrollbar">
<li v-for="(feedcategory, index) in categories" :key="feedcategory.id">
<input type="checkbox" name="category" #change="categoryChecked(feedcategory,$event.target.checked)"
:id="index + 1" :value="feedcategory" v-model="checkedCategories">
{{ feedcategory }}
</li>
</ul>
</v-layout>
</template>
My Typo,
I removed the brackets for my categories array and it worked:
feedsByCategories: function(categories) {
console.log(categories);
axios.post(process.env.apiURL + "feeds/feedsByCategories", {
categories: categories
}).then((res) => {
console.log(res);
})
}
I want to show from my api using pagination on a table with filtered data. When I put the function in methods, I get the data from (event-1), but I when I put the function of items in computed I don't get an array of data but an object. So, my data can't be showed. Please how can I get the data please?
<input type="text" class="form-control search ml-4 mb-4" placeholder="search" v-model="filterNameInput" :onChange="filterByName">
<b-table hover responsive="sm" :busy.sync="isBusy" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" :items="fetchPlaces" :fields="fields" :current-page="currentPage" :per-page="perPage" #row-clicked="rowClickHandler">
<template slot="created" slot-scope="data">
{{ data.item.created | moment().format("YYYY-MM-DD") }}
</template>
<template slot="updated" slot-scope="data">
{{ data.item.updated | moment().format("YYYY-MM-DD") }}
</template>
<template slot="categories" slot-scope="data">
<b-badge v-for="category in data.item.categories" :key="category.id" variant="primary">{{category.name}}</b-badge>
</template>
</b-table>
computed: {
fetchPlaces(ctx) {
let params = '?apikey=apiKey&lng=en&page=' + ctx.currentPage + '&limit=' + ctx.perPage
if (this.sortBy) {
params += '&sort=' + this.sortBy
if (this.sortDesc) {
params += '&dir=DESC'
}
}
if (this.filterStatus !== '' || this.filterNameInput !== '') {
params += '&sort=name&dir=ASC'
if (this.filterStatus !== '') {
params += '&filter[status]=like|' + this.filterStatus
}
console.log(this.filterNameInput)
if (this.filterNameInput !== '') {
params += '&filter[name]=%like%|' + this.filterNameInput
}
}
let promise = this.$http.get(apiUrl + params)
return promise.then((data) => {
let items = data.body.data
this.totalRows = data.body.totalItems
return (items || [])
})
}
}
Your computed is returning a Promise, not a value. Also, computeds (in their simple form) are like getters, they don't take arguments.
The proper place to make asynchronous computations is in watchers:
Create a computed that calculates params (which will recalculate every time a "part" of params changes).
Create a watcher for params to trigger the API call with the new params and update the data field fetchPlaces
Use fetchPlaces in the template, which will be updated asynchronously automatically when the API call returns.
Here's the suggested resulting code:
<b-table ... :items="fetchPlaces" ... >
data() {
// properties used somewhere in the code below (types may differ)
apiUrl: 'http://api.example.com',
currentPage: 1,
perPage: 1,
sortBy: 'somefield',
sortDesc: false,
filterStatus: 1,
filterNameInput: 'someinput',
totalRows: 0,
fetchPlaces: [],
},
computed: {
params() {
let params = '?apikey=apiKey&lng=en&page=' + this.currentPage + '&limit=' + this.perPage
if (this.sortBy) {
params += '&sort=' + this.sortBy
if (this.sortDesc) {
params += '&dir=DESC'
}
}
if (this.filterStatus !== '' || this.filterNameInput !== '') {
params += '&sort=name&dir=ASC'
if (this.filterStatus !== '') {
params += '&filter[status]=like|' + this.filterStatus
}
console.log(this.filterNameInput)
if (this.filterNameInput !== '') {
params += '&filter[name]=%like%|' + this.filterNameInput
}
}
return params;
}
},
watch: {
params(newParams, oldParams) {
this.updateFetchPlaces(newParams);
}
},
methods: {
updateFetchPlaces(newParams) {
this.$http.get(this.apiUrl + newParams).then((data) => {
let items = data.body.data
this.totalRows = data.body.totalItems
this.fetchPlaces = items || [];
});
}
},
created() {
this.updateFetchPlaces(this.params); // initial fetch
}
<v-select class="my-4 dropdownHashgroup" v-model="filterStatus" :onChange="statusOnChange" :options="placeStatus" label="label" placeholder="Status"></v-select>
<input type="text" class="form-control search ml-4 mb-4" placeholder="search" v-model="filterNameInput" :onChange="filterByName">
<b-table hover responsive="sm" :busy.sync="isBusy" :sort-by.sync="sortBy"
:sort-desc.sync="sortDesc" :items="fetchPlaces" :fields="fields" :current-page="currentPage" :per-page="perPage" #row-clicked="rowClickHandler">
</b-table>
import vSelect from 'vue-select'
export default {
name: 'grid-places',
data: () => {
return {
apiUrl: 'apiUrl',
apiKey: 'apiKey',
isBusy: false,
fields: [
{ key: 'name', sortable: true },
{ key: 'created', sortable: true },
{ key: 'updated', sortable: true },
{ key: 'score' },
{ key: 'categories' }
],
currentPage: 1,
perPage: 10,
totalRows: 0,
sortBy: 'name',
sortDesc: false,
placeStatus: ['DRAFT', 'PUBLISHED', 'DISABLED'],
filterStatus: 'PUBLISHED',
filterNameInput: '',
fetchPlaces: []
}
},
methods: {
updateFetchPlaces (newParams) {
this.$http.get(this.apiUrl + newParams).then((data) => {
let items = data.body.data
this.totalRows = data.body.totalItems
this.fetchPlaces = items || []
})
},
},
computed: {
params () {
let params = '?apikey=' + this.apiKey + '&lng=en&page=' + this.currentPage + '&limit=' + this.perPage
if (this.sortBy) {
params += '&sort=' + this.sortBy
if (this.sortDesc) {
params += '&dir=DESC'
}
}
if (this.filterStatus !== '' || this.filterNameInput !== '') {
params += '&sort=name&dir=ASC'
}
if (this.filterStatus !== '' && this.filterNameInput === '') {
params += '&filter[status]=like|' + this.filterStatus
}
if (this.filterNameInput !== '' && this.filterStatus === '') {
params += '&filter[name]=%like%|' + this.filterNameInput
}
return params
},
statusOnChange () {
},
filterByName () {
}
},
watch: {
params (newParams, oldParams) {
console.log('going to fetch for:', newParams)
this.$http.get(this.apiUrl + newParams).then((data) => {
let items = data.body.data
this.totalRows = data.body.totalItems
this.fetchPlaces = items || []
console.log(this.fetchPlaces)
console.log(this.currentPage)
})
}
},
created () {
this.updateFetchPlaces(this.params)
},
components: {
vSelect
}