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);
}
}
})
Related
<li
v-for="(schema) in typeSchema"
:key="schema.id"
>
<div style="display:inline-block; width:100%;">
<div style="display:flex; justify-content:space-between">
<span>{{ schema.title }}</span>
<span v-if="schema.controller">
<MdsSwitch
:checked="schema.controller.value"
:label="schema.controller.title"
#change="toggleController(schema, $event)"
/>
</span>
</div>
<div style="display:flex;flex-flow:column;place-items:flex-start;align-items:flex-start;margin-top:10px;">
<component
:is="schema.type"
v-bind="schema"
:data="data"
:is-disabled="schema.isDisabled"
#input="updateData"
/>
</div>
</div>
</li>
# toggleController(schema, event) {
if (schema.controller) {
// this.typeSchema.map(x => x).in
schema.controller.value = event;
schema.isDisabled = !event;
schema = { ...schema };
// const index = this.typeSchema.findIndex((x) => x.id === schema.id);
// console.log(index);
// this.$set(schema, "isDisabled", !event);
// this.typeSchema.splice(index, 0, schema);
}
},
When toggleController is executed it should disable the associated component, it was working earlier and I'm not sure what change I made and it stopped working, unfortunately everything is on my local so cannot refer pervious versions.
I have tried $set, splice but no luck
I am new at NuxtJS and I was wondering how to create a v-card through a v-dialog box.
For example, I create an add button then a v-dialog opens then I fill up the form then I "submit" and finally a v-card with what I filled before is created.
Thank you for your help.
I think this will solve you'r problem:
In you'r script:
data() {
return {
formInfo: {
title: '',
description: ''
}
}
},
methods: {
onSubmit() {
let container = document.getElementById('card-container');
this.formInfo.forEach((result, i) => {
// Create card element
let card = document.createElement('v-card');
card.classList = 'you'r classes';
// Construct card content
const content = `
<div class="card">
<div class="card-header" id="heading-{i}">
<h5 class="mb-0">
<button class="btn btn-link">
</button>
</h5>
</div>
<div id="collapse-{i}" class="collapse show">
<div class="card-body">
<h5>{result.title}</h5>
<p>{result.description}</p>
</div>
</div>
</div>
`;
// Append newly created card element to the container
container.innerHTML += content;
})
}
}
I'm working on a small project in vue.js connected to a lumen API (working).
I currently have a list of students ('Etudiants') in which I can click in the list to select one, and delete it via a button in a toolbar.
When a student is deleted I'm reloading the student list (since it's not up to date anymore), therefore I'm doing 2 api calls via axios.
DELETE http://www.url.com/etudiants (param: idEtudiant)
GET http://www.url.com/etudiants (param: page)
The problem is that my API calls are not done in the right order, as shown here on a screenshot of the calls (with watterfall):
This problem involves 3 vue files.
'Etudiants.vue' and its 2 childs: 'ListeEtudiants.vue' (student list) and 'BarreOutilsEtudiant.vue' (toolbar)
This simple image shows the hierarchy of the 3 files and the order of which everything should execute.
In my case (when it's not working) the action number 3, the axios DELETE, happens in last.
Here are the contents of my files:
Etudiants.vue:
<template>
<div id="etudiants" class="container-fluid h-100">
<div class="row">
<div class="col-3 borderR">
<ListeEtudiants ref="list" #idEtudiantChanged="updateIdEtudiant"/>
</div>
<div class="col-9 bg-light">
<BarreOutilsEtudiant v-if="idEtudiant != null" :idEtudiant="idEtudiant" #delEtudiant="delEtudiant"/>
<InfosEtudiant v-if="idEtudiant != null" :idEtudiant="idEtudiant"/>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import ListeEtudiants from '#/components/ListeEtudiants.vue'
import InfosEtudiant from '#/components/InfosEtudiant.vue'
import BarreOutilsEtudiant from '#/components/BarreOutilsEtudiant.vue'
export default {
components: {
ListeEtudiants,
InfosEtudiant,
BarreOutilsEtudiant
},
data: function(){
return {
idEtudiant: null
}
},
methods:{
updateIdEtudiant(idEtudiant){
this.idEtudiant=idEtudiant;
},
delEtudiant(){
axios
.delete('http://82ab2617.ngrok.io/etudiants', {params: {"idEtudiant" :this.idEtudiant}})
.then(this.$refs.list.loadList())
.catch(error => console.log(error));
}
}
}
</script>
ListeEtudiants.vue:
<template>
<div id="ListeEtudiants">
<div class="row bg-light">
<!-- Trigger Modal Ajout Etudiant -->
<button class="btn btn-light w-100" data-toggle="modal" data-target="#addModal">
<font-awesome-icon icon="plus" size="1x"/>
<span> Ajouter un Etudiant</span>
</button>
</div>
<ul v-if="etudiants != null" id="list" class="row flex-nowrap list-group list-group-flush pr-0">
<button v-for="etudiant in etudiants.data" v-on:click='$emit("idEtudiantChanged",etudiant.idEtudiant)' class="btn btn-light text-left list-group-item pl-5 py-1">{{ etudiant.nom }} {{ etudiant.prenom }}</button>
</ul>
<ul v-else class="row flex-nowrap list-group list-group-flush pr-0">
</ul>
<div class="row bg-light">
<button class="btn btn-light col-3" v-on:click="page -= 1" :disabled="page === 1 || disabled"><font-awesome-icon icon="chevron-left" size="1x"/></button>
<div class="align-middle col-6 my-auto">{{ page }} / {{ maxPage }}</div>
<button class="btn btn-light col-3" v-on:click="page += 1" :disabled="page === maxPage || disabled"><font-awesome-icon icon="chevron-right" size="1x"/></button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: "ListeEtudiants",
data: function(){
return {
etudiants: null,
maxPage:1,
disabled:false,
page:1,
}
},
methods: {
parseAndDisplay: function(data){
this.etudiants = data;
this.maxPage = data.last_page;
this.page = data.current_page;
this.disabled = false;
},
loadList: function(){
this.disabled = true;
this.etudiants = null;
axios
.get('http://82ab2617.ngrok.io/etudiants', {params: {page:this.page}})
.then(response =>this.parseAndDisplay(response.data))
.catch(error => console.log(error));
}
},
watch: {
'page': function(newVal, oldVal){
if((newVal === 0 && oldVal === 1) || (newVal === this.maxPage+1 && oldVal === this.maxPage)){
this.page = oldVal;
}else{
if(oldVal !== 0 && oldVal !== this.maxPage+1) {
this.loadList();
}
}
}
}
,
mounted() {
this.loadList();
}
}
</script>
BarreOutilsEtudiant.vue:
<template>
<div class="row p-2 navbar-expand navbar-info bg-info">
<button class="btn btn-info mr-5" type="button"><font-awesome-icon icon="download" size="1x"/> Télécharger le Bulletin</button>
<button class="btn btn-info ml-auto" type="button"><font-awesome-icon icon="user-edit" size="1x"/></button>
<button class="btn btn-danger ml-4" v-on:click="$emit('delEtudiant')" type="button"><font-awesome-icon icon="trash-alt" size="1x"/></button>
</div>
</template>
<script>
export default {
name: "BarreOutilsEtudiant"
}
</script>
<style scoped>
</style>
Thank you very much for helping me.
I believe the problem is here:
.then(this.$refs.list.loadList())
That will call loadList immediately and pass the value it returns to then, which isn't what you want.
Instead it should be something like this, wrapping it in a function:
.then(() => this.$refs.list.loadList())
I have this array of objects that I need to work with:
$scope.pdfs = [
{ "pdf_title": "Corporate Hire", "attached_file": "http://file1.jpg"},
{ "pdf_title": "Wedding Hire", "attached_file": "http://file2.jpg"},
{ "pdf_title": "Filming Hire", "attached_file": "http://file3.jpg"}
];
The pdf_file value is ng-repeated in li's.
What I want to do is if that li is clicked, to push its paired to another div, say the src for an href.
Here are my workings, but not quite correct:
Controller function:
$scope.bindWithFile = function(value) {
var currentValue = $scope.corpResult = value;
// pdfs support
var pdfs = $scope.pdfs;
for (var i = pdfs.length - 1; i >= 0; i--) {
if (currentValue == hasOwnProperty(key[pdfs])) {
value[pdfs] = $scope.corpLinkHref;
}
};
Markup:
<div class="w-12" ng-controller="corpHireController">
<div class="c-6-set">
<ul>
<li ng-repeat="pdf in pdfs" class="col-7 link link-inherit" ng-click="bindWithFile(pdf.pdf_title)">{{::pdf.pdf_title}}</li>
</ul>
</div>
<div class="c-6-set">
<div class="w-12">
<i class="fs-4 col-7 icon icon-pdf"></i>
</div>
<span class="col-7 h4" ng-bind="corpResult"></span>
<button ng-href="{{::corpLinkHref}}" class="button green2-button smaller-letters full-width">Download</button>
</div>
</div>
What is needed:
Clicking on the titles on the left, binds the pdf_title under the pdf icon and binds the attached_file to the button's href
Instead of passing the title of the selected pdf, why not passing the whole object. This way you don't have to performance any find or search function.
Markup:
<div class="w-12" ng-controller="corpHireController">
<div class="c-6-set">
<ul>
<li ng-repeat="pdf in pdfs" class="col-7 link link-inherit"
ng-click="bindWithFile(pdf)">
{{::pdf.pdf_title}}
</li>
</ul>
</div>
<div class="c-6-set">
<div class="w-12">
<i class="fs-4 col-7 icon icon-pdf"></i>
</div>
<span class="col-7 h4" ng-bind="corpResult"></span>
<button ng-href="{{::corpLinkHref}}"
class="button green2-button smaller-letters full-width">
Download
</button>
</div>
</div>
Controller
$scope.bindWithFile = function(selectedPdf) {
$scope.corpResult = selectedPdf.pdf_title;
$scope.corpLinkHref = selectedPdf.attached_file;
}
Backbone View:
var $ = require('jquery'),
Handlebars = require('handlebars'),
Backbone = require('backbone'),
channel = require('../../templates/dashboard/channelstats.html'),
channelstatsCollection = require('../../collections/dashboard/ChannelStatsCollection'),
mainJs = require('../../libs/main');
var ChannelStatsView = Backbone.View.extend({
el: "#divstatsview",
initialize: function () {
this.collection = new channelstatsCollection();
var api_token = mainJs.get_api_token();
this.collection.fetch(
{
headers: {'Authorization': 'Bearer ' + api_token.access_token},
success: function (collection, response, options) {
var tpl = channel;
console.log(JSON.stringify(response));
$(this.el).html(tpl({orders:JSON.stringify(response)}));
}
}
);
}
});
// Our module now returns our view
module.exports = ChannelStatsView;
Handlebars template:
{{#orders.records}}
{{#by_status}}
<div class="col-lg-2 col-md-6 new-item centered white-panel">
<h2 class="white-header"> {{PROCESSING}}</h2>
<!-- <div class="text-danger"><i class="fa fa-bell fa-3x"></i></div>-->
<div class="clearfix"></div>
<div> <b>{{PROCESSING}} new </b>Orders</div>
<div> <b>{{SHIPPED}} </b3>Pending</div>
<br/>
<div><i class="fa fa-refresh fa-2x"></i></div>
Sync
</div>
{{/by_status}}
{{/orders.records}}
My JSON:
{
"metadata":{
"recordcount":1
},
"records":[
{
"count":0,
"by_status":[
{
"OUTOFSTOCK":0
},
{
"PROCESSING":19,
"by_channel":[
{
"Some":1
},
{
"Some1":18
}
]
},
{
"RECEIVED":0
},
{
"SHIPPED":26,
"by_channel":[
{
"Demo":26
}
]
}
]
}
]
}
I get a blank page and nothing shows up.
NOTE:
I am using hbsfy to compile my templates for me so tpl is a compiled template and not an html file.
EDIT:
My json object
You need to use built-in each helper to iterate over the records and by_status arrays.
{{#each orders.records}}
{{#each by_status}}
<div class="col-lg-2 col-md-6 new-item centered white-panel">
<h2 class="white-header">{{PROCESSING}}</h2>
<div class="clearfix"></div>
<div><b>{{PROCESSING}} new </b>Orders</div>
<div><b>{{SHIPPED}} </b>Pending</div>
<br/>
<div><i class="fa fa-refresh fa-2x"></i></div>
Sync
</div>
{{/each}}
{{/each}}
I am not sure exactly what you are trying to accomplish, but the code above will output 4 divs. It will output 1 div for each element in the by_status array. It seems like you are just trying to output 1 div with a summary. If that is the case, you would probably need to do something like below.
<div class="col-lg-2 col-md-6 new-item centered white-panel">
<h2 class="white-header">{{orders.records.0.by_status.1.PROCESSING}}</h2>
<div class="clearfix"></div>
<div><b>{{orders.records.0.by_status.1.PROCESSING}} new </b>Orders</div>
<div><b>{{orders.records.0.by_status.3.SHIPPED}} </b>Pending</div>
<br/>
<div><i class="fa fa-refresh fa-2x"></i></div>
Sync
</div>
The code above assumes that the PROCESSING and SHIPPED elements always exist in the same place in the by_status array. This is probably not very wise. My recommendation would be to change the structure of your JSON response, or if that is not possible do some processing in your Backbone view before passing it to the template.
EDIT: Worked when i made my code to the following below
<div class="col-lg-2 col-md-6 new-item centered white-panel">
<h2 class="white-header">{{orders.records.0.by_status.1.PROCESSING}}</h2>
<div class="clearfix"></div>
<div><b>{{orders.0.records.0.by_status.1.PROCESSING}} new </b>Orders</div>
<div><b>{{orders.0.records.0.by_status.3.SHIPPED}} </b>Pending</div>
<br/>
<div><i class="fa fa-refresh fa-2x"></i></div>
Sync
</div>