PHPExcel setWidth doesnt work - php

i convert an excel file to HTML Table with PHPExcel
(PHPspreadsheet) with Symfony 2.5
I'm trying to set a filter to only load the range ('A','N') , the first 13 columns. not working..
I'm also trying to set the Width of the 'N' Column. not working..
when i dump the column's width value is correct..
I can increase the columns width but not decrease them..
it looks like the text inside the cell is defining the cell's width automatically..
Here is my controller :
public function showClientAction($client)
{
$excel = glob(''.path.'\\'.path.'\\filename_' .$client.'.{xlsx,xls,xlsm,xlsm.ink}', GLOB_BRACE);
$filterSubset = new \PHPExcel_Reader_DefaultReadFilter(1,1000,range('A','N'));
$objReader = \PHPExcel_IOFactory::createReaderForFile($excel[0]);
$objReader->setReadFilter($filterSubset);
/** Read the list of worksheet names and select the one that we want to load **/
$worksheetList = $objReader->listWorksheetNames($excel[0]);
$sheetname = $worksheetList[0];
/** Advise the Reader of which WorkSheets we want to load **/
$objReader->setLoadSheetsOnly($sheetname);
$objPHPExcel = $objReader->load($excel[0]);
$objPHPExcel->getActiveSheet()->getColumnDimensionByColumn('13')->setAutoSize(false);
$objPHPExcel->getActiveSheet()->getColumnDimensionByColumn('13')->setWidth(2.5);
// OUTPUT is : int (13) applied correctly
var_dump($objPHPExcel->getActiveSheet()->getColumnDimensionByColumn('13'));
$writer = \PHPExcel_IOFactory::createWriter($objPHPExcel, "HTML");
$writer->generateSheetData();
$writer->generateStyles();
return $this->render('SocPerfclientBundle:Default:testexcel.html.twig', array(
'excelHtml'=>$writer,
'stylesExcel'=>$writer,
'client'=>$nom_client
));
}
My filter :
class PHPExcel_Reader_DefaultReadFilter implements PHPExcel_Reader_IReadFilter
{
public $_startRow = 0;
public $_endRow = 0;
public $_columns = array();
/** Get the list of rows and columns to read */
public function __construct($startRow, $endRow, $columns) {
$this->_startRow = $startRow;
$this->_endRow = $endRow;
$this->_columns = $columns;
}
public function readCell($column, $row, $worksheetName = '') {
// Only read the rows and columns that were configured
if ($row >= $this->_startRow && $row <= $this->_endRow) {
if (in_array($column,$this->_columns)) {
return true;
}
}
return false;
}
}
my view :
{{ excelHtml.generateSheetData | raw }}
{{ stylesExcel.generateStyles | raw }}
Here a screenshot html view :
We can see the "RCA" column still having the initial width.. my setWidth isnt applied..
if i change the link by a shorter word like : yes.docx , the column decreases.

Related

Laravel excel get total number of rows before import

Straight forward question. How does one get the total number of rows in a spreadsheet with laravel-excel?
I now have a working counter of how many rows have been processed (in the CompanyImport file), but I need the total number of rows before I start adding the rows to the database.
The sheet I'm importing is almost 1M rows, so I am trying to create a progress bar.
My import:
public function model(array $row)
{
# Counter
++$this->currentRow;
# Dont create or validate on empty rows
# Bad workaround
# TODO: better solution
if (!array_filter($row)) {
return null;
}
# Create company
$company = new Company;
$company->crn = $row['crn'];
$company->name = $row['name'];
$company->email = $row['email'];
$company->phone = $row['phone'];
$company->website = (!empty($row['website'])) ? Helper::addScheme($row['website']) : '';
$company->save();
# Everything empty.. delete address
if (!empty($row['country']) || !empty($row['state']) || !empty($row['postal']) || !empty($row['address']) || !empty($row['zip'])) {
# Create address
$address = new CompanyAddress;
$address->company_id = $company->id;
$address->country = $row['country'];
$address->state = $row['state'];
$address->postal = $row['postal'];
$address->address = $row['address'];
$address->zip = $row['zip'];
$address->save();
# Attach
$company->addresses()->save($address);
}
# Update session counter
Session::put('importCurrentRow', $this->currentRow);
return $company;
}
My controller:
public function postImport(Import $request)
{
# Import
$import = new CompaniesImport;
# Todo
# Total number of rows in the sheet to session
Session::put('importTotalRows');
#
Excel::import($import, $request->file('file')->getPathname());
return response()->json([
'success' => true
]);
}
In Laravel Excel 3.1 you can get the total rows by implementing WithEvents and listening to beforeImport event.
<?php
namespace App\Imports;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\BeforeImport;
class UserImport extends ToModel, WithEvents {
[...]
public function registerEvents(): array
{
return [
BeforeImport::class => function (BeforeImport $event) {
$totalRows = $event->getReader()->getTotalRows();
if (!empty($totalRows)) {
echo $totalRows['Worksheet'];
}
}
];
}
[...]
}
You can use below code to calculate number of rows
Excel::import($import, 'users.xlsx');
dd('Row count: ' . $import->getRowCount());
You can check the Docs
Update
The above method was for calculating the rows which have been imported so far.
In order to get number of rows which are in the sheet, you need to use getHighestRow
Excel::load($file, function($reader) {
$lastrow = $reader->getActiveSheet()->getHighestRow();
dd($lastrow);
});
This has been referenced here by author of the Plugin.
1.- Make file for import
php artisan make:import ImportableImport
2.- Your File Import
<?php
namespace App\Imports;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\Importable;
class ImportablesImport implements ToCollection
{
use Importable;
/**
* #param Collection $collection
*/
public function collection(Collection $collection)
{
//
}
}
3.- Your controller
$array = (new ImportablesImport)->toArray($file);
dd(count($array[0]));
This doc: https://docs.laravel-excel.com/3.1/imports/importables.html
You can use below code to get number of rows before import
$fileExtension = pathinfo($file, PATHINFO_EXTENSION);
$temporaryFileFactory=new \Maatwebsite\Excel\Files\TemporaryFileFactory(
config('excel.temporary_files.local_path',
config('excel.exports.temp_path',
storage_path('framework/laravel-excel'))
),
config('excel.temporary_files.remote_disk')
);
$temporaryFile = $temporaryFileFactory->make($fileExtension);
$currentFile = $temporaryFile->copyFrom($file,null);
$reader = \Maatwebsite\Excel\Factories\ReaderFactory::make(null,$currentFile);
$info = $reader->listWorksheetInfo($currentFile->getLocalPath());
$totalRows = 0;
foreach ($info as $sheet) {
$totalRows+= $sheet['totalRows'];
}
$currentFile->delete();
The code taken from Laravel Excel libary
Check Below Example:
$sheet->getActiveSheet()->getStyle('A2:A' . $sheet->getHighestRow())->getFont()->setBold(true);
by using getHighestRow() method you can fetch the total number of rows. In the above code sample I've applied font as BOLD to the second cell of first column till the maximum row count of that same first column.
Detailed Code Snippet of another example:
$excel->sheet('Employee Details', function ($sheet) use ($AllData) {
$sheet->fromArray($AllData);
$sheet->setAutoSize(true);
$sheet->getStyle('A2:A' . $sheet->getHighestRow())->applyFromArray(array('alignment' => array('horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_LEFT)));
});

phpoffice/phpspreadsheet - How to exclude no value cells from getHighestRow()

$eanStyle = new \PHPExcel_Style();
$eanStyle->getNumberFormat()->applyFromArray([
'code' => '0000000000000'
]);
/* apply styles */
$mainSheet->duplicateStyle($eanStyle, 'A2:A10000');
Code above generates .xlsx template file, user enters data (7 rows) and upload file and then:
$mainSheet->getHighestRow('A'); // retruns 10000 instead of 8 (7 rows + header)
Thanks in advance for help.
I would advise you create a read filter to read only specific rows and columns. This would prevent the other empty rows being included:
$inputFileType = 'Xls';
$inputFileName = './sampleData/example1.xls';
$sheetname = 'Data Sheet #3';
/** Define a Read Filter class implementing \PhpOffice\PhpSpreadsheet\Reader\IReadFilter */
class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter {
public function readCell($column, $row, $worksheetName = '') {
// Read rows 1 to 7 and columns A to E only
if ($row >= 1 && $row <= 7) {
if (in_array($column,range('A','E'))) {
return true;
}
}
return false;
}
}
/** Create an Instance of our Read Filter **/
$filterSubset = new MyReadFilter();
/** Create a new Reader of the type defined in $inputFileType **/
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
/** Tell the Reader that we want to use the Read Filter **/
$reader->setReadFilter($filterSubset);
/** Load only the rows and columns that match our filter to Spreadsheet **/
$spreadsheet = $reader->load($inputFileName);

PHPExcel ReadFilter doesnt work

I'm still stuck with symfony2 and phpexcel..
First I don't know why my HTML writer doesn't generate <thead></thead> tags..
I don't understand why blanks columns still displaying as you can see on this screenshot, a ReadFilter is applied:
I just want to load the first 13 columns:
public function showClientAction()
{
$excel = glob(''.path.'\\'.tofile.'\\'.$file.'.{xlsx,xls,xlsm,xlsm.ink}', GLOB_BRACE);
$filterSubset = new \PHPExcel_Reader_DefaultReadFilter('A','N');
$objReader = \PHPExcel_IOFactory::createReaderForFile($excel[0]);
$objReader->setReadFilter($filterSubset);
/** Read the list of worksheet names and select the one that we want to load **/
$worksheetList = $objReader->listWorksheetNames($excel[0]);
$sheetname = $worksheetList[0];
/** Advise the Reader of which WorkSheets we want to load **/
$objReader->setLoadSheetsOnly($sheetname);
$objPHPExcel = $objReader->load($excel[0]);
$writer = \PHPExcel_IOFactory::createWriter($objPHPExcel, "HTML");
$writer->generateSheetData();
$writer->generateStyles();
return $this->render('SocietyPerfclientBundle:Default:testexcel.html.twig', array(
'excelHtml'=>$writer
));
}
My Filter :
class PHPExcel_Reader_DefaultReadFilter implements PHPExcel_Reader_IReadFilter {
public function __construct($fromColumn, $toColumn) {
$this->columns = array();
$toColumn++;
while ($fromColumn !== $toColumn) {
$this->columns[] = $fromColumn++;
}
}
public function readCell($column, $row, $worksheetName = '') {
// Read columns from 'A' to 'AF'
if (in_array($column, $this->columns)) {
return true;
}
return false;
}
}
I can't understand why these blank column are here...
ReadFilter is not working with HTML Writer ?
I can't just do :
.column14 { display:none;!important;}
.column15 { display:none;!important;}
.column16 { display:none;!important;}
.column17 { display:none;!important;}
etc...
Because I use jQuery Plugin "floatThead" to create a fixed <thead></thead>
In my view :
var table = $('#sheet0'); // select the table of interest
var thead = $('<thead/>').prependTo(table);
// create <thead></thead>
table.find('tbody tr.row0').appendTo(thead);
// Now the table is ready to have your chosen method applied to fix the position of the thead.
$('table.sheet0').floatThead({
position: 'fixed',
index: '8',
overflow: 'auto'
});
Please help me..
i think :
$this->columns[] = $fromColumn++;
do nothing .
try :
for ($i = $fromColumn; $i != $toColumns ; $i++)
{
$this->columns[$i]=$i;
}

How to generate a custom CSV export?

I have a page called EventPage that I am managing via a Model admin. Also using Catalog manager: https://github.com/littlegiant/silverstripe-catalogmanager
Question is I need to be able to export all the expired events (And all of the fields).
I have an 'EndDate' => 'Date', field on the EventPage.
So I want to only show EventPages in my CSV export where the EndDate is GreaterThanOrEqual to todays date e.g Expired.
The following generates an CSV export button, but currently it is exporting all the fields, where as I want to filter it so we only show the expired events.
How do I go about this?
<?php
class EventAdmin extends CatalogPageAdmin {
public $showImportForm = false;
private static $managed_models = array(
'EventPage',
'EventCategory',
'EventSubmission',
);
private static $url_segment = 'events';
private static $menu_title = 'Events';
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$gridFieldName = 'EventPage';
$gridField = $form->Fields()->fieldByName($gridFieldName);
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton());
}
return $form;
}
}
We can create a custom export button to filter the list of items before exporting them.
First we create a GridFieldExportExpiredEventsButton which extends GridFieldExportButton. This is a complete copy of the current SilverStripe 3.5 generateExportFileData function but with an added filterByCallback on the $items list to filter items that have an EndDate < date('Y-m-d').
class GridFieldExportExpiredEventsButton extends GridFieldExportButton {
public function getHTMLFragments($gridField) {
$button = new GridField_FormAction(
$gridField,
'export',
'Export expired events',
'export',
null
);
$button->setAttribute('data-icon', 'download-csv');
$button->addExtraClass('no-ajax action_export');
$button->setForm($gridField->getForm());
return array(
$this->targetFragment => '<p class="grid-csv-button">' . $button->Field() . '</p>',
);
}
public function generateExportFileData($gridField) {
$separator = $this->csvSeparator;
$csvColumns = $this->getExportColumnsForGridField($gridField);
$fileData = '';
$member = Member::currentUser();
if($this->csvHasHeader) {
$headers = array();
// determine the CSV headers. If a field is callable (e.g. anonymous function) then use the
// source name as the header instead
foreach($csvColumns as $columnSource => $columnHeader) {
$headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) ? $columnSource : $columnHeader;
}
$fileData .= "\"" . implode("\"{$separator}\"", array_values($headers)) . "\"";
$fileData .= "\n";
}
//Remove GridFieldPaginator as we're going to export the entire list.
$gridField->getConfig()->removeComponentsByType('GridFieldPaginator');
$items = $gridField->getManipulatedList();
$items = $items->filterByCallback(function($item) {
// The following line modifies what items are filtered. Change this to change what items are filtered
return $item->EndDate < date('Y-m-d');
});
// #todo should GridFieldComponents change behaviour based on whether others are available in the config?
foreach($gridField->getConfig()->getComponents() as $component){
if($component instanceof GridFieldFilterHeader || $component instanceof GridFieldSortableHeader) {
$items = $component->getManipulatedData($gridField, $items);
}
}
foreach($items->limit(null) as $item) {
if(!$item->hasMethod('canView') || $item->canView($member)) {
$columnData = array();
foreach($csvColumns as $columnSource => $columnHeader) {
if(!is_string($columnHeader) && is_callable($columnHeader)) {
if($item->hasMethod($columnSource)) {
$relObj = $item->{$columnSource}();
} else {
$relObj = $item->relObject($columnSource);
}
$value = $columnHeader($relObj);
} else {
$value = $gridField->getDataFieldValue($item, $columnSource);
if($value === null) {
$value = $gridField->getDataFieldValue($item, $columnHeader);
}
}
$value = str_replace(array("\r", "\n"), "\n", $value);
$columnData[] = '"' . str_replace('"', '""', $value) . '"';
}
$fileData .= implode($separator, $columnData);
$fileData .= "\n";
}
if($item->hasMethod('destroy')) {
$item->destroy();
}
}
return $fileData;
}
}
The extra line that we have added that filters the export items is:
return $item->EndDate < date('Y-m-d');
Alter this to alter the list of items that are exported. I have set this to only return items which have an EndDate that is in the past. Change this as you need.
We then add this export button to our grid field in our event model admin:
class EventAdmin extends CatalogPageAdmin {
private static $managed_models = array(
'EventPage'
);
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id);
if ($this->modelClass == 'EventPage') {
$gridField = $form->Fields()->fieldByName($this->modelClass);
$gridField->getConfig()->removeComponentsByType('GridFieldExportButton');
$gridField->getConfig()->addComponent(new GridFieldExportExpiredEventsButton('buttons-before-left'));
}
return $form;
}
}
This was originally two answers...
To limit the fields
Have you had a look at the GridFieldExportButton class ?
The constructor
/**
* #param string $targetFragment The HTML fragment to write the button into
* #param array $exportColumns The columns to include in the export
*/
public function __construct($targetFragment = "after", $exportColumns = null) {
$this->targetFragment = $targetFragment;
$this->exportColumns = $exportColumns;
}
So you should be able to pass $exportColumns as an argument.
in your code that would be
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton("after", ["field1", "field2"]));
}
OR - EVEN BETTER SOLUTION
you can define your summary fields on EventPage as such
private static $summary_fields = ["FIeld1", "Field2"];
Then make sure your flush and it should use that as fields.
To filter which items to export in your CSV
So in this case, I think you should create a new class that extends GridFieldExportButton (maybe called EventPageCSVExportButton or something) and override the methods you want. In your case it would probably be generateExportFileData(), just do a check in the loop and exclude data you don't want.
Then use that new class in your EventAdmin.
Have you had a look at the GridFieldExportButton class ?
The constructor
/**
* #param string $targetFragment The HTML fragment to write the button into
* #param array $exportColumns The columns to include in the export
*/
public function __construct($targetFragment = "after", $exportColumns = null) {
$this->targetFragment = $targetFragment;
$this->exportColumns = $exportColumns;
}
So you should be able to pass $exportColumns as an argument.
in your code that would be
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton("after", ["field1", "field2"]));
}
OR - EVEN BETTER SOLUTION
you can define your summary fields on EventPage as such
private static $summary_fields = ["FIeld1", "Field2"];
Then make sure your flush and it should use that as fields.

Trying to display headers on multiple pages with FPDF

I am using FPDF to create a report from a Mysql database. The PDF is created fine but I would like to display the headers for each column at the top of every page for easier navigation. Does anyone have any idea on how to do this?
Thanks for the help.
try to extend fpdf class, and implement your Ln function: every time you add a line, decrement counter of line number, and when is 0, add new page with header.
class _PDF extends FPDF
{
var $_num_Rows = null;
var $_heightCell = null;
const NUM_ROWS = 48; // set number line in page
function _PDF($orientation = 'P',$unit = 'mm' ,$page = 'A4')
{
$this->_numRows = self::NUM_ROWS;
$this->_heightCell = 4;
$this->SetAutoPageBreak(true,10);
$this->FPDF($orientation, $unit, $page);
}
public function init_numRows()
{
$this->_numRows = self::NUM_ROWS;
}
public function dec_numRows()
{
$this->_numRows--;
}
function Ln()
{
if ( $this->_numRows )
{
parent::Ln();
$this->dec_numRows();
}
else
{
$this->addHeader();
}
}
function addHeader()
{
$this->init_numRows();
$this->AddPage();
// ...
}
}
$pdf = new _PDF();

Categories