HOWTO: PHP and jQuery upload progress bar
Author: gaweee | Filed under: development, howtoWith the controllable jQuery Progress Bar, writing a form upload progress bar seems like a piece of cake now. Hypothetically, all we need is to create the bar, poll for the progress of the file upload, derive the new progress bar value (in percentage) and set it.
To do that you need to prepare the php script to do it. By default PHP cant report the progress of upload progress. However people smarter than me have already solved that problem. In 5 mins i’ve found 2 solutions: the Alternative PHP Cache (APC) method as well as the UploadProgress method. Both of them are PECL packages. Because i couldnt get APC to work on my server properly, i’ll document the UploadProgress more in detail here…
pecl install uploadprogress
Once that is done, register the extension to your PHP with the following line in your php.ini
extension=uploadprogress.so
then restart your apache/httpd
<form id="uploadform" enctype="multipart/form-data" method="post"> <input id="progress_key" name="UPLOAD_IDENTIFIER" type="hidden" value="<?= $uuid ?>" /> <input id="ulfile" name="ulfile" type="file" /> <input type="submit" value="Upload" /> <span id="uploadprogressbar" class="progressbar">0%</span> </form>
this creates the form with a file field as well as a unique UPLOAD_IDENTIFIER hidden field that allows our script to check the progress of the form submission.
header("Cache-Control: no-cache, must-revalidate"); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); if (@$_GET['id']) { echo json_encode(uploadprogress_get_info($_GET['id'])); exit(); }
The header no-cache declarations circumvents IE’s cache of the response. Basically this form does nothing but respond with a json encoded string of the uploadprogress_get_info function. The id argument is the same one we used in the form. Think of it as a form-upload-process-id. A typical response looks like this:
{"time_start":"1214384364","time_last":"1214384366","speed_average":"25889","speed_last":"40952","bytes_uploaded" :"51778","bytes_total":"8125518","files_uploaded":"0","est_sec":"311"}
the response encodes a good deal of data about the form submission. most importantly for us: bytes_uploaded and bytes_total
var progress_key = ''; // this sets up the progress bar $(document).ready(function() { $("#uploadprogressbar").progressBar(); }); // fades in the progress bar and starts polling the upload progress after 1.5seconds function beginUpload() { $("#uploadprogressbar").fadeIn(); setTimeout("showUpload()", 1500); } // uses ajax to poll the uploadprogress.php page with the id // deserializes the json string, and computes the percentage (integer) // update the jQuery progress bar // sets a timer for the next poll in 750ms function showUpload() { $.get("uploadprogress.php?id=" + progress_key, function(data) { if (!data) return; var response; eval ("response = " + data); if (!response) return; var percentage = Math.floor(100 * parseInt(response['bytes_uploaded']) / parseInt(response['bytes_total'])); $("#uploadprogressbar").progressBar(percentage); }); setTimeout("showUpload()", 750); }
viola! read the comments if you dont understand the code. it is _THAT_ straightforward. Of course there can be many improvements such as stopping the script when the upload reaches 100% but thats probably not really needed since the whole page is refreshed. But this approach allows the flexibility of ajax submissions and what nots.
Again, download the jQuery progressbar here: jQuery progressbar
or view the demo here
- Change the HTML hidden form field name from UPLOAD_IDENTIFIER to APC_UPLOAD_PROGRESS
- Change the PHP uploadprogress_get_info($_GET['id']) to apc_fetch(‘upload_’.$_GET['id']));
- Change the Javascript percentage calculation from:
Math.floor(100 * parseInt(response['bytes_uploaded']) / parseInt(response['bytes_total']));
to:
Math.floor(100 * parseInt(response['current']) / parseInt(response['total']));
i18n in struts is really flexible if set up correctly. Unfortunately searching for a document to learn the ‘hidden’ tricks of it is really frustrating… So in revenge, i’m writing my own! I hope someone out there finds this useful…
struts.custom.i18n.resources=languages_actions,languages_views
This definition instructs struts to use the files languages_actions.properties and languages_view.properties for your language definitions. When there is a new request locale, such as zh_CN (chinese), struts will find for the file languages_actions_zh_CN.properties, or if defaults to en_US (english), struts will use the file language_actions_en_US.properties.
On top of that, there can be many places where language definitions can be found. They will be searched in the following order:
- ActionClass.properties
- BaseClass.properties (all the way to Object.properties)
- Interface.properties (every interface and sub-interface)
- ModelDriven’s model (if implements ModelDriven), for the model object repeat from 1
- package.properties (of the directory where class is located and every parent directory all the way to the root directory)
- search up the i18n message key hierarchy itself
- global resource properties
I put my files in the main WEB-INF\classes folder where struts.properties can be found
Read here for more information on how struts 2 searches for i18n definitions.
user.submit.image=/images/en_US/submit.gif
user.welcome=Welcome, {0}
user.link=<a href="somelink.action?id={1}&token={2}">{0}</a>' document
user.validation.token.invalid={0} does not exists in the systemthe above is an example of a en_US language definition file. You can see how the image and welcome message would be different in a zh_CN language definition file. but just for the heck of it, here it is anyway
user.submit.image=/images/zh_CN/submit.gif
user.welcome={0}, 你好
user.link=<a href="somelink.action?id={1}&token={2}">{0}</a> 的文件
user.validation.token.invalid={0} 不纯在于系统The key here in flexibility is the use of # parameters for the definitions (very printf-ish). This allows the dynamic data to be reformatted to any language structure (left to right, right to left, orientation of grammer, verbs, subject, topics, etc). Really smart way to do it!
in JSP,
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<h1><s:text name="user.welcome">
<s:param value="user.name" />
</s:text></h1>
<s:text name="user.link">
<s:param value="document.name" />
<s:param value="document.id" />
<s:param value="document.token" />
</s:text>
<form input="">
<s:submit type="image" src="%{getText('user.submit.image')}" cssClass="submit" />
</form>Or in Java,
public class SomeActions extends ActionSupport { public String someMethod() throws Exception { if (!token.equalsIgnoreCase("abcdefghi")) { String [] messageargs = { token }; addActionError(getText("user.validation.token.invlaid", messageargs)); } } private String token; public String getToken() { return token; } public void setToken(String s) { token = s; } }
The above examples goes to show how you can combine dynamic data with the i18n strings in a JSP or a Java file
- When using a utf8 language definition file (such as for zh_CN), you have to write out your files then run it through the native2ascii program. On windows it would look like this:
"c:\Program Files\Java\jdk1.6.0_04\bin\native2ascii.exe" -encoding UTF8 \path\to\zh_CN.locale \new\path\to\languages_actions_zh_CN.properties
Change your jdk\bin directory to where appropriate
- Flood HTML with s:text language definitions – If you’ve already decided to make your app internationalized, its a big step, so make sure everything is pulled off a dynamic language pack.
- Always use the s:date taglib to represent date, save yourself all the time (and frustration) in the world
- Set up your default language definition file first. Struts makes it such that if your definition doesn’t exists then it searches the default file for it. That is to say if your definition for user.link doesn’t exists in languages_actions_zh_CN.properties, struts will continue to find for it in languages_actions.properties so at least there will be a link there, even if its untranslated.
If you have more access to more than 1 server, a good idea would be to schedule remote backups using a small script and crontab. This way, if 1 server goes down at least SOME data still resides on another. (a friend’s hosting service got prematurely terminted recently because someone else sharing a hosting plan was serving illegal content and the whole system was confiscated by the police in that country!)
of course backing up mysql is the only thing you can do, unless you’ve way too much disk space and way too much bandwidth… in that case please consult a professional on the actual 1001 ways to throw money at redundancy.
The script will first run remote commands on the target server over ssh to dump and gzip the database into a file. The file is then downloaded to the backup server. A final ssh command removes the dump file from the target server. Viola! And here is the shell script…
ssh root@wits.sg 'mysqldump -uroot twits> /tmp/twits.backup.sql && bzip2 -9 /tmp/twits.backup.sql' scp root@wits.sg:/tmp/twits.backup.sql.bz2 /var/backups/twits/`date "+%Y%m%d-%H%M"`.sql.bz2 ssh root@wits.sg 'rm /tmp/twits.backup.sql.bz2'
Save this few lines to a file on the backup server and give it execute permissions! (I saved it at /root/twits_backup.sh)
Note: Passwordless logins between the 2 servers must have been setup prior to running this script. Otherwise scp and ssh commands will both be prompted for a password.
Next set up the cron job on the backup server to run the script
30 05 * * * /root/twits_backup.sh
This sets the script to run at 5:30am everyday.
Here is a good place to read up more on cron jobs.
I always thought it was really cool to have those “Did you mean <some other spelling corrected word>”? Took me long enough to finally chance upon how it works! Basically it uses sound functions: soundex, metaphone or levenshtein distance to match words.
Different database supports different possible implementations of the functions. A sample mySQL query would look like:
SELECT `name` FROM `organizations` WHERE SOUNDEX(`name`) = SOUNDEX('dog');I’ve also seen some mysql stored procs that hack in a levenshtein distance calculation algorithm. This allows a even more dynamic and accurate match. Unfortunately, i’ve never gotten it to work. Do let me know if anyone has succeeded.
HOWTO: Codeigniter, IIS and ISAPI_Rewrite
Author: gaweee | Filed under: development, howtoPretty URLs on IIS is possible. Not so much to make it pretty but to make it convenient when testing your system. Its really simple too!
below is my configuration for a codeigniter virtual directory called firingrange
RewriteRule ^/firingrange/(images|css|files|js)(.*)$ /firingrange/webroot/$1$2 [L] RewriteRule ^/firingrange/$ /firingrange/index.php [L] RewriteRule ^/firingrange/index.php$ /firingrange/index.php [L] RewriteRule ^/firingrange/index.php?(.*)$ /firingrange/index.php?$1 [L] RewriteRule ^/firingrange/(\w+).php$ /firingrange/webroot/($1).php [L] RewriteRule ^/firingrange/(.+)\?(.*)$ /firingrange/index.php?$1&$2 [L] RewriteRule ^/firingrange/(.*)$ /firingrange/index.php?$1 [L]
Just like the mod_rewrite HOWTO, I threw all my static files into webroot folder so that you can access the site via http://t.wits.g/images/myimage.jpg easily. Same goes for css and js.
HOWTO: Passwordless logins between servers
Author: gaweee | Filed under: development, howtoSo if you’ve properly weighted your pros and cons, here we go…
gaweee@wits:~$ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/home/gaweee/.ssh/id_rsa): [enter] Enter passphrase (empty for no passphrase): [enter] Enter same passphrase again: [enter] Your identification has been saved in /home/gaweee/.ssh/id_rsa. Your public key has been saved in /home/gaweee/.ssh/id_rsa.pub. The key fingerprint is: 2c:1f:fb:f0:ae:2b:88:99:60:ee:eb:a5:83:3c:3c:c4 gaweee@wits
Why? Distribution of these keys is crucial to logging into the remote server without having to key in your password.
The 2 files created: /home/gaweee/.ssh/id_rsa and /home/gaweee/.ssh/id_rsa.pub are your private and public keys respectively. Keep your private key to yourself ONLY.
gaweee@wits:~$ scp .ssh/id_rsa.pub somenewserver:.ssh/authorized_keys gaweee@somenewserver password: [password]
this uses scp to put the public key onto the new server’s authorized_keys.
Viola! and you’re done!
HOWTO: Migrate your svn repository between servers
Author: gaweee | Filed under: development, howtosvnadmin dump /path/to/somerepository < somerespositry.svn.backup
The resulting file can be pretty large. Its basically a file of all your snapshots. Needless to say, if you’re at revision 300, be prepared to wait.
login to the new server to create the repository
svnadmin create /path/to/newrespository
Double check the created directory structure where possible, many a times my svn folders arent created with the correct group write permissions. So remote commits encounters transaction errors
svnadmin load /path/to/newrepository < somerepository.svn.backup
And you’re done! really simple wasnt it?
I cant remember the last time i got mod_rewrite to just work. I think i know regex, i think i know how http request works, i think i know some mod_rewrite. I try. For sure it breaks. This is undeniable proof that good things ONLY come to those who sweat and strain their necks…
The following is my mod_rewrite ruleset for codeigniter
Options +FollowSymlinks
RewriteEngine on
RewriteCond %{REQUEST_URI} (index\.php|webroot/|images/|css/|js/|robots\.txt|favicon\.ico)
RewriteRule ^(images|css|files|js)/(.*)$ webroot/$1/$2 [L]
RewriteRule ^(favicon\.ico)$ webroot/$1 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]I threw all my static files into webroot folder so that you can access the site via http://t.wits.g/images/myimage.jpg easily. Same goes for css and js.
So now you can access your controllers through the preferred pretty url: http://t.wits.sg/welcome
I’ll go hack the router.php file someday when i’m free enough so that it can accept GET requests too.
Most Popular
- HOWTO: PHP and jQuery upload progress bar (56)
- JQuery Progress Bar 1.1 (53)
- Howto: Repackageable custom extension development in Magento - Part 2 - Admin Controller (25)
- JQuery Progress Bar 2.0 (21)
- Howto: Repackageable custom extension development in Magento - Part 8 - CRUD - Update (18)
- HOWTO: struts 2 i18n (16)
- Howto: Repackageable custom extension development in Magento (12)
- JQuery Progress Bar 1.2 (11)
- Howto: Repackageable custom extension development in Magento - Part 9 - Frontend - List (10)
- Howto: Repackageable custom extension development in Magento - Part 3 - Database (9)
Recent Comments
- Karen: Great work around-thank you!!
- Sheldon: awesome possum!
- cmstop里所使用的有用的jquery插件 » Terry's Blog: [...] http://t.wits.sg/jquery-progress-bar/ 这篇日志的 t.cn [...]
- Lakshyami: Hi, Thank you very much for
- New site feature: User Poll « TechnoStripe: [...] progress bar used to
- seo agentur: @Krish Why do you need to
- 2kai: Hi Aromal, you need to flush
- Rob Rasner Magic Castle: I love what you guys
- รับทำเว็บไซต์: Thx for this. Nice and
- Lexus: ESxtYC I'm not easily impressed.
Latest Entries
- SD in the Community: Product Management Panel Recap
- Mac OS X and Ricoh Aficio C2051 - Making Printing "Just Work"
- How to impress your recruiter
- Thoughts on Attracting the attention of the Best Hires
- The Greg Syndrome
- The Parental Manager
- Attack of the Facebook Harvesters
- jQuery Progress Bar Configuration
- Extracting email addresses from inbox
- 10 Good (Free and Legal) Source for Photos and Images