Hi I'm working on a CRUD interface to update an Event.
Event Class Model a Event belongs to many Organizer
class Event extends Model
{
public function organizers()
{
return $this->belongsToMany('App\Organizer')->withTimestamps();
}
}
Organizer Class Model
class Organizer extends Model
{
public function event()
{
return $this->belongsToMany('App\Event')->withTimestamps();
}
}
Event controller I send the event along with its organizer(s)
public function edit($id)
{
$event = Event::with(['organizers'])->findOrFail($id);
return view('event.edit', ['event' => $event]);
}
event.edit view
<? $organizers = DB::table('organizers')->lists('name', 'id'); ?>
{!! Form::model($event, [
'method' => 'PATCH',
'route' => ['eventUpdate', $event->id]
]) !!}
<div id="organizer_id-group" class="form-group">
{{Form::select('organizer[id]', $organizers, null,['class'=>'form-control'])}}
</div>
{!! Form::submit('Update', ['class' => 'btn btn-primary']) !!}
{!! Form::close() !!}
For the moment I see the whole list of organizers which is good but the default value, or the selected one does not correspond to the one linked to the event.
I tried
<div id="organizer_id-group" class="form-group">
#foreach($event->organizers as $key =>$organizer)
{{Form::select($organizer->id, $organizers, null,['class'=>'form-control'])}}
#endforeach
</div>
but the selected organizer is always the first even if $organizer->id = 2
Any help would be welcome. Thank you
When working with Form::model it could be easier to work with the name of the property in the database.
If you have
{{Form::select('organizer[id]', $organizers, null,['class'=>'form-control'])}}
Then the value of the form will be set to $event->organizer->id if it exists.
Also from your use case it looks like an organizer belongsTo an event. You do not show the organizer model, but inside the event model you define a many-to-many relationship. This implies that you have $event->organizers.
From the information available you might want to change the relationship to
return $this->belongsTo('App\Organizer');
Amendment / Update (after additional information)
It is clear now, you are indeed working with a many-to-many collection. I have not managed to get Form model binding to work with belongsToMany collections so you need to set the 3rd parameter to physically define the organizers that need to be selected.
The most elegant way I know about is to define an accessor on your Event object for the event organizer ids. This way you can use a call to old for a validation error and then define a default as well which are the values for the initial edit.
Notice that in the form we are using an array for the multiple values.
{{Form::select('organizers[]', $organizers, old('organizers', $event->organizers_ids), ['class' => 'form-control', 'multiple'])}}
Then inside your Model you add the accessor to return the ids.
public function getOrganizersIdsAttribute()
{
return $this->organizers->lists('id')->all();
}
Answer# Update after Leon suggestions
This is what I needed to do to make it works
<div id="organizer_id-group" class="form-group">
{{Form::select('organizers[]', $organizers, $event->organizers->lists('id')->all(),['class'=>'form-control', 'multiple'])}}
<span class="help-block"></span>
</div>
To make a selection by default you have to change the third argument (https://laravelcollective.com/docs/5.0/html#opening-a-form)
Related
I have two tables (City and Area). The city has many areas. So I have successfully added and deleted the data. But I am facing multiple errors while editing and updating. I have just started learning Laravel. I used a resource controller but encountered a problem with declaring routes, so I tried the name route also.
This error I have encountered: Trying to get property 'id' of non-object
and indicates the form's route ID. which is $editArea->id. Here is my code.
web.php route:
Route::resource('area','Admin\AreaController');
Route::get('area/edit/{id}','Admin\AreaController#edit')->name('area.edit');
Route::post('area/update/{id}','Admin\AreaController#update')->name('area.update');
edit button: Edit
AreaController:
public function edit(Area $area, $id)
{
$editArea= Area::find($id);
$cityno= City::orderBy('city_name', 'asc')->get();
return view('admin.editArea', compact('editArea','cityno'));
}
public function update(Request $request, Area $area, $id)
{
$editArea=Area::find($id);
$editArea->city_id=$request->city_id;
$editArea->area_name=$request->area_name;
$editArea->save();
}
edit form:
<form method="POST" action="{{route('area.update', $editArea->id)}}">
#csrf
#method('put')
<select name="city_id">
#foreach($cityno as $row)
<option value="{{$row->id}}" >{{ucwords($row->city_name)}}</option>
#endforeach
</select>
<input type="text" name="area_name" value="{{$editArea->area_name}}">
<button type="submit">Update Area</button>
</form>
Area model:
public function city(){
return $this->belongsTo(City::class,'city_id');}
city model:
public function area(){
return $this->hasMany(Area::class); }
As I'm a new learner, I'm stuck in these files to find my wrong code.
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.
Why does the Edit screen show the value I'm editing but when I try to save, the Edit HTTPPOST has a null object?
Getting a an error I've tracked down but don't see the cause of. I have a class used for a drop list, so it only has AdvisoryTypeID and AdvisoryType. But now I'm creating screens for users to see all in a grid (Index), add new (Create), and change existing ones (Edit). The index screen reads from the DB no problem. So does Edit. Create doesn't need to.
But both Edit and Create give the same error on HTTPPOST. The error is "The Value 'whatever I typed in' is invalid". Debugging the code turned up why: the "advisoryTypes" variable is null for reasons I don't understand:
CONTROLLER (for Edit post)
[Authorize]
[HttpPost]
public ActionResult Edit(AdvisoryTypes advisoryType)
{
try
{
if (ModelState.IsValid) //never get past this because it's null above
etc....
}
I have compared this to another screen that does Edit and Create fine and they're identical (except field names, for example). I'm stumped.
MODEL
public class AdvisoryTypes
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AdvisoryTypeID { get; set; }
[Display(Name = "Type")]
[Required]
public string AdvisoryType { get; set; }
}
CONTROLLER (for Edit Get)
[Authorize]
public ActionResult Edit(int id = 0)
{
AdvisoryTypes advisoryType = db.AdvisoryType.Find(id);
if (advisoryType == null)
{
return HttpNotFound();
}
return View(advisoryType);
}
VIEW
model TheNewSolution.Models.AdvisoryTypes
#{
ViewBag.Title = "Edit Advisory Type";
}
<h2>Edit Advisory Type</h2>
#*<br />*#
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Advisory</legend>
<table>
<tr>
<td>
#Html.HiddenFor(model => model.AdvisoryTypeID)
<div class="editor-label">
#Html.LabelFor(model => model.AdvisoryType)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AdvisoryType)
#Html.ValidationMessageFor(model => model.AdvisoryType)
</div>
</td>
</tr>
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
PART TWO
Now some odd background: this is the first model/controller/view I created since updating EF from 5 to 6 alpha3 (had to because I had POCO and edmx in same project and this solved that bug). I created the model manually. When I tried to create the Controller via the wizard, the wizard gave an error "Unable to retrieve metadata for ProjectName.Models.AdvisoryProviders'. Unable to cast obect of type 'System.Data.Entity.Core.Objects.ObjectContext' to type 'System.Data.Objects.ObjectContext'.".
The result was having to manually create the controller by copying/pasting another one and making changes, then creating my views the same way. I'm suspicious this is all related but maybe not. If this proves unrelated I'll remove this and post as another question.
the error from EF of "Unable to retrieve metadata for ProjectName.Models.AdvisoryProviders i have seen before, but this has only been because i was using MySQL, the only way i found around this kind of error and to make sure everything worked was to use http://visualstudiogallery.msdn.microsoft.com/72a60b14-1581-4b9b-89f2-846072eff19d to create models from the database, and then use http://blog.stevensanderson.com/2011/01/13/scaffold-your-aspnet-mvc-3-project-with-the-mvcscaffolding-package/ to create the controllers, with view, rather than the buggy version of create view for EF.
I posted about these problems a while back with EF5 and its a real pain, MVCScaffolding seems to handle that pain alot better than the built in TT templates with MVC 4
hope this helps
I am not sure why this resolved, but here's what I did. I needed to create the screens mentioned above (index with grid, create, edit) for three different things (types, providers, categories) in my app. I did the first, type, resulting in the above issues.
I decided to create the same for "providers" by copying and pasting the controllers and views from "type" screens, then changing the model and field names as needed, expecting the screen to have the same bugs. But the screens all worked. I did it again for "categories", which also worked. Then I deleted my failing Create and Edit screens for "type", and recreated them from the "providers" screens. And they worked.
I have no explanation.
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 have the following action method in my Tournament controller:
public ActionResult Edit(int id) {
EditTournamentViewModel viewModel = new EditTournamentViewModel {
Tournament = _tournamentService.GetTournamentByID(id),
TournamentDivisions = _divisionService.GetTournamentDivisions(id).ToList()
};
ViewBag.SportID = new SelectList(_sportService.GetSports(), "SportID", "Name");
ViewBag.CountryID = new SelectList(_countryService.GetCountries(), "CountryID", "Name");
return View(viewModel);
}
The two viewbag items are select lists which will be filled up. When I inspect the page with firebug the name of the SelectList for ViewBag.SportID is SportID, which is what I would expect. But I want the value that is selected there to be entered in the SportID property of the Tournament property in my viewModel. So the name should be Tournament.SportID.
I don't understand how I can achieve this, I wasn't able to change the SelectList's name in Razor like this:
<div class="editor-field">
#Html.DropDownList("SportID", "Select a sport", new { name = "Tournament.SportID" } )
#Html.ValidationMessageFor(t => t.Tournament.Sport)
</div>
This is the code I have in my view:
<div class="editor-field">
#Html.DropDownList("SportID", "Select a sport")
#Html.ValidationMessageFor(t => t.Tournament.Sport)
</div>
A solution I could take is instead of having a Tournament property in my viewmodel, I could copy over the individual properties and then use automapper but I would like to hear if there is a fix without having to do that (unless of course what I am doing is considered very bad practice).
Did you try
#Html.DropDownListFor(m => m.Tournament.SportID, ViewBag.SportID, "Select a sport")
?
(if you need the acual value of Tournament.SportId, for an update, for example, you can just look at the different constructors of SelectList and change it in your "ViewBag creation")
CORRECTION :
SelectList sportList = ViewBag.SportID;
#Html.DropDownListFor(m => m.Tournament.SportID, sportList, "Select a sport")