grocery-crud
Grocery CRUD is a PHP Codeigniter Framework library that creates a full functional CRUD system without the requirement of extra customisation to the JavaScripts or the CSS to do it so.
Auto PHP Codeigniter CRUD | Grocery CRUD grocery crud is an auto php codeigniter crud generator that makes a developer's life easier. just few lines of code and you can create a full stable codeigniter crud with nice views.
I'm using Codeigniter and Grocery Crud for a project. The crud seems to be working fine but the problem is that when I try to add values, I don't get the success message. Instead I get a textbox like below

I tried reinstalling the crud from scratch but still the same.
add
and update
doesn't show the success message and when I click on update and goto to the list nothing happens.
Can someone please help me on this?
Source: (StackOverflow)
I am trying to change column value using callback_column
function.
$crud = new grocery_CRUD();
$crud -> set_table('booking');
//$crud->set_relation('room_id','rooms','name');
$crud->callback_column('room_id',array($this,'_visitor_details_popup'));
This code workings well (_visitor_details_popup
function return value to room_id
)
$crud = new grocery_CRUD();
$crud -> set_table('booking');
$crud->set_relation('room_id','rooms','name');
$crud->callback_column('room_id',array($this,'_visitor_details_popup'));
This code NOT workings well (room_id
not changing)
public function _visitor_details_popup($value, $row)
{
$visitor_details=explode(',',$value);
return '<a rel='nofollow' href="#">Name'.$visitor_details[1].'<a>'.'<div class="popup_content">Address'.$visitor_details[2].'Email'.$visitor_details[3].'</div>;
}
Is this a bug in grocery crud?? Any tricky way to solve it??
Source: (StackOverflow)
I stucked in a Problem When Playing with Drop Downs.I tried to use this
http://www.grocerycrud.com/forums/topic/1087-updated-24112012-dependent-dropdown-library/
But actually my requirement is quite different. I have a table fwld_products in which i am adding all other table's categories, from
fwld_cat_main (main Category's ID),
fwld_cat_sub1 (sub1 Category's id)
fwld_cat_sub2 (sub2 Category's id)
fwld_cat_sub3 (sub3 Category's id)
I want to Display Dropdown in such a way, when user Selects main
Category, the Drop Down Appear (sub1) Having Data related to main
category and when sub1 selected drop down appear (sub2) showing data
related to sub1, and sub2 selected and drop down appear(sub3) to show
data related to Drop down (sub2).
When submitted Finnally data inserted to [fwld_products].
Here I am attaching ERD, and result as well.

Please help
Source: (StackOverflow)
In grocery crud, searching is not happening for related tables.
Searching is only happening for that table fields.
function index() {
$crud = new grocery_CRUD();
$crud->set_theme('flexigrid');
$crud->set_table('table_name');
$crud->display_as('id','Name');
$crud->callback_column('id', array($this, 'changeName'));
$output = $crud->render();
}
function changeName($value, $row) {
$new = $this->db->select('name')->where('another_table.id', $row->id)->get('another_table')->result();
if(!empty($new)){
return $new[0]->name;
} else {
return $value;
}
}
Here search is not happening for name.
Any one have solution for this ?
Thanks in advance.
Source: (StackOverflow)
I am using CodeIgniter with GroceryCrud,
when I am tring to do a search the response is mysql error.
the error contains the next message:
Error Number: 1064</p><p>You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'both last_action_date LIKE '%11%'
both package_id LIKE '%11%'
both at line 6
and here is part of the relevant query
...FROM (`users`)
LEFT JOIN `users` as j9e598a93 ON `j9e598a93`.`id` = `users`.`accountant_id`
LEFT JOIN `users` as j0b70ca4c ON `j0b70ca4c`.`id` = `users`.`affiliate_id`
WHERE `id` LIKE '%11%'
both `last_action_date` LIKE '%11%'
both `package_id` LIKE '%11%'
I am not familiar with the "BOTH" mysql operator and I could not find it.
does anyone knows this operator?
is it really exists or it's a GroceryCrud bug?
Source: (StackOverflow)
I have done the example and installed everything.
The read or display method of table works correctly, but whenever I try to add, delete or edit a registry a windows appears and say:
404 Page Not Found
The page you requested was not found.
Here is my Controller
class Welcome extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('grocery_CRUD_model');
$this->load->database();
$this->load->helper('url');
$this->load->library('grocery_CRUD');
}
public function index()
{
$crud = new grocery_CRUD();
$crud->set_theme('datatables');
$crud->set_table('students');
$crud->set_relation('class','class','class');
$crud->display_as('name','Name of Student');
$crud->set_subject('Students');
$crud->columns('name','class','roll_no');
$crud->add_fields('name','class','roll_no');
$crud->required_fields('name','class','roll_no');
$crud->unset_export();
$crud->unset_print();
$output = $crud->render();
$this->load->view('home', $output);
}
}
when i click ADD button URL becomes
http://localhost/index.php/add
what is missed?
i am new in codeigniter and Grocery Crud...
Source: (StackOverflow)
Can I please have a design suggestion for the following problem:
I am using Codeigniter/Grocery_CRUD.
My system is multi tenanted - different autonomous sites - within the same client. I have quite a few instances of tables that have unique logical keys. One such table structure is:
equip_items
id (pk)
equip_type_id (fk to equip_types)
site_id (fk to sites)
name
Where (equip_type_id, site_id, name) together are a unique key in my db.
The issues is that when using a grocery_CRUD form to add or edit a record that breaks this database rule - the add or edit fails (due to the constraints in the db) but I get no feedback.
I need a variation on the is_unique form_validation rule by which I can specify the field*s* that must be unique.
The issues:
How to specify the rule? set_rules() is for a given field and I have multiple fields that the rule will apply to. Does that mean I should abandon the Form_validation pattern? Or do I follow the 'matches' rule pattern and somehow point to the other fields?
Perhaps a callback function would be better but this would mean writing a custom function in each model where I have this problem at last count this is 9 tables. It seems far better to do this in one place (extending form_validation).
Am I missing something already in codeigniter or grocery_CRUD that has already solved this problem?
Any suggestion/advice you might have would be appreciated.
EDIT:
Actually it appears the solution Johnny provided does not quite hit the mark - it enforces each field in unique_fields() being independently unique - the same as setting is_unique() on each one. My problem is that in my scenario those fields are a composite unique key (but not the primary key). I don't know if it is significant but further to the original problem statement: 1) site_id is a 'hidden' field_type - I don't want my users concerned they are on a different site so I'm dealing with site_id behind the scenes. 2) Same deal with an equip_status_id attribute (not part of the unique key). And 3) I have set_relations() on all these foreign key attributes and grocery_CRUD kindly deals with nice drop downs for me.
EDIT 2
I have solved this using a callback.
Source: (StackOverflow)
I have 2 tables
CREATE TABLE `tbl_patient` (
id_patient INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(25) NOT NULL DEFAULT "not available",
att1 VARCHAR(5 ) NOT NULL DEFAULT "att1",
att2 VARCHAR(25) NOT NULL DEFAULT "att2",
att3 VARCHAR(25) NOT NULL DEFAULT "att3",
CONSTRAINT `uc_Info_patient` UNIQUE (`id_patient`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
and
CREATE TABLE `tbl_patient_medicine` (
id_patient_medicine INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
id_patient INTEGER NOT NULL,
name_medicine VARCHAR(50) NOT NULL DEFAULT "",
dosis VARCHAR(50) NOT NULL DEFAULT "",
start_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
treatment VARCHAR(50) NOT NULL DEFAULT "",
times_per_day VARCHAR(50) NOT NULL DEFAULT "",
CONSTRAINT fk_ID_Patient_Medicine FOREIGN KEY (id_patient) REFERENCES `tbl_patient`(id_patient)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
As you can see table patient_medicine is the intermediate table of between tbla_medicines, and table patient.
Now I want to consult all data from tbl_patient_medicine with grocery crud like in this sqlfiddle
supossing I pass the id in the uri (in the example will be id_patient=1)
I have
public function details_medication($my_id = 0)
{
try{
$crud = new grocery_CRUD();
$crud->where('id_patient',$my_id);
$crud->set_table('tbl_patient_medicine');
//HOW TO DO IT?
$crud->set_relation('id_patient', 'tbl_patient', 'id_patient');
$output = $crud->render();
$this->_output($output);
}catch(Exception $e){
show_error($e->getMessage().' --- '.$e->getTraceAsString());
}
}
SO I have tried different ways but got errors like this:
A Database Error Occurred
Error Number: 1052
Column 'id_patient' in where clause is ambiguous
SELECT `tbl_patient_medicine`.*, j7a675883.id_patient AS s7a675883
FROM (`tbl_patient_medicine`)
LEFT JOIN `tbl_patient` as j7a675883
ON `j7a675883`.`id_patient` = `tbl_patient_medicine`.`id_patient` WHERE `id_patient` = '1' LIMIT 25
Line Number: 87
Source: (StackOverflow)
I follow the guide in Grocery CRUD web documentation, but I can't find if this library suport operations for a multilanguage application.
let's say I have a table articles, where I have a column "lang",
id lang title
1 EN Title for en
1 DE Title for de
How can I use or modify the view for edit, to put there tabs, or a dropdown. Can I do this with Grocery? If you another library, please share with us.
Thanks in advance
Source: (StackOverflow)
//inside admin controller
public function pictures($table)
{
$image = new image_CRUD();
$image -> set_table($table)
-> set_url_field('name')
-> set_title_field('title')
//-> set_ordering_field('priority')
-> set_image_path('assets/uploads');
$output = $image -> render();
$this->view_output('images/main.php',$image -> render());
}
url: localhost/my_site/admin/pictures/photos
and I get a bunch of errors for undefined variable of css_files and js_files and invalid argument for foreach(). when url is localhost/my_site/admin/pictures
and the code is as below, it works perfect.
//inside admin controller
public function pictures()
{
$image = new image_CRUD();
$image -> set_table('photos')
-> set_url_field('name')
-> set_title_field('title')
//-> set_ordering_field('priority')
-> set_image_path('assets/uploads');
$output = $image -> render();
$this->view_output('images/main.php',$image -> render());
}
The problem is while using codeigniter's method of passing arguments on image crud rendering function. While using normal method of php as http://localhost/my_site/admin/pictures?table=photos
and the code is as below, it works, BUT I can't upload images this way,there will be upload error.
public function pictures()
{
$image = new image_CRUD();
$table = $_GET['table'];
$image -> set_table($table)
......
}
How can I pass arguments as I stated first?
OK I got the solution. Previously the library image_crud.php
getState() function did not have the condition for uri segment being other than numberic,'upload_file', 'ajax_list', 'ordering' and 'insert_title'. So the error was due to not finding the suitable condition and code didn't execute. Below given code must be added to the library:image_crud.php
line 477:
else
{
$upload_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/upload_file');
$ajax_list_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/ajax_list');
$ordering_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/ordering');
$insert_title_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/insert_title');
$state = array( 'name' => 'list', 'upload_url' => $upload_url);
$state['ajax'] = isset($rsegments_array[3]) && $rsegments_array[3] == 'ajax_list' ? true : false;
$state['ajax_list_url'] = $ajax_list_url;
$state['ordering_url'] = $ordering_url;
$state['insert_title_url'] = $insert_title_url;
return (object)$state;
}
Okay this way has got a error while uploading files.
Previously while uploading files (while we dont have 3rd uri segment), it adds extra segments on the 3rd uri to define the state of the image_crud. I modified it to work while having 3rd uri segment.So in this case, I have a two conditions. First while having third uri segment and second, not having third uri segment. So $extra_segments variable is set to false( false for no extra segments,i.e localhost/my_site/admin/pictures in my case) while we don't have 3rd uri segment. For checking 3rd uri segment, the function set_table
of image_crud.php
is modified as:
function set_table($table_name)
{
$this->table_name = $table_name;
if($table_name == $this->ci->uri->segment(3))
{
$this->extra_segments = true;
}
return $this;
}
also variable is to be declared inside image_crud class(on line 47 I did) as:
protected $extra_segments = false;
Now for actual work, getState() function is modified as below. It can be furthur optimised to make it dry and modular.
protected function getState()
{
$rsegments_array = $this->ci->uri->rsegment_array();
if($this->extra_segments == true){
if(isset($rsegments_array[4]) && is_numeric($rsegments_array[4]))
{
$upload_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/upload_file/'.$rsegments_array[3]);
$ajax_list_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/'.$rsegments_array[3].'/ajax_list');
$ordering_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/ordering');
$insert_title_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/insert_title');
$state = array( 'name' => 'list', 'upload_url' => $upload_url, 'relation_value' => $rsegments_array[4]);
$state['ajax'] = isset($rsegments_array[5]) && $rsegments_array[5] == 'ajax_list' ? true : false;
$state['ajax_list_url'] = $ajax_list_url;
$state['ordering_url'] = $ordering_url;
$state['insert_title_url'] = $insert_title_url;
return (object)$state;
}
elseif( (empty($rsegments_array[4]) && empty($this->relation_field)) || (!empty($rsegments_array[4]) && $rsegments_array[4] == 'ajax_list'))
{
$upload_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/upload_file');
$ajax_list_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/ajax_list');
$ordering_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/ordering');
$insert_title_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/insert_title');
$state = array( 'name' => 'list', 'upload_url' => $upload_url);
$state['ajax'] = isset($rsegments_array[4]) && $rsegments_array[4] == 'ajax_list' ? true : false;
$state['ajax_list_url'] = $ajax_list_url;
$state['ordering_url'] = $ordering_url;
$state['insert_title_url'] = $insert_title_url;
return (object)$state;
}
elseif(isset($rsegments_array[4]) && $rsegments_array[4] == 'upload_file')
{
#region Just rename my file
$new_file_name = '';
//$old_file_name = $this->_to_greeklish($_GET['qqfile']);
$old_file_name = $this->_convert_foreign_characters($_GET['qqfile']);
$max = strlen($old_file_name);
for($i=0; $i< $max;$i++)
{
$numMatches = preg_match('/^[A-Za-z0-9.-_]+$/', $old_file_name[$i], $matches);
if($numMatches >0)
{
$new_file_name .= strtolower($old_file_name[$i]);
}
else
{
$new_file_name .= '-';
}
}
$file_name = substr( substr( uniqid(), 9,13).'-'.$new_file_name , 0, 100) ;
#endregion
$results = array( 'name' => 'upload_file', 'file_name' => $file_name);
if(isset($rsegments_array[5]) && is_numeric($rsegments_array[5]))
{
$results['relation_value'] = $rsegments_array[5];
}
return (object)$results;
}
elseif(isset($rsegments_array[4]) && isset($rsegments_array[5]) && $rsegments_array[4] == 'delete_file' && is_numeric($rsegments_array[5]))
{
$state = array( 'name' => 'delete_file', 'id' => $rsegments_array[4]);
return (object)$state;
}
elseif(isset($rsegments_array[4]) && $rsegments_array[4] == 'ordering')
{
$state = array( 'name' => 'ordering');
return (object)$state;
}
elseif(isset($rsegments_array[4]) && $rsegments_array[4] == 'insert_title')
{
$state = array( 'name' => 'insert_title');
return (object)$state;
}
else
{
$upload_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/upload_file');
$ajax_list_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/ajax_list');
$ordering_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/ordering');
$insert_title_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/insert_title');
$state = array( 'name' => 'list', 'upload_url' => $upload_url);
$state['ajax'] = isset($rsegments_array[4]) && $rsegments_array[4] == 'ajax_list' ? true : false;
$state['ajax_list_url'] = $ajax_list_url;
$state['ordering_url'] = $ordering_url;
$state['insert_title_url'] = $insert_title_url;
return (object)$state;
}
}
elseif($this->extra_segments == false)
{
if(isset($rsegments_array[3]) && is_numeric($rsegments_array[3]))
{
$upload_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/upload_file/'.$rsegments_array[3]);
$ajax_list_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/'.$rsegments_array[3].'/ajax_list');
$ordering_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/ordering');
$insert_title_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/insert_title');
$state = array( 'name' => 'list', 'upload_url' => $upload_url, 'relation_value' => $rsegments_array[3]);
$state['ajax'] = isset($rsegments_array[4]) && $rsegments_array[4] == 'ajax_list' ? true : false;
$state['ajax_list_url'] = $ajax_list_url;
$state['ordering_url'] = $ordering_url;
$state['insert_title_url'] = $insert_title_url;
return (object)$state;
}
elseif( (empty($rsegments_array[3]) && empty($this->relation_field)) || (!empty($rsegments_array[3]) && $rsegments_array[3] == 'ajax_list'))
{
$upload_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/upload_file');
$ajax_list_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/ajax_list');
$ordering_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/ordering');
$insert_title_url = site_url($rsegments_array[1].'/'.$rsegments_array[2].'/insert_title');
$state = array( 'name' => 'list', 'upload_url' => $upload_url);
$state['ajax'] = isset($rsegments_array[3]) && $rsegments_array[3] == 'ajax_list' ? true : false;
$state['ajax_list_url'] = $ajax_list_url;
$state['ordering_url'] = $ordering_url;
$state['insert_title_url'] = $insert_title_url;
return (object)$state;
}
elseif(isset($rsegments_array[3]) && $rsegments_array[3] == 'upload_file')
{
#region Just rename my file
$new_file_name = '';
//$old_file_name = $this->_to_greeklish($_GET['qqfile']);
$old_file_name = $this->_convert_foreign_characters($_GET['qqfile']);
$max = strlen($old_file_name);
for($i=0; $i< $max;$i++)
{
$numMatches = preg_match('/^[A-Za-z0-9.-_]+$/', $old_file_name[$i], $matches);
if($numMatches >0)
{
$new_file_name .= strtolower($old_file_name[$i]);
}
else
{
$new_file_name .= '-';
}
}
$file_name = substr( substr( uniqid(), 9,13).'-'.$new_file_name , 0, 100) ;
#endregion
$results = array( 'name' => 'upload_file', 'file_name' => $file_name);
if(isset($rsegments_array[4]) && is_numeric($rsegments_array[4]))
{
$results['relation_value'] = $rsegments_array[4];
}
return (object)$results;
}
elseif(isset($rsegments_array[3]) && isset($rsegments_array[4]) && $rsegments_array[3] == 'delete_file' && is_numeric($rsegments_array[4]))
{
$state = array( 'name' => 'delete_file', 'id' => $rsegments_array[4]);
return (object)$state;
}
elseif(isset($rsegments_array[3]) && $rsegments_array[3] == 'ordering')
{
$state = array( 'name' => 'ordering');
return (object)$state;
}
elseif(isset($rsegments_array[3]) && $rsegments_array[3] == 'insert_title')
{
$state = array( 'name' => 'insert_title');
return (object)$state;
}
}
}
Also url
helper must be autoloaded or loaded to uri checking function.
Source: (StackOverflow)
The function that I need to do deals with DateTime.
public function dtr() {
try {
$crud = new grocery_CRUD();
$crud->set_theme('flexigrid');
$crud->set_table('dtr');
$crud->set_subject('Employee DTR');
$crud->required_fields('employee_id', 'time_started', 'time_ended');
$crud->display_as('employee_id','Name')
->display_as('date','Date')
->display_as('time_started','Time Started')
->display_as('time_ended','Time Ended');
$crud->set_relation('employee_id', 'employee', '{employee_fname} {employee_mname} {employee_lname}');
$crud->columns('employee_id', 'time_started', 'time_ended');
$crud->fields('employee_id', 'time_started', 'time_ended', 'work_time');
$crud->field_type('work_time', 'hidden');
$crud->field_type('normal_time', 'hidden');
$crud->field_type('over_time', 'hidden');
$crud->callback_before_update(array($this,'Working_time'));
$crud->callback_before_insert(array($this,'working_time'));
$output = $crud->render();
$this->output($output);
} catch(Exception $e) {
show_error($e->getMessage().' --- '.$e->getTraceAsString());
}
}
With the help of callback, what I'm going to do is subtract time_started from time_ended. The result of such will be the value of the work_time.
public function working_time($post_array, $primary_key = null) {
$post_array['work_time'] = (STRTOTIME($post_array['time_ended']) - STRTOTIME($post_array['time_started'])) / 3600;
return $post_array;
}
The problem starts here, it returns 0000-00-00 00:00:00 and stores that value into the database. Any help will be highly appreciated.
Source: (StackOverflow)
I have to tables with these schema:
users(id, name, email)
user_details (id, user_id, physical_address, otherinfo)
I would like to display all contents of both tables in one grid using grocery crud
when i try to use set relation on the first table as shown: Note: I have reducted the part that does the rendering of the view;
$crud = new grocery_CRUD();
$crud->set_table('users');
$crud->set_relation('id', 'user_details', '{physical_address} + {otherinfo}');
the values of the id field as well as the referenced table don't appear in the grid, so it does not seem to work when using primary keys.
So I decided to start with the contents of the second table like so:
$crud = new grocery_CRUD();
$crud->set_table('user_details');
$crud->set_relation('user_id', 'users', '{name} + {email}');
This works, but the problem is that the values appear in one column in the grid. I would like to know how i can separate them to different columns and make them editable in separate input fields.
Source: (StackOverflow)
I have seen many people referring to the usage of call_user_func()
to debug the problems in a Grocery_CRUD
callback, but unfortunately no one has come off with a complete example to actually how to use it like where to to place a call to the test function [just_a_test()]
in the controller an example of what I am trying to discover is here.
I am unable to understand where do we call this
just_a_test()
,
- how are we able to pass on the desired parameters using
call_user_func(array($this,'insert_coupon_codes'));
when there are no para's being passed to the just_a_test()
?
- how come the
insert_coupon_codes
will be able to get the desired para's?
Source: (StackOverflow)
Is there anyone knows how to extend the set_relation()
?
Here is the idea, base on the example of Grocery Crud employee and offices table
$crud->set_relation('officeCode','offices','city');
the code output will show all city in dropdown, but I want to extent the set_relation()
to filter all city by state to show in dropdown?
for example I want to show all city of Arizona state in dropdown, Does anyone done this before using grocery crud?
reference example:
Grocery Crud
Source: (StackOverflow)
Is it possible to add a javascript function to the add/edit forms of grocery_CRUD?
E.g. As a user is typing in a particular field when adding or editing a record I want to execute a javascript on the keydown event.
If so, how?
Source: (StackOverflow)