Tag Archive header

PHP: no-cache css javascript

ตามคำแนะนำในการทำเว็บให้เร็วคือให้แยกไฟล์ภาพ, css และ javascript ออกมาเพราะว่าไฟล์พวกนี้แทบจะเหมือนๆ กันทุกๆหน้า และเปลี่ยนไม่บ่อยนัก การ set server จึงกำหนดให้ทางฝั่ง browser จดจำ static content (css, images,js files) พวกนี้เอาไว้ จะได้ไม่ต้องโหลดไฟล์พวกนี้ใหม่ ทำให้สิ้นเปลือง (สิ้นเปลือง bandwide แบนด์วิธอันล้ำค่าและมีราคาที่เช่ามาจาก isp และ cpu, ram ที่ใช่จัดการจ่ายไฟล์จาก server ไปให้บราวน์เซอร์ของยูเซอร์)

ปัญหาก็คือ เมื่อเราแก้ไฟล์ image, css หรือ javascript อัพโหลดขึ้นไปบน server แล้วแต่ user ยังได้รับไฟล์ version เดิมๆ อยู่ ที่นิยมกันก็คือ ใส่ตัวแปรแบบสุ่มหรือเวลาตามหลังชื่อไฟล์ เช่น

<link href="styles.css?ts=150151" rel="stylesheet" type="text/css">
...
<script src="scripts.js?ts=150151">
...
<img src="logo.png?ts=150151">

เมื่อต้องการให้ใช้ไฟล์ version ใหม่ก็เปลี่ยนค่าของ ts เป็นค่าอื่น ตัว browser จะถือว่าเป็นคนละไฟล์กับไฟล์เดิมที่เก็บไว้ใน cache ของมันเอง ปัญหาก็คือถ้ามีไฟล์พวกนี้ 100 จุด หรือ 1000 จุดก็ต้องไล่แก้ทั้งหมดให้เหมือนๆกันถึงจะเปลี่ยนไฟล์ทุกตัวให้เป็นตัวใหม่พร้อมๆกัน อาจจะทำเป็น global variable หรือใช้ function จัดการให้แต่มันซับซ้อนเกินไป

อีกวิธีที่ทำได้ง่ายแต่ได้ผลกับทุกๆ ไฟล์คือใช้

header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');

เมื่อต้องการให้ browser โหลดไฟล์ใหม่อีกครั้ง โดยใส่ใน header ของเว็บ

อ่านเพิ่มเติม HTTP Caching

PHPExcel : ตั้งค่าหน้ากระดาษ

วันนี้ตอนที่กำลังเขียน code โดยใช้ PHPExcel โดยพยามแต่งรูปแบบไฟล์ excel ให้มันสวยงามอยู่ เพื่อนโปรแกรมเมอร์อีกคน เตือนด้วยความหวังดีว่า จัดไปก็เท่านั้นเดี๋ยว user ก็พิมพ์ให้มันพัง ออกแบบให้พิมพ์แนวนอน แต่พี่แกก็ print ออกมาเป็นแบบแนวตั้ง

กลับมาบ้านลองหาคู่มือดู PHPExcel มันกำหนด page setup ให้เอกสารได้ด้วย ^_^ ก็เลยลองเขียนดู

<?php

include 'vendor/phpoffice/phpexcel/Classes/PHPExcel.php';

$objPHPExcel = new PHPExcel();

/* Set default style */
$defaultStyle = $objPHPExcel->getDefaultStyle();

$defaultStyle->getFont()
    ->setName('Arial')
    ->setSize(11);

$defaultStyle->getNumberFormat()
    ->setFormatCode('yyyy-mm-dd');

/* Set document properties */
$title = 'Exports_Datas_' . date('Y-m-d_H:i');
$objPHPExcel->getProperties()->setCreator('Pitt Phunsanit')
    ->setCategory('Exports Datas')
    ->setDescription($title)
    ->setKeywords('Exports Datas ' . date('Y-m-d'))
    ->setSubject($title)
    ->setTitle($title);

/* create new sheet */
$objWorkSheet = $objPHPExcel->getActiveSheet();
$objWorkSheet->setTitle('Exports Datas');

/*
printer page setup
https://github.com/PHPOffice/PHPExcel/blob/develop/Documentation/markdown/Overview/08-Recipes.md
 */

/* print header and footer of a worksheet */
$objWorkSheet->getHeaderFooter()->setOddFooter('&L&B' . $objPHPExcel->getProperties()->getTitle() . '&RPage &P of &N');
$objWorkSheet->getHeaderFooter()->setOddHeader('&C&HPlease treat this document as confidential!');

/* page margins */
$objWorkSheet->getPageMargins()->setBottom(1);
$objWorkSheet->getPageMargins()->setLeft(0.75);
$objWorkSheet->getPageMargins()->setRight(0.75);
$objWorkSheet->getPageMargins()->setTop(1);

/* Setting a worksheet's page orientation and size */
$objWorkSheet->getPageSetup()->setFitToPage(true);
$objWorkSheet->getPageSetup()->setFitToWidth(true);
$objWorkSheet->getPageSetup()->setHorizontalCentered(true);
$objWorkSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE);
$objWorkSheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4);

/* Specify printing area */
$objWorkSheet->getPageSetup()->setPrintArea('A1:E11');

/* add demo data */
for ($rowNo = 1; $rowNo < 10; $rowNo++) {
    for ($colNo = 0; $colNo < 5; $colNo++) {

        $colString = PHPExcel_Cell::stringFromColumnIndex($colNo);

        $coordinate = $colString . $rowNo;

        $objWorkSheet->setCellValue($coordinate, 'Add Data To ' . $coordinate);
    }
}

/* auto width column */
$cellIterator = $objWorkSheet->getRowIterator()->current()->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(true);
foreach ($cellIterator as $cell) {
    $objWorkSheet->getColumnDimension($cell->getColumn())->setAutoSize(true);
}

/* write */
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="' . $title . '.xlsx"');
header('Cache-Control: max-age=0');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
$objWriter->save('php://output');

เปิดกับ LibreOffice Writer ก็ใช้ได้จริงๆ (สมัยนี้ใครเค้าใช้ microsoft word กันแล้ว ถ้าต้องจ่ายตัง) การกำหนดรูปแบบอื่นๆ อ่านได้จากตัวอย่าง PHPExcel recipes จัดได้หลากหลายมาก จะเลือกกระดาษ มาร์จิ้น สี เส้น เลขหน้า แนวนอน แนวตั้ง ทำได้หมด

สร้างไฟล์ csv จาก phpexcel

phpexcel สร้างไฟล์ excel ได้สะดวกมาก แต่ไม่ใช่ทุกระบบจะสามารถ import ข้อมูลจากไฟล์แบบเอ็กเซลได้ การส่งออกไฟล์แบบตัวอักษรล้วนๆ และใช้เครื่องหมายคั่นแบบไฟล์ csv จึงยังจำเป็นอยู่

ที่สำคัญคือสามารถแก้หรือเพิ่มเงื่อนไขจาก code ส่งออกข้อมูลเป็น excel โดย phpexcel ที่มีอยู่เดิม โดยดัดแปลงเล็กน้อยในส่วน header และการ write data

<?php

/* PHPExcel_IOFactory - Reader */
include 'PHPOffice/PHPExcel/Classes/PHPExcel.php';

$objPHPExcel = new PHPExcel();

/* Set default style */
$defaultStyle = $objPHPExcel->getDefaultStyle();

$defaultStyle->getFont()
    ->setName('Arial')
    ->setSize(11);

$defaultStyle->getNumberFormat()
    ->setFormatCode('yyyy-mm-dd');

/* Set document properties */
$title = 'Exports_Datas_' . date('Y-m-d_H:i');
$objPHPExcel->getProperties()->setCreator('Pitt Phunsanit')
    ->setCategory('Exports Datas')
    ->setDescription($title)
    ->setKeywords('Exports Datas ' . date('Y-m-d'))
    ->setSubject($title)
    ->setTitle($title);

/* rename sheet */
$objWorkSheet = $objPHPExcel->getActiveSheet();
$objWorkSheet->setTitle('Exports Datas');

for ($rowNo = 1; $rowNo < 10; $rowNo++) {
    for ($colNo = 0; $colNo < 5; $colNo++) {

        $insert = rand(0, 1);
        if($insert == 1) {

            $colString = PHPExcel_Cell::stringFromColumnIndex($colNo);

            $coordinate = $colString . $rowNo;

            $objWorkSheet->setCellValue($coordinate, 'ส่งออกข้อมูล -> '.$coordinate);
        }
    }
}

header('Cache-Control: max-age=0');
header('Content-Encoding: UTF-8');
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment;filename="'.$title.'.csv"');
echo "\xEF\xBB\xBF"; /* UTF-8 BOM */

$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'CSV');

/* set Delimiter, (defaults ,) */
//$objWriter->setDelimiter('|');
//$objWriter->setEnclosure('');
/* Set line ending (defaults PHP_EOL) */
//$objWriter->setLineEnding('0x0d0a');

$objWriter->save('php://output');

ถ้าสังเกตุจะมี $insert = rand(0, 1); และ if($insert == 1) { ทำให้บาง cell ไม่มีข้อมูล เพื่อที่จะแสดงให้เห็นว่าการใช้ phpexcel จะดีกว่าการใช้ echo ส่งออกข้อมูลแบบที่ง่ายที่สุด (CSV) ในกรณีที่บางคอลัมข้อมูลไม่มี เราไม่จำเป็นต้อง echo ค่าว่าง หรือมานับ , ว่าครบมั๋ย phpexcel จะจัดการให้เอง

ถ้าเปลี่ยนรูปแบบอื่นเช่น

  • ต้องการใช้เครื่องหมายอื่นเช่น pipe | ก็สามารถกำหนดโดยใช่ setDelimiter
  • ต้องการ wrap ข้อมูลก็ใช้ setEnclosure จะปิดหัว ปิดท้ายข้อมูลแต่ละ cell ให้
  • ต้องการใช้เครื่องมายอื่นในการแบ่งข้อมูลออกเป็นชุดๆ ก็ setLineEnding

เขียนครั้งเดียวส่งออกข้อมูลได้ 2 รูปแบบ ส่วนคำสั่งที่ไม่ได้ใช้ในรูปแบบ csv เช่น setTitle ก็ไม่ทำให้มี error ดีจริงๆ phpexcel

ส่งออกข้อมูลแบบที่ง่ายที่สุด (CSV)

วิธีที่ส่งออกข้อมูลที่ง่ายที่สุด และอิมพอร์ตเข้าโปรแกรมต่างๆได้มากที่สุดคือ comma-separated values (CSV) แปลง่ายๆว่า ไฟล์ตารางที่คั่นด้วยคอมม่า แปลบ้านๆไปอีกคือ text file ที่ข้อมูลถูกแบ่งโดย , หรือเครื่องหมายอื่นเช่น pipe | โดยข้อมูลต่ละ record จะแบ่งโดยการขึ้นบรรทัดใหม่ (ใน php คือ \n)

เขียนง่ายๆ แค่มีการเพิ่ม header เข้าไปว่ามันเป็น csv นะและ echo ธรรมดาๆ

<?php

$filename = 'Exports_Datas_' . date('Y-m-d_H:i');

header('Cache-Control: max-age=0');
header('Content-Encoding: UTF-8');
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment;filename="'.$filename.'.csv"');
echo "\xEF\xBB\xBF"; /* UTF-8 BOM */

/* random add datas */
for($a = 0; $a < 10; $a++) {
	$datas = [];
	for($b = 0; $b < 5; $b++) {
		$datas[$b] = 'ส่งออกข้อมูล '.rand(1, 100);
	}
	echo implode(',', $datas)."\n";
}

หลังจากเรียกไฟล์ php ก็จะเห็นว่ามีการให้ download ขึ้นมาให้ save ลองคลิกเปิดดูถ้าเครื่องมี excel อยู่ก็จะเปิดอ่านข้อมูลได้ทันที หรือจะใช้ text editor เปิดขึ้นมาดูก็ได้ ทดลองเปลี่ยนเป็นให้ดึงข้อมูลจาก database ดูครับ

ลิงค์ดาวโหลดไฟล์แบบชื่อสวยๆ

ถ้าเขียน php คงจะคุ้นกับการที่ใช้ php อ่านไฟล์แล้วใช้คำสั้่ง header ให้ download เป็นชื่ออื่นๆ

ถ้าไฟล์ไม่เป็นความลับที่จะต้องปกปิด หรือไม่ใช่ไฟล์ที่ generate ออกมาใหม่ สามารถใช้ความสามารถใหม่ใน HTML5 ได้ คือ download attribute วิธีใช้ก็ง่ายมาก

<!DOCTYPE html>
<html>
    <head>
        <title>HTML5 : Download Attribute</title>
        <meta name="author" content="Pitt Phunsanit">
    </head>
    <body>
        <h1>Download With Other Name</h1>
        <a href="IMG_0003.JPG" download="Pitt_Phunsanit">Download File With Name</a>
    </body>
</html>

อธิบายง่ายๆคือ ทำลิงค์ตามปกติแค่ ใส่

  • attribute href ชี้ว่าไฟล์อยู่ที่ไหน
  • attribute download จะบอกชื่อไฟล์ ถ้าไม่ใส่นามสกุลให้ มันจะเอานามสกุลไฟล์เดิมใส่ให้อัตโนมัติ

แต่เพราะเป็นคำสั่งใหม่ จึงมี browser รองรับดังนี้

  • google chrome 14.0
  • microsoft edge 13.0
  • mozilla firefox 20.0
  • opera 15.0 นู๋ก็รับนะ
  • apple safari ไม่รับอยู่เจ้าเดียว

สรุปง่ายๆคือ windows ต่ำกว่า 10 กับ mac มันจะใช้ไม่ได้ 100% จึงเป็นตัวเลือกที่ต้องใช้เทคนิคอื่นร่วมด้วย

HTML5 IE หน้าเว็บเพี้ยน

อ้ายอีที่ขัดขวางการเจริญเติมโตของเว็บเทคโนโลยีกันมานานคงไม่พ้น internet explorer ไออี ตัวปัญหาเจ้าเก่าเจ้าประจำ ดั้งเดิมนั้นเอง เพราะว่ามันโดนฝั่งมากับ windows และต่างจาก chrome, Firefox, opera ตรงที่เจ้าอื่นๆมันจะ update อัตโนมัติ พยามพัฒนาตัวเองให้เป็นคนใหม่ มีความสามารถมากขึ้นเสมอๆ แต่เจ้าที่ตัวนี้ user ต้องพยามในการอัพเดตมันเอง แถมวินโดวน์เก่าๆ ยังไม่สามารถลงเวอร์ชั่นที่ล่าสุดได้ตามข้อจำกัดของวินโดว์นั้นๆ

ถ้าเราใช่ HTML5 และ tag ใหม่ อย่าง article, aside, footer, header, nav, section, time หวังว่าท่าน google จะทรงโปรด เพราะว่าแบ่งข้อมูลให้ท่านพิจารณาในการทำ SEO อย่างเต็มที่ แต่กลับกลายเป็นว่าหน้าเว็บเละอย่างไม่เคยเป็นมาก่อนใน IE8 ลงไป ลองใช้ compatibility mode ไล่ version ต่ำลงมาเรื่อยๆ ถึงจะเห็นว่าเป็นที่ IE9 ลงมา

เป็นเพราะว่ามันเห็น tag ใหม่ๆ อย่าง article, aside, footer, header, nav, section, time เป็นการเขียนผิด โดนกะลาครอบอยู่กับ HTML4 สมัยเมื่อ 10 ปีที่แล้ว แถมพาลไม่สนใจ css ที่มี tag พวกนี้อยู่ด้วย วิธีแก้คือสร้าง object ใหม่อีกครั้งด้วย JavaScript

'article aside footer header nav section time'.replace(/\w+/g,function(n){document.createElement(n)})

หรือจะใช้ tools อย่าง html5shiv

<!--[if lt IE 9]>
    <script src="bower_components/html5shiv/dist/html5shiv.js"></script>
<![endif]-->

เราก็จะสามารถใช้ css กับเท็กใหม่ๆ พวกนี้ได้และควรใช้ css reset ไว้ด้วยครับ ช่วยได้เยอะเลย เกือบจะต้องเริ่มต้นเขียนกันใหม่