Creating a CodeIgniter App (Part 5): Projects

With this post we get to the penultimate part of this series about CodeIgniter. We will continue the development of Simple Task Board with the projects part, inserting and updating the project information. We will update the dashboard as well to show the projects.

Introduction

In this article we will develop the section that handles projects in Simple Task Board. I’m not going to explain every bit because it will be quite similar to the last article. First we will create two additional database tables. Next we’ll create a controller and a model for projects. We’ll also update the Dashboard with the information about the user related projects. To conclude, we’ll do the views, styling and a quick JavaScript.

Database

Let’s create two database tables: project and user_project. The first will be responsible for the project information, while the second will be the relationship between users and projects (n to n). Create both tables according to these scripts:

project

CREATE TABLE IF NOT EXISTS `project` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `user` int(10) unsigned NOT NULL,
    `name` varchar(255) NOT NULL,
    `description` text NOT NULL,
    `date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 ;

user_project

CREATE TABLE IF NOT EXISTS `user_project` (
    `user` int(10) unsigned NOT NULL,
    `project` int(10) unsigned NOT NULL,
    `date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`user`,`project`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Controller

Create a new file inside the folder application/controllers called project.php. The content of this file will be the following (the explanation comes right after):

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Project extends CI_Controller {
    
    function Project()
    {
        parent::__construct();
        
        if(!$this->session->userdata('logged'))
            redirect('login');
    }
    
    public function index()
    {
        redirect('dashboard');
    }
    
    public function tasks($project_id)
    {
        // Load tasks - TODO: next

        // Load project info
        $this->load->model('project_model');
        $project = $this->project_model->get($project_id);
        
        $data['page_title'] = "Project: ".$project['name'];
        $data['project']    = $project_id;
        
        $data['current_user'] = $this->session->userdata('user');
        
        $db_users = $this->project_model->get_related_users($project_id);
        $users = array();
        foreach ($db_users as $user) {
            $users[$user['id']] = $user;
        }
        $data['users'] = $users;
        
        // Load text helper to be used in the view
        $this->load->helper('text');
        
        // Load View
        $this->template->show('task_board', $data);
    }

    public function add()
    {
        $this->load->model('project_model');
        
        $data['page_title']  = "New Project";
        $data['user']        = '';
        $data['name']        = '';
        $data['description'] = '';
        
        $users = $this->project_model->get_related_users();
        foreach ($users as $key => $value) {
            if($value['id'] == $this->session->userdata('user'))
                $users[$key]['project'] = 1;
            else
                $users[$key]['project'] = 0;
        }
        $data['users'] = $users;
        
        $this->template->show('project_add', $data);
    }

    public function edit($id)
    {
        $this->load->model('project_model');
        
        $data = $this->project_model->get($id);
        
        $data['page_title']  = "Edit Project #".$id;
        $data['project']  = $id;
        $data['users'] = $this->project_model->get_related_users($id);
        
        $this->template->show('project_add', $data);
    }
    
    public function save()
    {
        $this->load->model('project_model');
        
        $sql_data = array(
            'user'        => $this->session->userdata('user'),
            'name'        => $this->input->post('name'),
            'description' => $this->input->post('description')
        );
        
        $project_id = $this->input->post('id');
        
        if ($project_id)
            $this->project_model->update($project_id,$sql_data);
        else
            $project_id = $this->project_model->create($sql_data);
            
        // Related users
        $this->project_model->delete_related($project_id);
        
        $users = $this->input->post('users');
        foreach ($users as $user) {
            $sql_data = array(
                'user' => $user,
                'project' => $project_id
            );
            $this->project_model->create_related($sql_data);
        }

        if ($project_id)
            redirect('project/tasks/'.$project_id);
        else
            redirect('project');
    }
}

This controller is quite simple, with only the tasks() method being different from what was done in the user.php controller. Anyway, the explanation of each method is as follows:

  • Project(): class constructor. Loads the parent constructor and checks if the user is logged in.
  • index(): default method. As this controller will not be directly accessed through the address /project, we will redirect the user to the Dashboard.
  • tasks($project_id): this method is responsible for displaying the task board itself. This board will have four columns identifying the status of each task to be executed. As the tasks section will be developed in the last article of the series, I left a “TODO” for later. Right after it, we load the project data according to the ID and define some variables for the view: the page title, the project, the current user and an array with all users related to the project. The latter is create from the results of the method get_related_users, which will return an array of users. This array will be organized in a way that the Id will be the key (array[ID] = user). Finally, we load the “task_board” view using the template library.
  • add(): method to add a new project. Besides defining the basic project variables, we also define the array of related users. When we loop through this array, if the ID is equal to the logged user’s ID ($value[‘id’] == $this->session->userdata(‘user’)), we assign 1 to indicate that this user is selected – in the view this will be reproduced in related users checkboxes. Later we load the “project_add” view.
  • edit($id): method to edit a project. Similar to the add() method, but this one receives an ID as the parameter and loads the information according to this project ID.
  • save(): method responsible for saving the project data. First we load the project model, define the array to be inserted, using the session user and the project’s name/description sent via post. Next we get the project ID, in case it’s a new project the $project_id variable will be false. This way we can identify if it’s a new project and create (create($sql_data)) or update (update($project_id, $sql_data)) accordingly. Next we remove the related users from the database and create new entries according to what was selected by the user. Finally, we redirect the user tho the tasks() method. If something happened and the $project_id variable is 0 or false, the user will be redirected to the controller.

Model

Let’s create another file for our model. We’ll create inside application/models the file project_model.php. This is the code:

<?php

class Project_model extends CI_Model {

    public function create($data)
    {
        $insert = $this->db->insert('project', $data);
        if($insert)
            return $this->db->insert_id();
        else
            return false;
    }

    public function create_related($data)
    {
        $insert = $this->db->insert('user_project', $data);
        return $insert;
    }

    public function update($id, $data)
    {
        $this->db->where('id', $id);
        $update = $this->db->update('project', $data);
        return $update;
    }

    public function get($id = false)
    {
        if ($id) $this->db->where('id', $id);
        $this->db->order_by('name', 'asc');
        $get = $this->db->get('project');

        if($id) return $get->row_array();
        if($get->num_rows > 1) return $get->result_array();
        return array();
    }
    
    public function get_user_owned($user)
    {
        $this->db->where('user', $user);
        $this->db->order_by('name', 'asc');
        $get = $this->db->get('project');

        if($get->num_rows > 0) return $get->result_array();
        return array();
    }
    
    public function get_user_related($user)
    {
        $this->db->select('p.*, u.project');
        $this->db->from('project p');
        $this->db->join('user_project u', 'p.id = u.project');
        $this->db->where('u.user', $user);
        $this->db->order_by('p.name', 'asc');
        $get = $this->db->get();

        if($get->num_rows > 0) return $get->result_array();
        return array();
    }
    
    public function get_related_users($id = false)
    {
        if($id)
            $this->db->select('u.*, up.project');
        else
            $this->db->select('u.*');
        
        $this->db->from('user u');
        if($id)
            $this->db->join('user_project up', 'up.user = u.id and up.project = '.$id, 'left');
        $this->db->order_by('u.email', 'asc');
        $get = $this->db->get();

        if($get->num_rows > 0) return $get->result_array();
        return array();
    }

    public function delete($id)
    {
        $this->db->where('id', $id);
        $this->db->delete('project');
    }

    public function delete_related($id)
    {
        $this->db->where('project', $id);
        $this->db->delete('user_project');
    }
}

Explanation:

  • create($data): created an entry in the database, “project” table. If everything goes right it will return the project ID using $this->db->insert_id(). This function will return the ID of the last inserted entry, when the table have a simple key.
  • create_related($data): creates an entry in the relationship table “user_project” and returns if everything went correctly.
  • update($id, $data): updates an entry in the “project” table using the $id in the where condition.
  • get($id = false): fetches data of one or more projects. If the ID was sent, the where clause will be defined and only the project with this ID will be selected from the database. Otherwise all projects will be selected. In both cases we will return just the array (row_array() ou result_array()). We could return the entire object (row() ou result()), but in this case we don’t need it. If no project is found, we return and empty array (this way we can use it inside a foreach without problems).¬†It is important to note that in each return, the method finishes in that command, that’s why we don’t need an else for the if. For example, if the $id was sent the first return will be executed and the method will finish in that line. The next two lines will not be executed.
  • get_user_owned($user): selects the projects created by the user $user.
  • get_user_related($user): selects the projects related to the user $user. That’s done through the join with the “user_project” table.
  • get_related_users($id = false): selects users related to the project. If the ID was not sent, all users will be returned. That’s for when a new project is being created – when we need to fetch all users so they can be associated to the project. If the ID was sent, the join will be a left join (last parameter of $this->db->join). This way all users will be returned, but the ones that are related will have the field user_project.project, while the non-related will have NULL.
  • delete($id): method to delete a project.
  • delete_related($id): method to delete the relationship of a project.

Dashboard

Now let’s do some updates in the Dashboard. First open application/controllers/dashboard.php. Update the index() method so that it looks like this:

    public function index()
    {
        //Load models
        $this->load->model('project_model');
        
        //Load projects
        $projects = $this->project_model->get_user_related($this->session->userdata('user'));
        
        foreach ($projects as $key => $project) {
            $projects[$key]['tasks'] = array();
        }

        $data['projects'] = $projects;
        
        $data['page_title']  = "Dashboard";
        
        // Load View
        $this->template->show('dashboard', $data);
    }

Basically, we are adding here the logic to fetch the projects’ information. In the future we will add the tasks as well (where we are assigning the array()). We also need to modify the view, so open the file dashboard.php located inside application/views and paste the following:

<?php
// Load Menu
$this->template->menu('dashboard');
?>

<div id="container">
    <?php if(isset($projects)) { ?>
    <div id="dash_projects" class="dash_wrap">
        <div id="dash_projects_title" class="dash_wrap_title blue-gradient">
            Projects
        </div>
        <div id="dash_projects_items" class="dash_wrap_items">
    <?php foreach ($projects as $project) { ?>
        <div id="project_<?php echo $project['id']; ?>" class="dash_project_group">
            <p class="dash_project_title blue-gradient"><?php echo anchor('project/tasks/'.$project['id'], $project['name']); ?></p>
            <ul>
                <?php if($project['tasks']) { ?>
                <?php foreach ($project['tasks'] as $task) { ?>
                <li>
                    <?php echo anchor('task/edit/'.$project['id'].'/'.$task['id'], '#'.$task['id'].' - '.$task['title']); ?>
                    (<?php echo $status[$task['status']] ?>)
                </li>
                <?php } ?>
                <?php } else { ?>
                No tasks here!
                <?php } ?>
            </ul>
        </div>
    <?php } ?>
        </div>
    </div>
    <?php } ?>
</div>

The view contains an area of projects where we will display the projects associated to the user and the tasks as well (to be done). If you log in the system and go to the Dashboard, you’ll see this:

Task Board - Dashboard (no styling)

Task Board – Dashboard (no styling)

Let’s add some CSS to fix the projects display. Open the style.css and after the definition of #page-title, add the following:

/* MAIN */
/* DASHBOARD */
.dash_wrap{
    border:1px solid #D5D5D5;
    margin-bottom: 10px;
    padding:2px;
    height: 200px;
    overflow:hidden;
}
.dash_wrap:hover{
    -moz-box-shadow:0 0 5px #999;
    -webkit-box-shadow:0 0 5px #999;
    box-shadow:0 0 5px #999
}
.dash_wrap_title{
    float:left;
    height:198px;
    line-height: 198px;
    width:130px;
    font-size:14px;
    text-align: center;
    color:#fff;
    text-shadow:1px 1px 0 #3070D5;
    border:1px solid #3079ED
}
.dash_wrap_items{
    padding-left:130px;
    margin-left: 10px;
    width:80%;
}
.dash_project_group{
    border:1px solid #D5D5D5;
    padding:2px;
}
#dash_task_items ul{
    list-style: none;
    margin:3px 0;
    padding: 0 10px;
    float:left;
}
#dash_task_items li, .dash_project_group li{
    margin:4px 0;
}
.dash_project_group{
    border:1px solid #D5D5D5;
    margin:0 4px 4px 0;
    padding:2px;
    width:250px;
    float:left;
}
.dash_project_group ul{
    list-style: none;
    margin:3px 0;
    padding:0 10px;
}
.dash_project_title{
    margin:2px auto 8px;
    padding:2px;
    font-size:14px;
    text-align: center;
    border:1px solid #3079ED
}
.dash_project_title a{
    color:#fff;
    text-shadow:1px 1px 0 #3070D5;
}

If we visit the Dashboard again, this will be the result:

Task Board - Dashboard

Task Board – Dashboard

Views

We need two view, one to add a project (a form) and another for the task board. First let’s create project_add.php.

project_add.php

<?php
// Load Menu
if (isset($id))
    $this->template->menu('return_to_tasks');
else
    $this->template->menu('projects');
?>

<div id="container">

    <?php echo form_open('project/save'); ?>

    <table>
        <tr>
            <td>
                <?php echo form_label('Name', 'name'); ?>
            </td>
            <td>
                <?php echo form_input('name', $name); ?>
            </td>
        </tr>
        <tr>
            <td>
                <?php echo form_label('Description', 'description'); ?>
            </td>
            <td>
                <?php
                $data = array('name'        => 'description',
                              'id'          => 'description',
                              'value'       => $description,
                              'rows'        => '6',
                              'cols'        => '80');

                echo form_textarea($data); ?>
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <div class="project-users blue-gradient">
                    Associated Users
                    <span class="show-hide-event expand" target-id="associated-users"></span>
                </div>
            </td>
        </tr>
        <tbody id="associated-users">
        <tr>
            <td colspan="2">
            <?php foreach ($users as $user) { ?>
            <div class="half-width">
                <label>
                <?php echo form_checkbox('users[]', $user['id'], ($user['project'])?1:0); ?>
                <?php echo $user['email']; ?></label>
            </div>
            <?php } ?>
            </td>
        </tr>
        </tbody>
        <tr>
                <td colspan="2">
                    <?php if (isset($id)) echo form_hidden('id', $id); ?>

                    <div class="form-save-buttons">
                        <?php echo form_submit('save', 'Save', 'class="btn-blue"'); ?>
                        <?php echo form_button('cancel', 'Cancel', 'class="btn-blue" onClick="history.go(-1)"');; ?>
                    </div>
                </td>
        </tr>
    </table>

    <?php echo form_close(); ?>

</div>

In the beginning we have our menu being loaded in two different ways, according to the $id. What happens is that if the ID was sent, then the user is editing an exiting project, otherwise he/she is adding a new one. So we will be showing different menus for each case. After this we have a form with the project/save actin (save() method, created earlier) and the fields Name and Description.

After that we have a DIV with the text Associated Users and a SPAN. This SPAN has and target-id property that will be used by the jQuery to show or hide the TBODY that follows. If you look at this TBODY closely you’ll see that it contains one checkbox for each user inside a DIV with a class called half-width. This class will force two columns of users.

In the end we have the form_hidden with the ID to identify when we are editing or adding and the submit and cancel buttons.

task_board.php

For now we will keep this file almost empty. In the next article we will include the tasks.

<?php
// Load Menu
$this->template->menu('task_board');
?>

<div id="container">

</div>

In order to navigate through these pages we need to update our menu template as well.

Menu

Open the file application/views/template/menu.php.

<div class="home-title blue-gradient">Simple Task Board</div>
<div id="menu">
    <?php if($view == 'dashboard') { ?>
        <!-- Dashboard menu -->
        <?php echo anchor('user', 'Edit Users', 'class="btn btn_users"'); ?>
		<?php echo anchor('project/add', 'Add new project', 'class="btn btn_add"'); ?>
    <?php } elseif($view == 'task_board') { ?>
        <!-- Task board menu -->
        <?php echo anchor('dashboard', 'Dashboard', 'class="btn btn_dashboard"'); ?>
        <?php echo anchor('project/edit/'.$project, 'Edit project', 'class="btn btn_edit"'); ?>
    <?php } elseif($view == 'return_to_tasks') { ?>
        <!-- Projects menu -->
        <?php echo anchor('dashboard', 'Dashboard', 'class="btn btn_dashboard"'); ?>
        <?php echo anchor('project/tasks/'.$project, 'Task Board', 'class="btn btn_taskboard"'); ?>
    <?php } elseif($view == 'projects') { ?>
        <!-- Projects menu -->
        <?php echo anchor('dashboard', 'Dashboard', 'class="btn btn_dashboard"'); ?>
    <?php } elseif($view == 'users') { ?>
        <!-- Users menu -->
        <?php echo anchor('dashboard', 'Dashboard', 'class="btn btn_dashboard"'); ?>
        <?php echo anchor('user/add', 'Add new user', 'class="btn btn_add"'); ?>
    <?php } ?>
    <?php echo anchor('login/logout', 'Logout', 'class="btn btn_logout"'); ?>
    <div class="clear"></div>
</div>
<div id="page-title">
    <?php echo $page_title; ?>
</div>

In this file we added the following:

  • dashboard: the button to add a new project.
  • task_board: a new menu to be shown in the task_board, with a button to return to the Dashboard and another one to edit the project.
  • return_to_tasks: a new menu to be shown when we edit a project, with a button allowing you to return to the Dashboard and another one to return to the task board.
  • projects: a new menu to be shown when we add a project, with a button to return to the Dashboard.

Now you can navigate through the system normally. Some buttons are not showing up as they should, as well as the associated users list. So let’s update the CSS.

CSS

Add the following button definitions before #page-title:

.btn_taskboard{float:left;
    background-color:#FFB51B;background-image:url(../images/tasks.png);
    background-image:url(../images/tasks.png), -webkit-gradient(linear, left top, left bottom, from(#FFD500), to(#FF9C30));
    background-image:url(../images/tasks.png), -webkit-linear-gradient(top,#FFD500,#FF9C30); /* Chrome 10+, Saf5.1+ */
    background-image:url(../images/tasks.png), -moz-linear-gradient(top,#FFD500,#FF9C30); /* FF3.6+ */
    background-image:url(../images/tasks.png), -ms-linear-gradient(top,#FFD500,#FF9C30); /* IE10 */
    background-image:url(../images/tasks.png), -o-linear-gradient(top,#FFD500,#FF9C30); /* Opera 11.10+ */
    background-image:url(../images/tasks.png), linear-gradient(top,#FFD500,#FF9C30)
}
.btn_taskboard:hover{background-color:#FB8F19;background-image:url(../images/tasks.png);
    background-image:url(../images/tasks.png), -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F78400), to(#FF9C30));
    background-image:url(../images/tasks.png), -webkit-linear-gradient(top,#F78400,#FF9C30); /* Chrome 10+, Saf5.1+ */
    background-image:url(../images/tasks.png), -moz-linear-gradient(top,#F78400,#FF9C30); /* FF3.6+ */
    background-image:url(../images/tasks.png), -ms-linear-gradient(top,#F78400,#FF9C30); /* IE10 */
    background-image:url(../images/tasks.png), -o-linear-gradient(top,#F78400,#FF9C30); /* Opera 11.10+ */
    background-image:url(../images/tasks.png), -o-linear-gradient(top,#F78400,#FF9C30); /* Opera 11.10+ */
    background-image:url(../images/tasks.png), linear-gradient(top,#F78400,#FF9C30)
}
.btn_edit{float:left;
    background-color:#FFB51B;background-image:url(../images/edit.png);
    background-image:url(../images/edit.png), -webkit-gradient(linear, left top, left bottom, from(#FFD500), to(#FF9C30));
    background-image:url(../images/edit.png), -webkit-linear-gradient(top,#FFD500,#FF9C30); /* Chrome 10+, Saf5.1+ */
    background-image:url(../images/edit.png), -moz-linear-gradient(top,#FFD500,#FF9C30); /* FF3.6+ */
    background-image:url(../images/edit.png), -ms-linear-gradient(top,#FFD500,#FF9C30); /* IE10 */
    background-image:url(../images/edit.png), -o-linear-gradient(top,#FFD500,#FF9C30); /* Opera 11.10+ */
    background-image:url(../images/edit.png), linear-gradient(top,#FFD500,#FF9C30)
}
.btn_edit:hover{background-color:#FB8F19;background-image:url(../images/edit.png);
    background-image:url(../images/edit.png), -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F78400), to(#FF9C30));
    background-image:url(../images/edit.png), -webkit-linear-gradient(top,#F78400,#FF9C30); /* Chrome 10+, Saf5.1+ */
    background-image:url(../images/edit.png), -moz-linear-gradient(top,#F78400,#FF9C30); /* FF3.6+ */
    background-image:url(../images/edit.png), -ms-linear-gradient(top,#F78400,#FF9C30); /* IE10 */
    background-image:url(../images/edit.png), -o-linear-gradient(top,#F78400,#FF9C30); /* Opera 11.10+ */
    background-image:url(../images/edit.png), linear-gradient(top,#F78400,#FF9C30)
}

The image related to the .btn_edit was already created in the last part, but the image for the .btn_taskboard is still missing. Therefore, copy the following image to the images folder:

After this continue editing the CSS. Add the next code after the Dashboard definitions.

/* PROJECTS */
.project-users{
    margin:2px auto 8px;
    padding:2px;
    font-size:14px;
    text-align: center;
    color:#fff;
    text-shadow:1px 1px 0 #3070D5;
    border:1px solid #3079ED
}
#associated-users{
    display:none;
}

We still have some additional things to add to the CSS, but before we do it, copy two additional icons to the images folder:

After copying these two, we can define the last styles needed in this part (you can paste it in the end of the file):

.expand{
    background-image: url(../images/expand.png);
    background-repeat: no-repeat;
    padding-left:16px;
    cursor:pointer;
}
.collapse{
    background-image: url(../images/collapse.png);
    background-repeat: no-repeat;
    padding-left:16px;
    cursor:pointer;
}
.half-width{
    width:50%;
    float:left;
}
.form-save-buttons{
    text-align:center;
}

We are almost done for today, we just need a small part, the JavaScript.

JavaScript

If you copied the complete ZIP file last week, you’ll notice this is already there. I copied the complete file by mistake. But the explanation goes now.

    /* General actions */
    $('.show-hide-event').click(function(){
        var targetId = $(this).attr('target-id');
        if($(this).hasClass('expand')){
            $(this).removeClass('expand').addClass('collapse');
            $('#' + targetId).show('slow');
        } else {
            $(this).removeClass('collapse').addClass('expand');
            $('#' + targetId).hide('slow');
        }
    });

Copy this code into the $(document).ready. It will be responsible for showing and hiding the users lists inside the project_add.php view.

Conclusion

That’s all I had to show, for now. We’ll finish this series in the next article. I’ll try not to take so long to write it… And I hope it has been interesting so far. If you have any doubt or comment let me know.

To download what was done so far, just copy this file.



  • Nicholai

    Nice blogsite mate. very helpful, im now waiting for part six. thanks a lot for this!

  • Ella Cummings

    This really sounds interesting to me, being a CodeIgniter Developer would love to know more about this and even it got chance would love to deal with this type of projects too in future.

  • Such a wonderful idea sharing i shall go with this experience in my future. I will feel very happy if you will share your experience continue with us. I can understand as a php developer this is a very helpful.