I'm having a fundamental problem in understanding the concept of MVC and displaying more than one form at a time. I've tried a variety of methods but I'm still stuck - and that's because I don't think I'm understanding CI and MVC correctly.
I tried using 2 different views for the two different forms. Didn't work. I tried using one function per form in my controller. That didn't work either. I don't know what to do.
Should I be doing this;
Create a controller and have an index() function in it.
Build up my form elements for each form within this index()
Create 1 view that displays both forms and call it from within index()
Use form_open to direct the submit action to another function - call it validate()
Validate everything that comes in, send back errors
Somehow, and this is the main bit I don't get, complete an action if the form has been filled in correctly.
6 Is my biggest problem. Don't know how to do that. For example, on successful completion of the form I want my user to have created a directory at a chosen location - so I'm using mkdir() - so do I need an if statement within the validate() function or what??
UPDATE
Here is the code I have created so far;
Controller:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
// Forms CodeIgniter controller
class Admin extends CI_Controller {
// Controller constructor
public function __construct()
{
parent::__construct();
// Load form helper required to validate forms
$this->load->helper(array('form', 'url'));
$this->load->library('form_validation');
}
//*************************************************//
// Prepare data for the view to output the forms
public function index()
{
//*****************************************************//
//returns a drop down list of radio buttons, one for each directory
$map_one = $this->recursive_model->iterate_add_folder_names();
$data['folder_list_add'] = $map_one;
//****************************************************//
//*****************************************************//
//also returns a drop down list of radio buttons (slightly different), one for each directory
$map_two = $this->recursive_model->iterate_folder_names();
$data['folder_list_select'] = $map_two;
//****************************************************//
//load the views and the forms
$this->load->view('templates/header.php');
$this->load->view('admin/add_new_folder.php', $data);
$this->load->view('admin/add_new_file.php', $data);
$this->load->view('templates/small_footer.php');
}
//*************************************************//
//function if adding a new directory to the current structure
public function add_folder()
{
//need to select a directory for it to go under
$this->form_validation->set_rules('new_folder', 'New Folder', 'required');
//and name the new directory
$this->form_validation->set_rules('new_folder_name', 'New Folder Name', 'required');
if ($this->form_validation->run() === FALSE)
{
$this->index();
}
else
{
if($this->input->post())
{
$new_folder = $this->input->post('new_folder');
$new_folder_name = $this->input->post('new_folder_name');
$folder_path = "/var/www/html/mike/content".$new_folder."/".$new_folder_name;
mkdir($folder_path, 0777);
$this->index();
}
}
}
//*************************************************//
public function add_file()
{
//folder location and name of file
$folder_name = $this->input->post('folder_name');
$new_folder_name = $this->input->post('file_name');
//validation rules
$this->form_validation->set_rules('folder_name', 'Folder Name', 'required');
$this->form_validation->set_rules('file_name', 'File Name', 'required');
//if there is an error with validation
if ($this->form_validation->run() === FALSE)
{
//gets stuck here every time when trying to upload a new folder :(
$this->index();
}
//if there is not an error with validation
else
{
//$folder_name will be something like "http://www.example.com/publications/people/reports"
$config['upload_path'] = $folder_name;
$config['allowed_types'] = 'gif|jpg|png|html|pdf|xls';
$this->load->library('upload', $config);
//if file cannot be loaded (due to $config perhaps?)
if ( ! $this->upload->do_upload())
{
$error = array('error' => $this->upload->display_errors());
$this->index();
}
else
{
$data = array('upload_data' => $this->upload->data());
$this->index();
}
}
}
//*************************************************//
}
Here is one view (add_new_file.php);
<div id="container">
<h1>Upload A File/Publication</h1>
<div id="body">
<?php //echo $error;?>
<?php echo form_open_multipart('admin/add_file');?>
<?php echo $folder_list_select; ?>
<input type="file" name="file_name" size="20" />
<input type="submit" value="upload" />
</form>
</div>
Here is the other (add_new_folder.php)
div id="container">
<h1>Add A New Folder</h1>
<div id="body">
<?php echo validation_errors(); ?>
<?php echo form_open('admin/add_folder');?>
<?php echo $folder_list_add; ?>
New Folder Name: <input type="text" name="new_folder_name">
<input type="submit" value="upload" />
</form>
</div>
I hope this helps answer this thread.
Basically, I can get the first section to work - adding a folder - but I cannot get the adding a file to work. This is because if ($this->form_validation->run() === FALSE) is always returning false. I think it might be looking at the form elements in the other form - which it shouldn't do. What am I missing?
Should I be doing this;
1 . Create a controller and have an index() function in it.
[let's, for the sake of conversation, call this controller Users thx -ed]
Sure. That's cool. You could also have a function in that Controller called edit, or banana or whatever; either way works. With using just the index method (function), the url might look like http://example.com/index.php/users whereas if you add another method to the controller like banana, the url might look like http://example.com/index.php/users/banana.
2 . Build up my form elements for each form within this index()
Well, typically form elements are not created in the controllers. This is where the V in MVC comes in -- stuff you view goes into a view.
So, one might do something like
// Users Controller
class Users extends CI_Controller{
function index(){
//index method
}
function banana(){
$this->load->view('banana_view');
}
}
then in application/views/banana_view.php, you create your form. When you visit http://example.com/users/banana, you will see the form you created in banana_view.php.
3 . Create 1 view that displays both forms and call it from within index()
Sure, that'd work just fine. But remember that each <form></form> needs its own <input type="submit" name="Lets GO"> inside and thusly needs somewhere to send each forms data. This is the action="". You can leave it out, but beware that it will then send the form to whatever page you are currently on (in our case here, http://example.com/index.php/users/banana), so you have to have something in the banana() method to handle the form data. But, typically, it will be set via form_open(). Something like form_open('index.php/users/eat_banana'); will generate <form action="index.php/users/eat_banana"...
4 . Use form_open to direct the submit action to another function - call it validate()
Just don't call it late_for_dinner. But seriously, validate is a bit broad -- validate what? Validate why? As to validation, https://www.codeigniter.com/user_guide/libraries/form_validation.html. But you should cross that bridge after you grok the fundamentals of CodeIgniter (won't take long).
5 . Validate everything that comes in, send back errors
See last question.
6 . Somehow, and this is the main bit I don't get, complete an action if the form has been filled in correctly.
Many times people will display a success message
class Users extends CI_Controller{
function index(){
//index method
}
function banana(){
$this->load->view('banana_view');
}
// assuming form_open('index.php/users/eat_banana'); in banana_view
function eat_banana(){
//make sure that this is a POST
if($this->input->post()){
// do things with the data
// typically it gets saved to a database
// via a model (the M in MVC)
// http://ellislab.com/codeigniter/user-guide/general/models.html
if($saved_to_db){
// set message to send to the view
$data['message'] = "Everything went OK";
}else{
$data['message'] = "but who was database? data didn't save :(";
}
// load the view and send the data
$this->load->view('eat_banana', $data);
}
}
application/views/eat_banana.php:
<!DOCTYPE html>
<html>
<head></head>
<body>
<div>
<b>Form submitted.</b><br />
The message is: <?php echo $message; ?>
</div>
</html>
other times, one might instead prefer to redirect
class Users extends CI_Controller{
function index(){
//index method
}
function banana(){
$this->load->view('banana_view');
}
// assuming form_open('index.php/users/eat_banana'); in banana_view
function eat_banana(){
//make sure that this is a POST
if($this->input->post()){
// do things with the data
if($saved_to_db){
// just send them to the homepage
redirect('/');
}else{
// send them back to the form
redirect('index.php/users/banana');
}
}
}
So,
M is for model. Models are used to talk to the database.
V is for Vend view. Views render the text, forms, pictures, gifs, whatever to the screen. That's the idea anyway. There's nothing stopping you from echo'ing out an enormous unminimized javascript application from your controller. That would totally not be MVC tho.
C is for controller. Controllers call and send data to the views, receive data sent from views, take that data and send it to a model to be saved in the database (although CodeIgniter doesn't enforce this in any way either; you could if you wanted to save the data to a database directly from the controller, but this obviously defeats the MVC principal as well), retrieves data from the database and sends it to a view for display. Those are the basics anyway.
Related
I am looking for auto-complete to work by showing name in auto-complete text-field and storing the hidden field id value.
I am getting the names and id's when I inspect the networks..but able to show the names but its not picking the id for the record so unable to store the id
can anyone please give me any link/code which is working for auto-complete. is there any link/code which is working please..
class for auto-complete:::
class EAutoCompleteAction extends CAction{
public $model;
public $attribute;
public $id;
private $results = array();
public $returnVal = '';
public function run()
{
if(isset($this->model) && isset($this->attribute)) {
$criteria = new CDbCriteria();
$criteria->compare($this->attribute, $_GET['term'], true);
$model = new $this->model;
foreach($model->findAll($criteria) as $m)
{
$this->results[] = $m->{$this->attribute};
$this->results[] = $m->{$this->id};
//$this->results[] = array(
// 'name' => $m->{$this->attribute},
// 'id'=> $m->id
//);
}
}
echo CJSON::encode($this->results);
}
}
I am using controller/action like this::
public function actions()
{
return array(
'aclist'=>array(
'class'=>'application.extensions.EAutoCompleteAction',
'model'=>'Organisation', //My model's class name
'attribute'=>'name', //The attribute of the model i will search
)
}
and in my view form.php.
<div class="row">
<?php echo $form->labelEx($model,'organsiation'); ?>
<?php echo $form->hiddenField($model,'organisation_id',array()); ?>
<?php
$this->widget('zii.widgets.jui.CJuiAutoComplete', array(
'attribute'=>'organisation_id',
'model'=>$model,
'sourceUrl'=>array('benefit/aclist'),
'value'=>'Please select',
'name'=>'name',
'id'=>'organisation_id',
'options'=>array(
'minLength'=>'2',
'select'=>"js:function(event, ui) {
alert(ui.item.id);
$('#organisation_id').val(ui.item.id);
}",
),
'htmlOptions'=>array(
'size'=>45,
'maxlength'=>45,
),
)); ?>
<?php echo $form->error($model,'organisation_id'); ?>
Here my code for autocomplete
Create an action that return json
public function listaItemMarcaAction(){
$cmd = Yii::app()->db->createCommand();
$cmd->select('id, nombre as value, nombre as label, pais_origen');
$cmd->from('item_marca');
$cmd->where('nombre like :term', array(':term'=>'%'.request()->getParam('term').'%'));
$data = $cmd->queryAll();
header('Content-type: application/json');
echo CJavaScript::jsonEncode($data);
Yii::app()->end();
}
Create autocomplete field and hidden field (in view files, example _form.php)
<?php echo $form->labelEx($model,'marca_id'); ?>
<?php echo $form->hiddenField($model,'marca_id'); ?>
<?php $this->widget('zii.widgets.jui.CJuiAutoComplete', array(
'name'=>"Item[marca]",
'value'=>$model->isNewRecord ? null : $model->marca->nombre,
'sourceUrl'=>Yii::app()->createUrl('/item/listaitemmarca'),
'options'=>array(
'minLength'=>1,
'change'=>'js:function(event,ui){fn_item_data(event,ui)}'
),
)); ?>
Create a javascript function to set values retreives by autocomplete. Nota that: only use change event, no need any more. In the example, the ActiveRecord is 'Item', then, the input's id wil be Item_marca_id and Item_marca.
function fn_item_data(event,ui){
if(!ui.item){
$("#Item_marca_id").val("");
$("#Item_marca").val("");
}else{
$("#Item_marca_id").val(ui.item.id);
$("#Item_marca").val(ui.item.value);
//and use ui.item.pais_origen for another things
if(ui.item.pais_origen == 'EEUU') alert('ok');
}
}
In your situation I would start with plain JQuery instead of CJuiAutoComplete on the client side. JQuery UI docs have a nice demo with working source code at http://jqueryui.com/autocomplete/#custom-data. There are three essential steps to get things working:
Supply data as array of JSON objects, not strings.
Use custom _renderItem function to render your JSON objects into readable strings.
Use custom select function to record visible name of selected JSON object into text field and record id of this object into hidden field.
To make step 1 you'll need to uncomment commented out part of EAutoCompleteAction, and remove two lines above it. After that you should be able to see item id and name in your alert() messages.
Step 2 (overriding _renderItem) is particularly tricky to do with CJuiAutoComplete, that is the reason I suggested to go with plain JQuery UI. See the above link for the example with plain JQuery UI. An example with CJuiAutoComplete can be found in the comments section of Yii docs: http://www.yiiframework.com/doc/api/1.1/CJuiAutoComplete#c8376. After step 2 you should be able to see readable autocomplete suggestions.
To make step 3 you'll need to add something like
$('#organisation_name').val( ui.item.name );
into your select function, provided that organisation_name is the id of your text field and organisation_id is the id of hidden form field (you'll need some changes to make it so).
I've just (as in today) started working on some ZF stuff.
I have a Form that needs to have some text in a div appear at the top of the form, but I have no idea how to include it.
The structure of the form is:
class MyForm extends \app\forms\FormType {
public function init() {
// gets all the form elements of the parent
parent::init();
// A few additional form elements for MyForm created here
}
}
Any help would be apprecaited!
In your controller where you instantiate the form object just set it with the view object like this:
public function actionNameAction()
{
// ...
if (/* some condition to check form page */) {
$this->view->divText = 'your text';
}
}
Then put the div in the action-name.phtml script:
views/scripts/controller/action-name.phtml
Contents:
<?php if (!empty($this->divText)): ?>
<div><?php echo $this->divText; ?></div>
<?php endif; ?>
Additionally, you could pass the view object by reference to your form class. Just overload the construct function like so:
public function __construct($options = null, &$view)
{
parent::__construct($options);
$this->view = $view;
}
Then in your controller when you instantiate your form object do this:
$form = new MyForm(null, $this->view);
Let's go back to your form class once again and modify the init() method:
public function init()
{
// ...
$this->view->divText = 'Text set from within ' . __CLASS__;
}
Using this way, you won't have to put any conditional if statements checking anything in the controller. You're already checking if $this->divText is not empty in the view, so by passing the view object to your form class you can ensure that that text will only be set when the form is being used.
I have a database table worksheet(qID varchar(5), answer varchar(50), wsheetid varchar(5)) with qID as the primary key. One worksheet has many questions.
I want to fetch all the questions where wsheetid =1 and display them as a form so that user can enter the answers.
And i want to check the user entered ans with the answer column in the database.
How can i do that in Yii.
Tried google and the guide to yii, couldnt find any solution. Any suggestion would be helpful.
Update:
I have the below view where i am am needed to get the attributes into a array and display the form. Is there a better way to do this? and for activeTextArea since model has the data, the text area contains data from database but i need a blank text area.
<div class="form">
<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'wdetails-form',
'enableAjaxValidation'=>false,
)); ?>
<?php echo CHtml::beginForm(); ?>
<?php foreach($questions as $i=>$questions): ?>
<?php $array = $questions->getAttributes();
echo CHtml::activeLabel($questions,"[$i]question",array('label'=>"$array[question]"));
echo CHtml::activeTextArea($questions,"[$i]answer",array('id'=>"$array[question_ID]"));
endforeach;
?>
</br>
<?php echo CHtml::submitButton('Submit'); ?>
<?php echo CHtml::endForm(); ?>
<?php $this->endWidget(); ?>
</div>
and in the controller, i need to insert the data that i got from the above form and insert into a different table(worksheetResults). DO i need to get the data into an array and use Yii DAO or is there any better way to do this.
table worksheetResults (username, worksheetID,question_ID,submitted_ans)
I think you should have a Worksheet model, a Worksheet controller and a Worksheet view.
Here is a skeleton for the controller part:
class WorksheetController extends CController
{
public function actionQuestions()
{
$criteria = new CDbCriteria;
$criteria->addCondition('(wsheetid = :id)');
$criteria->params[':id'] = '1';
$questions = Worksheet::model()->findAll($criteria);
$this->render('questions_form', array('questions'=>$questions));
}
public function actionAnswers()
{
//check the contents of $_POST['Worksheet']
}
}
Did you use Gii to create the scaffolding? That would get you the model and CRUD files and you can then customize the look and functionality. If your data model is good, this will save you a lot of time, even if you end up redoing the form view, you can still use Yii to create and maintain the model and controller classes.
See: http://www.yiiframework.com/doc/guide/1.1/en/topics.gii
I'm teaching myself Zend am and having a problem with using my session to call a View Helper action.
My controller:
<?php
class SessionController extends Zend_Controller_Action
{
protected $session;
public function init() //Like a constructor
{
$this->_helper->viewRenderer->setNoRender(); // Will not automatically go to views/Session
$this->_helper->getHelper('layout')->disableLayout(); // Will not load the layout
}
public function preDispatch() //Invokes code before rendering. Good for sessions/cookies etc.
{
$this->session = new Zend_Session_Namespace(); //Create session
if(!$this->session->__isset('view'))
{
$this->session->view = $this->view; //if the session doesn't exist, make it's view default
}
}
public function printthingAction()
{
echo $this->session->view->tabbedbox($this->getRequest()->getParam('textme'));
}
}
?>
My view helper
<?php
class App_View_Helper_Tabbedbox extends Zend_View_Helper_Abstract
{
public $wordsauce = "";
public function tabbedbox($message = "")
{
$this->wordsauce .= $message;
return '<p>' . $this->wordsauce . "</p>";
}
}
?>
My view:
<p>I GOT TO THE INDEX VIEW</p>
<input id='textme' type='input'/>
<input id='theButton' type='submit'/>
<div id="putstuffin"></div>
<script type="text/javascript">
$(function()
{
$("#theButton").click(function()
{
$.post(
"session/printthing",
{'textme' : $("#textme").val()},
function(response)
{
$("#putstuffin").append(response);
});
});
});
</script>
The first time I click on theButton, it works, and appends my word like it's supposed to. For every time after, though, it gives me this error message:
Warning: call_user_func_array() [function.call-user-func-array]: First argument is expected to be a valid callback, '__PHP_Incomplete_Class::tabbedbox' was given in C:\xampp\htdocs\BC\library\Zend\View\Abstract.php on line 341
I copied the Zendcasts.com video almost line for line, and it's still not working. It seems like my session is getting destroyed or something. I would be forever grateful to anyone who could tell me what's happening.
When you store an object in the session, you're really storing a serialized representation of it. The __PHP_Incomplete_Class::tabbedbox occurs because, on subsequent requests, PHP has forgotten what an App_View_Helper_Tabbedbox is.
The solution: make sure you include the App_View_Helper_Tabbedbox class file before Zend_Session::start() is called.
And, the best way to do that is to place this at the opening of your app:
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
Imagine I have 4 database tables, and an interface that presents forms for the management of the data in each of these tables on a single webpage (using the accordion design pattern to show only one form at a time). Each form is displayed with a list of rows in the table, allowing the user to insert a new row or select a row to edit or delete. AJAX is then used to send the request to the server.
A different set of forms must be displayed to different users, based on the application ACL.
My question is: In terms of controllers, actions, views, and layouts, what is the best architecture for this interface?
For example, so far I have a controller with add, edit and delete actions for each table. There is an indexAction for each, but it's an empty function. I've also extended Zend_Form for each table. To display the forms, I then in the IndexController pass the Forms to it's view, and echo each form. Javascript then takes care of populating the form and sending requests to the appropraite add/edit/delete action of the appropriate controller. This however doesn't allow for ACL to control the display or not of Forms to different users.
Would it be better to have the indexAction instantiate the form, and then use something like $this->render(); to render each view within the view of the indexAction of the IndexController? Would ACL then prevent certain views from being rendered?
Cheers.
There are a couple of places you could run your checks against your ACL:
Where you have your loop (or hardcoded block) to load each form.
In the constructor of each of the Form Objects, perhaps throwing a custom exception, which can be caught and appropriately handled.
From the constructor of an extension of Zend_Form from which all your custom Form objects are extended (probably the best method, as it helps reduce code duplication).
Keep in mind, that if you are using ZF to perform an AJAXy solution for your updating, your controller needs to run the ACL check in it's init() method as well, preventing unauthorized changes to your DB.
Hope that helps.
Have you solved this one yet?
I'm building a big database app with lots of nested sub-controllers as panels on a dashboard shown on the parent controller.
Simplified source code is below: comes from my parentController->indexAction()
$dashboardControllers = $this->_helper->model( 'User' )->getVisibleControllers();
foreach (array_reverse($dashboardControllers) as $controllerName) // lifo stack so put them on last first
{
if ($controllerName == 'header') continue; // always added last
// if you are wondering why a panel doesn't appear here even though the indexAction is called: it is probably because the panel is redirecting (eg if access denied). The view doesn't render on a redirect / forward
$this->_helper->actionStack( 'index', $this->parentControllerName . '_' . $controllerName );
}
$this->_helper->actionStack( 'index', $this->parentControllerName . '_header' );
If you have a better solution I'd be keen to hear it.
For my next trick I need to figure out how to display these in one, two or three columns depending on a user preference setting
I use a modified version of what's in the "Zend Framework in Action" book from Manning Press (available as PDF download if you need it now). I think you can just download the accompanying code from the book's site. You want to look at the Chapter 7 code.
Overview:
The controller is the resource, and the action is the privilege.
Put your allows & denys in the controller's init method.
I'm also using a customized version of their Controller_Action_Helper_Acl.
Every controller has a public static getAcls method:
public static function getAcls($actionName)
{
$acls = array();
$acls['roles'] = array('guest');
$acls['privileges'] = array('index','list','view');
return $acls;
}
This lets other controllers ask about this controller's permissions.
Every controller init method calls $this->_initAcls(), which is defined in my own base controller:
public function init()
{
parent::init(); // sets up ACLs
}
The parent looks like this:
public function init()
{
$this->_initAcls(); // init access control lists.
}
protected function _initAcls()
{
$to_call = array(get_class($this), 'getAcls');
$acls = call_user_func($to_call, $this->getRequest()->getActionName());
// i.e. PageController::getAcls($this->getRequest()->getActionName());
if(isset($acls['roles']) && is_array($acls['roles']))
{
if(count($acls['roles'])==0) { $acls['roles'] = null; }
if(count($acls['privileges'])==0){ $acls['privileges'] = null; }
$this->_helper->acl->allow($acls['roles'], $acls['privileges']);
}
}
Then I just have a function called:
aclink($link_text, $link_url, $module, $resource, $privilege);
It calls {$resource}Controller::getAcls() and does permission checks against them.
If they have permission, it returns the link, otherwise it returns ''.
function aclink($link_text, $link_url, $module, $resource, $privilege)
{
$auth = Zend_Auth::getInstance();
$acl = new Acl(); //wrapper for Zend_Acl
if(!$acl->has($resource))
{
$acl->add(new Zend_Acl_Resource($resource));
}
require_once ROOT.'/application/'.$module.'/controllers/'.ucwords($resource).'Controller.php';
$to_call = array(ucwords($resource).'Controller', 'getAcls');
$acls = call_user_func($to_call, $privilege);
if(isset($acls['roles']) && is_array($acls['roles']))
{
if(count($acls['roles'])==0) { $acls['roles'] = null; }
if(count($acls['privileges'])==0){ $acls['privileges'] = null; }
$acl->allow($acls['roles'], $resource, $acls['privileges']);
}
$result = $acl->isAllowed($auth, $resource, $privilege);
if($result)
{
return ''.$link_text.'';
}
else
{
return '';
}
}