This commit is contained in:
MOHD ARIFF 2024-12-24 16:30:20 +08:00
parent 292f516e33
commit 082be76c51
18 changed files with 8835 additions and 57 deletions

View File

@ -32,8 +32,8 @@
</div> </div>
<div class="modal-body d-flex justify-content-center align-items-center"> <div class="modal-body d-flex justify-content-center align-items-center">
<div class="container"> <div class="container">
<div class="row" > <div class="row" ref="qrInfo" id="qrInfo">
<div class="col-6 text-center"> <div class="col-5 text-center">
<div class="row"> <div class="row">
<div id="QrContainer"></div> <div id="QrContainer"></div>
</div> </div>
@ -43,19 +43,24 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-6 text-center"> <div class="col-7 d-flex align-items-center justify-content-center">
<div class="col-12 text-center"> <div class="text-center fs-4 text">
Department: {{thisQRInfo.departmentName}} <div class="col-12 my-3">
{{thisQRInfo.departmentName}}
</div> </div>
<div class="col-12 text-center"> <div class="col-12 my-3">
Item: {{thisQRInfo.productName}} {{thisQRInfo.productName}}
</div> </div>
<div class="col-12 text-center"> <div class="col-12 my-3">
Warranty: {{thisQRInfo.endWDate}} {{thisQRInfo.endWDate}}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<button v-on:click="printQRInfo">Print QR Info</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -622,11 +627,27 @@
}); });
$('#itemDatatable tbody').on('click', '.print-btn', function () { $('#itemDatatable tbody').on('click', '.print-btn', function () {
const itemId = $(this).data('id'); const $button = $(this); // The clicked button
var $row = $(this).closest('tr'); // get the row containing the button const $row = $button.closest('tr'); // The parent row of the button
var imageSrc = $row.find('img').attr('src'); // find the img element in the row and get its src const itemId = $button.data('id'); // Get the item ID from the button's data attribute
// console.log(imageSrc);
self.printItem(itemId, imageSrc); let imageSrc;
// Check if the table is collapsed
if ($row.hasClass('child')) {
// For collapsed view: Look for the closest `.dtr-data` that contains the img
imageSrc = $row.prev('tr').find('td:first-child img').attr('src');
} else {
// For expanded view: Find the img in the first column of the current row
imageSrc = $row.find('td:first-child img').attr('src');
}
if (imageSrc) {
console.log(imageSrc);
self.printItem(itemId, imageSrc); // Call the print function with the itemId and imageSrc
} else {
console.error("Image source not found.");
}
}); });
this.loading = false; this.loading = false;
@ -898,6 +919,120 @@
} }
return this.items.find(item => item.uniqueID === uniqueID); return this.items.find(item => item.uniqueID === uniqueID);
}, },
// printQRInfo() {
// const qrElement = this.$refs.qrInfo;
// if (qrElement) {
// const qrElement = this.$refs.qrInfo;
// if (qrElement) {
// domtoimage.toPng(qrElement,{
// quality: 1,
// })
// .then(function (dataUrl) {
// // Print the image using printJS
// printJS({
// printable: dataUrl, // Image data URL
// type: 'image',
// css: '/../lib/bootstrap/dist/css/bootstrap.css',
// style: `
// @@media print {
// @@page { margin-top: 15px; margin-bottom: 15px; }
// body { margin: 0; }
// img {
// display: block;
// margin: auto;
// width: auto;
// max-width: 100%;
// height: auto;
// max-height: 100vh;
// }
// }
// `
// });
// })
// .catch(function (error) {
// console.error("Error generating image:", error);
// });
// }
// else {
// console.error("QR Info element not found.");
// }
// }
// },
printQRInfo() {
// Create a virtual DOM element
const virtualElement = document.createElement('div');
virtualElement.style.width = '500px'; // Set dimensions
virtualElement.style.height = 'auto';
virtualElement.style.position = 'absolute';
virtualElement.style.left = '-9999px'; // Position offscreen
// Populate the virtual DOM with content
virtualElement.innerHTML = `
<div class="row" id="qrInfo">
<div class="col-5 text-center">
<div id="QrContainer">
<!-- QR Code can be dynamically inserted here -->
</div>
<div>
<p>${this.thisQRInfo.uniqueID}</p>
</div>
</div>
<div class="col-7 d-flex align-items-center justify-content-center">
<div class="text-center fs-4 text">
<div>${this.thisQRInfo.departmentName}</div>
<div>${this.thisQRInfo.productName}</div>
<div>${this.thisQRInfo.endWDate}</div>
</div>
</div>
</div>
`;
// Append the virtual DOM to the body (temporarily)
document.body.appendChild(virtualElement);
// Use html2canvas to convert the virtual DOM to an image
html2canvas(virtualElement, {
scale: 10, // Increase scale for sharper image
useCORS: true,
allowTaint: true,
}).then((canvas) => {
// Convert the canvas to an image
const imgData = canvas.toDataURL('image/png');
// Use printJS to print the image
printJS({
printable: imgData,
type: 'image',
css: '/../lib/bootstrap/dist/css/bootstrap.css',
style: `
@@media print {
@@page {
margin-top: 15px;
margin-bottom: 15px;
}
body { margin: 0; }
img {
display: block;
margin: auto;
width: auto;
max-width: 100%;
height: auto;
max-height: 100vh;
}
}
`
});
// Remove the virtual DOM from the body after use
document.body.removeChild(virtualElement);
}).catch((error) => {
console.error("Error generating image:", error);
// Remove the virtual DOM if an error occurs
document.body.removeChild(virtualElement);
});
},
}, },
}); });
</script> </script>

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Mono.TextTemplating; using Mono.TextTemplating;
@ -19,11 +20,13 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
{ {
private readonly ILogger<InvMainAPI> _logger; private readonly ILogger<InvMainAPI> _logger;
private readonly CentralSystemContext _centralDbContext; private readonly CentralSystemContext _centralDbContext;
private readonly UserManager<UserModel> _userManager;
public InvMainAPI(ILogger<InvMainAPI> logger, CentralSystemContext centralDbContext) public InvMainAPI(ILogger<InvMainAPI> logger, CentralSystemContext centralDbContext, UserManager<UserModel> userManager)
{ {
_logger = logger; _logger = logger;
_centralDbContext = centralDbContext; _centralDbContext = centralDbContext;
_userManager = userManager;
} }
public class DepartmentCompany public class DepartmentCompany
@ -209,8 +212,30 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
[HttpPost("ItemList")] [HttpPost("ItemList")]
public async Task<IActionResult> ItemList() public async Task<IActionResult> ItemList()
{ {
try
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return BadRequest("User not found");
}
else
{
user.departmentId = user.departmentId != null ? user.departmentId : 0;
}
var userRole = await _userManager.GetRolesAsync(user);
var isAdmin = userRole.Contains("SystemAdmin") || userRole.Contains("SuperAdmin");
List<ItemModel> itemList = new List<ItemModel>();
// Get the item list // Get the item list
var itemList = await _centralDbContext.Items.Include("CreatedBy").Include("Department").Include("Product").ToListAsync(); if (isAdmin)
{
itemList = await _centralDbContext.Items.Include("CreatedBy").Include("Department").Include("Product").ToListAsync();
}
else
{
itemList = await _centralDbContext.Items.Include("CreatedBy").Include("Department").Include("Product").Where(i => i.DepartmentId == user.departmentId).ToListAsync();
}
// Get the departments list (DepartmentId references Departments) // Get the departments list (DepartmentId references Departments)
var departments = await _centralDbContext.Departments.ToListAsync(); var departments = await _centralDbContext.Departments.ToListAsync();
@ -226,7 +251,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
item.SerialNumber, item.SerialNumber,
item.Quantity, item.Quantity,
item.Supplier, item.Supplier,
item.PurchaseDate, PurchaseDate = item.PurchaseDate.ToString("dd/MM/yyyy"),
item.PONo, item.PONo,
item.Currency, item.Currency,
item.DefaultPrice, item.DefaultPrice,
@ -234,7 +259,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
item.ConvertPrice, item.ConvertPrice,
item.DODate, item.DODate,
item.Warranty, item.Warranty,
item.EndWDate, EndWDate = item.EndWDate.ToString("dd/MM/yyyy"),
item.InvoiceDate, item.InvoiceDate,
item.Department?.DepartmentName, item.Department?.DepartmentName,
item.CreatedBy!.UserName, item.CreatedBy!.UserName,
@ -244,8 +269,11 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
return Json(itemListWithDetails); return Json(itemListWithDetails);
} }
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
[HttpPost("GenerateItemQr/{id}")] [HttpPost("GenerateItemQr/{id}")]
public IActionResult GenerateItemQr(string id) public IActionResult GenerateItemQr(string id)

View File

@ -28,7 +28,7 @@
<link rel="stylesheet" href="~/assets/libs/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css" /> <link rel="stylesheet" href="~/assets/libs/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css" />
<link rel="stylesheet" href="~/assets/libs/quill/dist/quill.snow.css" /> <link rel="stylesheet" href="~/assets/libs/quill/dist/quill.snow.css" />
<link href="~/dist/css/style.min.css" rel="stylesheet" /> <link href="~/dist/css/style.min.css" rel="stylesheet" />
<link href="~/lib/printjs/print.min.css" rel="stylesheet" />
<!-- DataTables CSS--> <!-- DataTables CSS-->
<link href="~/lib/datatables/datatables.css" rel="stylesheet" /> <link href="~/lib/datatables/datatables.css" rel="stylesheet" />
<!-- Vue Js --> <!-- Vue Js -->
@ -740,6 +740,9 @@
<script src="~/assets/libs/jquery-minicolors/jquery.minicolors.min.js"></script> <script src="~/assets/libs/jquery-minicolors/jquery.minicolors.min.js"></script>
<script src="~/assets/libs/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script> <script src="~/assets/libs/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script>
<script src="~/assets/libs/quill/dist/quill.min.js"></script> <script src="~/assets/libs/quill/dist/quill.min.js"></script>
<script src="~/lib/printjs/print.min.js"></script>
<script src="~/lib/html2canvas/html2canvas.min.js"></script>
<script src="~/lib/html2canvas/dom-to-image.min.js"></script>
<!-- Datatables JS--> <!-- Datatables JS-->
<script src="~/lib/datatables/datatables.js"></script> <script src="~/lib/datatables/datatables.js"></script>
@await RenderSectionAsync("Scripts", required: false) @await RenderSectionAsync("Scripts", required: false)

View File

@ -0,0 +1,29 @@
const Browser = {
// Firefox 1.0+
isFirefox: () => {
return typeof InstallTrigger !== 'undefined'
},
// Internet Explorer 6-11
isIE: () => {
return navigator.userAgent.indexOf('MSIE') !== -1 || !!document.documentMode
},
// Edge 20+
isEdge: () => {
return !Browser.isIE() && !!window.StyleMedia
},
// Chrome 1+
isChrome: (context = window) => {
return !!context.chrome
},
// At least Safari 3+: "[object HTMLElementConstructor]"
isSafari: () => {
return Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 ||
navigator.userAgent.toLowerCase().indexOf('safari') !== -1
},
// IOS Chrome
isIOSChrome: () => {
return navigator.userAgent.toLowerCase().indexOf('crios') !== -1
}
}
export default Browser

View File

@ -0,0 +1,103 @@
import Modal from './modal'
import Browser from './browser'
export function addWrapper (htmlData, params) {
const bodyStyle = 'font-family:' + params.font + ' !important; font-size: ' + params.font_size + ' !important; width:100%;'
return '<div style="' + bodyStyle + '">' + htmlData + '</div>'
}
export function capitalizePrint (obj) {
return obj.charAt(0).toUpperCase() + obj.slice(1)
}
export function collectStyles (element, params) {
const win = document.defaultView || window
// String variable to hold styling for each element
let elementStyle = ''
// Loop over computed styles
const styles = win.getComputedStyle(element, '')
for (let key = 0; key < styles.length; key++) {
// Check if style should be processed
if (params.targetStyles.indexOf('*') !== -1 || params.targetStyle.indexOf(styles[key]) !== -1 || targetStylesMatch(params.targetStyles, styles[key])) {
if (styles.getPropertyValue(styles[key])) elementStyle += styles[key] + ':' + styles.getPropertyValue(styles[key]) + ';'
}
}
// Print friendly defaults (deprecated)
elementStyle += 'max-width: ' + params.maxWidth + 'px !important; font-size: ' + params.font_size + ' !important;'
return elementStyle
}
function targetStylesMatch (styles, value) {
for (let i = 0; i < styles.length; i++) {
if (typeof value === 'object' && value.indexOf(styles[i]) !== -1) return true
}
return false
}
export function addHeader (printElement, params) {
// Create the header container div
const headerContainer = document.createElement('div')
// Check if the header is text or raw html
if (isRawHTML(params.header)) {
headerContainer.innerHTML = params.header
} else {
// Create header element
const headerElement = document.createElement('h1')
// Create header text node
const headerNode = document.createTextNode(params.header)
// Build and style
headerElement.appendChild(headerNode)
headerElement.setAttribute('style', params.headerStyle)
headerContainer.appendChild(headerElement)
}
printElement.insertBefore(headerContainer, printElement.childNodes[0])
}
export function cleanUp (params) {
// If we are showing a feedback message to user, remove it
if (params.showModal) Modal.close()
// Check for a finished loading hook function
if (params.onLoadingEnd) params.onLoadingEnd()
// If preloading pdf files, clean blob url
if (params.showModal || params.onLoadingStart) window.URL.revokeObjectURL(params.printable)
// Run onPrintDialogClose callback
let event = 'mouseover'
if (Browser.isChrome() || Browser.isFirefox()) {
// Ps.: Firefox will require an extra click in the document to fire the focus event.
event = 'focus'
}
const handler = () => {
// Make sure the event only happens once.
window.removeEventListener(event, handler)
params.onPrintDialogClose()
// Remove iframe from the DOM
const iframe = document.getElementById(params.frameId)
if (iframe) {
iframe.remove()
}
}
window.addEventListener(event, handler)
}
export function isRawHTML (raw) {
const regexHtml = new RegExp('<([A-Za-z][A-Za-z0-9]*)\\b[^>]*>(.*?)</\\1>')
return regexHtml.test(raw)
}

View File

@ -0,0 +1,65 @@
import { collectStyles, addHeader } from './functions'
import Print from './print'
export default {
print: (params, printFrame) => {
// Get the DOM printable element
const printElement = document.getElementById(params.printable)
// Check if the element exists
if (!printElement) {
window.console.error('Invalid HTML element id: ' + params.printable)
return
}
// Clone the target element including its children (if available)
params.printableElement = cloneElement(printElement, params)
// Add header
if (params.header) {
addHeader(params.printableElement, params)
}
// Print html element contents
Print.send(params, printFrame)
}
}
function cloneElement (element, params) {
// Clone the main node (if not already inside the recursion process)
const clone = element.cloneNode()
// Loop over and process the children elements / nodes (including text nodes)
const childNodesArray = Array.prototype.slice.call(element.childNodes)
for (let i = 0; i < childNodesArray.length; i++) {
// Check if we are skiping the current element
if (params.ignoreElements.indexOf(childNodesArray[i].id) !== -1) {
continue
}
// Clone the child element
const clonedChild = cloneElement(childNodesArray[i], params)
// Attach the cloned child to the cloned parent node
clone.appendChild(clonedChild)
}
// Get all styling for print element (for nodes of type element only)
if (params.scanStyles && element.nodeType === 1) {
clone.setAttribute('style', collectStyles(element, params))
}
// Check if the element needs any state processing (copy user input data)
switch (element.tagName) {
case 'SELECT':
// Copy the current selection value to its clone
clone.value = element.value
break
case 'CANVAS':
// Copy the canvas content to its clone
clone.getContext('2d').drawImage(element, 0, 0)
break
}
return clone
}

View File

@ -0,0 +1,48 @@
import { addHeader } from './functions'
import Print from './print'
import Browser from './browser'
export default {
print: (params, printFrame) => {
// Check if we are printing one image or multiple images
if (params.printable.constructor !== Array) {
// Create array with one image
params.printable = [params.printable]
}
// Create printable element (container)
params.printableElement = document.createElement('div')
// Create all image elements and append them to the printable container
params.printable.forEach(src => {
// Create the image element
const img = document.createElement('img')
img.setAttribute('style', params.imageStyle)
// Set image src with the file url
img.src = src
// The following block is for Firefox, which for some reason requires the image's src to be fully qualified in
// order to print it
if (Browser.isFirefox()) {
const fullyQualifiedSrc = img.src
img.src = fullyQualifiedSrc
}
// Create the image wrapper
const imageWrapper = document.createElement('div')
// Append image to the wrapper element
imageWrapper.appendChild(img)
// Append wrapper to the printable element
params.printableElement.appendChild(imageWrapper)
})
// Check if we are adding a print header
if (params.header) addHeader(params.printableElement, params)
// Print image
Print.send(params, printFrame)
}
}

168
wwwroot/lib/PrintJs/init.js Normal file
View File

@ -0,0 +1,168 @@
'use strict'
import Browser from './browser'
import Modal from './modal'
import Pdf from './pdf'
import Html from './html'
import RawHtml from './raw-html'
import Image from './image'
import Json from './json'
const printTypes = ['pdf', 'html', 'image', 'json', 'raw-html']
export default {
init () {
const params = {
printable: null,
fallbackPrintable: null,
type: 'pdf',
header: null,
headerStyle: 'font-weight: 300;',
maxWidth: 800,
properties: null,
gridHeaderStyle: 'font-weight: bold; padding: 5px; border: 1px solid #dddddd;',
gridStyle: 'border: 1px solid lightgray; margin-bottom: -1px;',
showModal: false,
onError: (error) => { throw error },
onLoadingStart: null,
onLoadingEnd: null,
onPrintDialogClose: () => {},
onIncompatibleBrowser: () => {},
modalMessage: 'Retrieving Document...',
frameId: 'printJS',
printableElement: null,
documentTitle: 'Document',
targetStyle: ['clear', 'display', 'width', 'min-width', 'height', 'min-height', 'max-height'],
targetStyles: ['border', 'box', 'break', 'text-decoration'],
ignoreElements: [],
repeatTableHeader: true,
css: null,
style: null,
scanStyles: true,
base64: false,
// Deprecated
onPdfOpen: null,
font: 'TimesNewRoman',
font_size: '12pt',
honorMarginPadding: true,
honorColor: false,
imageStyle: 'max-width: 100%;'
}
// Check if a printable document or object was supplied
const args = arguments[0]
if (args === undefined) {
throw new Error('printJS expects at least 1 attribute.')
}
// Process parameters
switch (typeof args) {
case 'string':
params.printable = encodeURI(args)
params.fallbackPrintable = params.printable
params.type = arguments[1] || params.type
break
case 'object':
params.printable = args.printable
params.fallbackPrintable = typeof args.fallbackPrintable !== 'undefined' ? args.fallbackPrintable : params.printable
params.fallbackPrintable = params.base64 ? `data:application/pdf;base64,${params.fallbackPrintable}` : params.fallbackPrintable
for (var k in params) {
if (k === 'printable' || k === 'fallbackPrintable') continue
params[k] = typeof args[k] !== 'undefined' ? args[k] : params[k]
}
break
default:
throw new Error('Unexpected argument type! Expected "string" or "object", got ' + typeof args)
}
// Validate printable
if (!params.printable) throw new Error('Missing printable information.')
// Validate type
if (!params.type || typeof params.type !== 'string' || printTypes.indexOf(params.type.toLowerCase()) === -1) {
throw new Error('Invalid print type. Available types are: pdf, html, image and json.')
}
// Check if we are showing a feedback message to the user (useful for large files)
if (params.showModal) Modal.show(params)
// Check for a print start hook function
if (params.onLoadingStart) params.onLoadingStart()
// To prevent duplication and issues, remove any used printFrame from the DOM
const usedFrame = document.getElementById(params.frameId)
if (usedFrame) usedFrame.parentNode.removeChild(usedFrame)
// Create a new iframe for the print job
const printFrame = document.createElement('iframe')
if (Browser.isFirefox()) {
// Set the iframe to be is visible on the page (guaranteed by fixed position) but hidden using opacity 0, because
// this works in Firefox. The height needs to be sufficient for some part of the document other than the PDF
// viewer's toolbar to be visible in the page
printFrame.setAttribute('style', 'width: 1px; height: 100px; position: fixed; left: 0; top: 0; opacity: 0; border-width: 0; margin: 0; padding: 0')
} else {
// Hide the iframe in other browsers
printFrame.setAttribute('style', 'visibility: hidden; height: 0; width: 0; position: absolute; border: 0')
}
// Set iframe element id
printFrame.setAttribute('id', params.frameId)
// For non pdf printing, pass an html document string to srcdoc (force onload callback)
if (params.type !== 'pdf') {
printFrame.srcdoc = '<html><head><title>' + params.documentTitle + '</title>'
// Attach css files
if (params.css) {
// Add support for single file
if (!Array.isArray(params.css)) params.css = [params.css]
// Create link tags for each css file
params.css.forEach(file => {
printFrame.srcdoc += '<link rel="stylesheet" href="' + file + '">'
})
}
printFrame.srcdoc += '</head><body></body></html>'
}
// Check printable type
switch (params.type) {
case 'pdf':
// Check browser support for pdf and if not supported we will just open the pdf file instead
if (Browser.isIE()) {
try {
console.info('Print.js doesn\'t support PDF printing in Internet Explorer.')
const win = window.open(params.fallbackPrintable, '_blank')
win.focus()
params.onIncompatibleBrowser()
} catch (error) {
params.onError(error)
} finally {
// Make sure there is no loading modal opened
if (params.showModal) Modal.close()
if (params.onLoadingEnd) params.onLoadingEnd()
}
} else {
Pdf.print(params, printFrame)
}
break
case 'image':
Image.print(params, printFrame)
break
case 'html':
Html.print(params, printFrame)
break
case 'raw-html':
RawHtml.print(params, printFrame)
break
case 'json':
Json.print(params, printFrame)
break
}
}
}

109
wwwroot/lib/PrintJs/json.js Normal file
View File

@ -0,0 +1,109 @@
import { capitalizePrint, addHeader } from './functions'
import Print from './print'
export default {
print: (params, printFrame) => {
// Check if we received proper data
if (typeof params.printable !== 'object') {
throw new Error('Invalid javascript data object (JSON).')
}
// Validate repeatTableHeader
if (typeof params.repeatTableHeader !== 'boolean') {
throw new Error('Invalid value for repeatTableHeader attribute (JSON).')
}
// Validate properties
if (!params.properties || !Array.isArray(params.properties)) {
throw new Error('Invalid properties array for your JSON data.')
}
// We will format the property objects to keep the JSON api compatible with older releases
params.properties = params.properties.map(property => {
return {
field: typeof property === 'object' ? property.field : property,
displayName: typeof property === 'object' ? property.displayName : property,
columnSize: typeof property === 'object' && property.columnSize ? property.columnSize + ';' : 100 / params.properties.length + '%;'
}
})
// Create a print container element
params.printableElement = document.createElement('div')
// Check if we are adding a print header
if (params.header) {
addHeader(params.printableElement, params)
}
// Build the printable html data
params.printableElement.innerHTML += jsonToHTML(params)
// Print the json data
Print.send(params, printFrame)
}
}
function jsonToHTML (params) {
// Get the row and column data
const data = params.printable
const properties = params.properties
// Create a html table
let htmlData = '<table style="border-collapse: collapse; width: 100%;">'
// Check if the header should be repeated
if (params.repeatTableHeader) {
htmlData += '<thead>'
}
// Add the table header row
htmlData += '<tr>'
// Add the table header columns
for (let a = 0; a < properties.length; a++) {
htmlData += '<th style="width:' + properties[a].columnSize + ';' + params.gridHeaderStyle + '">' + capitalizePrint(properties[a].displayName) + '</th>'
}
// Add the closing tag for the table header row
htmlData += '</tr>'
// If the table header is marked as repeated, add the closing tag
if (params.repeatTableHeader) {
htmlData += '</thead>'
}
// Create the table body
htmlData += '<tbody>'
// Add the table data rows
for (let i = 0; i < data.length; i++) {
// Add the row starting tag
htmlData += '<tr>'
// Print selected properties only
for (let n = 0; n < properties.length; n++) {
let stringData = data[i]
// Support nested objects
const property = properties[n].field.split('.')
if (property.length > 1) {
for (let p = 0; p < property.length; p++) {
stringData = stringData[property[p]]
}
} else {
stringData = stringData[properties[n].field]
}
// Add the row contents and styles
htmlData += '<td style="width:' + properties[n].columnSize + params.gridStyle + '">' + stringData + '</td>'
}
// Add the row closing tag
htmlData += '</tr>'
}
// Add the table and body closing tags
htmlData += '</tbody></table>'
return htmlData
}

View File

@ -0,0 +1,62 @@
const Modal = {
show (params) {
// Build modal
const modalStyle = 'font-family:sans-serif; ' +
'display:table; ' +
'text-align:center; ' +
'font-weight:300; ' +
'font-size:30px; ' +
'left:0; top:0;' +
'position:fixed; ' +
'z-index: 9990;' +
'color: #0460B5; ' +
'width: 100%; ' +
'height: 100%; ' +
'background-color:rgba(255,255,255,.9);' +
'transition: opacity .3s ease;'
// Create wrapper
const printModal = document.createElement('div')
printModal.setAttribute('style', modalStyle)
printModal.setAttribute('id', 'printJS-Modal')
// Create content div
const contentDiv = document.createElement('div')
contentDiv.setAttribute('style', 'display:table-cell; vertical-align:middle; padding-bottom:100px;')
// Add close button (requires print.css)
const closeButton = document.createElement('div')
closeButton.setAttribute('class', 'printClose')
closeButton.setAttribute('id', 'printClose')
contentDiv.appendChild(closeButton)
// Add spinner (requires print.css)
const spinner = document.createElement('span')
spinner.setAttribute('class', 'printSpinner')
contentDiv.appendChild(spinner)
// Add message
const messageNode = document.createTextNode(params.modalMessage)
contentDiv.appendChild(messageNode)
// Add contentDiv to printModal
printModal.appendChild(contentDiv)
// Append print modal element to document body
document.getElementsByTagName('body')[0].appendChild(printModal)
// Add event listener to close button
document.getElementById('printClose').addEventListener('click', function () {
Modal.close()
})
},
close () {
const printModal = document.getElementById('printJS-Modal')
if (printModal) {
printModal.parentNode.removeChild(printModal)
}
}
}
export default Modal

View File

@ -0,0 +1,57 @@
import Print from './print'
import { cleanUp } from './functions'
export default {
print: (params, printFrame) => {
// Check if we have base64 data
if (params.base64) {
const bytesArray = Uint8Array.from(atob(params.printable), c => c.charCodeAt(0))
createBlobAndPrint(params, printFrame, bytesArray)
return
}
// Format pdf url
params.printable = /^(blob|http|\/\/)/i.test(params.printable)
? params.printable
: window.location.origin + (params.printable.charAt(0) !== '/' ? '/' + params.printable : params.printable)
// Get the file through a http request (Preload)
const req = new window.XMLHttpRequest()
req.responseType = 'arraybuffer'
req.addEventListener('error', () => {
cleanUp(params)
params.onError(req.statusText)
// Since we don't have a pdf document available, we will stop the print job
})
req.addEventListener('load', () => {
// Check for errors
if ([200, 201].indexOf(req.status) === -1) {
cleanUp(params)
params.onError(req.statusText)
// Since we don't have a pdf document available, we will stop the print job
return
}
// Print requested document
createBlobAndPrint(params, printFrame, req.response)
})
req.open('GET', params.printable, true)
req.send()
}
}
function createBlobAndPrint (params, printFrame, data) {
// Pass response or base64 data to a blob and create a local object url
let localPdf = new window.Blob([data], { type: 'application/pdf' })
localPdf = window.URL.createObjectURL(localPdf)
// Set iframe src with pdf document url
printFrame.setAttribute('src', localPdf)
Print.send(params, printFrame)
}

View File

@ -0,0 +1,102 @@
import Browser from './browser'
import { cleanUp } from './functions'
const Print = {
send: (params, printFrame) => {
// Append iframe element to document body
document.getElementsByTagName('body')[0].appendChild(printFrame)
// Get iframe element
const iframeElement = document.getElementById(params.frameId)
// Wait for iframe to load all content
iframeElement.onload = () => {
if (params.type === 'pdf') {
// Add a delay for Firefox. In my tests, 1000ms was sufficient but 100ms was not
if (Browser.isFirefox()) {
setTimeout(() => performPrint(iframeElement, params), 1000)
} else {
performPrint(iframeElement, params)
}
return
}
// Get iframe element document
let printDocument = (iframeElement.contentWindow || iframeElement.contentDocument)
if (printDocument.document) printDocument = printDocument.document
// Append printable element to the iframe body
printDocument.body.appendChild(params.printableElement)
// Add custom style
if (params.type !== 'pdf' && params.style) {
// Create style element
const style = document.createElement('style')
style.innerHTML = params.style
// Append style element to iframe's head
printDocument.head.appendChild(style)
}
// If printing images, wait for them to load inside the iframe
const images = printDocument.getElementsByTagName('img')
if (images.length > 0) {
loadIframeImages(Array.from(images)).then(() => performPrint(iframeElement, params))
} else {
performPrint(iframeElement, params)
}
}
}
}
function performPrint (iframeElement, params) {
try {
iframeElement.focus()
// If Edge or IE, try catch with execCommand
if (Browser.isEdge() || Browser.isIE()) {
try {
iframeElement.contentWindow.document.execCommand('print', false, null)
} catch (e) {
iframeElement.contentWindow.print()
}
} else {
// Other browsers
iframeElement.contentWindow.print()
}
} catch (error) {
params.onError(error)
} finally {
if (Browser.isFirefox()) {
// Move the iframe element off-screen and make it invisible
iframeElement.style.visibility = 'hidden'
iframeElement.style.left = '-1px'
}
cleanUp(params)
}
}
function loadIframeImages (images) {
const promises = images.map(image => {
if (image.src && image.src !== window.location.href) {
return loadIframeImage(image)
}
})
return Promise.all(promises)
}
function loadIframeImage (image) {
return new Promise(resolve => {
const pollImage = () => {
!image || typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0 || !image.complete
? setTimeout(pollImage, 500)
: resolve()
}
pollImage()
})
}
export default Print

1
wwwroot/lib/PrintJs/print.min.css vendored Normal file
View File

@ -0,0 +1 @@
.printModal{font-family:sans-serif;display:flex;text-align:center;font-weight:300;font-size:30px;left:0;top:0;position:absolute;color:#045fb4;width:100%;height:100%;background-color:hsla(0,0%,100%,.9)}.printClose{position:absolute;right:10px;top:10px}.printClose:before{content:"\00D7";font-family:Helvetica Neue,sans-serif;font-weight:100;line-height:1px;padding-top:.5em;display:block;font-size:2em;text-indent:1px;overflow:hidden;height:1.25em;width:1.25em;text-align:center;cursor:pointer}.printSpinner{margin-top:3px;margin-left:-40px;position:absolute;display:inline-block;width:25px;height:25px;border:2px solid #045fb4;border-radius:50%;animation:spin .75s linear infinite}.printSpinner:after,.printSpinner:before{left:-2px;top:-2px;display:none;position:absolute;content:"";width:inherit;height:inherit;border:inherit;border-radius:inherit}.printSpinner,.printSpinner:after,.printSpinner:before{display:inline-block;border-color:#045fb4 transparent transparent;animation-duration:1.2s}.printSpinner:before{transform:rotate(120deg)}.printSpinner:after{transform:rotate(240deg)}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}

1
wwwroot/lib/PrintJs/print.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
import Print from './print'
export default {
print: (params, printFrame) => {
// Create printable element (container)
params.printableElement = document.createElement('div')
params.printableElement.setAttribute('style', 'width:100%')
// Set our raw html as the printable element inner html content
params.printableElement.innerHTML = params.printable
// Print html contents
Print.send(params, printFrame)
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long