Vue2 component rendering on ajax response - ajax

I have created a component that reacts to this template:
<table-sort index="job.job_id"></table-sort>
However, when I get this template from a ajax response and add it to the DOM. It doesn't recognize it as a Vue component. How do I render this component?
Here is my component:
Vue.component('table-sort', {
name: 'table-sort',
template: `
<div class="table-sort" :data-index="index">
<i v-on:click="sortTable($event)" direction="ASC" class="fa fa-caret-up"></i>
<i v-on:click="sortTable($event)" direction="DESC" class="fa fa-caret-down"></i>
</div>
`,
props: {
index: {
type: String,
required: true,
},
},
methods: {
sortTable: function ($event) {
const self = $event.target;
const index = this.index;
const direction = self.getAttribute('direction');
let filters = Url.getQueryParameters();
filters.sort = index;
filters.direction = direction;
filters.page = 1;
const url = Url.getUrlWithoutQueryString();
const newUrl = Url.appendParamsToUrl(filters, url);
Url.redirect(newUrl);
},
},
});
Here is how I call the code to generate the table:
var ajaxTable = new AjaxTable({
name: 'jobs',
request: inputs,
container: '#job-table',
fields: fields,
});
Here is the code that appends the template:
public async drawTable() {
this.table = $(`<table class="table table-striped table-bordered table-hover table-condensed"><thead><tr id="header"></tr></thead><tbody></tbody></table>`);
this.count = $(`<div class="count pull-left"></div>`);
this.pagination = $(`<div class="pagination pull-right"></div>`);
this.fields.map((field: ColumnData) => {
let out = `<th>${field.title}`;
if (!!field.sort) {
out += ` <table-sort index="${field.sort}"></table-sort>`;
}
out += `</th>`;
this.table.find('#header').append(out);
});
this.container.append(this.table, this.count, this.pagination);
}

Related

Show component only after all images loaded

I am using Vue 3 and what i would like to achieve is to load all images inside a card (Album Card) and only then show the component on screen..
below is an how it looks now and also my code.
Does anybody have an idea how to achieve this?
currently component is shown first and then the images are loaded, which does not seem like a perfect user experience.
example
<template>
<div class="content-container">
<div v-if="isLoading" style="width: 100%">LOADING</div>
<album-card
v-for="album in this.albums"
:key="album.id"
:albumTitle="album.title"
:albumId="album.id"
:albumPhotos="album.thumbnailPhotos.map((photo) => photo)"
></album-card>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import albumCard from "#/components/AlbumCard.vue";
interface Album {
userId: number;
id: number;
title: string;
thumbnailPhotos: Array<Photo>;
}
interface Photo {
albumId: number;
id: number;
title: string;
url: string;
thumbnailUrl: string;
}
export default defineComponent({
name: "Albums",
components: {
albumCard,
},
data() {
return {
albums: [] as Album[],
isLoading: false as Boolean,
};
},
methods: {
async getAlbums() {
this.isLoading = true;
let id_param = this.$route.params.id;
fetch(
`https://jsonplaceholder.typicode.com/albums/${
id_param === undefined ? "" : "?userId=" + id_param
}`
)
.then((response) => response.json())
.then((response: Album[]) => {
//api returns array, loop needed
response.forEach((album: Album) => {
this.getRandomPhotos(album.id).then((response: Photo[]) => {
album.thumbnailPhotos = response;
this.albums.push(album);
});
});
})
.then(() => {
this.isLoading = false;
});
},
getRandomPhotos(albumId: number): Promise<Photo[]> {
var promise = fetch(
`https://jsonplaceholder.typicode.com/photos?albumId=${albumId}`
)
.then((response) => response.json())
.then((response: Photo[]) => {
const shuffled = this.shuffleArray(response);
return shuffled.splice(0, 3);
});
return promise;
},
/*
Durstenfeld shuffle by stackoverflow answer:
https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array/12646864#12646864
*/
shuffleArray(array: Photo[]): Photo[] {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
},
},
created: function () {
this.getAlbums();
},
});
</script>
What i did to solve this problem was using function on load event on (img) html tag inside album-card component. While images are loading a loading spinner is shown. After three images are loaded show the component on screen.
<template>
<router-link
class="router-link"
#click="selectAlbum(albumsId)"
:to="{ name: 'Photos', params: { albumId: albumsId } }"
>
<div class="album-card-container" v-show="this.numLoaded == 3">
<div class="photos-container">
<img
v-for="photo in this.thumbnailPhotos()"
:key="photo.id"
:src="photo.thumbnailUrl"
#load="loaded()"
/>
</div>
<span>
{{ albumTitle }}
</span>
</div>
<div v-if="this.numLoaded != 3" class="album-card-container">
<the-loader></the-loader>
</div>
</router-link>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { store } from "#/store";
export default defineComponent({
name: "album-card",
props: {
albumTitle: String,
albumsId: Number,
},
data: function () {
return {
store: store,
numLoaded: 0,
};
},
methods: {
thumbnailPhotos() {
return this.$attrs.albumPhotos;
},
selectAlbum(value: string) {
this.store.selectedAlbum = value;
},
loaded() {
this.numLoaded = this.numLoaded + 1;
},
},
});
</script>
Important note on using this approach is to use v-show instead of v-if on the div. v-show puts element in html(and sets display:none), while the v-if does not render element in html so images are never loaded.

How to display data on blade from Vue Js?

I had grabbed some code for test purpose, I have tried to fixing "item not define" error but I am stuck.
//Blade View
<v-app id="app" v-cloak><div class="card" :posts="{{$posts}}"></div></v-app>
//Vuejs template
<table striped hover :items="imageList"> <img :scr="'/storage/image/'+data.items.image"></table>
Vue Js:
<script>
export default {
// name: "ExampleComponent",
props: \['posts'\],
data() {
return {
imageList: \[\]
};
},
mounted() {
const fetch = this.fetch_image_list();
},
methods: {
fetch_image_list() {
let items = \[\];
if (Array.isArray(this.posts.data) && this.posts.length.data) {
this.posts.data.forEach((post, key) => {
let currentImage = {
id: post.id,
name: post.name,
image: post.img
};
items.push(currentImage);
});
this.imageList = items;
}
}
}
};
</script>]
item is not define
Isn't it because you are "calling" :
data.items.image instead of items[index].image ?

Vue.js component conflict in laravel blade view

I have 2 of the same components in my laravel blade view, but they are conflicting.
What i'm trying to accomplish:
I've made a vue component that uploads a file to firebase and stores it in my database. In my blade view i have 2 places where i want to use this component. I configure the component with props so the component knows where to store the file.
What going wrong:
Every time i try to upload a file with the second component, i fire the function in the first component. How do i fix that the components can't conflict?
My laravel balde view:
component 1
<uploadfile
:key="comp100"
:user_data="{{ Auth::user()->toJson() }}"
store_path="/users/{{ Auth::user()->username }}/settings/email_backgrounds"
:store_route="'settings.project_email'"
:size="1000"
fillmode="cover"
></uploadfile>
component 2
<uploadfile
:key="comp200"
:user_data="{{ Auth::user()->toJson() }}"
store_path="/users/{{ Auth::user()->username }}/settings/email_backgrounds"
:store_route="'settings.project_email'"
:size="1000"
fillmode="cover"
></uploadfile>
The Vue component:
<template>
<div class="vue-wrapper">
<FlashMessage position="right top"></FlashMessage>
<div v-if="loading" class="lds-dual-ring"></div>
<div class="field">
<div class="control">
<label class="button main-button action-button m-t-20" for="uploadFiles"><span style="background-image: url('/images/icons/upload.svg')"></span>Kies bestand</label>
<input type="file" name="uploadFiles" id="uploadFiles" class="dropinput" #change="selectFile">
</div>
</div>
</div>
</template>
<script>
import { fb } from '../../firebase.js';
export default {
data() {
return {
fileObject: {
filePath: null,
url: null,
file: null,
resizedPath: null
},
loading: false
};
},
mounted() {
console.log(this.size)
console.log(this.fillmode)
},
props: [
'user_data',
'store_path',
'store_route',
'size',
'fillmode'
],
methods: {
selectFile(event)
{
var file = event.target.files[0];
this.fileObject.file = file
this.fileObject.filePath = this.store_path + '/' + file.name
this.fileObject.resizedPath = this.store_path + '/resized-' + file.name
if(file.type == 'image/png' || file.type == 'image/jpeg')
{
this.uploadFile(this.fileObject)
} else {
this.flashMessage.success({
title: 'Oeps!',
message: 'De afbeelding moet een png of een jpeg zijn!',
blockClass: 'success-message'
});
}
},
uploadFile(fileObject)
{
var vm = this
console.log(fileObject)
var storageRef = fb.storage().ref(fileObject.filePath)
var uploadTask = storageRef.put(fileObject.file)
this.loading = true
uploadTask.on('state_changed', function(snapshot){
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
},function(error) {
}, function() {
var resizeImage = fb.functions().httpsCallable('resizeImage')
resizeImage({filePath: fileObject.filePath, contentType: fileObject.file.type, watermark: false, size: vm.size, fit: vm.fillmode}).then(function(result){
var downloadRef = fb.storage().ref(fileObject.resizedPath);
downloadRef.getDownloadURL().then(function(url){
fileObject.url = url
vm.loading = false
vm.storeImage(fileObject)
}).catch(function(error){
console.log(error)
})
}).catch(function(error){
});
});
},
storeImage(file)
{
axios.post('/api/app/store_file', {
api_token: this.user_data.api_token,
user: this.user_data,
file: file,
storeRoute: this.store_route
}).then((res) => {
location.reload()
}).catch((e) => {
});
}
}
}
</script>
Does someone know how to fix this?

Datatable not refreshing on save

I want my table to refresh on save click, it's working for normal table with *ngFor, but I'm using Smartadmin angular template. I think the solution may be related to table.ajax.reload() , but how do i execute this in angular way.
save-tax.component.ts
import { Component, OnInit, Input ,Output, EventEmitter } from '#angular/core';
// Forms related packages
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { FormValidation } from 'app/shared/validators/formValidation'; //custom form validation
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { FlashMessagesService } from 'angular2-flash-messages';
#Component({
selector: 'save-tax',
templateUrl: './save-tax.component.html'
})
export class SaveTaxComponent implements OnInit {
#Output() reloadTableData = new EventEmitter();
saveTaxForm: FormGroup;
constructor(private _fb: FormBuilder,
private _config: ConfigurationService,
private _flash: FlashMessagesService) { }
ngOnInit() {
this.saveTaxForm_builder();
}
saveTaxForm_builder() {
this.saveTaxForm = this._fb.group({
tax_title: [null, [
Validators.required
]],
tax_rate: [null, [
Validators.required,
Validators.pattern(FormValidation.patterns().price),
]],
});
}
tTitle = "input"; tRate = "input";
validInput(val) {
var classSuccess = "input state-success";
val == 'tax_title' ? this.tTitle = classSuccess : null;
val == 'tax_rate' ? this.tRate = classSuccess : null;
}
invalidInput(val) {
var classError = "input state-error";
val == 'tax_title' ? this.tTitle = classError : null;
val == 'tax_rate' ? this.tRate = classError : null;
}
classReset() {
this.tTitle = "input";
this.tRate = "input";
}
save_tax() {
if (this.saveTaxForm.value) {
this._config.createTax(this.saveTaxForm.value).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-success', timeout: 1000 });
this.saveTaxForm.reset();
this.classReset();
this.reloadTableData.emit(); // Emitting an event
} else {
this.saveTaxForm.reset();
this.classReset();
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-danger', timeout: 3500 });
}
},
error => {
this.saveTaxForm.reset();
this.classReset();
this._flash.show("Please contact customer support. " + error.status + ": Internal server error.", { cssClass: 'alert alert-danger', timeout: 5000 });
});
} else {
this._flash.show('Something went wrong! Please try again..', { cssClass: 'alert alert-warning', timeout: 3000 });
}
}
}
datatable.component.ts
import {Component, Input, ElementRef, AfterContentInit, OnInit} from '#angular/core';
declare var $: any;
#Component({
selector: 'sa-datatable',
template: `
<table class="dataTable responsive {{tableClass}}" width="{{width}}">
<ng-content></ng-content>
</table>
`,
styles: [
require('smartadmin-plugins/datatables/datatables.min.css')
]
})
export class DatatableComponent implements OnInit {
#Input() public options:any;
#Input() public filter:any;
#Input() public detailsFormat:any;
#Input() public paginationLength: boolean;
#Input() public columnsHide: boolean;
#Input() public tableClass: string;
#Input() public width: string = '100%';
constructor(private el: ElementRef) {
}
ngOnInit() {
Promise.all([
System.import('script-loader!smartadmin-plugins/datatables/datatables.min.js'),
]).then(()=>{
this.render()
})
}
render() {
let element = $(this.el.nativeElement.children[0]);
let options = this.options || {}
let toolbar = '';
if (options.buttons)
toolbar += 'B';
if (this.paginationLength)
toolbar += 'l';
if (this.columnsHide)
toolbar += 'C';
if (typeof options.ajax === 'string') {
let url = options.ajax;
options.ajax = {
url: url,
complete: function (xhr) {
options.ajax.reload();
}
}
}
options = $.extend(options, {
"dom": "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-6 col-xs-12 hidden-xs text-right'" + toolbar + ">r>" +
"t" +
"<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-xs-12 col-sm-6'p>>",
oLanguage: {
"sSearch": "<span class='input-group-addon'><i class='glyphicon glyphicon-search'></i></span> ",
"sLengthMenu": "_MENU_"
},
"autoWidth": false,
retrieve: true,
responsive: true,
initComplete: (settings, json)=> {
element.parent().find('.input-sm', ).removeClass("input-sm").addClass('input-md');
}
});
const _dataTable = element.DataTable(options);
if (this.filter) {
// Apply the filter
element.on('keyup change', 'thead th input[type=text]', function () {
_dataTable
.column($(this).parent().index() + ':visible')
.search(this.value)
.draw();
});
}
//custom functions
element.on('click', 'delete', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( $(this).hasClass('delete') ) {
row.remove().draw(false);
console.log(row);
}
else {
//$(table).$('tr.selected').removeClass('selected');
$(this).addClass('selected');
}
console.log($(this).attr("class"))
});
//end custom functions
if (!toolbar) {
element.parent().find(".dt-toolbar").append('<div class="text-right"><img src="assets/img/logo.png" alt="SmartAdmin" style="width: 111px; margin-top: 3px; margin-right: 10px;"></div>');
}
if(this.detailsFormat){
let format = this.detailsFormat
element.on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( row.child.isShown() ) {
row.child.hide();
tr.removeClass('shown');
}
else {
row.child( format(row.data()) ).show();
tr.addClass('shown');
}
})
}
}
}
tax-list.component.html
<!-- NEW COL START -->
<article class="col-sm-12 col-md-12 col-lg-12">
<!-- Widget ID (each widget will need unique ID)-->
<div sa-widget [editbutton]="false" [custombutton]="false">
<header>
<span class="widget-icon"> <i class="fa fa-percent"></i> </span>
<h2>Tax Rule List</h2>
</header>
<!-- widget div-->
<div>
<!-- widget content -->
<div class="widget-body no-padding">
<sa-datatable [options]="tableData" paginationLength="true" tableClass="table table-striped table-bordered table-hover" width="100%">
<thead>
<tr>
<th data-hide="phone"> ID </th>
<th data-hide="phone,tablet">Tax Title</th>
<th data-class="expand">Tax Rate</th>
<th data-hide="phone,tablet">Status</th>
<th data-hide="phone,tablet"> Action </th>
</tr>
</thead>
</sa-datatable>
</div>
<!-- end widget content -->
</div>
<!-- end widget div -->
</div>
<!-- end widget -->
</article>
<!-- END COL -->
tax-list.component.ts
import { FlashMessagesService } from 'angular2-flash-messages';
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { Component, OnInit } from '#angular/core';
declare var $: any;
#Component({
selector: 'tax-list',
templateUrl: './tax-list.component.html'
})
export class TaxListComponent implements OnInit {
tableData: any;
constructor(private _config: ConfigurationService, private _flash: FlashMessagesService) { }
ngOnInit() {
this.fetchTableData();
this.buttonEvents();
}
fetchTableData() {
this.tableData = {
ajax: (data, callback, settings) => {
this._config.getTaxRules().subscribe(data => {
if (data.success) {
callback({
aaData: data.data
});
} else {
alert(data.msg);
}
},
error => {
alert('Internal server error..check database connection.');
});
},
serverSIde:true,
columns: [
{
render: function (data, type, row, meta) {
return meta.row + 1;
}
},
{ data: 'tax_title' },
{ data: 'tax_rate' },
{ data: 'status' },
{
render: function (data, type, row) {
return `<button type="button" class="btn btn-warning btn-xs edit" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Edit</button>
<button type="button" class="btn btn-danger btn-xs delete" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Delete</button>`;
}
}
],
buttons: [
'copy', 'pdf', 'print'
]
};
}
buttonEvents(){
document.querySelector('body').addEventListener('click', (event) => {
let target = <Element>event.target; // Cast EventTarget into an Element
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('edit')) {
this.tax_edit(target.getAttribute('data-element-id'));
}
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('delete')) {
this.tax_delete(target.getAttribute('data-element-id'));
}
});
}
tax_edit(tax_id) {
}
tax_delete(tax_id) {
this._config.deleteTaxById(tax_id).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-info fade in', timeout: 3000 });
this.fetchTableData();
} else {
this._flash.show(data.msg, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
}
},
error => {
this._flash.show(error, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
});
}
reloadTable(){
this.ngOnInit();
}
}
You can add a refresh button in your widget using <div class='widget-toolbar'>...</div> and using (click) event binding, attach a method with it. I named it as onRefresh() ...
<div sa-widget [editbutton]="false" [colorbutton]="false">
<header>
<span class="widget-icon">
<i class="fa fa-chart"></i>
</span>
<h2>Sample Datatable</h2>
<div class="widget-toolbar" role="menu">
<a class="glyphicon glyphicon-refresh" (click)="onRefresh('#studentTable table')"></a>
</div>
</header>
<div>
<div class="widget-body no-padding">
<sa-datatable id="studentTable" [options]="datatableOptions" tableClass="table table-striped table-bordered table-hover table-responsive">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Rank</th>
<th>Options</th>
</tr>
</thead>
</sa-datatable>
</div>
</div>
</div>
Focus at the method which I have given in the click event binding, I passed the id of the <sa-datatable> with table, that is #studentTable table that mentions the table tag according to the datatable implementation of the smartadmin.
Now in the component, add a method 'onRefresh()' which should be like
onRefresh(id: any) {
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
In this method, that #studentTable table will come in this id which is a parameter in the method. Using jQuery you can do the table.ajax.reload().
But you need to declare the jQuery at the top.
declare var $ : any;
app.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
declare var $: any;
#Component({
selector: 'app-order',
templateUrl: './order.component.html',
styleUrls: ['./order.component.css']
})
export class OrderComponent implements OnInit, OnDestroy {
datatableOptions:{
...
}
constructor(){
...
}
ngOnInit(){
...
}
onRefresh(){
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
}

Display Remove and Edit Item button in Order summary in Onepage Checkout page Magento2

I want to display remove and Edit Icon in order summary section in checkout page, I have tried below changes:
/magento_demo/vendor/magento/module-checkout/view/frontend/web/template/summary/item/details.html
I have added Edit / Delete links like this:
<div class="primary">
<a data-bind="attr: {href: getConfigUrl($parent.item_id),title: $t('Edit item')}" class="action edit">
<span data-bind="i18n: 'Edit'"></span>
</a>
</div>
<div class="secondary">
<a href="#" data-bind="attr: {'data-post': getDataPost($parent.item_id),title: $t('Delete item')}" class="action delete">
<span data-bind="i18n: 'Remove'"></span>
</a>
</div>
In
/magento_demo/vendor/magento/module-checkout/view/frontend/web/js/view/summary/item/details.js
I have done below changes:
define(
[
'uiComponent',
'mage/url',
'Magento_Customer/js/customer-data',
'jquery',
'ko',
'underscore',
'sidebar',
'mage/translate'
],
function (Component,url,customerData,$,ko, _) {
"use strict";
return Component.extend({
defaults: {
template: 'Magento_Checkout/summary/item/details'
},
getValue: function(quoteItem) {
var itemId = elem.data('cart-item'),
itemQty = elem.data('item-qty');
return quoteItem.name;
},
getDataPost: function(itemId) {
console.log(itemId);
var itemsData = window.checkoutConfig.quoteItemData;
var obj = {};
var obj = {
data: {}
};
itemsData.forEach(function (item) {
if(item.item_id == itemId) {
var mainlinkUrl = url.build('checkout/cart/delete/');
var baseUrl = url.build('checkout/cart/');
console.log(mainlinkUrl);
obj.action = mainlinkUrl;
obj.data.id= item.item_id;
obj.data.uenc = btoa(baseUrl);
}
});
return JSON.stringify(obj);
},
getConfigUrl: function(itemId) {
var itemsData = window.checkoutConfig.quoteItemData;
var configUrl = null;
var mainlinkUrl = url.build('checkout/cart/configure');
var linkUrl;
itemsData.forEach(function (item) {
var itm_id = item.item_id;
var product_id = item.product.entity_id;
if(item.item_id == itemId) {
linkUrl = mainlinkUrl+"/id/"+itm_id+"/product_id/"+product_id;
}
});
if(linkUrl != null) {
return linkUrl;
}
else {
return '';
}
}
});
}
);
But when I click on delete button, it just redirecting to cart page but not deleting product, what can be the issue here.

Resources