Quick Add Product Modal Implementation Guide
This guide will walk you through implementing a quick add product modal in your Laravel application. The modal allows users to quickly add new products without leaving the product index page.
📁 Complete Implementation Tree
your-laravel-app/
├── 📁 app/
│ └── 📁 Http/
│ └── 📁 Controllers/
│ └── 📄 ProductController.php ← ADD METHOD: quickAddProductModal()
│
├── 📁 lang/
│ └── 📁 en/
│ └── 📄 lang_v1.php ← ADD: 'save_and_redirect_to_label'
│
├── 📁 resources/
│ └── 📁 views/
│ └── 📁 product/
│ ├── 📄 index.blade.php ← ADD: Button + Modal + JavaScript
│ └── 📁 partials/
│ └── 📄 quick_add_product_modal.blade.php ← NEW FILE
│
└── 📁 routes/
└── 📄 web.php ← ADD: Route::get('/products/quick_add_mini_form', ...)
Overview
The Quick Add Product Modal provides a streamlined interface for adding new products with essential fields like product name, SKU, unit, category, brand, product image, and pricing information. It uses AJAX to submit the form and provides options to save, save & add another, or save & redirect to labels.
🎯 Implementation Steps
1️⃣ Route Registration
// routes/web.php
Route::get('/products/quick_add_mini_form', [ProductController::class, 'quickAddProductModal']);
2️⃣ Controller Method
// app/Http/Controllers/ProductController.php
public function quickAddProductModal()
{
// Controller implementation
}
public function saveQuickProduct()
{
// Controller implementation
}
3️⃣ Blade View
<!-- resources/views/product/partials/quick_add_product_modal.blade.php -->
<div class="modal-dialog modal-lg" role="document">
<!-- Modal content -->
</div>
4️⃣ Product Index Updates
<!-- resources/views/product/index.blade.php -->
<!-- ADD: Quick Add Button -->
<a class="tw-dw-btn ... product_add_quick_product">Quick Add</a>
<!-- ADD: Modal Container -->
<div class="modal fade quick_add_product_modal"></div>
<!-- ADD: JavaScript Handler -->
<script>
$(document).on('click', '.product_add_quick_product', function() {
// Modal handling code
});
</script>
5️⃣ Language File
// lang/en/lang_v1.php
'quick_add' => 'Quick Add',
'save_and_redirect_to_label' => 'Save and Redirect to Label',
Step 1: Add Route
First, add the route for the quick add modal in your web.php
file:
// Add this route to your web.php
Route::get('/products/quick_add_mini_form', [ProductController::class, 'quickAddProductModal']);
Important: Place this route before any wildcard routes like Route::resource('products', ProductController::class)
to avoid conflicts.
Step 2: Create Controller Method
Add the quickAddProductModal
method to your ProductController
:
public function quickAddProductModal()
{
if (! auth()->user()->can('product.create')) {
abort(403, 'Unauthorized action.');
}
$product_name = ! empty(request()->input('product_name')) ? request()->input('product_name') : '';
$product_for = ! empty(request()->input('product_for')) ? request()->input('product_for') : null;
$business_id = request()->session()->get('user.business_id');
$categories = Category::forDropdown($business_id, 'product');
$brands = Brands::forDropdown($business_id);
$units = Unit::forDropdown($business_id, true);
$tax_dropdown = TaxRate::forBusinessDropdown($business_id, true, true);
$taxes = $tax_dropdown['tax_rates'];
$tax_attributes = $tax_dropdown['attributes'];
$barcode_types = $this->barcode_types;
$default_profit_percent = Business::where('id', $business_id)->value('default_profit_percent');
$locations = BusinessLocation::forDropdown($business_id);
$business_locations = BusinessLocation::forDropdown($business_id);
$common_settings = session()->get('business.common_settings');
$warranties = Warranty::forDropdown($business_id);
$enable_expiry = request()->session()->get('business.enable_product_expiry');
$enable_lot = request()->session()->get('business.enable_lot_number');
$module_form_parts = $this->moduleUtil->getModuleData('product_form_part');
// Add image required setting
$is_image_required = !empty($common_settings['is_product_image_required']);
return view('product.partials.quick_add_product_modal')->with(compact(
'categories',
'brands',
'units',
'taxes',
'barcode_types',
'default_profit_percent',
'tax_attributes',
'product_name',
'locations',
'product_for',
'enable_expiry',
'enable_lot',
'module_form_parts',
'business_locations',
'common_settings',
'warranties',
'is_image_required'
));
}
Add the saveQuickProduct
method to your ProductController
:
public function saveQuickProduct(Request $request)
{
if (!auth()->user()->can('product.create')) {
abort(403, 'Unauthorized action.');
}
try {
$business_id = $request->session()->get('user.business_id');
$form_fields = [
'name',
'brand_id',
'unit_id',
'category_id',
'tax',
'type',
'barcode_type',
'sku',
'alert_quantity',
'tax_type',
'weight',
'product_description',
'sub_unit_ids',
'product_custom_field1',
'product_custom_field2',
'product_custom_field3',
'product_custom_field4',
];
$module_form_fields = $this->moduleUtil->getModuleFormField('product_form_fields');
if (!empty($module_form_fields)) {
$form_fields = array_merge($form_fields, $module_form_fields);
}
$product_details = $request->only($form_fields);
$product_details['business_id'] = $business_id;
$product_details['created_by'] = $request->session()->get('user.id');
$product_details['enable_stock'] = (!empty($request->input('enable_stock')) && $request->input('enable_stock') == 1) ? 1 : 0;
$product_details['not_for_selling'] = (!empty($request->input('not_for_selling')) && $request->input('not_for_selling') == 1) ? 1 : 0;
if (!empty($request->input('sub_category_id'))) {
$product_details['sub_category_id'] = $request->input('sub_category_id');
}
if (empty($product_details['sku'])) {
$product_details['sku'] = ' ';
}
if (!empty($product_details['alert_quantity'])) {
$product_details['alert_quantity'] = $this->productUtil->num_uf($product_details['alert_quantity']);
}
$expiry_enabled = $request->session()->get('business.enable_product_expiry');
if (!empty($request->input('expiry_period_type')) && !empty($request->input('expiry_period')) && !empty($expiry_enabled) && ($product_details['enable_stock'] == 1)) {
$product_details['expiry_period_type'] = $request->input('expiry_period_type');
$product_details['expiry_period'] = $this->productUtil->num_uf($request->input('expiry_period'));
}
if (!empty($request->input('enable_sr_no')) && $request->input('enable_sr_no') == 1) {
$product_details['enable_sr_no'] = 1;
}
if (!empty($request->input('enable_serial_number')) && $request->input('enable_serial_number') == 1) {
$product_details['enable_serial_number'] = 1;
}
// Handle image upload
$product_details['image'] = $this->productUtil->uploadFile($request, 'image', config('constants.product_img_path'), 'image');
$product_details['warranty_id'] = !empty($request->input('warranty_id')) ? $request->input('warranty_id') : null;
DB::beginTransaction();
$product = Product::create($product_details);
if (empty(trim($request->input('sku')))) {
$sku = $this->productUtil->generateProductSku($product->id);
$product->sku = $sku;
$product->save();
}
// Add product locations
$product_locations = $request->input('product_locations');
if (!empty($product_locations)) {
$product->product_locations()->sync($product_locations);
}
// Handle single product variation (default for quick add)
$product_type = $request->input('type', 'single');
if ($product_type == 'single' || empty($product_type)) {
$this->productUtil->createSingleProductVariation(
$product->id,
$product->sku,
$request->input('single_dpp', 0),
$request->input('single_dpp_inc_tax', 0),
$request->input('profit_percent', 0),
$request->input('single_dsp', 0),
$request->input('single_dsp_inc_tax', 0),
[]
);
}
// Set Module fields
if (!empty($request->input('has_module_data'))) {
$this->moduleUtil->getModuleData('after_product_saved', ['product' => $product, 'request' => $request]);
}
DB::commit();
// Determine the action based on request
$action = 'default';
if ($request->has('save_and_add_another')) {
$action = 'add_another';
} elseif ($request->has('redirect_to_label')) {
$action = 'redirect_to_label';
}
$output = [
'success' => true,
'msg' => __('product.product_added_success'),
'product' => $product,
'variation' => $product->variations->first(),
'action' => $action
];
return response()->json($output);
} catch (\Exception $e) {
DB::rollBack();
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output = [
'success' => false,
'msg' => __('messages.something_went_wrong')
];
return response()->json($output);
}
}
Step 3: Create the Modal View
Create the file resources/views/product/partials/quick_add_product_modal.blade.php
:
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
{!! Form::open(['url' => action([\App\Http\Controllers\ProductController::class, 'saveQuickProduct']), 'method'
=> 'post', 'id' => 'quick_add_product_form', 'files' => true, 'enctype' => 'multipart/form-data']) !!}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title" id="modalTitle">@lang( 'product.add_new_product' )</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-4">
<div class="form-group">
{!! Form::label('name', __('product.product_name') . ':*') !!}
{!! Form::text('name', $product_name, ['class' => 'form-control', 'required', 'autofocus',
'placeholder' => __('product.product_name')]); !!}
{!! Form::select('type', ['single' => 'Single', 'variable' => 'Variable'], 'single', ['class' =>
'hide', 'id' => 'type']); !!}
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
{!! Form::label('sku', __('product.sku') . ':') !!} @show_tooltip(__('tooltip.sku'))
{!! Form::text('sku', null, ['class' => 'form-control',
'placeholder' => __('product.sku')]); !!}
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
{!! Form::label('unit_id', __('product.unit') . ':*') !!}
{!! Form::select('unit_id', $units, null, ['class' => 'form-control select2', 'required']); !!}
</div>
</div>
@php
$default_location = null;
if(count($business_locations) == 1){
$default_location = array_key_first($business_locations->toArray());
}
@endphp
<div class="col-sm-4 @if(count($business_locations) == 1) hide @endif">
<div class="form-group">
{!! Form::label('product_locations', __('business.business_locations') . ':') !!}
@show_tooltip(__('lang_v1.product_location_help'))
{!! Form::select('product_locations[]', $business_locations, $default_location, ['class' =>
'form-control select2', 'multiple', 'id' => 'product_locations']); !!}
</div>
</div>
<div class="col-sm-4 hide">
<div class="form-group">
{!! Form::label('barcode_type', __('product.barcode_type') . ':*') !!}
{!! Form::select('barcode_type', $barcode_types, 'C128', ['class' => 'form-control select2',
'required']); !!}
</div>
</div>
{{-- <div class="clearfix "></div> --}}
<div class="col-sm-4 enable_serial_number_div hide">
<div class="form-group">
<br>
<label>
{!! Form::checkbox('enable_serial_number', 1, false, ['class' =>
'input-icheck','id'=>'enable_serial_number']); !!}
<strong>@lang('lang_v1.enable_serial_number')</strong>
</label>
@show_tooltip(__('lang_v1.tooltip_enable_serial_number'))
</div>
</div>
<div class="col-sm-4 @if(!session('business.enable_sub_units')) hide @endif hide">
<div class="form-group">
{!! Form::label('sub_unit_ids', __('lang_v1.related_sub_units') . ':') !!}
@show_tooltip(__('lang_v1.sub_units_tooltip'))
{!! Form::select('sub_unit_ids[]', [], null, ['class' => 'form-control select2', 'multiple',
'id' => 'sub_unit_ids']); !!}
</div>
</div>
<div class="col-sm-4 @if(!session('business.enable_sub_units')) hide @endif hide">
<div class="form-group">
{!! Form::label('sub_unit_ids', __('lang_v1.related_sub_units') . ':') !!}
@show_tooltip(__('lang_v1.sub_units_tooltip'))
{!! Form::select('sub_unit_ids[]', [], null, ['class' => 'form-control select2', 'multiple',
'id' => 'sub_unit_ids']); !!}
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
{!! Form::label('brand_id', __('product.brand') . ':') !!}
{!! Form::select('brand_id', $brands, null, ['placeholder' => __('messages.please_select'),
'class' => 'form-control select2']); !!}
</div>
</div>
<!-- <div class="clearfix"></div> -->
<div class="col-sm-4">
<div class="form-group">
{!! Form::label('category_id', __('product.category') . ':') !!}
{!! Form::select('category_id', $categories, null, ['placeholder' =>
__('messages.please_select'), 'class' => 'form-control select2']); !!}
</div>
</div>
<div
class="col-sm-4 @if(!(session('business.enable_category') && session('business.enable_sub_category'))) hide @endif hide">
<div class="form-group">
{!! Form::label('sub_category_id', __('product.sub_category') . ':') !!}
{!! Form::select('sub_category_id', [], null, ['placeholder' => __('messages.please_select'),
'class' => 'form-control select2']); !!}
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
{!! Form::label('image', __('lang_v1.product_image') . ':') !!}
{!! Form::file('image', ['id' => 'upload_image', 'accept' => 'image/*',
'required' => $is_image_required, 'class' => 'upload-element']); !!}
<small>
<p class="help-block">@lang('purchase.max_file_size', ['size' =>
(config('constants.document_size_limit') / 1000000)]) <br>
@lang('lang_v1.aspect_ratio_should_be_1_1')</p>
</small>
</div>
</div>
<div class="col-sm-4 hide">
<div class="form-group">
<br>
<label>
{!! Form::checkbox('enable_stock', 1, true, ['class' => 'input-icheck', 'id' =>
'enable_stock']); !!} <strong>@lang('product.manage_stock')</strong>
</label>@show_tooltip(__('tooltip.enable_stock')) <p class="help-block">
<i>@lang('product.enable_stock_help')</i>
</p>
</div>
</div>
<!-- <div class="clearfix"></div> -->
<div class="col-sm-4 hide" id="alert_quantity_div">
<div class="form-group">
{!! Form::label('alert_quantity', __('product.alert_quantity') . ':') !!}
{!! Form::text('alert_quantity', null, ['class' => 'form-control input_number',
'placeholder' => __('product.alert_quantity'), 'min' => '0']); !!}
</div>
</div>
<div class="clearfix"></div>
<div class="row">
<div class="form-group col-sm-12">
@include('product.partials.single_product_form_part', ['profit_percent' =>
$default_profit_percent, 'quick_add' => true ])
</div>
</div>
@if(!empty($common_settings['enable_product_warranty']))
<div class="col-sm-4 hide">
<div class="form-group">
{!! Form::label('warranty_id', __('lang_v1.warranty') . ':') !!}
{!! Form::select('warranty_id', $warranties, null, ['class' => 'form-control select2',
'placeholder' => __('messages.please_select')]); !!}
</div>
</div>
@endif
@if(session('business.enable_product_expiry'))
@if(session('business.expiry_type') == 'add_expiry')
@php
$expiry_period = 12;
$hide = true;
@endphp
@else
@php
$expiry_period = null;
$hide = false;
@endphp
@endif
<div class="col-sm-4 @if($hide) hide @endif hide">
<div class="form-group">
<div class="multi-input">
{!! Form::label('expiry_period', __('product.expires_in') . ':') !!}<br>
{!! Form::text('expiry_period', $expiry_period, ['class' => 'form-control pull-left
input_number',
'placeholder' => __('product.expiry_period'), 'style' => 'width:60%;']); !!}
{!! Form::select('expiry_period_type', ['months'=>__('product.months'),
'days'=>__('product.days'), '' =>__('product.not_applicable') ], 'months', ['class' =>
'form-control select2 pull-left', 'style' => 'width:40%;', 'id' => 'expiry_period_type']);
!!}
</div>
</div>
</div>
@endif
<div class="col-sm-4 hide">
<div class="form-group">
{!! Form::label('weight', __('lang_v1.weight') . ':') !!}
{!! Form::text('weight', null, ['class' => 'form-control', 'placeholder' =>
__('lang_v1.weight')]); !!}
</div>
</div>
<!-- <div class="clearfix"></div> -->
<div class="col-sm-8 hide">
<div class="form-group">
{!! Form::label('product_description', __('lang_v1.product_description') . ':') !!}
{!! Form::textarea('product_description', null, ['class' => 'form-control']); !!}
</div>
</div>
<!-- <div class="clearfix"></div> -->
<div class="col-sm-4 hide">
<div class="form-group">
{!! Form::label('tax', __('product.applicable_tax') . ':') !!}
{!! Form::select('tax', $taxes, null, ['placeholder' => __('messages.please_select'), 'class' =>
'form-control select2'], $tax_attributes); !!}
</div>
</div>
<div class="col-sm-4 hide">
<div class="form-group">
{!! Form::label('tax_type', __('product.selling_price_tax_type') . ':*') !!}
{!! Form::select('tax_type', ['inclusive' => __('product.inclusive'), 'exclusive' =>
__('product.exclusive')], 'exclusive',
['class' => 'form-control select2', 'required']); !!}
</div>
</div>
<div class="col-sm-4 hide">
<div class="checkbox">
<br>
<label>
{!! Form::checkbox('enable_sr_no', 1, false, ['class' => 'input-icheck']); !!}
<strong>@lang('lang_v1.enable_imei_or_sr_no')</strong>
</label>@show_tooltip(__('lang_v1.tooltip_sr_no'))
</div>
</div>
<!-- <div class="clearfix"></div> -->
@php
$custom_labels = json_decode(session('business.custom_labels'), true);
$product_custom_field1 = !empty($custom_labels['product']['custom_field_1']) ?
$custom_labels['product']['custom_field_1'] : __('lang_v1.product_custom_field1');
$product_custom_field2 = !empty($custom_labels['product']['custom_field_2']) ?
$custom_labels['product']['custom_field_2'] : __('lang_v1.product_custom_field2');
$product_custom_field3 = !empty($custom_labels['product']['custom_field_3']) ?
$custom_labels['product']['custom_field_3'] : __('lang_v1.product_custom_field3');
$product_custom_field4 = !empty($custom_labels['product']['custom_field_4']) ?
$custom_labels['product']['custom_field_4'] : __('lang_v1.product_custom_field4');
@endphp
<div class="col-sm-4 hide">
<div class="form-group">
<br>
<label>
{!! Form::checkbox('not_for_selling', 1, false, ['class' => 'input-icheck']); !!}
<strong>@lang('lang_v1.not_for_selling')</strong>
</label> @show_tooltip(__('lang_v1.tooltip_not_for_selling'))
</div>
</div>
<!-- <div class="clearfix"></div> -->
<div class="col-sm-3 hide">
<div class="form-group">
{!! Form::label('product_custom_field1', $product_custom_field1 . ':') !!}
{!! Form::text('product_custom_field1', null, ['class' => 'form-control', 'placeholder' =>
$product_custom_field1]); !!}
</div>
</div>
<div class="col-sm-3 hide">
<div class="form-group">
{!! Form::label('product_custom_field2', $product_custom_field2 . ':') !!}
{!! Form::text('product_custom_field2',null, ['class' => 'form-control', 'placeholder' =>
$product_custom_field2]); !!}
</div>
</div>
<div class="col-sm-3 hide">
<div class="form-group">
{!! Form::label('product_custom_field3', $product_custom_field3 . ':') !!}
{!! Form::text('product_custom_field3', null, ['class' => 'form-control', 'placeholder' =>
$product_custom_field3]); !!}
</div>
</div>
<div class="col-sm-3 hide">
<div class="form-group">
{!! Form::label('product_custom_field4', $product_custom_field4 . ':') !!}
{!! Form::text('product_custom_field4', null, ['class' => 'form-control', 'placeholder' =>
$product_custom_field4]); !!}
</div>
</div>
<!-- <div class="clearfix"></div> -->
@if(!empty($module_form_parts))
@foreach($module_form_parts as $key => $value)
@if(!empty($value['template_path']))
@php
$template_data = $value['template_data'] ?: [];
@endphp
@include($value['template_path'], $template_data)
@endif
@endforeach
@endif
</div>
<div class="modal-footer">
<button type="button" id="submit_quick_product" class="btn btn-primary">@lang('messages.save')</button>
<button type="button" id="submit_quick_product_add_another"
class="btn btn-success">@lang('lang_v1.save_n_add_another')</button>
<button type="button" id="submit_quick_product_redirect_to_label"
class="btn btn-info">@lang('lang_v1.save_and_redirect_to_label')</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">@lang('messages.close')</button>
</div>
{!! Form::close() !!}
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
<script type="text/javascript">
$(document).ready(function(){
$("form#quick_add_product_form").validate({
rules: {
sku: {
remote: {
url: "/products/check_product_sku",
type: "post",
data: {
sku: function() {
return $("#sku").val();
},
product_id: function() {
if($('#product_id').length > 0 ){
return $('#product_id').val();
} else {
return '';
}
},
}
}
},
expiry_period:{
required: {
depends: function(element) {
return ($('#expiry_period_type').val().trim() != '');
}
}
}
},
messages: {
sku: {
remote: LANG.sku_already_exists
}
},
submitHandler: function (form) {
var form = $("form#quick_add_product_form");
var url = form.attr('action');
form.find('button[type="submit"]').attr('disabled', true);
// Create FormData object for file upload
var formData = new FormData(form[0]);
// Perform the AJAX request to save the product
$.ajax({
method: "POST",
url: url,
dataType: 'json',
data: formData,
processData: false, // Important: Don't process the data
contentType: false, // Important: Don't set content type
success: function(data){
form.find('button[type="submit"]').attr('disabled', false);
if (data.success) {
toastr.success(data.msg);
// Trigger an event to notify other parts of the application
$(document).trigger({type: "quickProductAdded", 'product': data.product, 'variation': data.variation });
// Handle the "Add another" action
if (data.action === "add_another") {
console.log('add_another');
// Reset the form for another entry
$("form#quick_add_product_form").trigger("reset");
$("form#quick_add_product_form").find("input:visible:first").focus();
// Remove any previously added hidden inputs to avoid duplicate flags
$("form#quick_add_product_form").find("input[name='save_and_add_another']").remove();
// Keep the modal open for another entry
}
else if (data.action === "redirect_to_label") {
// Redirect to label page
var labelUrl = "{{ action([\App\Http\Controllers\LabelsController::class, 'show']) }}?product_id=" + data.product.id;
console.log(labelUrl); // Log the URL for debugging
window.location.href = labelUrl;
}
else {
// Default: close the modal
$('.quick_add_product_modal').modal('hide');
}
} else {
toastr.error(data.msg);
}
},
error: function(xhr, status, error) {
form.find('button[type="submit"]').attr('disabled', false);
console.log('AJAX Error:', error);
console.log('Response:', xhr.responseText);
toastr.error('An error occurred while saving the product.');
}
});
return false;
}
});
// Save button click handler
$('#submit_quick_product').on('click', function() {
$("form#quick_add_product_form").submit(); // Trigger form submission
});
// Save and Redirect button click handler
$('#submit_quick_product_redirect_to_label').on('click', function() {
// Add a custom flag in the form data to indicate redirection
$("form#quick_add_product_form").append('<input type="hidden" name="redirect_to_label" value="1">');
$("form#quick_add_product_form").submit(); // Trigger form submission
});
// Save and Add Another button click handler
$("#submit_quick_product_add_another").on("click", function () {
// Add a custom flag to indicate add another
$("form#quick_add_product_form").append('<input type="hidden" name="save_and_add_another" value="1">');
$("form#quick_add_product_form").submit(); // Trigger form submission
});
});
</script>
Step 4: Add Language Translation
Add the translation key to your lang_v1.php
file:
// In lang/en/lang_v1.php (or your language file)
'quick_add' => 'Quick Add',
'save_and_redirect_to_label' => 'Save and Redirect to Label',
Step 5: Add Button to Product Index
In your product index view, add the Quick Add button after the existing Add button:
<!-- Existing Add button -->
<a class="tw-dw-btn tw-bg-gradient-to-r tw-from-indigo-600 tw-to-blue-500 tw-font-bold tw-text-white tw-border-none tw-rounded-full pull-right tw-m-2"
href="{{ action([\App\Http\Controllers\ProductController::class, 'create']) }}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-plus">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 5l0 14" />
<path d="M5 12l14 0" />
</svg> @lang('messages.add')
</a>
<!-- Add this Quick Add button after the existing Add button -->
<a class="tw-dw-btn tw-bg-gradient-to-r tw-from-indigo-600 tw-to-blue-500 tw-font-bold tw-text-white tw-border-none tw-rounded-full pull-right tw-m-2 product_add_quick_product"
href="javascript:void(0);"
data-href="{{ url('/products/quick_add_mini_form') }}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-plus">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 5l0 14" />
<path d="M5 12l14 0" />
</svg>
@lang('lang_v1.quick_add')
</a>
Key differences between the buttons:
- Add button: Redirects to full product creation page (
href
with controller action) - Quick Add button: Opens modal (
href="javascript:void(0)"
withdata-href
for AJAX) - CSS classes: Quick Add has additional
product_add_quick_product
class for JavaScript targeting
Step 6: Add Modal Container
Add the modal container to your product index page before the closing content section:
<!-- Your existing product content goes here -->
<!-- ... product table, filters, etc. ... -->
<!-- Add this modal container before @endsection -->
<div class="modal fade quick_add_product_modal" tabindex="-1" role="dialog" aria-labelledby="modalTitle"></div>
@endsection
@section('javascript')
<!-- Your JavaScript code will go here in Step 7 -->
@endsection
Important placement notes:
- Place the modal container inside the main content section (before
@endsection
) - The modal should be outside of any specific content divs but inside the main template section
- This ensures the modal is available to the JavaScript but doesn't interfere with the page layout
Step 7: Add JavaScript Handler
Add the JavaScript code to handle modal opening in your product index page. Place this code before the closing </script>
tag in your existing JavaScript section:
@section('javascript')
<script type="text/javascript">
$(document).ready(function() {
// Your existing JavaScript code here...
// ... other product page functionality ...
// Add this Quick Add modal handler before </script>
$(document).on('click', '.product_add_quick_product', function (e) {
e.preventDefault();
// Get the URL from the data-href attribute
let url = $(this).data('href');
// Load the content into the modal
$('.quick_add_product_modal').load(url, function () {
// Show the modal once content is loaded
$(this).modal('show');
});
});
// Your other existing JavaScript continues here...
});
</script>
@endsection
Important implementation notes:
- Place this code inside your existing
$(document).ready()
function if you have one - If you don't have existing JavaScript, create the structure shown above
- The code should be before the closing
</script>
tag - Uses
$(document).on()
for event delegation to handle dynamically loaded content
Step 8: Clear Caches
Clear Laravel caches to ensure changes are reflected:
php artisan route:clear
php artisan cache:clear
php artisan config:clear
php artisan view:clear
or
php artisan optimize:clear
Testing
-
Test Modal Opening: Navigate to the product index page and click the "Quick Add" button. The modal should open with the form fields.
-
Test Form Submission: Fill out the required fields (Product Name, Unit) and click "Save". The product should be created and the modal should close.
-
Test Save & Add Another: Use the "Save & Add Another" button to verify the form resets for another entry.
-
Test Save & Redirect to Label: Use the "Save & Redirect to Label" button to verify redirection functionality.
Troubleshooting
Modal Not Opening
- Check browser console for JavaScript errors
- Verify the route exists:
php artisan route:list | grep quick_add_mini_form
- Ensure jQuery and Bootstrap JS are loaded
Controller Method Not Found
- Verify the method name matches the route
- Check for syntax errors in the controller
- Ensure proper imports at the top of the controller
View Not Found
- Verify the view file path:
resources/views/product/partials/quick_add_product_modal.blade.php
- Check file permissions
- Ensure proper Blade syntax
Form Validation Errors
- Check that required variables are passed to the view
- Verify the
saveQuickProduct
method exists in your controller - Check for missing form fields or validation rules
Customization
Adding More Fields
To add more fields to the modal:
- Add the field to the view file
- Update the controller to pass necessary data
- Modify the form validation rules if needed
Styling
The modal uses Bootstrap classes and custom Tailwind classes for the button. Modify the CSS classes to match your application's design.
Form Actions
The modal supports three actions:
- Save: Normal save and close
- Save & Add Another: Save and reset form for another entry
- Save & Redirect to Label: Save and redirect to label printing page
You can modify these actions by updating the JavaScript handlers and controller logic.
Conclusion
You now have a fully functional quick add product modal that integrates seamlessly with your Laravel application. The modal provides a streamlined way for users to add products without leaving the product index page, improving the user experience and workflow efficiency.
💛 Support this project
Binance ID:
478036326