WordPress.org

Ready to get started?Download WordPress

Forums

Dynamically Create new table items in a CUSTOM field (10 posts)

  1. raybeam
    Member
    Posted 4 months ago #

    Hello,

    I was wondering if any of you amazing programmers out there might be able to help me with something. I want to create a custom field, i.e build my own "custom" custom field that allows the user to dynamically add new table items depending on how many they need. For example, say the user creates a new post and in this post he wants to add information such as, information about the units in a condo. Lets say some condos have 2 units to showcase, while others have 5 or 6 or any number. How could I set it up so I could add new table rows dynamically from the back end?

    I imagine something intuitive like a plus symbol under the default table rows that allowed the user to easily add a new input field.

    Here are some of the project requirements:

    1) Must have 5 columns
    2) Undefined number of rows but say a base number of about 4 rows
    3) When user adds new item, it must add all the columns needed: unit size, number of beds, Size Meter Square, Price from, Price to
    4) Newly added rows must update and be added to the database (of course)

    I know how to create the main body of the code here.. I just don't know how to add a function that will allow me to increment a new table row with table data included.

    Any help will be much appreciated

    Thanks

  2. bcworkz
    Member
    Posted 4 months ago #

    You can certainly add a random number of rows per posts, but then you need to keep track of which rows go with each post. I think it's better to store the values of each column in an array and store the arrays in a single row per post. That way a single column in the table is sufficient to tie the row to the post.

    So let's say a condo has 3 units to showcase in a post. The first column in the table would be the post ID. The Area column value for this condo would be array(65, 80, 93,); for the square meters of each unit. The Bedroom column value would be array(0, 1, 2) corresponding to the same units in order.

    When the row is retrieved to display a listing, the template code for the first unit might look something like this:
    echo "Location: $condo->location[0], $condo->bedrooms[0] bedrooms , $condo->area[0] m^2 for only $condo->price[0] Euros";

    Which would look like this in a browser:
    Location: Arr. 5, 0 bedrooms, 65 m^2 for only 185.000 Euros

    The next unit's listing would use index 1 in place of the zeros and so on for every unit.

  3. raybeam
    Member
    Posted 4 months ago #

    Thank you, this is the code I wrote to output one row in a table

    /********************************************************************/
    /* UNITS CUSTOM FIELDS */
    /*********************************************************************/
    add_action( 'admin_init', 'unit_type_admin' );
    
    function unit_type_admin() {
    	add_meta_box(
    		'unit_type_meta_box',
    		'Unit Type',
    		'display_unit_type_meta_box',
    		'post',
    		'normal',
    		'high'
    	);
    }
    
    function display_unit_type_meta_box( $unit_type ) {
    	$unit_name = esc_html( get_post_meta( $unit_type->ID, 'unit_name', true) );
    	$num_beds = esc_html( get_post_meta( $unit_type->ID, 'num_beds', true) );
    	$size = esc_html( get_post_meta( $unit_type->ID, 'size', true) );
    	$price_from = esc_html( get_post_meta( $unit_type->ID, 'price_from', true) );
    	$price_to = esc_html( get_post_meta( $unit_type->ID, 'price_to', true) );
    
    	?>
    		<label for="unit_name_text">Unit Name: </label>
    		<input type="text" id="unit_name_text" name="unit_name_text" value="<?php echo $unit_name; ?>" /><br />
    
    		<label for="num_beds_text">Number of Beds: </label>
    		<input type="text" id="num_beds_text" name="num_beds_text" value="<?php echo $num_beds; ?>" /><br />
    
    		<label for="size_text">Size M²: </label>
    		<input type="text" id="size_text" name="size_text" value="<?php echo $size; ?>" /><br />
    
    		<label for="price_from_text">Price From (THB): </label>
    		<input type="text" id="price_from_text" name="price_from_text" value="<?php echo $price_from; ?>" /><br />
    
    		<label for="price_to_text">Price To (THB): </label>
    		<input type="text" id="price_to_text" name="price_to_text" value="<?php echo $price_to; ?>" /><br />
    
    <?php
    
    	}
    
    add_action( 'save_post', 'unit_type_fields', 10, 2 );
    
    function unit_type_fields( $unit_type_id, $unit_type) {
    	if ( $unit_type->post_type == 'post') {
    		if ( isset( $_POST['unit_name_text'] ) && $_POST['unit_name_text'] != '' ) {
    			update_post_meta( $unit_type_id, 'unit_name', $_POST['unit_name_text'] );
    		}
    		if ( isset( $_POST['num_beds_text'] ) && $_POST['num_beds_text'] != '' ) {
    			update_post_meta( $unit_type_id, 'num_beds', $_POST['num_beds_text'] );
    		}
    		if ( isset( $_POST['size_text'] ) && $_POST['size_text'] != '' ) {
    			update_post_meta( $unit_type_id, 'size', $_POST['size_text'] );
    		}
    		if ( isset( $_POST['price_from_text'] ) && $_POST['price_from_text'] != '' ) {
    			update_post_meta( $unit_type_id, 'price_from', $_POST['price_from_text'] );
    		}
    		if ( isset( $_POST['price_to_text'] ) && $_POST['price_to_text'] != '' ) {
    			update_post_meta( $unit_type_id, 'price_to', $_POST['price_to_text'] );
    		}
    	}
    }
    
    function display_unit_type() {
    	global $post;
    
    	$unit_name = get_post_meta( $post->ID, 'unit_name', true );
    	$num_beds = get_post_meta( $post->ID, 'num_beds', true );
    	$size = get_post_meta( $post->ID, 'size', true );
    	$price_from = get_post_meta( $post->ID, 'price_from', true );
    	$price_to = get_post_meta( $post->ID, 'price_to', true );
    
    	$allowed_html = array(
    	'a' => array(
    		'href' => array(),
    		'title' => array()
    		),
    	'em' => array(),
    	'strong' => array()
    	);
    
    	$_unit_name_output = wp_kses( $unit_name, $allowed_html );
    	$_num_beds_output = wp_kses( $num_beds, $allowed_html );
    	$_size_output = wp_kses( $size, $allowed_html );
    	$_price_from_output = wp_kses( $price_from, $allowed_html );
    	$_price_to_output = wp_kses( $price_to, $allowed_html );
    
    		$output = '<div class="col-md-12">
    					<div class="table-responsive">
    						<table class="table table-bordered table-striped">
    							<tr>
    								<th>Unit Name</th>
    							  	<th>Number of Beds</th>
    							  	<th>Size M²</th>
    							  	<th>Price from THB</th>
    							  	<th>Price up to THB</th>
    							</tr>
    							<tr>
    								<td>'.$_unit_name_output.'</td>
    								<td>'.$_num_beds_output.'</td>
    								<td>'.$_size_output.'</td>
    								<td>'.$_price_from_output.'</td>
    								<td>'.$_price_to_output.'</td>
    							</tr>
    						</table>
    					</div>
    				</div>';
    			return $output;
    }
    add_shortcode( 'project-info-box', 'display_unit_type' );

    How could I create a function that says either:

    1) Only display a row if it is filled in.

    0r

    2) Allows user to add new inout fields dynamically in backend?

    Thanks again

  4. bcworkz
    Member
    Posted 4 months ago #

    Even though you've switched to storing data in post_meta, my suggested array concept still applies. When you get post meta as an array, use a foreach(){} looping structure to output rows of input fields. It'd probably be a good idea to place the inputs in a table structure, so output the <tr> and <td> tags as well. There might be issues if the column data you are using to drive the loop has missing data that is present in other fields of the same row. You may want to select one column where some data is always required, and use javascript to enforce it.

    To add additional rows, place a button or link some where in the meta box for the user to click on. You will need some javascript (or jQuery) that runs on the onclick event which appends another row of cells and inputs to the existing table. Use some care in generating the field names, it effects how easy it is to collect each column of values into an array before storing it as a meta value.

    I've little experience with input grids like this, more investigation could pay off. For example, can a column of inputs have the same name? If so, what does the $_POST data look like? I seem to recall using empty square brackets in the name (as in "field_name[]") will collect the data into an array for you. That'd be sweet! And if you don't, you only get the last value. The thing is, I'm not sure this applies to this situation or not, but it's worth investigating.

    Be sure to investigate what happens when empty fields are present. An empty field in one column but not others could cause the remaining data to be out of synch with the other columns if not properly dealt with.

    The alternative, using unique names like "name1", "name2", etc. will work, but is a little more work to implement. If you do go this route, do not hardcode all the field names! Use a looping structure to generate the names. Seems obvious right? I've recently inherited some code where someone had hardcoded all the attributes of each row!! What PITA!

  5. raybeam
    Member
    Posted 4 months ago #

    Thanks.

    A lot of this makes sense to me, but my experience as a programmer is pretty limited. I'm not exactly sure how I would write a lot of what you're saying here, or how I would include jQuery in a php document.

    My usual method of writing code like this is to find an example online that is close to what I want and then modify it to my needs.

    Could you give me some examples of code?

    I think the main one would be how to write that function to add new table rows dynamically.

    I was thinking, as each table cell currently has a unique var name, it would get pretty crazy if I had to create say 10 rows and then hard code the var names for each cell.

    Could you show what the array would look like? Or would be some thing like:

    `$var = array ($unit_type, $size_msq, $price_from $etc) {
    //code here
    }

    then to access these variables in the array I would use [0][1] etc so, something like $var[2], would get the $price_from ?

    I will have a go writing some code and see what I can do.

    Thanks

  6. bcworkz
    Member
    Posted 4 months ago #

    I do have a sample, though it's not my work. It's actually part of that awful project where all of the attributes were hardcoded. If you ignore that part (unless you need a laugh :) ), the jQuery part that actually adds rows works fine. It doesn't have to be jQuery, it could be translated for straight javascript.

    The other bad thing this code does is how it loads the jQuery library. Do not do it this way! The best way is to place your jQuery script on an external file and use wp_enqueue_script() in your PHP to cause it to load along with WP's own jQuery library. All this jQuery code is doing is when a button is clicked, the table is located by ID and the complete row HTML is added to the table element's value. The button at the end of the row serves to remove that particular row.

    I don't think the owner will mind my sharing. He didn't write it either, but he did post it in a public forum so it's presumably free to use, not that anyone would want to use most of it. Here's the link, the jQuery code starts at line 874:
    http://pastebin.com/7epgb5dz

    I suggest you first learn to simply load a hello world javascript into a WP page using Codex examples under wp_enqueue_script(). Only once you have a basic script working properly in WP should you attempt something more complex like appending HTML to an element.

    The array structure I had in mind was more like this:

    $unit_type = array('Park Place','Boardwalk','Pacific Ave',);
    $size_msq = array('65','75','90',);
    //etc....

    Mainly so the column data is kept together which works with your example code better. There's nothing wrong with doing it by row like you are suggesting though. If you do go by row, you probably should just keep everything for the entire property in one giant two dimensional array. One reason is you don't know how many rows any one property will use, and you don't want to hardcode each row variable. By using a two dimensional array you can add as many rows as you need and reference them all with one variable.

    The reason my column idea works is you always know how many columns you have, so there is no problem hardcoding them. Any number of elements can be added for each row. It's easier to keep track of things if they have names instead of index numbers. You can actually have named columns in the one giant array concept as well, so there's no big advantage either way, go with whatever makes sense to you.

    IMO. there's no better way to learn coding than by having a real project to work on. You do need lots of extra time to fumble about, and there will be times where learning is very frustrating, but once it's finally done it's tremendously gratifying. And you will have learned exactly what you needed to learn and little of what you didn't so it's fairly efficient on your learning capacity.

  7. raybeam
    Member
    Posted 4 months ago #

    I appreciate all the help you've given me on this. I'm not sure if I can make sense of the code example you given me, or how I could modify it to my needs.

    I'm light years behind the level I need to be at and I have to finish this by the end of the week. I'm thinking I'll just have to come up with some artificial solution such as hard coding 10 rows then only displaying them if they're !=' ';

    Thanks man!

  8. bcworkz
    Member
    Posted 4 months ago #

    You're most welcome. I suspect that awful code was also created under a tight deadline :) With a deadline looming you need to go with what you know and just make it work. I hope you will be able to find the time in the future to advance far enough to produce some elegant code. Best of luck to you.

  9. raybeam
    Member
    Posted 4 months ago #

    Hey I think I might have solution in the form of Types plugin. It wasn't the way I wanted to go, but as I have a tight deadline and I don't see myself coming up with some amazing code in 3 days, I think it's the best i can find. This is the code I am using with this plugin to generate one line of the table

    <?php 
    
    echo '<table class="table-striped table-bordered table-responsive table">';
    echo
    '<tr>
    	<th>Unit Name</th>
      	<th>Number of Beds</th>
      	<th>Size M²</th>
      	<th>Price from THB</th>
      	<th>Price up to THB</th>
    </tr>';
    echo '<tr>';
    $unit_types=get_post_meta($post->ID,'wpcf-unit-type');
    
    foreach ($unit_types as $unit_type) {
    
            echo '<td>'.$unit_type.'</td>';
    
    }
    $num_beds=get_post_meta($post->ID,'wpcf-number-of-beds');
    
    foreach ($num_beds as $num_bed) {
    
            echo '<td>'.$num_bed.'</td>';
    
    }
    $size_sq_mtrs=get_post_meta($post->ID,'wpcf-size-square-meters');
    
    foreach ($size_sq_mtrs as $size_sq_mtr) {
    
            echo '<td>'.$size_sq_mtr.'</td></tr>';
    
    }
    $prices_from=get_post_meta($post->ID,'wpcf-price-from');
    
    foreach ($prices_from as $price_from) {
    
            echo '<td>'.$price_from.'</td>';
    
    }
    $prices_to=get_post_meta($post->ID,'wpcf-price-to');
    
    foreach ($prices_to as $price_to) {
    
            echo '<td>'.$price_to.'</td>';
    
    }
    echo '</tr>';
    echo '</table>';     
    
    ?>

    Only problem occurs when I try to add a new row. I can see why that's happening, its because my block of code is wrapper in <tr></tr> tags and just forces all the content into an ever growing row. How can I write is so that one block of code will be put into a new row every time I add a new items?

    Could I, for example, but that block of items in <td>s and make it a block of code that is always wrapped in <tr>s? Would i use a function, or an array, or just create a variable..?

    P.S Working on this project has really improved my understanding of programming, variables and arrays and all that suddenly make sense to me.. I just can't seem to use them when it comes to writing my own stuff!

  10. bcworkz
    Member
    Posted 4 months ago #

    Instead of running individual loops for each column, you want a single loop where each iteration only outputs a single element from each column. As I mentioned earlier, you need to pick a column that reliably has data for each row. Columns with missing data can cause the data to be misaligned with the associated unit. I'm guessing the unit name is as good as any, my example will use that to drive the loop.

    You must also initialize a counter so that the proper array element can be easily referenced to output any particular row. I'm assuming the data in each column is in an indexed array (as in my previous example), not an associative array like this:

    array( 'keyname'=>'Park Place',
            'key2'   =>'Boardwalk',);

    The code after the table headers are output should be organized similar to this:

    $unit_types=get_post_meta($post->ID,'wpcf-unit-type');
    $num_beds=get_post_meta($post->ID,'wpcf-number-of-beds');
    $size_sq_mtrs=get_post_meta($post->ID,'wpcf-size-square-meters');
    $prices_from=get_post_meta($post->ID,'wpcf-price-from');
    $prices_to=get_post_meta($post->ID,'wpcf-price-to');
    $i = 0; //initialize counter
    foreach ( $unit_types as $unit_type) {
       echo "<tr><\n";
       echo "  <td>$unit_type</td>\n";
       echo "  <td>$num_beds[$i]</td>\n";
       echo "  <td>$size_sq_mtrs[$i]</td>\n";
       echo "  <td>$prices_from[$i]</td>\n";
       echo "  <td>$prices_to[$i]</td>\n";
       echo "</tr>\n";
       $i++;
    }
    echo '</table>';

    This is untested, there may be some stupid typo errors, but the basic structure is valid.

    It's good to hear this is all starting to make sense. While you still need to copy code snippets to get functional code, you will increasingly be able to alter snippets so they do exactly what you need. Soon enough you will be writing your own simple code lines. After a while, you will have seen enough code like the above that illustrate how to do certain tasks that you will be able to write your own versions from memory. You've become a full fledged coder!

Reply

You must log in to post.

About this Topic