In Laravel I have a resource controller in which I need to handle picture uploads. In my create.blade.php I have the following form:
{{ Form::open(array('action' => 'PhotoController#store', 'method' => 'post')); }}
{{Form::text('text'); }}
{{Form::file('photo');}}
{{Form::submit('Submit');}}
{{ Form::close() }}
In my PhotoController I have the following store function
public function store()
{
if (Input::hasFile('photo'))
{
return 'Got the photo';
}
else { return Input::get('text'); }
}
The input from the field 'text' is returned, however it does not seem like Laravel can find the photo I upload.
I tried to change the method to put and create a custom function to handle this but with the same result. How come I cannot make Laravel recognize that I've attached a file?
Thanks in advance
You need to add 'files' => true and 'enctype' =>"multipart/form-data" to your form like so
{{ Form::open(array('action' => 'PhotoController#store', 'method' => 'post', 'files' => true, 'enctype' =>"multipart/form-data")); }}
In regard to the POST and PUT options it depends on what you are doing. As you are using the store resource method, you should use POST. If you are going to use the update method then you would use PUT.
For resource controllers the methods and HTTP requests work like this
+-------------------+-------------+
| Controller method | HTTP Method |
+-------------------+-------------+
| index | GET |
| create | GET |
| store | POST |
| show | GET |
| edit | GET |
| update | PUT / PATCH |
| destroy | DELETE |
+-------------------+-------------+
The confusing ones are create and edit which you may think should be POST but remember this are the pages that 'get' the forms in order to edit or create an item.
Once you have your file you will need to do something with it. Therefore, next you are going to want to do something with the file, so you will need
$photo = Input::file('photo');
$photo->move($destinationPath, $fileName);
Documentation on these and all the other Input::file() methods can be found in the Laravel documentation here.
{{ Form::open(array('route' => 'images.store', 'role' => 'form', 'files' => true)) }}
This is what I use for my file upload form, I don't have 'enctype' or 'method' and I point to a route instead of an action.
This is what it translates to in HTML:
<form method="POST" action="action url" accept-charset="UTF-8" role="form" enctype="multipart/form-data"><input name="_token" type="hidden" value="token value">
As you can see the 'enctype' and 'method' is added by the Form::open helper function. The role="form" is for Bootstrap to format the form correctly.
About HTML and forms: http://laravel.com/docs/4.2/html
Related
Good day. I'm using Vue.js to render an arbitrary number of select elements from the data in a component.
Here's sample JSON data that indicates there are two select elements, each with one or more options.
{
"dropdowns":[
{
"cd":"UG9ydGZvbGlv",
"formname":"sp_filter_UG9ydGZvbGlv",
"nm":"Portfolio",
"selected":"1a",
"options":[
{
"cd":"1a",
"val":"Option 1A"
}
]
},
{
"cd":"UHJvZHVjdCBOYW1l",
"formname":"sp_filter_UHJvZHVjdCBOYW1l",
"nm":"Product Name",
"selected":"2b",
"options":[
{
"cd":"2a",
"val":"Option 2A"
},
{
"cd":"2b",
"val":"Option 2B"
}
]
}
]
}
Here's the template HTML:
<form>
<div v-for="dropdown in dropdowns">
<div v-if="dropdown.availableToView">
<h4>{{dropdown.nm}}</h4>
<select v-model="dropdown.selected" v-on:change="triggerUpdate">
<option value="">(Make a selection)</option>
<option v-for="option in dropdown.options" :value="option.cd">{{option.val}}</option>
</select>
</div>
</div>
</form>
So far so good.
I've got the data loading and Vue is building the dropdowns.
When the user changes any select box (remember there can be an arbitrary number of them), the trigger action needs to submit ALL of the elements in the form via ajax. It sounds like the most correct option is to bind the form fields to the underlying component data, as I've done.
My triggerUpdate looks like this:
methods: {
triggerUpdate: function() {
axios({
method: "post",
url: actionURL,
data: this.dropdowns
})
.then(response => (this.data = response));
}
}
...but this submits the entire dropdowns data element, including all of the options in each select box. It's unnecessary to send all of the options in. I just want to send each field name along with its selected option (i.e. the "value").
I know i could serialize the whole form and make that my ajax payload. But that seems to be making an "end run" around Vue.js. Everyone talks about having your form fields bound to the Vue model...is it then correct to basically ignore the model when making an ajax request whose purpose is to then update the model?
I'm relatively new to Vue.js so I'd appreciate help with what I'm overlooking here. How should I go about sending in the data from the form (a) while using proper Vue.js binding and (b) without sending extraneous data?
Thanks for your time.
If you need to post only the selected values, and you store those in each dropdown's selected property, the sensible approach seems to be just mapping it to a simple array of name/value objects.
Try this (it assumes the name of each field is the formname property, if it isn't you can just replace it):
var submitData = this.dropdowns.map((dropdown) => {
return { name: dropdown.formname, value: dropdown.selected };
});
Then you send submitData in your ajax request.
Hi I'm trying to get the last four blog posts from contentful api in angular 5. I've created a service and I'm capable of retrieving 1 with the following code.
getContent(contentId) {
const promise = this.client.getEntry(contentId);
return Observable.fromPromise(promise).map(entry => entry.fields);
};
I would like to return it as an observable, however if returned as a promise, I would like to know how to work with a promise in the component.ts and -.html.
getLastByCount(number) {
var promise = this.client.getEntries({
limit: number
})
...
}
If i do the same for getting multiple entries I get a 'PromiseObservable', which contains a 'promise: ZoneAwarePromise'. In which it has 'items: Array', where object are as when i log the single entry. How do I work which such objects?
Edited:
I've done as suggested by: Stephan
getLastByCount(number) {
var promise = this.client.getEntries({
limit: number
})
return Observable.fromPromise(promise).mergeMap((collection) => (
Observable.from(collection.items)
))
}
And in my component.ts in OnInit()
posts$: Observable<any>;
this.posts$ = this.contentfulService.getLastByCount(4).map(entry => entry.fields);
In my component.html, i do this when displaying the one entry
<div *ngIf="post$ | async as post">
<h1>{{ post.headline }}</h1>
<time>Published on {{ post.published | date: 'fullDate' }}</time>
<hr>-->
</div>
I try this when using the collection:
<div *ngIf="posts$ | async as posts">
<ul>
<li class="post" *ngFor="let post of posts$ | async">
<h1>{{ post.headline }}</h1>
<time>Published on {{ post.published | date: 'fullDate' }}</time>
<hr>
</li>
</ul>
</div>
I get this error: Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
What you probably want is an Observable that emits every single item of the collection that the Contentful client returns.
To do so, you can use the mergeMap functionality:
Observable
.fromPromise(promise)
.mergeMap((collection) => (
Rx.Observable.from(collection.items)
))
Now you can work with every item in the observable sequence as with the single entry above, e.g. you can
.map(entry => entry.fields)
EDIT:
Turns out Angular expects you to have an Observable<Entry[]>, instead of Observable<Entry>, so the proper way is:
Observable
.fromPromise(promise)
.map((collection) => collection.items)
I tried to build an instant search using vuejs and laravel 5.3 but somehow It wont work with no errors showing
Controller (fullcode https://pastebin.com/6mQ4eWTf) :
public function index(Request $request) {
$search = $request->search;
$items = Staff::where('nama', 'LIKE', '%'.$search.'%')->paginate(5);
$response = [
'pagination' => [
'total' => $items->total(),
'per_page' => $items->perPage(),
'current_page' => $items->currentPage(),
'last_page' => $items->lastPage(),
'from' => $items->firstItem(),
'to' => $items->lastItem()
],
'data' => $items
];
staff.js method (fullcode https://pastebin.com/NDxzqsyp) :
methods: {
getVueItems: function (page) {
this.$http.get('/staffitems?page=' + page + '&search=' + this.search).then((response) => {
this.$set('items', response.data.data.data);
this.$set('pagination', response.data.pagination);
});
setTimeout(this.getVueItems, 5000);
},
Blade (fullcode https://pastebin.com/6uDZRryE) :
<input v-on:keyup.enter="getVueItems" type="text" class="form-control" name="search" placeholder="Cari..." v-model="search"/>
Routes :
Route::get('/staffcrud', 'StaffController#Crud');
Route::resource('/staffitems', 'StaffController');
The data correctly shown (tested by getting json response from /staffitems?page=1&search=jon with or without search value), but somehow when I do type words to search in input search column, nothing happened as soon as i finished typing, probably event handling in blade are wrong or my method in staff.js any solution for this?
You should add the debounce in your mounted() method:
mounted() {
this.getVueItems = _.debounce(this.getVueItems, 5000); // i'm using lodash here.
}
To build a really effective instant database search you should consider using pusher and laravel echo together with vuex
You can check out this out >>> Ethiel Adiassa's Live Search Tutorial with laravel and pusher
In your blade template use only:
v-on:keyup='vueGetItems'
Because your instant search is firing only on enter key up. I hope this will work or contact me to get full working code.
I started using Laravel a month ago. Trying to make a page with one form on it and I keep getting a "Controller method not found." error. There is only one form on the page and that method is being called fine and making the query that I want. It does redirect to the same page with that form, /dashboard/edit/module.
It would be awesome if I could see what method is not being found or where it is in my code. Any ideas how I can do this? Someone mentioned App::error or messing with Log::error in global.php but I can't get any more information shown.
Edit:
I found that the form action is only being found if I upload a video to the form. Other then that it gives me the same error. Here is the controller method of this form. The page with this form is /dashboard/edit/module and I would like to reload that page after this runs.
public function postSection(){
$path = public_path() . "/videos/";
$filename = uniqid() . '.mp4';
$file_path = $path . $filename;
$moduleID = Input::get('moduleID');
if(!Input::hasFile('video')){
return Redirect::back()->with('error', 'Must enter a valid video file.');
}else{
try {
Input::file('video')->move($path, $filename);
$section = New Section;
$section->module_id = $moduleID;
$section->video = $file_path;
$section->save();
//I was trying to use this instead of View::make to stop form resubmission
//return Redirect::to('/dashboard/edit/module')->with('moduleID', $moduleID);
return View::make('forms.editMod')->with('moduleID', $moduleID);
}catch ( Exception $e){
return Redirect::back()->with('error', 'Unable to save the video file.');
}
}
}
Edit: And my routes.php
//Route for the home page
Route::get('/', function(){ return View::make('home'); });
//Controllers
Route::controller('dashboard/edit', "ContentController");
Route::controller('user', "UserController");
Route::group(array('before'=> 'auth|dash'), function(){
//dashboard home
Route::get('/dashboard', function(){ return View::make('dashboard');});
//dashboard subs
Route::get('dashboard/modules', function(){ return View::make('forms.module');});
Route::get('dashboard/users', function(){ return View::make('users');});
Route::get('dashboard/reporting', function(){ return View::make('reporting');});
//user pages
Route::get('dashboard/register', function(){ return View::make('forms.register');});
Route::post('dashboard/edit/user', array('before'=>'csrfajax', function(){
$id = Input::get('userID');
$user = User::find($id);
return View::make('forms.editUser')->with('user', $user);
}));
});
And the form...
{{ Form::open(array('files' => true, 'action' => 'ContentController#postSection', 'id' => 'sectionform')) }}
<fieldset>
{{ Form::label('Video (mp4)') }}
{{ Form::hidden('moduleID', $module->id)}}
{{ Form::file('video', array('id' => 'video', 'class' => 'form-control')) }}<br>
{{ Form::submit('Save') }}
</fieldset>
{{ Form::close() }}
In routes.php, to reach this method you're using Route::controller, which results in RESTful routing. If the browser's HTTP request used the POST action, you'd correctly be routed to public function postSection(){}. But if the HTTP request used GET, which is the default for web browsing, especially when no data is being submitted by the browser, Laravel would be looking for a method called public function getSection(){}. This would explain why when you're uploading something (i.e. using POST), you reach the page, but otherwise you don't.
See the official documentation for the full low-down on implicit routing: http://laravel.com/docs/controllers#implicit-controllers
I need to render email templates in variable to send them later (which are stored in .phtml files), and i really don't want to implement my special class for handling this.
Is it possible to render not controller action view, but custom one?
I tried following code, but it outputs NULL :((
// Controller context
$view = new Phalcon\Mvc\View();
$view->setViewsDir('app/views/');
$view->setVar('var1', 'var2');
// Setting some vars...
$view->start();
$view->partial($emailTemplatePath);
$view->finish();
$result = $view->getContent();
var_dump($result); // Gives null
In addition to the response by Nikolaos, you can use $view->getRender() to render a single view returning its output.
$view->setViewsDir('apps/views/');
echo $view->getRender('partials', 'test'); // get apps/views/partials/test.phtml
You need to check the path of the $emailTemplatePath. It should point to the correct file i.e.
// points to app/views/partials/email.phtml
$view->partial('partials/email');
If you are using Volt and have registered that as your engine, then your file will need to be:
// app/views/partials/email.volt
I have a project where I use email and pdf templates and what I did was to have the rendering all take place within components.
Firstly, my folder structure contains (and I will only put here what is relevant) a cache, components and views directory. Let's look at the email setup rather than the PDF as this is more relevant to your situation.
/app
/cache
/email
/components
/views
/email
/elements
Of course there is public, controllers etc but let's not think about them for this.
I'm using Swift mailer for mine but I hope you will be able to use this all the same. In /app/components/Swift.php I have a __construct that calls for this->init_template_engine();
/**
* Create a volt templating engine for generating html
*/
private function init_template_engine() {
$this->_template = new \Phalcon\Mvc\View\Simple();
$di = new \Phalcon\DI\FactoryDefault();
$this->_template->setDI($di);
$this->_template->registerEngines([
'.volt' => function($view, $di) {
$volt = new \Phalcon\Mvc\View\Engine\Volt($view, $di);
$volt->setOptions([
'compiledPath' => APP_DIR."cache".DS."email".DS, // render cache in /app/cache/email/
'compiledSeparator' => '_'
]);
return $volt;
// or use ".phtml" => 'Phalcon\Mvc\View\Engine\Php' if you want,
// both will accept PHP code if ya don't fancy it being a 100% volt.
},
]);
// tell it where your templates are
$this->_template->setViewsDir(APP_DIR.'views'.DS.'email'.DS);
return $this->_template;
}
The constants above (like APP_DIR) are something I have already made in my bootstrap and all they do is store full paths to directories.
Once the $_template variable has a template engine set up I can then use it to render my templates.
/**
* Returns HTML via Phalcon's volt engine.
* #param string $template_name
* #param array $data
*/
private function render_template($template_name = null, $data = null) {
// Check we have some data.
if (empty($data)) {
return false; // or set some default data maybe?
}
// Use the template name given to render the file in views/email
if(is_object($this->_template) && !empty($template_name)) {
return $this->_template->render($template_name, ['data' => $data]);
}
return false;
}
A sample volt email template may look like this:
{{ partial('elements/email_head') }}
<h2>Your Order has been dispatched</h2>
<p>Dear {{ data.name }}</p>
<p>Your order with ACME has now been dispatched and should be with you within a few days.</p>
<p>Do not hesitate to contact us should you have any questions when your waste of money arrives.</p>
<p>Thank you for choosing ACME Inc.</p>
{{ partial('elements/email_foot') }}
All I have to do then is grab the html and use swiftmailer's setBody method and I'm done:
->setBody($this->render_template($template, $data), 'text/html');
You don't need to place separate view engines like this in components, it could become memory hungry like that, but it does show the whole process. Hope that makes sense :)
The easiest way to render a view and return it as a variable is to use the Phalcon\Mvc\View\Simple class. In your controller, declare a new instance of the Simple view class and attach a rendering engine to it. You can then use its render() method to select a view file and pass in variables:
// create a simple view to help render sections of the page
$simple_view = new \Phalcon\Mvc\View\Simple();
$simple_view->setViewsDir( __DIR__ . '/../views/' );
$simple_view->setDI( $this->di );
$simple_view->registerEngines(array(
'.volt' => 'Phalcon\Mvc\View\Engine\Volt'
));
// use the simple view to generate one or more widgets
$widget_html = array();
$widget_objects = $widget_search->getWidgetObjects();
forEach( $widget_objects as $widget ){
$widget_html[] = $simple_view->render('index/widgetview',array('widget'=>$widget));
}
// pass the html snippets as a variable into your regular view
$this->view->setVar('widget_html',$widget_html);
use $view->render('partials/email') instead of calling partial method.
I usually use Volt engine and a simple way is a redefine view in DI container, like that:
$view = $this->view;
$content = $view->getRender('mail', 'show',
array(
"var1" => "some value 1",
"var2" => "some value 2"
),
function($view) {
$view->setRenderLevel(\Phalcon\Mvc\View::LEVEL_LAYOUT);
}
);
echo $content;