CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – VI

In the previous articles of this series about an Ajax controlled file upload with PHP progress tracking

CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – V
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – IV
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – III
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – II
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – I

we have shown how we can measure and display the progress of a file upload process with a series of Ajax controlled polling jobs and the progress tracking features of PHP > 5.4. At least in our test example this worked perfectly.

However, for practical purposes and especially when our server users deal with large files we must in addition take better care of some limiting PHP parameters on the server. Both a good server admin and a program developer would, of course, try to find out what file sizes are to be expected on a regular basis and adjust the server parameters accordingly. However, you never know what our beloved users may do. What happens if we talked about file sizes of less than 100MB and suddenly a file with 200MB is transferred to the server?

For which limiting PHP parameters on the server may we run into serious trouble?

Due to security considerations the PHP module interaction with incoming data streams is limited by parameters set for the Apache server. The relevant configuration file is e.g. on Opensuse located at

/etc/php5/apache2/php.ini

The most important limits (set in different sections of the file) are:

; Maximum amount of time each script may spend parsing request data. It’s a good
; idea to limit this time on productions servers in order to eliminate unexpectedly
; long running scripts.
; Default Value: -1 (Unlimited)
; Production Value: 60 (60 seconds)
; http://php.net/max-input-time
max_input_time = 200
 
; Maximum size of POST data that PHP will accept.
; Its value may be 0 to disable the limit. It is ignored if POST data reading
; is disabled through enable_post_data_reading.
; http://php.net/post-max-size
post_max_size = 200M
 
; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize =150M
 
; Maximum amount of memory a script may consume (128MB)
; http://php.net/memory-limit
memory_limit = 500M

The description of the first parameter above is somewhat unclear. What is meant by the “time spent on parsing request data”? Is this a part of the (also limited) execution time of the PHP target program of our Ajax transaction? Or is this limit imposed on the time required to read incoming POST data and to fill the $_POST array? If the latter were true a small bandwidth could lead to a violation of the “max_input_time” limit …

Regarding the second parameter the question turns up, whether this
limit is imposed on all transferred POST data including the file data?

The third parameter seems to speak for itself. There is a limit for the size of a file that can be transmitted to the server. However, it is not clear how this parameter affects real world scenarios. Does it stop a transfer already before it starts or only when the limit is reached during the transfer?

Regarding the 4th parameter we may suspect that it becomes important already during the handling (reading, parsing) of the incoming POST data. So, how much of memory (RAM) do we need at the server to handle large files during an upload process?

Warning regarding PHP parameter changes for multi-user situation on real world servers

We were and are discussing a privileged situation in this article series: Only one user uploads exactly one big Zip-container file to a server.

In such a situation it is relatively safe to fiddle around with PHP parameters of the central “php.ini” file (or PHP parameter settings in directory specific files; see the last section of this article). However, as an administrator of a server you should always be aware of the consequences of PHP parameter changes, e.g for memory limits, in a multi-user environment.

In addition you must also take into account that our code examples may be extended towards the case that one user may upload multiple files in parallel in one Ajax transaction.

Remarks on “max_input_time” – you can probably ignore it!

If you look up information about “max_input_time” available on the Internet you may experience that some confusion over the implications of this parameter remains. Especially as PHP’s own documentation is a bit contradictory – just compare what is said in the following manual pages:

 
Therefore, I tested a bit with files up to 1 GByte over slow and fast connections to PHP servers on the Internet. I came to the conclusion that the answer in the following “stackoverflow” discussion
http://stackoverflow.com/questions/11387113/php-file-upload-affected-or-not-by-max-input-time
describes the server behavior correctly. This means:

This parameter has no consequences with respect to connection bandwidth and the resulting upload time required for file data: It does not limit the required upload time. Neither does it reduce the amount of allowed maximum execution time of the PHP program triggered at the end of the file transfer process to the server.

“max_input_time” imposes a limit on the time to read/parse the data

  • after they have completely arrived at the server
  • and before the PHP program, which shall work with the data, is started.

This “parsing” time normally is very small and the standard value of 60 secs should be enough under most circumstances. If these findings are true we do not need to care much about this parameter during our file transfer process to the server. A value of 60 secs should work even for large files of 1 GB ore more on modern servers. At least for a server with sufficient resources under average load.

See also:

 
However, I can imagine circumstances on a server with many users under heavy load, for which this parameter nevertheless needs to be adjusted.

What does the PHP documentation say about the parameters “post_max_size”, “upload_max_filesize” and “memory_limit”?

Regarding these parameters we get at least some clear – though disputable – recommendation from the PHP documentation. At

 
we find the following explanation for “post_max_size”:

Sets max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize. If memory limit is enabled by your configure script, memory_limit also affects file uploading. Generally speaking, memory_limit should be larger than post_max_size. When an integer is used, the value is measured in bytes. Shorthand notation, as described in this FAQ, may also be used. If the size of post data is greater than post_max_size, the $_POST and $_FILES superglobals are empty. This can be tracked in various ways, e.g. by passing the $_GET variable to the script processing the data, i.e. <form action=”edit.php?processed=1″>, and then checking if $_GET[‘processed’] is set.

Off topic: For those who find the track-recommendation in the last sentence confusing as it refers to $_GET, see e.g.

 
You can add parameters to your URL and these parameters will appear in $_GET, but if you decided to use the POST mechanism for data transfer these URL-parameters are included in the POST data mechanism of HTTP.

The recommendation for memory sizing is misleading in case of file uploads!

Following the recommendation quoted above would lead to the following relation for the PHP setup:

memory_limit > post_max_size > ( upload_max_file_size * number of files uploaded in parallel ).

Regarding the right side: My understanding is that “upload_max_file_size” sets a limit for each individual file during an upload process. See

 
Actually, I find the recommendation for the parameter “memory_limit” very strange. This would mean that somebody who has to deal with an upload file with a size of 2 GByte would have to allow for memory allocation for a single PHP process in the RAM > 2 GByte. Shall we take such a requirement seriously?

My
answer is NO ! But, of course, you should always test yourself ….

To me only the last relation on the right side of the relation chain makes sense during an upload process. Of course PHP needs some RAM and during file uploads also buffering requires sufficient server RAM. But several GByte to control a continuous incoming stream of data which shall be saved as a file into a directory (for temporary files) on the server? No way! I did some tests – e.g. limit the memory to 32 MB and successfully upload a 1 GB file. Therefore, I agree completely with the findings in the following article:

 
See also:

 
So:

Despite you need RAM for buffering during file uploads it is NOT required to use as much physical RAM as the size of the file you want to upload.

However, it may be wise to have as much RAM as possible if you intend to operate on the file as a whole. This may e.g. become important during phases when a PHP program wants to rewrite file data or read them as fast as possible for whatever purpose. A typical example where you may need sufficient memory is image manipulation.

Nevertheless: Regarding the file transfer process to the server itself the quoted recommendation is in my opinion really misleading. And: Do not forget that a high value for “memory_limit” may lead to server problems in a multi-user situation.

“post_max_size” and “upload_max_filesize” as the main limiting PHP parameters for file uploads

So, only the following condition remains:

post_max_size > upload_max_file_size * number of files uploaded in parallel

But this condition should be taken seriously! There are several things that need to be said about these parameters.

  1. A quick test shows: “post_max_size” imposes a limit on all POST data transferred from client – including file data.
  2. Even for situations in which only one file is uploaded I personally would choose “post_max_size” to be several MBs bigger than “upload_max_filesize”. Just to account for overhead.
  3. In case of an upload of multiple files in parallel (i.e. a situation, which we have not studied in this article series) you have to get an idea about the typical size and number of files to be uploaded in parallel. In such a situation you may also want to adjust the parameter

    ; Maximum number of files that can be uploaded via a single request
    max_file_uploads = 20

  4. There may be differences depending on the PHP version of how and when the server reacts to a violation of either of both parameters. For PHP 5.4 it seems that the server does not allow for an upload if either of the parameters is violated by the size of the transferred file(s) – meaning: the upload does not even start. This in turn may lead to different error situations on the server and
    messages issued by the server – depending on which parameter was violated.
  5. From a developer’s perspective it is a bit annoying that the PHP servers reaction to a violation of “upload_max_filesize” is indeed very different from its reaction a violation of “post_max_size”. See below.

Server reactions to violations of “post_max_size” and “upload_max_filesize”

We need to discuss a bit the reactions of a PHP server towards a violation of the named parameters before we can decide how to react within our PHP or Javascript programs in the course of an Ajax transaction.

Server reaction to a violation of “upload_max_filesize”
The Apache/PHP server reacts to a violation of “upload_max_filesize” by a clear message in

$_FILES[‘userfile’][“error”]

where ‘userfile’ corresponds to the “name” attribute of the HTML file input element. A reasonable way how to react to PHP error messages in $_FILES by PHP applications is described in the highest ranked comment of
http://php.net/manual/en/features.file-upload.errors.php
and also here
https://blog.hqcodeshop.fi/archives/185-PHP-large-file-uploads.html

Server reaction to a violation of “post_max_size”
What about a violation of “post_max_size”? We can only react reliably to an error via our PHP target programs if an error number or a clear, structured message is provided. Unfortunately, this is not the case when the sum of uploaded data via POST becomes bigger than “post_max_size”. When the server detects the violation no content at all is made available in $_POST or $_FILES. So, we have no error-message there a PHP program could react to.

However, we can combine

  • a test for emptiness of the superglobals $POST and $_FILES
  • with some HTTP information from the client, which is saved in $_SERVER,

to react properly in our PHP programs. Such a reaction within our Ajax transactions would naturally include

  • the creation of an error code and an error-message
  • and sending both back within the JSON response to the Javascript client for error control.

When we make a POST request to the server a value of the POST content size is provided by the client and available via the variable

$_SERVER[‘CONTENT_LENGTH’].

See:

 
So, for the purpose of error control we will need to add some test code to the “initial” PHP target program “handle_uploaded_init_files.php5” of our Ajax transaction which started the file upload.

Reasonable reactions of our PHP upload and polling programs to a violation of “post_max_size”

Remember that our initial Ajax transaction for upload triggered the server file “handle_uploaded_init_files.php5”. Therefore, we should some additional code that investigates the violation of post_max_size” there. This would probably look similar to:

if (
	isset( $_SERVER['REQUEST_METHOD'] )      &&
        ($_SERVER['REQUEST_METHOD'] === 'POST' ) &&
        isset( $_SERVER['CONTENT_LENGTH'] )      &&
        (
 empty( $_POST ) )
 ) {
	$max_post_size = ini_get('post_max_size');
	$content_length = $_SERVER['CONTENT_LENGTH'] / 1024 / 1024;
	if ($content_length > $max_post_size ) {
		....
		// Our error treatment ....
		$err_code = ....;
		// create an error message and send it to the Ajax client 
		$err_post_size_msg = ".....";
		....
	}

	....
	....
	// transfer the error code and error message to some named element of the JSON object 
	....
	$ajax_response['err_code'] = $err_code;
	$ajax_response['err_msg'] = $err_post_size_msg;
	.....
	$response = json_encode($ajax_response);
	echo $response;
	exit;
}

 
See also:

&nbsP;
Note that we cannot assume a certain timing of the reaction of the main program in comparison to our polling jobs. It may happen that we have already started the polling sequence before the error messages from our first Ajax transaction arrive at the client. Therefore, also our polling jobs “check_progress.php5” should be able to react to empty superglobals $_POST and $_FILES :

if ( ( empty( $_POST ) ) && empty ( $_FILES ) ) {
	// Our error treatment ....
	// create an error message and send it to the Ajax client 
	// refer to messages that may turn up in parallel from the main PHP program
	....
}

 
The different Javascript client methods which receive their respective Ajaj messages should evaluate the error messages and error numbers from the server, display them and, of course, stop the polling loop in case it is still active. As these are trivial programming steps we do not look deeper into them.

Avoid trouble with limiting PHP parameters before starting the file upload

Although we can react to error situations as described above I think it is better to avoid them. Therefore, I suggest to check file size limits before starting any upload process.

In our special situation with just one big Zip-file to upload we can initiate a file size limit check on the server as soon as we choose the file on the client. This means that the Javascript client must be enabled to react to the file selection action and request some information about the parameters “post_max_size” and “upload_max_filesize” from the server. In addition we need a method to compare the server limits with the size of the chosen file.

Looking into
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress control – II
we see that we had defined a proper Javascript Control Object [CtrlO] for the upload form

<form id="form_upload" name="init_file_form"  action="handle_uploaded_init_files.php5" method="POST" enctype="multipart/form-data" >

 
which – among other things – contains the file selection input tag:

<input type="
file" name="init_file" id="inp_upl_file" >

 
However, we had not assigned any method to the file selection process itself. We are changing this now:

function Ctrl_File_Upl(my_name) {
	
	this.obj_name = "Obj_" + my_name; 
	
	// Controls related to GOC and dispatched object addresses
	this.GOC = GOC;
        this.SO_Tbl_Info = null; 
        this.SO_Msg      = null; 
			
	// ay to keep the selected file handles 
	this.ay_files = new Array(); 
		
	// msg for 1st Ajax phasefor file upload; 
	this.msg1 = ''; 
		
	// Timeout for file transfer process
	// this.timeout = 500000; // internet servers  
	this.timeout = 300000; 
		
	// define selectors (form, divs) 
	this.div_upload_cont_sel 	= "#" + "div_upload_cont";
	this.div_upload_sel 		= "#" + "div_upload";
	this.p_header_upload_sel 	= "#" + "upl_header" + " > span";
			
	this.form_upload_sel 		= "#" + "form_upload";
	this.input_file_sel 		= "#" + "inp_upl_file";
	this.upl_submit_but 		= "#" + "but_submit_upl";

	this.hinp_upl_tbl_num_sel	= "#" + "hinp_upl_tbl_num";			
	this.hinp_upl_tbl_name_sel	= "#" + "hinp_upl_tbl_name";			
	this.hinp_upl_tbl_snr_sel	= "#" + "hinp_upl_tbl_snr";			
	this.hinp_upl_succ_sel 		= "#" + "hinp_upl_succ";			
	this.hinp_upl_run_type_sel 	= "#" + "hinp_upl_run_type";			
	this.hinp_upl_file_name_sel 	= "#" + "hinp_upl_file_name";			
	this.hinp_upl_file_pipe_sel 	= "#" + "hinp_upl_file_pipe";

	// display the number of extracted and processed files 
	this.num_open_files_sel		= '#' + "num_open_files";
	this.num_extracted_files_sel 	= '#' + "num_extracted_files";
	
	// Other objects on the web page - progress area 
	this.trf_msg_cont	= '#' + "trf_msg_cont";
	this.trf_msg		= '#' + "trf_msg";
	this.imp_msg_cont	= '#' + "imp_msg_cont";
	this.imp_msg		= '#' + "imp_msg";
				
	// Status (!) message box (not the right msg box) 
	this.status_div_cont = '#' + "status_div_cont";
	this.id_progr_msg_p  = "#progr_msg"; 
	
	//progress bar 
	this.id_bar = "#bar"; 
			
	// right msg block
	this.span_main_msg	= "span_msg"; 
	
	// variables to control the obligatory check of the file size 
	this.file_size_is_ok = 1; 
			
	// variables for the Ajax response 
	this.upl_file_succ 	= 0; 
	this.upl_file_name 	= ''; 
        
	// File associated variables
	this.file_name      = '';
        this.file_size_js   = 0; 	// file size detected by JS
        this.file_size      = '';	// file size detected by server
        this.allowed_file_size = 0;	// allowed file size for uploads on the server  
        
	// Processing of files     
        this.num_extracted_files = 0;
	this.file_pipeline 	 = 0; 
	this.import_time 	 = 0; 
	this.transfer_time 	 = 0; 
        
	this.name_succ_dir = ''; 
	this.num_open_files = 1;  

	// transfer time measurement 
	this.date_start = 0;   
	this.date_end = 0; 
	this.ajax_transfer_start = 0; 
	this.ajax_transfer_end 	 = 0; 
			
	// database import time measurement 
	this.date_data_import_start = 0; 
	this.date_data_import_end = 0; 
	this.data_import_start = 0; 
	this.data_import_end = 0; 
			
	this.transfer_time	= 0; 
	this.processing_time = 0; 
	this.time_start = 0; 
			
	// Determine URL for the Form 
	this.url = $(this.form_upload_sel).attr('action'); 
	console.log("Form_Upload_file - url = " + this.url);  				

	// Register methods for event handling 
	this.register_form_events(); 
			 
}	
	
// Method to start uploading the file  
// -------------------------------------------------------------------
Ctrl_File_Upl.prototype.register_form_events = function() {
			
	$(this.input_file_sel).
click(
		$.proxy(this, 'select_file') 
	);
			
	$(this.input_file_sel).change(
		$.proxy(this, 'fetch_allowed_file_size') 
	);
	
	$(this.upl_submit_but).click(
		$.proxy(this, 'submit_form') 
	);
				
	$(this.form_upload_sel).submit( 
		$.proxy(this, 'upl_file') 
	); 
					
}; 

 
The reader recognizes that in contrast to the version of the CtrlO “Ctrl_File_Upl” discussed in previous articles of this series we have added some selector IDs for fields of some other web page areas. But the really important change is an extension of the methods for additional events in “Ctrl_File_Upl.prototype.register_form_events()”:

First, we react to a click of the file selection button of the file input field. This only serves the purpose of resetting fields and message areas on the web page. But we react also to the file selection itself by using the change event of the file input field. This triggers a method “fetch_allowed_file_size()” which retrieves the parameter “upload_max_filesize” from the server.

Note:
We assume here that the server admin was clever enough to set post_max_size > upload_max_filesize!
Therefore, we only will perform a file size comparison with the value of “upload_max_filesize”. If you do not trust your server admin just extend the methods and programs presented below by an additional and separate check for file sizes bigger than “post_max_size”. This should be an easy exercise for you.

Now, let us have a look at the new methods of our Javascript CtrlO :

	
// Method to react to a click on the file selection box 
// ---------------------------------------------------- 
Ctrl_File_Upl.prototype.select_file = function (e) {
	// Call method to reset information and message fields 
	// Note: The following method also deactivates the file submit button !  
	this.reset_upl_info(); 
};

	
// Method to check whether file size is too big   
// ---------------------------------------------
// We check whether the file size is too big 
Ctrl_File_Upl.prototype.fetch_allowed_file_size = function (e) {
			
	this.file_size_is_ok = 0; 
			
	// size of the file im MByte determined on the client 
	this.file_size_js = $(this.input_file_sel)[0].files[0].size/1024/1024;
	console.log("actual file size of chosen file = " + this.file_size_js); 	
			
	// Now trigger an Ajaj transaction 
	var ajax_url = "../func/get_allowed_file_size.php5"; 
	var form_data = ''; 
			
	// 03.07.2015: we avoid setup as this would be taken as the standard for subsequent Ajax jobs 
	$.ajax({
                //contentType: "application/x-www-form-urlencoded; charset=ISO-8859-1",
                // context:  Ctrl_Status
                url: ajax_url, 
		context:  this, 
		data: form_data, 
		type: 'POST', 
		dataType: 'json', 
                success: this.response_allowed_file_size, 
                error: this.error_allowed_file_size
        });
};

// Method for Ajaj error handling during file size check transaction 		
// --------------------------------------------------------------	
Ctrl_File_Upl.prototype.error_allowed_file_size = function(jqxhr, error_type) {
			
	// Reset the cursor 
	$('body').css('cursor', 'default' ); 

	// Error handling
	console.log("From Ctrl_File_Upl ::  got Ajax error for fetch_allowed_file_size" );  
	var status = jqxhr.status; 
	var status_txt = jqxhr.statusText; 
	console.log("From Ctrl_File_Upl.prototype.error_allowed_file_size() ::  status = " + status );  
	console.log("From Ctrl_File_Upl.prototype.allowed_file_size ::  status_text = " + status_txt ); 
	console.log("From Ctrl_File_Upl.prototype.allowed_file_size ::  error_type = " + error_type ); 
			
	var 
msg = "<br>Status: " + status + "  Status text: " + status_txt;    
	this.SO_Msg.show_msg(1, msg); 
};

// Method for Ajaj rsponse handling after file size check transaction 		
// --------------------------------------------------------------			
Ctrl_File_Upl.prototype.response_allowed_file_size = function (json_response, success_code, jqxhr) {
	
	// Reset the cursor 
	$('body').css('cursor', 'default' ); 
				
	var new_msg; 
	var status = jqxhr.status; 
	var status_txt = jqxhr.statusText; 
	console.log("response_allowed_fsize: status = " + status + " , status_text = " + status_txt ); 	

	// The allowed file size on the server
	this.allowed_file_size = json_response['allowed_size'];	
	// parseInt required due to possible MB or GB endings on the server 
	this.allowed_file_size = parseInt(this.allowed_file_size); 
	console.log("allowed file size on server = " + this.allowed_file_size); 	

	// size comparison
	// ----------------
	if ( this.file_size_js > this.allowed_file_size ) {
		this.file_size_is_ok = 0; 
		new_msg = $(this.span_main_msg).html();
		if (new_msg == undefined) {
			new_msg = ""; 
		}
		new_msg += "<br><span style=\"color:#A90000;\">File size too big.</span><br>" +
		"The server allows for files with a size ≤ " +  	
		parseFloat(this.allowed_file_size).toFixed(2) + " MB." + "<br>" + 
		"The size of the chosen file is " + parseFloat(this.file_size_js).toFixed(2) + " MB." + "<br><br>" + 
		"<span style=\"color:#A90000;\">Please choose a different file or reduce the contents !</span>" + "<br><br>" + 
		"If you permanently need a bigger file size limit on the server, please contact your administrator"; 
					
		this.SO_Msg.show_msg(0, new_msg); 
				
	// file size within limits 
	// -------------------------
	else {
		this.file_size_is_ok = 1; 
					
		new_msg = $(this.span_main_msg).html();
		if (new_msg == undefined) {
			new_msg = ""; 
		}
		new_msg += "<br><span style=\"color:#007700;\">File size within server limits.</span><br>" +
		"The server allows for files with a size ≤ " +  parseFloat(this.allowed_file_size).toFixed(2) + " MB." + "<br>" + 
		"The size of the chosen file is " + parseFloat(this.file_size_js).toFixed(2) + " MB." + "<br><br>" + 
		"<span style=\"color:#007700;\">Use the "Start Upload" button to start the file upload!</span>"; 
					
		this.SO_Msg.show_msg(0, new_msg); 
					
		// reactivate the submit button 
		// -----------------------------
		$(this.upl_submit_but).on("click", $.proxy(this, 'submit_form') ); 
		$(this.upl_submit_but).css("color", "#990000"); 
	}
};
	
// Method to reset some form and information fields on the web page 
// ---------------------------------------------------------------
// We have to reset some form and message fields
Ctrl_File_Upl.prototype.reset_upl_info = function() {
				
	var msg_progr = ''; 
	$(this.id_progr_msg_p).html('');
				
	var msg_trf = ''; 
	$(this.trf_msg_cont).css('display', 'none');
	$(this.trf_msg).css('color: #666'); 
	$(this.trf_msg).html(msg_trf);
		    	
	var msg_imp = ''; 
	$(this.imp_msg_cont).css('display', 'none'); 
	$(this.imp_msg).css('color: #666'); 
	$(this.imp_msg).html(msg_imp); 
		    	
	// Deactivate the "Start Upload" Button 
	// ------------------------------------
	$(this.upl_submit_but).off("click"); 
	$(this.upl_submit_but).css("color", "#BBB"); 

	// Reset also the main message area  
	// ----------------------------------------------
	this.SO_Msg.show_msg(0, ''); 

};	

 
This is all pretty straightforward and parts of it are already well known of our
previous descriptions for handling the Ajaj interactions with the server by the help of jQuery functionality.

A short description of what happens is:

  • When you click on the button of the file selection input field contents of fields in the message area of our web page and information fields about upload progress are reset as we assume that a new upload will be started.
  • During reset also the form’s submit button to start a file upload via Ajax/Ajaj is disabled. Note that we use jQuery’s “off(‘event’)”-functionality to to this.
  • As soon as the user selects a specific file we trigger a method which determines and saves the size of the chosen file to a variable and starts an Ajax transaction afterwards. This Ajax interaction calls a target PHP program “get_allowed_file_size.php5” in some directory.
  • The JSON-response of the PHP program is handled by the method
    Ctrl_File_Upl.prototype.response_allowed_file_size.

      The main purpose of this method is to make a comparison of the already determined file size with the limit set on the server and issue some warnings or positive messages. If the file size of the chosen file is within the server’s limit we reactivate our “submit” button of the upload form. (Note that we use jQuery’s “on(‘event’)”-functionality to to this.) Otherwise we keep it inactive – until a more suitable file is chosen by the user.

Thus, by very simple means we prevent any unreasonable upload process already before it can be started by the user.

It remains to show an excerpt of the simple PHP target file:

<?php

// start session and output buffer
session_start();
ob_start(); 

$file_size_limit = ini_get("upload_max_filesize");
$ajax_response = array();
$ajax_response['allowed_size'] = $file_size_limit; 

$ajax_response['sys_msg'] .= ob_get_contents();
ob_end_clean();

$response = json_encode($ajax_response);
echo $response;
exit; 

?>

Nothing special to discuss here.

Can we change the limiting parameters during PHP program execution?

No, we can not. But as a developer you may be able to define directory specific settings both for “post_max_size” and “upload_max_filesize” on the server by uploading “.htaccess”-files or “.user.ini”-files to program directories – if this is allowed by the administrator.

The web page php.net/manual/en/ini.core.php shows a column “Changeable” for all important parameters and the respective allowed change mechanisms.
See also:
http://php.net/manual/en/ini.list.php

Different methods of how to change PHP parameters as a user are described here:

 
However, if you are not a developer but a server admin, preventing users from changing PHP ini-paramters may even be more important for you:

 
Enough for today. In the next article of this series

CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – VII

we shall have a look at possible problems resulting from timeout limits set for our Ajax transactions.

Responsive fluid multi-column layouts – the DIV order problem – II

In the last article of this series

Responsive fluid multi-column layouts – the DIV order problem – I

I discussed a standard 3 column fluid design. HTML and CSS codes for a simple example were presented. Our solution depended on a certain order of DIV tags in the HTML code. The tag of the DIV container for the main content “div#main_content” got the last tag position and the CSS property “position:relative;” inside our outer container “div#float_cont”, where the container for 2 of the visible outer left and right “columns” were floated. By setting proper margins for the main content container we gained fluidity and at the same time automatic control over the apparent visual heights of all containers.

In this second article we make a first step towards a responsive design. We assume that the reader is already familiar with the definitions of the major DIV elements of our example page. As in the first article we shall identify the DIVs by their CSS ID representation.

We shall need to overcome some obstacles whilst getting responsive. Because this is a Linux blog we do not care if older and not W3C compliant MS IE browsers can reproduce our suggested CSS recipes for a solution. Code examples given below were tested with Firefox for Linux.

Responsiveness based on viewport width intervals

To start with responsiveness we first define some view port width intervals, for which we want to rearrange major (HTML) elements of our test page. We choose the width intervals a bit arbitrarily below. You may adapt them for your needs:

  • Range III: width range beyond 800px and larger
  • Range II: width range between 540px and 800px
  • Range I: width range below 540px

What are our objectives for the page layout in these ranges?

I shall set up some requirements and conditions for which the DIV order problem gets obvious. What I want to show is that when you deal with responsiveness you may have to align your initial standard HTML/CSS layout with what you want to achieve in view port ranges below certain width thresholds. Some people claim that one should always start designing for the narrow view port situation. My point of view is that with fluid layouts you need to consider both ends of your ranges right from the beginning.

Objectives for Range III:

In range III we just use our (or an improved) initial fluid design.

Objectives for Range II:

In range II the left column shall remain where it is. However, we remove the information in the right column to get more space for the main contents. Instead of omitting “div#right” completely we want to move it to the bottom of the page – and change its width at the same time to 100%. The optical and real height of “div#right” shall be determined by its contents. The “div#main_cont”, however, should keep its position, but use all the space being available at the right of the left column to the right edge of the view port. At the same time “div#main_cont” shall determine the optical (not necessarily the css) height of its own and the left column – if it is the longest. Otherwise the contents of the left column shall determine the height of the upper part on the page.

Objectives for Range I:

We set up an even more complicated scenario for range I:
The “div#main_cont” originally positioned in
the middle shall become dominant now and take the whole width offered. In addition we want to move the left side menu (div#left_nav”) from its original position. To make things tricky we want it to move to the bottom of the expanded DIV container “div#main_cont)” and give it a 100% width, too – so that if someone read and scrolled to the bottom of the “div#main_cont” he would see the available link options. The originally right column “div#right” shall move further down below the repositioned “div#left_nav”.

In addition we want to stop a further shrinking of the displayed main DIV container by a minimum width of 200px.

Later on

  • we shall in addition take care of a better treatment of the horizontal menu placed at the top of our test page in range I
  • and offer additional options to deal with the side menu originally placed left with vertical orientation.

Additional assumptions and requirements:

To make things really difficult, we want to achieve the transitions between Range III and Range II as well as between Range II and Range I without changing the HTML code defined for Range III. In addition we want to avoid Javascript/jQuery involvement to change the node order in the HTML tree during transitions. The reordering of the DIV containers defined above shall be achieved by CSS measures only.

Objective of this article – let us cover range II

We take these challenges step by step. For this article our goal is to cover Range II correctly – we do not care about Range I, yet. Already this limited objective will require some fundamental changes of our initial HTML code for Range III – and some interesting CSS tricks. But it will result in a solution which can also be used for Range I later on.

The order of the DIV tags prevents the repositioning of the DIV containers

We shall of course try to add some width dependent statements to our CSS file:

@media only screen and (min-width: 540px) and (max-width: 800px) { … }

But, what CSS directives could help us to move the container “div#right” to the bottom, i.e. below “div#main_cont” – without loosing our design achievements described in the last article? The frustrating result of many trials and errors was:

Given our assumptions above: none, actually. At least I was too stupid to find a reasonable solution.

You may play around a bit yourself. Even if you introduced some additional (floated) helper DIVs which would only be displayed in range II: Whatever you do to move the “div#right” – some of our requirements would be broken and/or the flow of the text in “div#main_cont” would be affected.

The first ugly thing is the following:
If we floated all major elements of the “div#float_cont” container “left” or “right” the final visible vertical order would not be correct. “div#main_cont” comes at the bottom of the original HTML code – so it moves to the bottom – whatever else happens. The same thing would happen if you turned off floating completely and used only relative positioning. You do not get the final order right.

So, what if we moved “div#main_cont” in our original HTML code and floated it instead of working with margins? Well, you cannot just float “div#main_cont” after (!) “div#left_nav” and keep up the adaptivity to any further width reduction of the viewport. This is mainly due to the fact that our left column container shall keep up a fixed width! You could solve
the adaptivity by changing “div#main_cont” to “position:absolute;” (and use “left:16rem; right:0;”) but then again you would loose any height control for “div#float_cont” and its external DIV containers. A loss of height control would also happen if you changed “div#info” to “position:absolute;” or if you floated it.

Now, you may think about additional floated helper DIVs which you switched on in the background of “div#main_cont” only after you passed a width threshold. But such a background floated DIV would have an impact onto any text written in “div#main_cont” ! Now, you could solve this by moving the text to an absolutely positioned inner container – and again loose height control. And so on, and so on …

What do we need?

From all my own experiments I could conclude 2 things which one needs to meet the adaptivity scenarios posed above:

  1. You need the HTML tag of “div#main_cont” as the first one inside the “div#float_cont” tag – more precise: before the other floated DIVs, which define the other columns.
  2. You sooner or later need “div#main_cont” to float itself.

However, as we have already seen at the end of our last article: Just moving the “div#main_cont” tag to the top and keeping up “position:relative;” will not work. However, if we floated it we would loose width adaptivity and/or would not get “div#left_cont” at the correct position with a fixed width. Is the last statement really true?

Negative margins to the rescue

We need to dig a bit deeper in our box of dirty CSS tricks. Let us assume that “div#main_cont” really were the first element of “div#float_cont”:

<div id="outer_cont">
	<div id="nav_left_bg"></div>
	<div id="right_bg"></div>
	<div id="info_bg"></div>
	<div id="float_cont">
		<div id="main_cont">
			<div id="info"> ... </div>
		</div>
		<div id="left_nav">
			<div id="left_inner"> ... </div>
		</div> 
		<div id="right">
			<div id="right_inner"> ... </div>
		</div> 
		<p class="floatstop"> </p>
	</div>
</div>

And let us further assume that we really floated “div#main_cont”. How could we regain width adaptivity and our text of “div#info” at the right position? Actually, this is pretty easy:

div#main_cont {
	position: relative; 	
	float: left;
	margin: 0; 
	width:100%;  /*adaptivity to the port view width!*/
}
div#info {
	/* height control: the contents defines the height of all parents */ 
	position: relative; 
	margin: 0 27% 0 16rem;
	padding: 0 0.8rem 0 0.8rem;
}

Note the “width:100%;” definition! It guarantees width adaptivity despite floating! To reserve enough pace at the left and right side of the main contents, we just use the same margin trick for “div#info” inside its container which we previously used for “div#main_cont” itself. Nice!

But, how do we get “div#left_nav” to its left position, now? We could use “position:absolute;” for this element. But, by doing this, we could no longer control container heights in case that the left column due to more contents ever became the longest of all columns. However, if we floated “div#left_nav”, it would move it downwards because “div#main_cont” already consumes all of the view port’s width.

Wait a second: What if we used negative margins? This could lead to a solution, because successive float elements can move across and over each other.
So, lets try it:

div#left_nav { 
	position:relative; 
	float:left;
	width: 14.2rem;
	margin-left: -100%;
}
div#right { 
	position:relative; 
	float:left;
	margin-left: -26%;
	width: 26%;
}

Some experiments with your browser would prove that this really works! After corresponding changes to the HTML and CSS codes we find that we more or less have come back to our original fluid scenario – but now the initial order of the HTML tags has changed fundamentally. Everything is floated – but by applying some tricky margins to our central containers we achieved the requested fluid layout again. How can we make use of the new tag order and the floating in range II ?

Elementary settings for range II

As soon as we reach the view port width threshold for range II, only some minor CSS changes are required for our new basic HTML/CSS layout:

@media only screen and (min-width: 540px) and (max-width: 800px) {
	div#right {
        	margin-left: 0;
		margin-top:1.0rem;
		width:100%;
		background-color: #FEEEBB;
	}
	div#info { 
		margin: 0 0 0 16rem;
	}
	div#right_bg {
		visibility: hidden; 
	}
	div#info_bg {
		right:0;
	}
}

This is pretty cool, isn’t it? We use our 100%-width of “div#main_cont”, change only one “left-margin” statement – and we have moved the “div#right” down. In addition we change the “right-margin” of “div#info” to use the total (adaptive!) width to the right for our main text information. The rest of the CSS changes just repairs some small gaps in the “optical appearance”:

fluid4

How to improve the optical appearance in Range III and Range II substantially?

As a developer, I thought I had achieved something and deserved a beer. However, I met strong criticism from my wife, who cares much more for optical design aspects than me. She pretty soon detected a major drawback of my HTML/CSS design:

The repositioned “div_right” still extends the height of the container “div#float_cont”. Thus, the heights of our colored background stripes “nav_left_bg”, “info_bg” and “right_bg” get bigger, too. The horizontally enlarged and moved “div_right” just overlaps and hides the background stripes.

Note, that we defined a “margin-top:10px;” for the repositioned “div#right;” in the CSS code above. Nevertheless, you see that our background stripes stretch down without any visible gap. As a result, I was confronted with a new requirement:

New additional requirement

What if you wanted to decouple the repositioned “div#right” from the 2 floated DIV containers above throughout Range II – in a clear optical way by a vertical gap – and also in the sense of HTML? And provide a region or stripe of transparency in between – i.e. in the gap opened by the margin-top definition)? Transparency, to allow the user e.g. to look at a background image of the whole page?

This requirement gave me some headache. But I understood the related objective very well. Web pages with seemingly floating areas (with rounded corners) above background images do look fancy, true enough. The eventual solution required additional changes to our initial HTML setup. I had to implement a kind of repetition of our new basic approach in a more convoluted way. Decoupling inevitably leads to more
containers …

Before looking at the code, let us first have a look at the result. As a starting point the new page appearance within Range III:

fluid5

A comparison with the next picture provides an impression of “width fluidity” in Range III:

fluid6

Now, let us have a look at the transition to Range II:

fluid7

And the width fluidity again:

fluid8

Even my wife accepted this – though, of course, she did not like the chosen background colors and the spacing of my test example. But related changes are within the reach of her work domain, now.

The new HTML code

Here are the resulting HTML and CSS codes for our test example. In comparison to our first article the statements may look a bit more complex. But, we only apply already discussed recipes at additional nested tag levels:

New HTML code:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>fluid standard 2</title>
<link href="css/fluid_standard2.css" rel="stylesheet">
</head>

<body>
<!-- 3 spalten layout -->
<div id="all">
	<div id="head">
		<div id="hmen_knapp_cont" class="hmen_knapp_cont">
			<a id="hmen_knapp"></a>
		</div>
		<div id="hmen_cont" class="hmen_cont">
			<ul>
				<li><div class="hmen_pkt">h-menu 1</div></li>
				<li><div class="hmen_pkt">h-menu 2</div></li>
				<li><div class="hmen_pkt">h-menu 3</div></li>
				<li><div class="hmen_pkt">h-menu 4</div></li>
				<li><div class="hmen_pkt">h-menu 5</div></li>
				<li><div class="hmen_pkt"><a id="but2" href="#"></a></div></li>
				<li class="floatstop"></li>
			</ul>
		</div>
	</div>
	
	<div id="outer_cont">
		<div id="nav_left_bg"></div>
		<div id="right_bg">
			<!-- do not remove ! -->
			<div id="right_inner_bg"></div>
		</div>
		<div id="info_bg"></div>
		
		<div id="float_cont">
			<div id="main_cont">
				<div id="bg_left"></div>
				<div id="bg_info"></div>
				
				<div id="info_cont">
					<div id="float_info">
						<div id="info"> 
							<p>Lorem ipsum dolor .... </p>
							<p> </p>
							<p>Lorem ipsum dolor .... </p>
							<p> </p>
							<p>This is the end, my friend<br> </
p>
						</div>
					</div>
					<div id="left_nav">
						<div id="left_inner">
							<p><a href="#">sub menu point 1</a></p>
							<p><a href="#">sub menu point 2</a></p>
							<p><a href="#">sub menu point 3</a></p>
						</div>
					</div>
					<p class="floatstop"> </p>
				</div>
			</div>
			
			<div id="right">
				<div id="right_inner">
					<p>Some stupid text - just to show something</p>
					<p> </p>
					<p>Some stupid text - just to show something</p>
					<p>Some stupid text - just to show something</p>
				</div>
			</div> 
			
			<p class="floatstop"> </p>

		</div>
	</div>
</div>
</body>
</html>

 

You see that I took the decoupling of the left navigation area and the main contents container from the “div#right” container seriously. Actually, we reproduced the very same solution found above – but now inside an additional container “div#info_cont”, which itself resides inside the now floated container “div#main_cont”. Note, that by applying “position: relative;” for the container “div#info_cont” we can keep up full height control. Of course, in addition the floating inside the new additional float container “div#info_cont” has to be stopped to guarantee the dynamical propagation of height information to the outer enclosing DIVs.

As you can see, we placed some new and additional (colored) background DIVs inside the (now floated) “div#main_cont”. But throughout Range III we keep these elements hidden! However, in Range II we hide the original background stripes and activate the new ones instead.

The positioning of the container “div#right” works exactly as before – as log as we arrange for proper margins of “div#info”. To get a really nice padding of the background stripes in both ranges and to keep a constant distance of the contents of “div#right” to the contents of “div#info”, we need to include some more internal DIV containers – “div#right_inner_bg”, “div#left_inner”, “div#right_inner”. They provide some required “optical cosmetics”.

The new CSS code

The required new CSS statements for our example are the following:

   
@CHARSET "UTF-8";

html {
	font-size:10px;
}

body {
    margin-left:0; 
    margin-right:0;
    margin-top: 0; 
    background-image: url(../image/hg_drm_ap_7_1.jpg);
    background-repeat:no-repeat; 
    background-position:top center;
}

p { 
	font-size: 1.6rem; 
	line-height: 1.4;
	margin: 0;
}
	

div#all { 
	position:relative; 
	width:100%; 
	padding-bottom: 1.0rem;
	padding-top: 1.0rem;

}

/* The header region */	

div#head { 
	position:relative; 
	width:100%; 
	min-height: 3.0rem;
}

/* The main contents container */
	
div#outer_cont { 
	position:relative; 
	width:100%; 
	min-height: 10.0rem;
	margin-top:1.0rem;
}
	

/* some elementary width definitions */

div#left_nav,
div#nav_left_bg,
div#bg_left {
    width: 14rem;
    margin-left: 1.0rem;
}

div#right,
div#right_bg {
    width: 27%;
}


/* background elements for all columns in range I */

div#nav_left_bg, 
div#right_bg,
div#info_bg,
div#bg_left,
div#bg_info,
div#right_inner_bg {
    position: absolute;
    top:0;
    bottom:0;
    border-radius: 0.8rem;
}

div#nav_left_bg {
    position: absolute;
    left:0;
    
background-color: #DDD;
    border: 0px solid #F00;
    z-index:1;

}

div#info_bg {
    position: absolute;
    left:15.8rem;
    right:27%; 
    background-color: #FFB;
    border: 0px solid #00F;
    z-index:1;
}

div#right_bg {
    right: 0;
    border: 0px solid #F00;
    z-index:1;

}

div#right_inner_bg {
    left: 1.0rem;
    right: 1.0rem;
    background-color: #FEEEBB;
}


	
/* The float container and its basic elements */	

div#float_cont { 
    position:relative; 
    width: 100%; 
    border: 0px solid #FF0000;
    z-index:5;
}

/* floated left main container and 
 * its background elements for range II*/

div#main_cont { 
    position: relative;
    float: left; 
    width:100%;
    min-height: 2.0rem;
    border: 0px solid #009900;
    z-index:2;
}


div#bg_left {
    visibility: hidden; 
    position: absolute;
    left:0;
    background-color: #DDD;
    border: 0px solid #F00;
    z-index:1;

}

div#bg_info {
    visibility: hidden; 
    left:16.0rem;
    right:27%; 
    background-color: #FFB;
    border: 0px solid #00F;
    z-index:1;
}


/* The main column */

div#info_cont {
    position: relative; 
    width:100%; 
    border:0px #F00 solid;
}

div#float_info { 
    position:relative; 
    float:left;
    width: 100%; 
    border: 0px solid #FF0000;
    z-index:2;
}

div#info { 
    position: relative; 
    margin: 0 27% 0 16rem;
    width:auto;
    padding:0.8rem;
    min-height:2.0rem;
    z-index: 1;
}


/* right column */

div#right { 
    position:relative; 
    float:left;
    margin-left: -27%;
    min-height:2.0rem;
    border: 0px solid #009900;
    z-index:2;
}

div#right_inner {
    position:relative;
    width:auto;
    margin-left: 1.0rem;
    margin-right: 1.0rem;
    padding: 0.8rem;
}


/* left column */

div#left_nav { 
    position:relative; 
    float:left;
    border: 0px solid #009900;
    margin-left: -100%;
    padding-left: 1.0rem;
    z-index:5;
}

div#left_inner {
    width:auto;
    padding: 0.8rem;
}


/* Support elements */ 	

p.floatstop {
	clear:both;
	height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}



/* contents of the upper horizontal menu */

div.hmen_cont {
	display: block; 
	position: relative;
	min-height: 3.0rem;
	width: auto;
	margin-right:0.8rem;
	margin-left:0.8rem;
	background-color: #ccc;
	border-radius: 1.0rem;
}

div.hmen_cont ul {
	position: relative;
	list-style-type: none;
	width: 100%;
	margin: 0;
	padding: 0;
}

div.hmen_cont ul li {
	float: left;
	padding: 0.2rem 4.0rem 0.2rem 4.0rem;
	border-right: #a90000 0.2rem solid;
	min-height: 2.0rem;
	font-size: 1.6rem;
}
	
div.hmen_cont ul li.floatstop {
	float:none;
	clear:both;
	min-height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}

div#hmen_knapp_cont {
	display: none;
	position: absolute;
	right:0;
	top:10px;
	width: 50%;
	height: 2.4rem;
	border: 1px #A90000 solid;
}

a#hmen_knapp {
	display: block;
	width: 100%;
	height: 100%;
	background-color: #009999;		
}

a#but2 {
	display: block;
	width: 2.0rem;
	height: 2.0rem;
	background-color: #EEE;
}


/* @media screen decision and settings for range II */

@media screen and (min-width : 540px) and (max-width :800px) {
    
    div#info { 
        margin: 0 1.0rem 0 16rem;
        background-color:transparent;
    
    }
   
    div#right { 
        position:relative; 
        float:left;
        margin-top:1.0rem;
        margin-left: 0;
        margin-
right: 0; 
        width:100%;
     }
    
    div#right_inner {
        margin-left: 1.0rem; 
        margin-right: 1.0rem;
        width: auto; 
        background-color: #FEEEBB;
        border-radius: 0.8rem;
    }
    
    
    div#right_bg {
        visibility: hidden; 
    }

    div#info_bg {
        right:0;
    }

    div#nav_left_bg {
        visibility: hidden; 
    }

    div#info_bg {
        visibility: hidden; 
    }
    
    div#bg_left { 
        visibility: visible;
    } 
  
    div#bg_info { 
        visibility: visible;
        right: 1.0rem;
    }
}

 
Of course, you should replace the background image by your own.

Whilst studying the CSS statements, you may find that we extended the width of the right column to 27% of the view port width (instead of 26% before). Thus, physically “div#right” now connects directly to “div#info” at its left side. We did this to build up a seemingly constant (and not percentage dependent) visual distance between the middle and the right container contents. This may sound contradictory at first – but by using the additional inner DIV containers mentioned above and by applying proper margin/padding definitions we do achieve this objective.

At the bottom of the CSS file you find the necessary “@media screen” decisions and settings for range II. Because we set the negative left margin for “div#right” to zero this container moves down. The display of its contents requires some cosmetics; but the related CSS statements are very elementary.

One of the more interesting tricks is that we obviously hide the original (absolutely positioned and colored) background stripe containers and activate the new inner colored background stripe containers instead as soon as we perform the transition to Range II. This allows for a transparent gap between the visible upper part and the lower part of the web page in Range II – without loosing any height control for the visual columns in the upper part.

What have we achieved for our view port Ranges III and II?

Actually, all we wanted. We keep up optical height control where necessary in Range III and II. The CSS height of each of the container DIVs which represent the columns is completely determined by its contents (which we could change at any time, e.g. by a CMS). The longest of the three columns stirs the apparent visual height of all three columns in Range III.

In Range II we detach “div#right” and move it to the bottom of the page. Although we now create a gap between “div#right” and the upper part of the web page, full height control remains in place both for the floating 2 upper DIVs containers and their enclosing containers.

Fluidity is completely fulfilled in accordance with our objectives both for Range III and Range II.

The transition between Range III and Range II requires only some minor and obvious CSS changes. We did not have to change the HTML code at all during transition. We did not involve any Javascript so far.

Enough for today. We have not done anything, yet, to cover the objectives for Range I. I shall describe some appropriate measures in the next article:

Responsive fluid multi-column layouts – the DIV order problem – III

A good question the reader may consider in the meantime is:
What needs to be changed if we wanted to move the third column of Range III upwards instead of downwards during the transition to Range II?

Responsive fluid multi-column layouts – the DIV order problem – I

My wife works on a web project in Norway. As it is typical these days, functionality gets more weight than design when displaying web pages on mobile devices. Especially after Google’s decisions to explicitly require responsiveness. [Which was in my opinion mostly driven by their own interests to promote Android driven devices and their own development tools. And then maybe by general market and selling requirements. But this is a different story… ]

IT and communication in Norway is all about smart phones and tablets. So, of course, our Norwegian customer wanted to get some “responsive design”. OK … Although not really interested, I do take some challenges when my wife asks me for assistance …

As I am getting pretty IT-conservative with growing age my starting point with “responsiveness” was a fluid layout combined with “@media screen” CSS decisions – and a reluctance to use Javascript [JS] and jQuery. (This reluctance, of course, reflects the nagging question: What do we do when a customer has deactivated JS?)

My opinion was: In a certain range of browser window width – or more generally viewport width – the fluidity of the web design should predominantly govern the responsive reaction to a width change or a small target screen. I regard such a careful JS-free approach to responsive web design as reasonable – it helps already with different browser sizes on standard desktop screens – completely independent of mobile devices.

More extreme measures as removing visible parts of the layout or rearranging the positions, sizes and vertical order of whole HTML DIV container blocks would in this approach only be involved below certain viewport width thresholds via “@media” decisions. All in all, no big deal, I thought …. (In the first articles of this series we do not look at more detailed things as image resizing or font size adaptions).

What I underestimated is the fact that “old fashioned” fluid layouts may require a certain HTML tag order when we define its multiple columns”. However, in a responsive design approach you may eventually stumble across situations where the natural DIV element order you are used to is NOT suitable below some viewport width limits – i.e. when you want to rearrange the position and size of these DIV containers in a certain way. It took me some time to realize that there was a kind of contradiction between fluidity and responsiveness.

At this point I felt tempted again to use jQuery to freely rearrange the order of nodes of container DIVs in the HTML node tree or to use CSS flex boxes. However, flexboxes are not really standardized yet – and once you start with jQuery and responsiveness you must take care to align your Javascript conditions with the CSS @media decisions in a precise and non contradictory way. In addition I thought it better to understand JS free options and possibilities first before implementing some parametrizable JS machinery.

Whilst experimenting I all the time had the feeling that with some dirty CSS trick one should be able to gain control over the DIV element order – without destroying the optical appearance – and become able to adapt it to any predefined responsive reordering requirements for small viewport widths. I needed some hours to figure out how I could overcome at least some of the problems. I had some fun with this and maybe others find tweaking CSS interesting, too.

In this first article of a series I like to describe a standard flexible 3 column fluid layout and the CSS directives behind it. In a second article I shall add some elementary responsiveness and deduct the problem of the DIV order. Then I shall describe some CSS “trick” which will enable us to (manually) change or adapt the required DIV order in the HTML code without loosing fluidity and without loosing the optical appearance of the inital horizontal sequence of our columns. This will help us to achieve the pursued responsive and
vertical container rearrangement with a few very simple CSS changes below some width threshold of the viewport.

In other words: Our first objective is find CSS manipulations which enable us to code any required DIV order for the column grid in HTML such

  • that we optically do not change the column order or layout,
  • that we do not disturb the ability of our 3 column layout to adapt optically to the height of its largest (= highest) column,
  • that we can produce any predefined vertical content order of the column contents during a responsive reaction to view port width changes
  • and that we at the same time can realize responsiveness easily by basically changing some float or position statements in @media screen based decisions in our CSS file.

Note that the second point may impose additional difficulties!

Note further that even if we fulfilled our list of objectives we still would have to plan ahead our HTML code and its DIV order for the specific responsive layout we want to achieve. But being able to choose the required DIV order for our HTML code would already gives us the chance to create templates for different responsive reactions required by customers. In combination e.g. with PHP such templates would give us the freedom to switch any horizontal column order into any desired vertical order during responsive reactions – without involving JS.

Nevertheless, later in this series I want to show how we can use Javascript/jQuery to enforce a predefined responsive vertical order of the column contents from a basic 3-column layout we develop in the next two articles. But, at least I myself need some learning curve to get there.

We shall concentrate our efforts on a fluid 3-column layout.

Study example: A fluid/fixed layout with 3 columns

Below, we construct a simple example of a 3 column layout, where you arrange

  • a kind of navigation column on the left web page side,
  • a main contents area in the middle
  • and some column like area, e.g. for teaser elements, on the right page side.

Your fluidity objectives for a certain range of viewport widths would e.g. be

  • that the left column keeps up a fixed width,
  • whereas the middle main column changes its width adaptively with the viewport width
  • and that the right column adapts its width to a defined percentage of the viewport width.

Thus we have defined a combination of fixed/fluid elements which provides a good insight into the basic CSS techniques. Generalizations will be obvious. You require of course that the width adaption happens automatically whilst resizing the viewport width in the given range. All this should be governed by CSS statements, only. Imagine now, that you want to tweak things a bit further in the form that the longest of the three defined “columns” determines the height of the whole page and the apparent “visible” height of the other (not so high) columns, too. Above a responsiveness width threshold at least.

Typically, the main content column may become largest in size (height), whereas e.g. the left column may contain only some few menu points. Nevertheless, you want to create an appearance as if all 3 columns got the same height as the largest one with respect to height – and this should work automatically even if you changed the text and image contents of the content column significantly (e.g. via an CMS). This means that in case you add many lines of new text to the middle column the whole page and its right and left areas should somehow
optically adapt in height.
And: Everything should work analogously in case either the left or right column becomes the largest in height on some pages.

There is of course a simple, “conservative” CSS solution for this problem which I shall discuss a bit below. However, the requirement of fluidity in width in combination with the requirement of extending the web pages height automatically will unavoidably lead to a certain order of the HTML DIV containers.

Some details of our conventional fluid approach

The HTML file is pretty simple:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>fluid standard</title>
<link href="css/fluid_standard.css" rel="stylesheet">
</head>

<body>
<!-- 3 spalten layout -->
<div id="all">
	<div id="head">
		<div id="hmen_knapp_cont" class="hmen_knapp_cont">
			<a id="hmen_knapp"></a>
		</div>
		<div id="hmen_cont" class="hmen_cont">
			<ul>
				<li><div class="hmen_pkt">h-menu 1</div></li>
				<li><div class="hmen_pkt">h-menu 2</div></li>
				<li><div class="hmen_pkt">h-menu 3</div></li>
				<li><div class="hmen_pkt">h-menu 4</div></li>
				<li><div class="hmen_pkt">h-menu 5</div></li>
				<li><div class="hmen_pkt"><a id="but2" href="#"></a></div></li>
				<li class="floatstop"></li>
			</ul>
		</div>
	</div>
	
	<div id="outer_cont">
		<div id="nav_left_bg"></div>
		<div id="right_bg"></div>
		<div id="info_bg"></div>
		
		<div id="float_cont">
			<div id="left_nav">
				<p><a href="#">sub menu point 1</a></p>
				<p><a href="#">sub menu point 2</a></p>
				<p><a href="#">sub menu point 3</a></p>
			</div> 
			<div id="right">
				<div id="right_inner">
					<p>Some stupid text - just to show something</p>
					<p> </p>
					<p>Some stupid text - just to show something</p>
				</div>
			</div> 
			<div id="main_cont">
				<div id="info"> 
					<p>Lorem ipsum .... </p>
					<p> </p>
					<p>Lorem ipsum ....</p>
					<p> </p>
					<p>This is the end, my friend<br> </p>
				</div>
			</div>
			<p class="floatstop"> </p>
		</div>
	</div>
</div>
</body>
</html>

The central DIV that shall contain most of the information of our web page is “div#info”. Why we have placed it inside an extra container “div#main_cont” may become clear in the next article. In the pure fluid approach it would not really have been necessary. You clearly detect the “left” sided DIV for e.g. a column with navigation points and the “right” sided DIV, which may contain something else. Note that there are “background” DIVs (with an ID like “…_bg”) that provide the impression of colored columns with exactly the length of the “outer_cont” DIV.

To understand the fluid behavior let us have a look at the CSS :

@CHARSET "UTF-8";

html {
	font-size:10px;
}

body {
    margin-left:0; 
    margin-right:0;
}

p { 
	font-size: 1.6rem; 
	line-height: 1.4;
	margin: 0;

}

div#all { 
	position:relative; 
	width:100%; 
	background-color: #000; 
}

/* The header region */	

div#head { 
	position:relative; 
	width:100%; 
	background-color: #FF0; 
	min-height: 3.0rem;
}

/* The main contents container */
	
div#outer_cont { 
	position:relative; 
	width:100%; 
	background-color: #FF0; 
	min-height: 10.0rem;
}
	
/* background elements for all columns */

div#left_nav,
div#nav_left_bg {
    width: 14.2rem;
}

div#right,
div#right_bg {
    width: 26%;
}

div#nav_left_bg, 
div#right_bg,
div#info_bg {
    top:0;
    bottom:0;
}

div#nav_left_bg {
    position: absolute;
    left:0;
    background-color: #DDD;
    z-index:1;
    border: 1px solid #F00;
}

div#right_bg {
    position: absolute;
    right: 0;
    background-color: #FEEEBB;
    z-index:1;
    border: 0px solid #F00;
}

	
/* The float container and its basic elements */	

div#float_cont { 
	position:relative; 
	width: 100%; 
	/*background-color: #99e;*/ 
	border: 0px solid #FF0000;
	z-index:5;
}


/* left column */

div#left_nav { 
    position:relative; 
    float:left;
    border: 0px solid #009900;
    padding-left:8px;
    /*margin-left: -320px;*/
    z-index:2;
}

/* middle column */

div#main_cont { 
	position: relative;
	margin: 0 27% 0 15rem;
	/*width:74%;*/
	/*min-width: 462px;*/
	/*background-color: #fff;*/ 
	border: 0px solid #009900;
	z-index:2;
}

div#info { 
	position: relative; 
	/*margin: 0 2px 0 160px;*/  
	width:auto;
	background-color: #0f0; 
	padding-left:8px;
	/*height:200px;*/
}

/* right column */

div#right { 
	position:relative; 
	float:right;
	/*min-width: 15.2rem;*/ 
	width: 26%;
	/*background-color: #00f;*/ 
	border: 0px solid #009900;
}

div#right_inner {
    width:auto;
    padding: 0 0.8rem 0 0.8rem;
}


/* Other elements */ 	

p.floatstop {
	clear:both;
	height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}

/* contents of the upper horozontal menu */
div.hmen_cont {
	display: block; 
	position: relative;
	min-height: 3.0rem;
	width: 100%;
	background-color: #ccc;
}

div.hmen_cont ul {
	position: relative;
	list-style-type: none;
	width: 100%;
	margin: 0;
	padding: 0;
	background-color: #00EE00;
}

div.hmen_cont ul li {
	float: left;
	padding: 2px 40px 2px 40px;
	border-left: #ffffff 1px solid;
	border-right: #a90000 1px solid;
	min-height: 2.0rem;
	font-size: 1.6rem;
}
	
div.hmen_cont ul li.floatstop {
	float:none;
	clear:both;
	min-height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}

div#hmen_knapp_cont {
	display: none;
	position: absolute;
	right:0;
	top:10px;
	width: 50%;
	height: 2.4rem;
	border: 1px #A90000 solid;
}

a#hmen_knapp {
	display: block;
	width: 100%;
	height: 100%;
	background-color: #009999;		
}

a#but2 {
	display: block;
	width:20px;
	height: 20px;
	background-color: #eee;
}

Note that all of the DIVs

  • div#all,
  • div#outer_cont,
  • div#float_cont,
  • div#main_cont,
  • div#info

got a “position: relative;”, but none got any defined height. Actually, as we stop the floating at the end of “div#float_cont” via an artificial p-element, the “div#float_cont” container responds dynamically to the maximum height of one of its inner floated elements or relatively positioned elements and imposes its own height on the surrounding DIV container “div#outer_cont”. The resulting height of this container can be used to indirectly define the height of absolutely positioned background
DIV elements which “simulate” a colored virtual height of all columns – at least as long as we do not rearrange DIV containers due to responsiveness.

Whereas the width adaptivity of the right column is understandable from its CSS width statement, the adaptivity of the “div#main_cont” results from its relative positioning with left and right margins on top of the vertical level which is automatically defined by the floated elements inside the “div#float_cont”. The margins guarantee that we leave enough space on the left and right side not to cover the floated columns contents there.

This fluid design is quite adaptive already – see the following images taken with Firefox.

fluid1

and

fluid2

But, of course, the column adaptivity will not be of much use on small smart phone displays: First there comes an end to shrinking as soon as some words get too large to fit into their column width. And on a smart phone we would appreciate very much to use all of the available width for the text in the main column. We shall look at related measures in the next article.

For now, I want you to realize that the above fluid design relies totally on the order of the DIVs inside the “div#float_cont”. See, what happens, if we changed the order of the DIVs to

<div id="float_cont">
	<div id="main_cont"> ... </div>
	<div id="left_nav">..</div>
	<div id="right"> ... </div>
	<p class="floatstop"> </p>
</div>

fluid3

Then the first DIV spans a region with height that defines the position of the layer for the floated elements – namely below the “div#main_cont”. So our nice fluid design would be doomed to fail in case we needed some other DIV order.

Outlook on forthcoming posts

In the next post

Responsive fluid multi-column layouts – the DIV order problem – II

we introduce some elementary responsiveness and discuss, why the defined DIV order in the HTML code may conflict with a responsive rearrangement. We shall present a responsive solution where some few CSS tricks allow us to (manually) change the DIV order in the HTML code in whatever required way to maintain an easy realization of responsiveness. See also

Responsive fluid multi-column layouts – the DIV order problem – III

for a full discussion.

In the articles
Responsive fluid multi-column layouts – with a responsive horizontal menu – IV
and
Responsive fluid multi-column layouts – with a responsive horizontal menu – V
we discuss menu adaptions without and with Javascript.

Our final objective is to use JS to switch the DIV tag order in the HTML node tree for
a basic 3 column layout in accordance with the required DIV order for a defined responsiveness – in a parametrized way.