├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config └── config.php ├── migrations ├── 2018_09_30_110932_create_forms_table.php ├── 2018_09_30_142113_create_form_submissions_table.php └── 2018_10_16_000926_add_custom_submit_url_column_to_the_forms_table.php ├── public ├── css │ └── styles.css └── js │ ├── clipboard │ ├── clipboard.js │ └── clipboard.min.js │ ├── create-form.js │ ├── edit-submission-form.js │ ├── footable │ ├── css │ │ ├── footable.standalone.css │ │ └── footable.standalone.min.css │ └── js │ │ ├── footable.js │ │ └── footable.min.js │ ├── jquery-formbuilder │ ├── control_plugins │ │ ├── starRating.min.js │ │ └── textarea.trumbowyg.min.js │ ├── form-builder.min.js │ ├── form-builder.min.js.gz │ ├── form-render.min.js │ └── form-render.min.js.gz │ ├── jquery-ui.min.js │ ├── moment.js │ ├── parsleyjs │ ├── i18n │ │ ├── al.js │ │ ├── ar.js │ │ ├── bg.js │ │ ├── ca.js │ │ ├── cs.extra.js │ │ ├── cs.js │ │ ├── da.js │ │ ├── de.extra.js │ │ ├── de.js │ │ ├── el.extra.js │ │ ├── el.js │ │ ├── en.extra.js │ │ ├── en.js │ │ ├── es.js │ │ ├── et.js │ │ ├── eu.js │ │ ├── fa.js │ │ ├── fi.extra.js │ │ ├── fi.js │ │ ├── fr.extra.js │ │ ├── fr.js │ │ ├── he.extra.js │ │ ├── he.js │ │ ├── hr.extra.js │ │ ├── hr.js │ │ ├── hu.extra.js │ │ ├── hu.js │ │ ├── id.extra.js │ │ ├── id.js │ │ ├── it.extra.js │ │ ├── it.js │ │ ├── ja.extra.js │ │ ├── ja.js │ │ ├── ko.js │ │ ├── lt.extra.js │ │ ├── lt.js │ │ ├── lv.extra.js │ │ ├── lv.js │ │ ├── ms.extra.js │ │ ├── ms.js │ │ ├── nl.extra.js │ │ ├── nl.js │ │ ├── no.js │ │ ├── pl.js │ │ ├── pt-br.js │ │ ├── pt-pt.js │ │ ├── ro.extra.js │ │ ├── ro.js │ │ ├── ru.extra.js │ │ ├── ru.js │ │ ├── sk.extra.js │ │ ├── sk.js │ │ ├── sl.extra.js │ │ ├── sl.js │ │ ├── sq.js │ │ ├── sr.extra.js │ │ ├── sr.js │ │ ├── sv.extra.js │ │ ├── sv.js │ │ ├── th.js │ │ ├── tk.js │ │ ├── tr.js │ │ ├── ua.extra.js │ │ ├── ua.js │ │ ├── uk.extra.js │ │ ├── uk.js │ │ ├── zh_cn.extra.js │ │ ├── zh_cn.js │ │ └── zh_tw.js │ ├── parsley.js │ ├── parsley.js.map │ ├── parsley.min.js │ └── parsley.min.js.map │ ├── preview-form.js │ ├── render-form.js │ ├── script.js │ └── sweetalert.min.js ├── routes.php ├── src ├── Controllers │ ├── FormController.php │ ├── MySubmissionController.php │ ├── RenderFormController.php │ └── SubmissionController.php ├── Events │ └── Form │ │ ├── FormCreated.php │ │ ├── FormDeleted.php │ │ └── FormUpdated.php ├── FormBuilderServiceProvider.php ├── Helper.php ├── Middlewares │ ├── FormAllowSubmissionEdit.php │ └── PublicFormAccess.php ├── Models │ ├── Form.php │ └── Submission.php ├── Requests │ └── SaveFormRequest.php ├── Services │ └── RolesProvider.php └── Traits │ └── HasFormBuilderTraits.php └── views ├── forms ├── create.blade.php ├── edit.blade.php ├── index.blade.php └── show.blade.php ├── layout.blade.php ├── my_submissions ├── edit.blade.php ├── index.blade.php └── show.blade.php ├── render ├── feedback.blade.php └── index.blade.php └── submissions ├── index.blade.php └── show.blade.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | notes.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Laravel Form Builder Package 3 | 4 | Note: THIS PACKAGE MAY NOT B COMPATIBLE WITH LARAVEL 7 or 8. I am no longer maintaining this package so please use the code for reference on how to create a jquery form builder. 5 | 6 | A [Laravel](https://laravel.com) package for creating a drag-and-drop form builder using the [JQuery Form Builder](https://formbuilder.online). 7 | 8 | Demo: [http://demoform.jazmy.com/](http://demoform.jazmy.com/) 9 | 10 | *Note: Features like email, registration and file uploads are disabled in the demo* 11 | 12 | Screenshot: 13 | 14 | ![alt text](http://demoform.jazmy.com/img/formbuilderdemo_screenshot.jpg "Form Builder Screenshot") 15 | 16 | The forms fields are saved as json and stored in your database. A member of your site can create, edit and delete forms. Forms belong to the users that created them and each form has a unique link that can be shared publicly. 17 | 18 | The json version of the form can be used to render the form for users to fill. 19 | 20 | Example Json Form: 21 | ```json 22 | [{"type":"header","subtype":"h1","label":"Demo Form 01"},{"type":"paragraph","subtype":"p","label":"This demo form is a potluck sign-up sheet"},{"type":"text","label":"Name","className":"form-control","name":"name","subtype":"text"},{"type":"radio-group","label":"Food Category","name":"foodcategory","other":true,"values":[{"label":"Appetizer","value":"Appetizer"},{"label":"Beverage","value":"Beverage"},{"label":"Salad","value":"Salad"},{"label":"Main","value":"Main"},{"label":"Dessert","value":"Dessert"}]},{"type":"number","label":"How many will it serve","className":"form-control","name":"numberserved","min":"1","max":"50","step":"1"},{"type":"text","label":"Dish Name","className":"form-control","name":"dishname","subtype":"text"},{"type":"checkbox-group","label":"Dietary Restrictions","description":"Which of the following does your dish contain?","name":"dietaryrestrictions","values":[{"label":"Alcohol","value":"Alcohol"},{"label":"Carbs","value":"Carbs"},{"label":"Dairy","value":"Dairy"},{"label":"Egg","value":"Egg"},{"label":"Fish","value":"Fish"},{"label":"Gluten","value":"Gluten"}]},{"type":"textarea","label":"Comment","className":"form-control","name":"comment","subtype":"textarea"}] 23 | ``` 24 | 25 | Form permission options 26 | + Public - anyone can submit the form without needing to login 27 | + Private - only authenticated members of your site can access the form. Provide the option for users to edit their previous submissions. 28 | 29 | ## Requirements 30 | + Laravel 5.7 31 | + MySQL 32 | + [Laravel Authentication](https://laravel.com/docs/5.7/authentication) - php artisan make:auth 33 | 34 | *If you are looking for a managed web host, with easy laravel site creation, then I highly recommend [Cloudways](https://www.cloudways.com/en/?id=45081). Cloudways will setup a laravel site and mysql database in minutes.* 35 | 36 | ## Dependencies Included with Package 37 | + jQuery UI - User interface actions built on jQuery. [View jQuery ui Documentation](https://jqueryui.com/) 38 | + jQuery Formbuilder - Drag and drop full-featured form editing. [View jQuery Formbuilder Documentation](https://formbuilder.online) 39 | + clipboard.js - Copy text to clipboard. [View Clipboard Documentation](https://clipboardjs.com/) 40 | + parsley.js - JavaScript form validation library. [View Parsley Documentation](http://parsleyjs.org/) 41 | + moment.js - Parse, validate, manipulate, and display dates and times in JavaScript. [View Moment Documentation](https://momentjs.com/) 42 | + footable - A responsive table plugin built on jQuery and made for Bootstrap. [View FooTable Documentation](https://fooplugins.github.io/FooTable/) 43 | + sweetalert - A beautiful replacement for site error/warning/confirmation messages. [View SweetAlert Documentation](https://sweetalert.js.org/) 44 | 45 | ## Installation Instructions 46 | This custom package takes a couple steps to install but I will try to make it as simple as possible. 47 | 48 | ### Step One: 49 | Edit your composer.json file manually or simply type 50 | 51 | ```bash 52 | composer require jazmy/jaz-form-builder 53 | ``` 54 | 55 | ### Step Two: 56 | Ensure you have all your dependencies installed 57 | 58 | ```bash 59 | composer install 60 | ``` 61 | 62 | *Note: The package will automatically register itself using [Laravel's](https://laravel.com) package discovery feature for versions 5.6 and above. This means you do not need to update your config/app.php file.* 63 | 64 | ### Step Three: 65 | We need to add the additional database tables so run the following command 66 | 67 | ```bash 68 | php artisan migrate 69 | ``` 70 | ### Step Four: 71 | Create the form package's configuration file. This will place a formbuilder.php file in your config folder. In the configuration file you can change the url path for the package's routes, layout template to use and script / style output sections. 72 | 73 | Run the following command: 74 | ```bash 75 | php artisan vendor:publish --tag formbuilder-config 76 | ``` 77 | ### Step Five: 78 | Update your blade template file. In the default laravel install the template file is located here: /resources/views/layouts/app.blade.php 79 | 80 | You need to add tags for the new styles and scripts 81 | At the top of the blade file, just above the closing head tag: 82 | ```php 83 | @stack('styles') 84 | ``` 85 | 86 | At the bottom of the blade file, just above the closing the closing body tag: 87 | ```php 88 | @stack('scripts') 89 | ``` 90 | *Note: If you ever need to change which files are called using these @stack values, you can update the config file.* 91 | 92 | 93 | 94 | ### Step Six: 95 | Publish the custom blade view files by running 96 | ```bash 97 | php artisan vendor:publish --tag formbuilder-views 98 | ``` 99 | Publish the public assets by running 100 | ```bash 101 | php artisan vendor:publish --tag formbuilder-public 102 | ``` 103 | Or you can publish everything at once with 104 | ```bash 105 | php artisan vendor:publish --provider="jazmy\FormBuilder\FormBuilderServiceProvider" 106 | ``` 107 | 108 | ### Step Seven: 109 | In order to properly link to attachments, you need to run the storage:link command which makes the storage folder publicly accessibly 110 | 111 | ```bash 112 | php artisan storage:link 113 | ``` 114 | 115 | ## Accessing The Application 116 | Ta Da! You did it! Now to access the form application. 117 | http://your.domain.com/form-builder/forms 118 | 119 | To view submissions: 120 | http://your.domain.com/form-builder/my-submissions 121 | 122 | ## Using The Trait 123 | You can access forms and submissions that belong to a user in your application. To use the trait add a use statement to your user model class. 124 | 125 | ```php 126 | use jazmy\FormBuilder\Traits\HasFormBuilderTraits; 127 | 128 | class User extends Authenticatable 129 | { 130 | use HasFormBuilderTraits; 131 | } 132 | ``` 133 | 134 | You can now access the user's forms and submissions by running 135 | 136 | ```php 137 | $user = User::first(); 138 | 139 | // get the user's forms 140 | $user->forms; 141 | 142 | // get the user's submissions 143 | $user->submissions; 144 | 145 | // or use static methods on the jazmy\FormBuilder\Models\Form class 146 | $user_forms = Form::getForUser($user); // returns a paginated resultset 147 | 148 | // the jazmy\FormBuilder\Models\Submission class also has a static method for getting the submissions 149 | // that belong to a user 150 | $my_submissions = Submission::getForUser($user); // returns a paginated resultset 151 | ``` 152 | 153 | ## Using Events 154 | The package dispatches a number of events when records are created or updated so that you can listen to these events and perform custom tasks in your application's logic 155 | 156 | ## Precautions 157 | 1. Make sure you have a table name users with a colum id {bigSignedInteger} in your database. 158 | 2. Once you have submission(s) on a form , dont attempt to edit the form again bacause it will break the display of earlier submissions 3. 159 | 160 | ## Acknowledgments 161 | Special Thanks to Farayola Oladele, one of the best Laravel programmers on Fiverr: https://www.fiverr.com/harristech He taught me so much and I highly recommend him for assistance on your Laravel projects. 162 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jazmy/jaz-form-builder", 3 | "description": "Laravel Package for Creating a Drag-and-Drop Form Builder Using JQuery Form Builder", 4 | "authors": [ 5 | { 6 | "name": "Jasmine Robinson", 7 | "email": "jaz@jazmy.com" 8 | } 9 | ], 10 | "require": { 11 | }, 12 | "autoload": { 13 | "psr-4": { 14 | "jazmy\\FormBuilder\\": "src/" 15 | } 16 | }, 17 | "extra": { 18 | "laravel": { 19 | "providers": [ 20 | "jazmy\\FormBuilder\\FormBuilderServiceProvider" 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | '/form-builder', 13 | 14 | /** 15 | * Template layout file. This is the path to the layout file your application uses 16 | */ 17 | 'layout_file' => 'layouts.app', 18 | 19 | /** 20 | * The stack section in the layout file to output js content 21 | * Define something like @stack('stack_name') and provide the 'stack_name here' 22 | */ 23 | 'layout_js_stack' => 'scripts', 24 | 25 | /** 26 | * The stack section in the layout file to output css content 27 | */ 28 | 'layout_css_stack' => 'styles', 29 | 30 | /** 31 | * The class that will provide the roles we will display on form create or edit pages? 32 | */ 33 | 'roles_provider' => jazmy\FormBuilder\Services\RolesProvider::class, 34 | 35 | /** 36 | * Models used in form builder 37 | */ 38 | 'models' => [ 39 | 'user' => \App\User::class, 40 | ], 41 | ]; 42 | -------------------------------------------------------------------------------- /migrations/2018_09_30_110932_create_forms_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 23 | 24 | $table->unsignedBigInteger('user_id')->nullable(); 25 | 26 | $table->string('name'); 27 | $table->string('visibility'); 28 | $table->boolean('allows_edit')->default(false); 29 | 30 | $table->string('identifier')->unique(); 31 | $table->text('form_builder_json')->nullable(); 32 | 33 | $table->softDeletes(); 34 | $table->timestamps(); 35 | 36 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | * 43 | * @return void 44 | */ 45 | public function down() 46 | { 47 | Schema::dropIfExists('forms'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /migrations/2018_09_30_142113_create_form_submissions_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 23 | 24 | $table->unsignedInteger('form_id'); 25 | 26 | $table->unsignedBigInteger('user_id')->nullable(); 27 | 28 | $table->text('content'); 29 | 30 | $table->timestamps(); 31 | 32 | $table->foreign('form_id')->references('id')->on('forms')->onDelete('CASCADE'); 33 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 34 | }); 35 | } 36 | 37 | /** 38 | * Reverse the migrations. 39 | * 40 | * @return void 41 | */ 42 | public function down() 43 | { 44 | Schema::dropIfExists('form_submissions'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /migrations/2018_10_16_000926_add_custom_submit_url_column_to_the_forms_table.php: -------------------------------------------------------------------------------- 1 | string('custom_submit_url')->nullable()->after('form_builder_json'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::table('forms', function (Blueprint $table) { 34 | $table->dropColumn('custom_submit_url'); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | .fb-editor { 2 | background-image: #f2f2f2; 3 | /*background: url('http://formbuilder.readthedocs.io/en/latest/img/noise.png');*/ 4 | } 5 | .five { 6 | width: 5% !important; 7 | } 8 | .ten { 9 | width: 10% !important; 10 | } 11 | .fifteen { 12 | width: 15% !important; 13 | } 14 | .twenty { 15 | width: 20% !important; 16 | } 17 | .twenty-five { 18 | width: 25% !important; 19 | } 20 | .d-inline-block { 21 | display: inline-block !important; 22 | } 23 | -------------------------------------------------------------------------------- /public/js/clipboard/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v1.6.1 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Clipboard=e()}}(function(){var e,t,n;return function e(t,n,o){function i(a,c){if(!n[a]){if(!t[a]){var l="function"==typeof require&&require;if(!c&&l)return l(a,!0);if(r)return r(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var s=n[a]={exports:{}};t[a][0].call(s.exports,function(e){var n=t[a][1][e];return i(n?n:e)},s,s.exports,e,t,n,o)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;a0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function e(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function e(){var t=this,n="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=document.body.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[n?"right":"left"]="-9999px";var o=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=o+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,document.body.appendChild(this.fakeElem),this.selectedText=(0,i.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function e(){this.fakeHandler&&(document.body.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(document.body.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function e(){this.selectedText=(0,i.default)(this.target),this.copyText()}},{key:"copyText",value:function e(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function e(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function e(){this.target&&this.target.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function e(){this.removeFake()}},{key:"action",set:function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function e(){return this._action}},{key:"target",set:function e(t){if(void 0!==t){if(!t||"object"!==("undefined"==typeof t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function e(){return this._target}}]),e}();e.exports=c})},{select:5}],8:[function(t,n,o){!function(i,r){if("function"==typeof e&&e.amd)e(["module","./clipboard-action","tiny-emitter","good-listener"],r);else if("undefined"!=typeof o)r(n,t("./clipboard-action"),t("tiny-emitter"),t("good-listener"));else{var a={exports:{}};r(a,i.clipboardAction,i.tinyEmitter,i.goodListener),i.clipboard=a.exports}}(this,function(e,t,n,o){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t){var n="data-clipboard-"+e;if(t.hasAttribute(n))return t.getAttribute(n)}var u=i(t),s=i(n),f=i(o),d=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText}},{key:"listenClick",value:function e(t){var n=this;this.listener=(0,f.default)(t,"click",function(e){return n.onClick(e)})}},{key:"onClick",value:function e(t){var n=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new u.default({action:this.action(n),target:this.target(n),text:this.text(n),trigger:n,emitter:this})}},{key:"defaultAction",value:function e(t){return l("action",t)}},{key:"defaultTarget",value:function e(t){var n=l("target",t);if(n)return document.querySelector(n)}},{key:"defaultText",value:function e(t){return l("text",t)}},{key:"destroy",value:function e(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],n="string"==typeof t?[t]:t,o=!!document.queryCommandSupported;return n.forEach(function(e){o=o&&!!document.queryCommandSupported(e)}),o}}]),t}(s.default);e.exports=h})},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)}); -------------------------------------------------------------------------------- /public/js/create-form.js: -------------------------------------------------------------------------------- 1 | jQuery(function() { 2 | $('#visibility').change(function(e) { 3 | e.preventDefault() 4 | var ref = $(this) 5 | 6 | if (ref.val() == "" || ref.val() == 'PUBLIC') { 7 | $('#allows_edit_DIV').hide() 8 | } else { 9 | $('#allows_edit_DIV').slideDown() 10 | $('#allows_edit').val('0') 11 | } 12 | }); 13 | 14 | // create the form editor 15 | var fbEditor = $(document.getElementById('fb-editor')) 16 | var formBuilder 17 | var fbOptions = { 18 | dataType: 'json', 19 | formData: window._form_builder_content ? window._form_builder_content : '', 20 | controlOrder: [ 21 | 'header', 22 | 'paragraph', 23 | 'text', 24 | 'textarea', 25 | 'select', 26 | 'number', 27 | 'date', 28 | 'autocomplete', 29 | 'file', 30 | ], 31 | disableFields: [ 32 | 'button', // buttons are not needed since we are the one handling the submission 33 | ], // field types that should not be shown 34 | disabledAttrs: [ 35 | // 'access', 36 | ], 37 | typeUserDisabledAttrs: { 38 | 'file': [ 39 | 'multiple', 40 | 'subtype', 41 | ], 42 | 'checkbox-group': [ 43 | 'other', 44 | ], 45 | }, 46 | showActionButtons: false, // show the actions buttons at the bottom 47 | disabledActionButtons: ['data'], // get rid of the 'getData' button 48 | sortableControls: false, // allow users to re-order the controls to their liking 49 | editOnAdd: false, 50 | fieldRemoveWarn: false, 51 | roles: window.FormBuilder.form_roles || {}, 52 | notify: { 53 | error: function(message) { 54 | return swal('Error', message, 'error') 55 | }, 56 | success: function(message) { 57 | return swal('Success', message, 'success') 58 | }, 59 | warning: function(message) { 60 | return swal('Warning', message, 'warning'); 61 | } 62 | }, 63 | onSave: function() { 64 | // var formData = formBuilder.formData 65 | // console.log(formData) 66 | }, 67 | } 68 | 69 | formBuilder = fbEditor.formBuilder(fbOptions) 70 | 71 | var fbClearBtn = $('.fb-clear-btn') 72 | var fbShowDataBtn = $('.fb-showdata-btn') 73 | var fbSaveBtn = $('.fb-save-btn') 74 | 75 | // setup the buttons to respond to save and clear 76 | fbClearBtn.click(function(e) { 77 | e.preventDefault() 78 | 79 | if (! formBuilder.actions.getData().length) return 80 | 81 | sConfirm("Are you sure you want to clear all fields from the form?", function() { 82 | formBuilder.actions.clearFields() 83 | }) 84 | }); 85 | 86 | fbShowDataBtn.click(function(e) { 87 | e.preventDefault() 88 | formBuilder.actions.showData() 89 | }); 90 | 91 | fbSaveBtn.click(function(e) { 92 | e.preventDefault() 93 | 94 | var form = $('#createFormForm') 95 | 96 | // make sure the form is valid 97 | if ( ! form.parsley().validate() ) return 98 | 99 | // make sure the form builder is not empty 100 | if (! formBuilder.actions.getData().length) { 101 | swal({ 102 | title: "Error", 103 | text: "The form builder cannot be empty", 104 | icon: 'error', 105 | }) 106 | return 107 | } 108 | 109 | // ask for confirmation 110 | sConfirm("Save this form definition?", function() { 111 | fbSaveBtn.attr('disabled', 'disabled'); 112 | fbClearBtn.attr('disabled', 'disabled'); 113 | 114 | var formBuilderJSONData = formBuilder.actions.getData('json') 115 | // console.log(formBuilderJSONData) 116 | // var formBuilderArrayData = formBuilder.actions.getData() 117 | // console.log(formBuilderArrayData) 118 | 119 | var postData = { 120 | name: $('#name').val(), 121 | visibility: $('#visibility').val(), 122 | allows_edit: $('#allows_edit').val(), 123 | form_builder_json: formBuilderJSONData, 124 | _token: window.FormBuilder.csrfToken 125 | } 126 | 127 | var method = form.data('formMethod') ? 'PUT' : 'POST' 128 | jQuery.ajax({ 129 | url: form.attr('action'), 130 | processData: true, 131 | data: postData, 132 | method: method, 133 | cache: false, 134 | }) 135 | .then(function(response) { 136 | fbSaveBtn.removeAttr('disabled') 137 | fbClearBtn.removeAttr('disabled') 138 | 139 | if (response.success) { 140 | // the form has been created 141 | // send the user to the form index page 142 | swal({ 143 | title: "Form Saved!", 144 | text: response.details || '', 145 | icon: 'success', 146 | }) 147 | 148 | setTimeout(function() { 149 | window.location = response.dest 150 | }, 1500); 151 | 152 | // clear out the form 153 | // $('#name').val('') 154 | // $('#visibility').val('') 155 | // $('#allows_edit').val('0') 156 | } else { 157 | swal({ 158 | title: "Error", 159 | text: response.details || 'Error', 160 | icon: 'error', 161 | }) 162 | } 163 | }, function(error) { 164 | handleAjaxError(error) 165 | 166 | fbSaveBtn.removeAttr('disabled') 167 | fbClearBtn.removeAttr('disabled') 168 | }) 169 | }) 170 | 171 | }) 172 | 173 | // show the clear and save buttons 174 | $('#fb-editor-footer').slideDown() 175 | }) 176 | -------------------------------------------------------------------------------- /public/js/edit-submission-form.js: -------------------------------------------------------------------------------- 1 | jQuery(function() { 2 | var fbRenderOptions = { 3 | container: false, 4 | dataType: 'json', 5 | formData: window._form_builder_content ? window._form_builder_content : '', 6 | render: true, 7 | } 8 | 9 | $('#fb-render').formRender(fbRenderOptions) 10 | }) -------------------------------------------------------------------------------- /public/js/jquery-formbuilder/control_plugins/starRating.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};n.m=t,n.c=e,n.i=function(t){return t},n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="/assets/js/control_plugins",n(n.s=0)}([function(){"use strict";function t(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")}function n(t,n){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!n||"object"!=typeof n&&"function"!=typeof n?t:n}function e(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Super expression must either be null or a function, not "+typeof n);t.prototype=Object.create(n&&n.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),n&&(Object.setPrototypeOf?Object.setPrototypeOf(t,n):t.__proto__=n)}var r=function(){function t(t,n){for(var e,r=0;r '+ref.data('message')) 66 | 67 | setTimeout(function() { 68 | ref.html(' '+ref.data('original')) 69 | }, 1200); 70 | }); 71 | } 72 | 73 | jQuery(function($){ 74 | $('.table').footable({ 75 | "filtering": { 76 | "enabled": true, 77 | }, 78 | "paging": { 79 | "enabled": true, 80 | "size": 100, 81 | "position": "right", 82 | }, 83 | "sorting": { 84 | "enabled": true, 85 | }, 86 | }); 87 | }); 88 | 89 | function initilizeConfirmListeners() { 90 | $('.confirm').click( function( e ) { 91 | e.preventDefault() 92 | 93 | var ref = $(this) 94 | var data = ref.data() 95 | 96 | var message = data.message ? data.message : ref.attr('title') 97 | 98 | sConfirm(message, function() { 99 | window.location = ref.attr('href') 100 | }) 101 | }) 102 | 103 | $('.confirm-form').click( function( e ) { 104 | e.preventDefault() 105 | 106 | var ref = $(this) 107 | var data = ref.data() 108 | 109 | var message = data.message ? data.message : ref.attr('title') 110 | 111 | var form = $('#'+ref.data('form')) 112 | 113 | if ( ! form.parsley().validate() ) return 114 | 115 | sConfirm(message, function() { 116 | form.submit() 117 | }) 118 | }) 119 | } 120 | 121 | $(function () { 122 | $('[data-toggle="tooltip"]').tooltip() 123 | 124 | setTimeout(function() { 125 | initilizeConfirmListeners() 126 | }, 1000); 127 | }) 128 | -------------------------------------------------------------------------------- /routes.php: -------------------------------------------------------------------------------- 1 | prefix(config('formbuilder.url_path', '/form-builder')) 5 | ->namespace('jazmy\FormBuilder\Controllers') 6 | ->name('formbuilder::') 7 | ->group(function () { 8 | Route::redirect('/', url(config('formbuilder.url_path', '/form-builder').'/forms')); 9 | 10 | /** 11 | * Public form url 12 | */ 13 | Route::get('/form/{identifier}', 'RenderFormController@render')->name('form.render'); 14 | Route::post('/form/{identifier}', 'RenderFormController@submit')->name('form.submit'); 15 | Route::get('/form/{identifier}/feedback', 'RenderFormController@feedback')->name('form.feedback'); 16 | 17 | /** 18 | * My submission routes 19 | */ 20 | Route::resource('/my-submissions', 'MySubmissionController'); 21 | 22 | /** 23 | * Form submission management routes 24 | */ 25 | Route::name('forms.') 26 | ->prefix('/forms/{fid}') 27 | ->group(function () { 28 | Route::resource('/submissions', 'SubmissionController'); 29 | }); 30 | 31 | /** 32 | * Form management routes 33 | */ 34 | Route::resource('/forms', 'FormController'); 35 | }); 36 | -------------------------------------------------------------------------------- /src/Controllers/FormController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 31 | } 32 | 33 | /** 34 | * Display a listing of the resource. 35 | * 36 | * @return \Illuminate\Http\Response 37 | */ 38 | public function index() 39 | { 40 | $pageTitle = "Forms"; 41 | 42 | $forms = Form::getForUser(auth()->user()); 43 | 44 | return view('formbuilder::forms.index', compact('pageTitle', 'forms')); 45 | } 46 | 47 | /** 48 | * Show the form for creating a new resource. 49 | * 50 | * @return \Illuminate\Http\Response 51 | */ 52 | public function create() 53 | { 54 | $pageTitle = "Create New Form"; 55 | 56 | $saveURL = route('formbuilder::forms.store'); 57 | 58 | // get the roles to use to populate the make the 'Access' section of the form builder work 59 | $form_roles = Helper::getConfiguredRoles(); 60 | 61 | return view('formbuilder::forms.create', compact('pageTitle', 'saveURL', 'form_roles')); 62 | } 63 | 64 | /** 65 | * Store a newly created resource in storage. 66 | * 67 | * @param jazmy\FormBuilder\Requests\SaveFormRequest $request 68 | * @return \Illuminate\Http\Response 69 | */ 70 | public function store(SaveFormRequest $request) 71 | { 72 | $user = $request->user(); 73 | 74 | $input = $request->merge(['user_id' => $user->id])->except('_token'); 75 | 76 | DB::beginTransaction(); 77 | 78 | // generate a random identifier 79 | $input['identifier'] = $user->id.'-'.Helper::randomString(20); 80 | $created = Form::create($input); 81 | 82 | try { 83 | // dispatch the event 84 | event(new FormCreated($created)); 85 | 86 | DB::commit(); 87 | 88 | return response() 89 | ->json([ 90 | 'success' => true, 91 | 'details' => 'Form successfully created!', 92 | 'dest' => route('formbuilder::forms.index'), 93 | ]); 94 | } catch (Throwable $e) { 95 | info($e); 96 | 97 | DB::rollback(); 98 | 99 | return response()->json(['success' => false, 'details' => 'Failed to create the form.']); 100 | } 101 | } 102 | 103 | /** 104 | * Display the specified resource. 105 | * 106 | * @param int $id 107 | * @return \Illuminate\Http\Response 108 | */ 109 | public function show($id) 110 | { 111 | $user = auth()->user(); 112 | $form = Form::where(['user_id' => $user->id, 'id' => $id]) 113 | ->with('user') 114 | ->withCount('submissions') 115 | ->firstOrFail(); 116 | 117 | $pageTitle = "Preview Form"; 118 | 119 | return view('formbuilder::forms.show', compact('pageTitle', 'form')); 120 | } 121 | 122 | /** 123 | * Show the form for editing the specified resource. 124 | * 125 | * @param int $id 126 | * @return \Illuminate\Http\Response 127 | */ 128 | public function edit($id) 129 | { 130 | $user = auth()->user(); 131 | 132 | $form = Form::where(['user_id' => $user->id, 'id' => $id])->firstOrFail(); 133 | 134 | $pageTitle = 'Edit Form'; 135 | 136 | $saveURL = route('formbuilder::forms.update', $form); 137 | 138 | // get the roles to use to populate the make the 'Access' section of the form builder work 139 | $form_roles = Helper::getConfiguredRoles(); 140 | 141 | return view('formbuilder::forms.edit', compact('form', 'pageTitle', 'saveURL', 'form_roles')); 142 | } 143 | 144 | /** 145 | * Update the specified resource in storage. 146 | * 147 | * @param jazmy\FormBuilder\Requests\SaveFormRequest $request 148 | * @param int $id 149 | * @return \Illuminate\Http\Response 150 | */ 151 | public function update(SaveFormRequest $request, $id) 152 | { 153 | $user = auth()->user(); 154 | $form = Form::where(['user_id' => $user->id, 'id' => $id])->firstOrFail(); 155 | 156 | $input = $request->except('_token'); 157 | 158 | if ($form->update($input)) { 159 | // dispatch the event 160 | event(new FormUpdated($form)); 161 | 162 | return response() 163 | ->json([ 164 | 'success' => true, 165 | 'details' => 'Form successfully updated!', 166 | 'dest' => route('formbuilder::forms.index'), 167 | ]); 168 | } else { 169 | response()->json(['success' => false, 'details' => 'Failed to update the form.']); 170 | } 171 | } 172 | 173 | /** 174 | * Remove the specified resource from storage. 175 | * 176 | * @param int $id 177 | * @return \Illuminate\Http\Response 178 | */ 179 | public function destroy($id) 180 | { 181 | $user = auth()->user(); 182 | $form = Form::where(['user_id' => $user->id, 'id' => $id])->firstOrFail(); 183 | $form->delete(); 184 | 185 | // dispatch the event 186 | event(new FormDeleted($form)); 187 | 188 | return back()->with('success', "'{$form->name}' deleted."); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Controllers/MySubmissionController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 26 | 27 | // only allow submission edit on forms that allow it 28 | $this->middleware('submisson-editable')->only(['edit', 'update']); 29 | } 30 | 31 | /** 32 | * Display a listing of the resource. 33 | * 34 | * @return \Illuminate\Http\Response 35 | */ 36 | public function index() 37 | { 38 | $user = auth()->user(); 39 | 40 | $submissions = Submission::getForUser($user); 41 | 42 | $pageTitle = "My Submissions"; 43 | 44 | return view('formbuilder::my_submissions.index', compact('submissions', 'pageTitle')); 45 | } 46 | 47 | /** 48 | * Display the specified resource. 49 | * 50 | * @param int $id 51 | * @return \Illuminate\Http\Response 52 | */ 53 | public function show($id) 54 | { 55 | $user = auth()->user(); 56 | $submission = Submission::where(['user_id' => $user->id, 'id' => $id]) 57 | ->with('form') 58 | ->firstOrFail(); 59 | 60 | $form_headers = $submission->form->getEntriesHeader(); 61 | 62 | $pageTitle = "View Submission"; 63 | 64 | return view('formbuilder::my_submissions.show', compact('submission', 'pageTitle', 'form_headers')); 65 | } 66 | 67 | /** 68 | * Show the form for editing the specified resource. 69 | * 70 | * @param int $id 71 | * @return \Illuminate\Http\Response 72 | */ 73 | public function edit($id) 74 | { 75 | $user = auth()->user(); 76 | $submission = Submission::where(['user_id' => $user->id, 'id' => $id]) 77 | ->with('form') 78 | ->firstOrFail(); 79 | 80 | // load up my current submissions into the form json data so that the 81 | // form is pre-filled with the previous submission we are trying to edit. 82 | $submission->loadSubmissionIntoFormJson(); 83 | 84 | $pageTitle = "Edit My Submission for '{$submission->form->name}'"; 85 | 86 | return view('formbuilder::my_submissions.edit', compact('submission', 'pageTitle')); 87 | } 88 | 89 | /** 90 | * Update the specified resource in storage. 91 | * 92 | * @param \Illuminate\Http\Request $request 93 | * @param int $id 94 | * @return \Illuminate\Http\Response 95 | */ 96 | public function update(Request $request, $id) 97 | { 98 | $user = auth()->user(); 99 | $submission = Submission::where(['user_id' => $user->id, 'id' => $id])->firstOrFail(); 100 | 101 | DB::beginTransaction(); 102 | 103 | try { 104 | $input = $request->except(['_token', '_method']); 105 | 106 | // check if files were uploaded and process them 107 | $uploadedFiles = $request->allFiles(); 108 | foreach ($uploadedFiles as $key => $file) { 109 | // store the file and set it's path to the value of the key holding it 110 | if ($file->isValid()) { 111 | $input[$key] = $file->store('fb_uploads', 'public'); 112 | } 113 | } 114 | 115 | $submission->update(['content' => $input]); 116 | 117 | DB::commit(); 118 | 119 | return redirect() 120 | ->route('formbuilder::my-submissions.index') 121 | ->with('success', 'Submission updated.'); 122 | } catch (Throwable $e) { 123 | info($e); 124 | 125 | DB::rollback(); 126 | 127 | return back()->withInput()->with('error', Helper::wtf()); 128 | } 129 | } 130 | 131 | /** 132 | * Remove the specified resource from storage. 133 | * 134 | * @param int $id 135 | * @return \Illuminate\Http\Response 136 | */ 137 | public function destroy($id) 138 | { 139 | $user = auth()->user(); 140 | $submission = Submission::where(['user_id' => $user->id, 'id' => $id])->firstOrFail(); 141 | $submission->delete(); 142 | 143 | return redirect() 144 | ->route('formbuilder::my-submissions.index') 145 | ->with('success', 'Submission deleted!'); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Controllers/RenderFormController.php: -------------------------------------------------------------------------------- 1 | middleware('public-form-access'); 27 | } 28 | 29 | /** 30 | * Render the form so a user can fill it 31 | * 32 | * @param string $identifier 33 | * @return Response 34 | */ 35 | public function render($identifier) 36 | { 37 | $form = Form::where('identifier', $identifier)->firstOrFail(); 38 | 39 | $pageTitle = "{$form->name}"; 40 | 41 | return view('formbuilder::render.index', compact('form', 'pageTitle')); 42 | } 43 | 44 | /** 45 | * Process the form submission 46 | * 47 | * @param Request $request 48 | * @param string $identifier 49 | * @return Response 50 | */ 51 | public function submit(Request $request, $identifier) 52 | { 53 | $form = Form::where('identifier', $identifier)->firstOrFail(); 54 | 55 | DB::beginTransaction(); 56 | 57 | try { 58 | $input = $request->except('_token'); 59 | 60 | // check if files were uploaded and process them 61 | $uploadedFiles = $request->allFiles(); 62 | foreach ($uploadedFiles as $key => $file) { 63 | // store the file and set it's path to the value of the key holding it 64 | if ($file->isValid()) { 65 | $input[$key] = $file->store('fb_uploads', 'public'); 66 | } 67 | } 68 | 69 | $user_id = auth()->user()->id ?? null; 70 | 71 | $form->submissions()->create([ 72 | 'user_id' => $user_id, 73 | 'content' => $input, 74 | ]); 75 | 76 | DB::commit(); 77 | 78 | return redirect() 79 | ->route('formbuilder::form.feedback', $identifier) 80 | ->with('success', 'Form successfully submitted.'); 81 | } catch (Throwable $e) { 82 | info($e); 83 | 84 | DB::rollback(); 85 | 86 | return back()->withInput()->with('error', Helper::wtf()); 87 | } 88 | } 89 | 90 | /** 91 | * Display a feedback page 92 | * 93 | * @param string $identifier 94 | * @return Response 95 | */ 96 | public function feedback($identifier) 97 | { 98 | $form = Form::where('identifier', $identifier)->firstOrFail(); 99 | 100 | $pageTitle = "Form Submitted!"; 101 | 102 | return view('formbuilder::render.feedback', compact('form', 'pageTitle')); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Controllers/SubmissionController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 26 | } 27 | 28 | /** 29 | * Display a listing of the resource. 30 | * 31 | * @param integer $form_id 32 | * @return \Illuminate\Http\Response 33 | */ 34 | public function index($form_id) 35 | { 36 | $user = auth()->user(); 37 | 38 | $form = Form::where(['user_id' => $user->id, 'id' => $form_id]) 39 | ->with(['user']) 40 | ->firstOrFail(); 41 | 42 | $submissions = $form->submissions() 43 | ->with('user') 44 | ->latest() 45 | ->paginate(100); 46 | 47 | // get the header for the entries in the form 48 | $form_headers = $form->getEntriesHeader(); 49 | 50 | $pageTitle = "Submitted Entries for '{$form->name}'"; 51 | 52 | return view( 53 | 'formbuilder::submissions.index', 54 | compact('form', 'submissions', 'pageTitle', 'form_headers') 55 | ); 56 | } 57 | 58 | /** 59 | * Display the specified resource. 60 | * 61 | * @param int $form_id 62 | * @param integer $submission_id 63 | * @return \Illuminate\Http\Response 64 | */ 65 | public function show($form_id, $submission_id) 66 | { 67 | $submission = Submission::with('user', 'form') 68 | ->where([ 69 | 'form_id' => $form_id, 70 | 'id' => $submission_id, 71 | ]) 72 | ->firstOrFail(); 73 | 74 | $form_headers = $submission->form->getEntriesHeader(); 75 | 76 | $pageTitle = "View Submission"; 77 | 78 | return view('formbuilder::submissions.show', compact('pageTitle', 'submission', 'form_headers')); 79 | } 80 | 81 | /** 82 | * Remove the specified resource from storage. 83 | * 84 | * @param int $form_id 85 | * @param int $submission_id 86 | * @return \Illuminate\Http\Response 87 | */ 88 | public function destroy($form_id, $submission_id) 89 | { 90 | $submission = Submission::where(['form_id' => $form_id, 'id' => $submission_id])->firstOrFail(); 91 | $submission->delete(); 92 | 93 | return redirect() 94 | ->route('formbuilder::forms.submissions.index', $form_id) 95 | ->with('success', 'Submission successfully deleted.'); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Events/Form/FormCreated.php: -------------------------------------------------------------------------------- 1 | form = $form; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Events/Form/FormDeleted.php: -------------------------------------------------------------------------------- 1 | form = $form; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Events/Form/FormUpdated.php: -------------------------------------------------------------------------------- 1 | form = $form; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/FormBuilderServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom( 26 | __DIR__.'/../config/config.php', 'formbuilder' 27 | ); 28 | } 29 | 30 | /** 31 | * Perform post-registration booting of services. 32 | * 33 | * @return void 34 | */ 35 | public function boot() 36 | { 37 | // load custom route overrides 38 | $this->loadRoutesFrom( __DIR__.'/../routes.php' ); 39 | 40 | // register the middleware 41 | Route::aliasMiddleware('public-form-access', PublicFormAccess::class); 42 | Route::aliasMiddleware('submisson-editable', FormAllowSubmissionEdit::class); 43 | 44 | // load migrations 45 | $this->loadMigrationsFrom( __DIR__.'/../migrations' ); 46 | 47 | // load the views 48 | $this->loadViewsFrom( __DIR__.'/../views', 'formbuilder' ); 49 | 50 | // publish config files 51 | $this->publishes([ 52 | __DIR__.'/../config/config.php' => config_path('formbuilder.php', 'formbuilder'), 53 | ], 'formbuilder-config'); 54 | 55 | // publish view files 56 | $this->publishes([ 57 | __DIR__.'/../views' => resource_path('views/vendor/formbuilder', 'formbuilder::'), 58 | ], 'formbuilder-views'); 59 | 60 | // publish public assets 61 | $this->publishes([ 62 | __DIR__.'/../public' => public_path('vendor/formbuilder'), 63 | ], 'formbuilder-public'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Helper.php: -------------------------------------------------------------------------------- 1 | route('my_submission'); 25 | 26 | $user = $request->user(); 27 | $submission = Submission::where(['user_id' => $user->id, 'id' => $submission_id])->firstOrFail(); 28 | 29 | if (! $submission->form->allowsEdit()) { 30 | // this form does not allow edit 31 | return redirect() 32 | ->route('formbuilder::my-submissions.show', $submission->id) 33 | ->with('error', "Form '{$submission->form->name}' does not allow submission edit."); 34 | } 35 | 36 | return $next($request); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Middlewares/PublicFormAccess.php: -------------------------------------------------------------------------------- 1 | route('identifier'); 25 | 26 | $form = Form::where('identifier', $identifier)->firstOrFail(); 27 | 28 | if ($form->isPrivate()) { 29 | // the user must be authenticated 30 | if (! auth()->check()) { 31 | return redirect() 32 | ->route('login') 33 | ->with('error', "Form '{$form->name}' requires you to login before you can access it."); 34 | } 35 | } 36 | 37 | return $next($request); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Models/Form.php: -------------------------------------------------------------------------------- 1 | self::FORM_PUBLIC, 'name' => self::FORM_PUBLIC.' (available to all users)'], 25 | ['id' => self::FORM_PRIVATE, 'name' => self::FORM_PRIVATE.' (available to only logged in users)'], 26 | ]; 27 | 28 | /** 29 | * The attributes that are not assignable. 30 | * 31 | * @var array 32 | */ 33 | protected $guarded = [ 34 | 'id', 'created_at', 'updated_at', 35 | ]; 36 | 37 | /** 38 | * The attributes that should be casted to another data type 39 | * 40 | * @var array 41 | */ 42 | protected $casts = [ 43 | 'allows_edit' => 'boolean', 44 | ]; 45 | 46 | /** 47 | * A Form belongs to a User 48 | * 49 | * @return Illuminate\Database\Eloquent\Relations\BelongsTo 50 | */ 51 | public function user() 52 | { 53 | return $this->belongsTo(config('formbuilder.models.user')); 54 | } 55 | 56 | /** 57 | * A Form has many Submission 58 | * 59 | * @return Illuminate\Database\Eloquent\Relations\HasMany 60 | */ 61 | public function submissions() 62 | { 63 | return $this->hasMany(Submission::class); 64 | } 65 | 66 | /** 67 | * Scope publicly visible forms 68 | * 69 | * @param Illuminate\Database\Eloquent\Builder $query 70 | * @return Illuminate\Database\Eloquent\Builder 71 | */ 72 | public function scopePublic($query) 73 | { 74 | return $query->where('visibility', self::FORM_PUBLIC); 75 | } 76 | 77 | /** 78 | * Get a json decoded version of the form_builder_json string 79 | * 80 | * @return array 81 | */ 82 | public function getFormBuilderArrayAttribute($value) : array 83 | { 84 | return json_decode($this->attributes['form_builder_json'], true); 85 | } 86 | 87 | /** 88 | * Check if the form allows edit 89 | * 90 | * @return boolean 91 | */ 92 | public function allowsEdit() : bool 93 | { 94 | return $this->allows_edit; 95 | } 96 | 97 | /** 98 | * Check if the form has public visibility 99 | * 100 | * @return boolean 101 | */ 102 | public function isPublic() : bool 103 | { 104 | return $this->visibility === self::FORM_PUBLIC; 105 | } 106 | 107 | /** 108 | * Check if the form has private visibility 109 | * 110 | * @return boolean 111 | */ 112 | public function isPrivate() : bool 113 | { 114 | return $this->visibility === self::FORM_PRIVATE; 115 | } 116 | 117 | /** 118 | * Check if the form has custom submit url 119 | * 120 | * @return boolean 121 | */ 122 | public function hasCustomSubmitUrl() : bool 123 | { 124 | return ! empty($this->custom_submit_url); 125 | } 126 | 127 | /** 128 | * Get the forms that belong to the provided user 129 | * 130 | * @param User $user 131 | * @return Illuminate\Database\Eloquent\Collection 132 | */ 133 | public static function getForUser($user) 134 | { 135 | return static::where('user_id', $user->id) 136 | ->withCount('submissions') 137 | ->latest() 138 | ->paginate(100); 139 | } 140 | 141 | /** 142 | * Get an array containing the name of the fields in the form and their label 143 | * 144 | * @return Illuminate\Support\Collection 145 | */ 146 | public function getEntriesHeader() : Collection 147 | { 148 | return collect($this->form_builder_array) 149 | ->filter(function ($entry) { 150 | return ! empty($entry['name']); 151 | }) 152 | ->map(function ($entry) { 153 | return [ 154 | 'name' => $entry['name'], 155 | 'label' => $entry['label'] ?? null, 156 | 'type' => $entry['type'] ?? null, 157 | ]; 158 | }); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Models/Submission.php: -------------------------------------------------------------------------------- 1 | 'array', 40 | ]; 41 | 42 | /** 43 | * A Submission may belong to a User 44 | * 45 | * @return Illuminate\Database\Eloquent\Relations\BelongsTo 46 | */ 47 | public function user() 48 | { 49 | return $this->belongsTo(config('formbuilder.models.user')); 50 | } 51 | 52 | /** 53 | * A Submission belongs to a Form 54 | * 55 | * @return Illuminate\Database\Eloquent\Relations\BelongsTo 56 | */ 57 | public function form() 58 | { 59 | return $this->belongsTo(Form::class); 60 | } 61 | 62 | /** 63 | * Get the forms that belong to the provided user 64 | * 65 | * @param User $user 66 | * @return Illuminate\Contracts\Pagination\LengthAwarePaginator 67 | */ 68 | public static function getForUser($user) 69 | { 70 | return static::where('user_id', $user->id)->with('form')->latest()->paginate(100); 71 | } 72 | 73 | /** 74 | * Load the values the user provided in this submission into the json of the form 75 | * so that when we render the form, the user's previous values are pre-filled 76 | * 77 | * @return void 78 | */ 79 | public function loadSubmissionIntoFormJson() : void 80 | { 81 | $submission_content = $this->content; 82 | 83 | $n = collect($this->form->form_builder_array) 84 | ->map(function ($entry) use ($submission_content) { 85 | if ( 86 | ! empty($entry['name']) && 87 | array_key_exists($entry['name'], $submission_content) 88 | ) { 89 | // the field has a 'name' which means it is not a header or paragraph 90 | // and the user previously have an entry for that field in the $submission_content 91 | $current_submitted_val = $submission_content[$entry['name']] ?? ''; 92 | 93 | if ((empty($entry['value']) && empty($entry['values']))) { 94 | // for input types that do not get their values from a 'values' array 95 | // set the staight 'value' string and move on 96 | $entry['value'] = $current_submitted_val; 97 | } else if (! empty($entry['values'])) { 98 | // this will hold what will think is the value of the 'other' input 99 | // in a checkbox-group that allows the 'other' option 100 | $otherInputVal = null; 101 | 102 | // manipulate the values array so we can preselect the entries that 103 | // were chosen in the submission we have on file. 104 | if (is_array($current_submitted_val)) { 105 | $entry['values'] = collect($entry['values']) 106 | ->map(function ($v) use ($current_submitted_val) { 107 | // if this value in the 'values' array is in the 108 | // previous selection made by the user in their 109 | // submission, we will add the selected and checked 110 | // flag to the value so that it will be pre-selected 111 | // when we render the form 112 | if (in_array($v['value'], $current_submitted_val)) { 113 | $v['selected'] = true; 114 | $v['checked'] = 'checked'; 115 | } 116 | 117 | return $v; 118 | }) 119 | ->toArray(); 120 | } 121 | 122 | // check if the 'other' input option is available 123 | if (! empty($entry['other']) && $entry['other'] === true) { 124 | // let's attempt to get the value that was provided via the 125 | // 'other' input field of a checkbox-group 126 | // get the submitted value that is not part of the 'values' 127 | // array for this entry 128 | $values_names = collect($entry['values']) 129 | ->map(function ($v) { 130 | return $v['value']; 131 | }) 132 | ->toArray(); 133 | 134 | $other = collect($current_submitted_val) 135 | ->filter(function ($sv) use ($values_names) { 136 | return ! in_array($sv, $values_names); 137 | }) 138 | ->values(); 139 | 140 | $otherInputVal = $other[0] ?? null; 141 | } 142 | 143 | // still set the value on the entry as we have it 144 | $entry['value'] = $otherInputVal ?? $current_submitted_val; 145 | } 146 | } 147 | 148 | return $entry; 149 | }); 150 | 151 | $this->form->form_builder_json = $n; 152 | } 153 | 154 | /** 155 | * Turn the current value we are trying to display to string we can actually display 156 | * 157 | * @param string $key 158 | * @param string $type the type of the input type that this key belongs to on the form 159 | * @param boolean $limit_string 160 | * @return Illuminate\Support\HtmlString 161 | */ 162 | public function renderEntryContent($key, $type = null, $limit_string = false) : HtmlString 163 | { 164 | $str = ''; 165 | 166 | if( 167 | ! empty($this->content[$key]) && 168 | is_array($this->content[$key]) 169 | ) { 170 | $str = implode(', ', $this->content[$key]); 171 | } else { 172 | $str = $this->content[$key] ?? ''; 173 | } 174 | 175 | if ($limit_string) { 176 | $str = Str::limit($str, 20, ''); 177 | } 178 | 179 | // if the type is 'file' then we have to render this as a link 180 | if ($type == 'file') { 181 | if(isset($this->content[$key])){ 182 | $file_link = Storage::url($this->content[$key]); 183 | $str = "{$str}"; 184 | } else { 185 | $str = "No file"; 186 | } 187 | } 188 | 189 | return new HtmlString($str); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/Requests/SaveFormRequest.php: -------------------------------------------------------------------------------- 1 | 'required|max:100', 35 | 'visibility' => ['required', Rule::in([Form::FORM_PUBLIC, Form::FORM_PRIVATE])], 36 | 'allows_edit' => 'required|boolean', 37 | 'form_builder_json' => 'required|json', 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Services/RolesProvider.php: -------------------------------------------------------------------------------- 1 | 'Role Name', 17 | * ] 18 | * @return array 19 | */ 20 | public function __invoke() : array 21 | { 22 | return [ 23 | 1 => 'Default', 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Traits/HasFormBuilderTraits.php: -------------------------------------------------------------------------------- 1 | hasMany(Form::class); 23 | } 24 | 25 | /** 26 | * A User can have one or many submission 27 | * 28 | * @return Illuminate\Database\Eloquent\Relations\HasMany 29 | */ 30 | public function submissions() 31 | { 32 | return $this->hasMany(Submission::class); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /views/forms/create.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ $pageTitle ?? '' }} 11 | 12 | 13 | Back To My Form 14 | 15 |
16 |
17 | 18 |
19 | @csrf 20 | 21 |
22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 | @if ($errors->has('name')) 30 | 31 | {{ $errors->first('name') }} 32 | 33 | @endif 34 |
35 |
36 |
37 |
38 | 39 | 40 | 46 | 47 | @if ($errors->has('visibility')) 48 | 49 | {{ $errors->first('visibility') }} 50 | 51 | @endif 52 |
53 |
54 | 72 |
73 | 74 |
75 |
76 | 80 | 81 |
82 |
83 |
84 |
85 |
86 | 87 | 95 |
96 |
97 |
98 |
99 | @endsection 100 | 101 | @push(config('formbuilder.layout_js_stack', 'scripts')) 102 | 106 | 107 | @endpush 108 | -------------------------------------------------------------------------------- /views/forms/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ $pageTitle ?? '' }} 11 | 12 | 22 |
23 |
24 | 25 |
26 | @csrf 27 | @method('PUT') 28 | 29 |
30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 | @if ($errors->has('name')) 38 | 39 | {{ $errors->first('name') }} 40 | 41 | @endif 42 |
43 |
44 |
45 |
46 | 47 | 48 | 56 | 57 | @if ($errors->has('visibility')) 58 | 59 | {{ $errors->first('visibility') }} 60 | 61 | @endif 62 |
63 |
64 |
isPublic()) style="display: none;" id="allows_edit_DIV" @endif> 65 |
66 | 69 | 70 | 78 | 79 | @if ($errors->has('allows_edit')) 80 | 81 | {{ $errors->first('allows_edit') }} 82 | 83 | @endif 84 |
85 |
86 |
87 | 88 |
89 |
90 | 94 | 95 |
96 |
97 |
98 |
99 |
100 | 101 | 109 |
110 |
111 |
112 |
113 | @endsection 114 | 115 | @push(config('formbuilder.layout_js_stack', 'scripts')) 116 | 122 | 123 | @endpush 124 | -------------------------------------------------------------------------------- /views/forms/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | Forms 11 | 12 | 23 |
24 |
25 | 26 | @if($forms->count()) 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | @foreach($forms as $form) 41 | 42 | 43 | 44 | 45 | 46 | 47 | 70 | 71 | @endforeach 72 | 73 |
#NameVisibilityAllows Edit?SubmissionsActions
{{ $loop->iteration }}{{ $form->name }}{{ $form->visibility }}{{ $form->allowsEdit() ? 'YES' : 'NO' }}{{ $form->submissions_count }} 48 | 49 | Data 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 60 | 61 |
62 | @csrf 63 | @method('DELETE') 64 | 65 | 68 |
69 |
74 |
75 | @if($forms->hasPages()) 76 | 79 | @endif 80 | @else 81 |
82 |

83 | No form to display. 84 |

85 |
86 | @endif 87 |
88 |
89 |
90 |
91 | @endsection 92 | -------------------------------------------------------------------------------- /views/forms/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | Form Preview for '{{ $form->name }}' 11 | 12 | 28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Details 41 | 42 | 45 |
46 |
47 | 48 |
    49 |
  • 50 | Public URL: 51 | 52 | {{$form->identifier}} 53 | 54 |
  • 55 |
  • 56 | Visibility: {{ $form->visibility }} 57 |
  • 58 |
  • 59 | Allows Edit: 60 | {{ $form->allowsEdit() ? 'YES' : 'NO' }} 61 |
  • 62 |
  • 63 | Owner: {{ $form->user->name }} 64 |
  • 65 |
  • 66 | Current Submissions: 67 | {{ $form->submissions_count }} 68 |
  • 69 |
  • 70 | Last Updated On: 71 | 72 | {{ $form->updated_at->toDayDateTimeString() }} 73 | 74 |
  • 75 |
  • 76 | Created On: 77 | 78 | {{ $form->created_at->toDayDateTimeString() }} 79 | 80 |
  • 81 |
82 |
83 |
84 |
85 |
86 | @endsection 87 | 88 | @push(config('formbuilder.layout_js_stack', 'scripts')) 89 | 92 | 93 | @endpush 94 | -------------------------------------------------------------------------------- /views/layout.blade.php: -------------------------------------------------------------------------------- 1 | @extends(config('formbuilder.layout_file', 'layouts.app')) 2 | 3 | @prepend(config('formbuilder.layout_js_stack', 'scripts')) 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | @endprepend 19 | 20 | @prepend(config('formbuilder.layout_css_stack', 'scripts')) 21 | 22 | 23 | 24 | @endprepend 25 | -------------------------------------------------------------------------------- /views/my_submissions/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ $pageTitle }} 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 | @csrf 20 | @method('PUT') 21 | 22 |
23 |
24 |
25 | 26 | 31 |
32 |
33 |
34 |
35 |
36 | @endsection 37 | 38 | @push(config('formbuilder.layout_js_stack', 'scripts')) 39 | 42 | 43 | @endpush 44 | -------------------------------------------------------------------------------- /views/my_submissions/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ $pageTitle }} ({{ $submissions->count() }}) 11 | 12 | 13 | My Forms 14 | 15 |
16 |
17 | 18 | @if($submissions->count()) 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | @foreach($submissions as $submission) 32 | 33 | 34 | 35 | 36 | 37 | 57 | 58 | @endforeach 59 | 60 |
#FormUpdated OnCreated OnActions
{{ $loop->iteration }}{{ $submission->form->name }}{{ $submission->updated_at->toDayDateTimeString() }}{{ $submission->created_at->toDayDateTimeString() }} 38 | 39 | View 40 | 41 | 42 | @if($submission->form->allowsEdit()) 43 | 44 | 45 | 46 | @endif 47 | 48 | {{--
49 | @csrf 50 | @method('DELETE') 51 | 52 | 55 |
--}} 56 |
61 |
62 | @if($submissions->hasPages()) 63 | 66 | @endif 67 | @else 68 |
69 |

70 | No submission to display. 71 |

72 |
73 | @endif 74 |
75 |
76 |
77 |
78 | @endsection 79 | -------------------------------------------------------------------------------- /views/my_submissions/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | Viewing my submission for form 11 | {{ $submission->form->name }} 12 | 13 | 33 |
34 |
35 | 36 |
    37 | @foreach($form_headers as $header) 38 |
  • 39 | {{ $header['label'] ?? title_case($header['name']) }}: 40 | 41 | {{ $submission->renderEntryContent($header['name'], $header['type']) }} 42 | 43 |
  • 44 | @endforeach 45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
Details
53 |
54 | 55 |
    56 |
  • 57 | Form: 58 | {{ $submission->form->name }} 59 |
  • 60 |
  • 61 | Submitted By: 62 | {{ $submission->user->name ?? 'Guest' }} 63 |
  • 64 |
  • 65 | Last Updated On: 66 | {{ $submission->updated_at->toDayDateTimeString() }} 67 |
  • 68 |
  • 69 | Submitted On: 70 | {{ $submission->created_at->toDayDateTimeString() }} 71 |
  • 72 |
73 |
74 |
75 |
76 |
77 | @endsection 78 | -------------------------------------------------------------------------------- /views/render/feedback.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | Form Successfully submitted 11 | 12 | @auth 13 | 14 | Go To My Submissions 15 | 16 | @endauth 17 |
18 |
19 | 20 |
21 |

22 | Your entry for {{ $form->name }} was successfully submitted. 23 |

24 |
25 | 26 | 31 |
32 |
33 |
34 |
35 | @endsection 36 | -------------------------------------------------------------------------------- /views/render/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
{{ $pageTitle }}
10 |
11 | 12 |
13 | @csrf 14 | 15 |
16 |
17 |
18 | 19 | 24 |
25 |
26 |
27 |
28 |
29 | @endsection 30 | 31 | @push(config('formbuilder.layout_js_stack', 'scripts')) 32 | 35 | 36 | @endpush 37 | -------------------------------------------------------------------------------- /views/submissions/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ $pageTitle }} ({{ $submissions->count() }}) 11 | 12 | 13 | Back To Forms 14 | 15 |
16 |
17 | 18 | @if($submissions->count()) 19 |
20 | 21 | 22 | 23 | 24 | 25 | @foreach($form_headers as $header) 26 | 27 | @endforeach 28 | 29 | 30 | 31 | 32 | @foreach($submissions as $submission) 33 | 34 | 35 | 36 | @foreach($form_headers as $header) 37 | 44 | @endforeach 45 | 59 | 60 | @endforeach 61 | 62 |
#User Name{{ $header['label'] ?? title_case($header['name']) }}Actions
{{ $loop->iteration }}{{ $submission->user->name ?? 'n/a' }} 38 | {{ 39 | $submission->renderEntryContent( 40 | $header['name'], $header['type'], true 41 | ) 42 | }} 43 | 46 | 47 | View 48 | 49 | 50 |
51 | @csrf 52 | @method('DELETE') 53 | 54 | 57 |
58 |
63 |
64 | @if($submissions->hasPages()) 65 | 68 | @endif 69 | @else 70 |
71 |

72 | No submission to display. 73 |

74 |
75 | @endif 76 |
77 |
78 |
79 |
80 | @endsection 81 | -------------------------------------------------------------------------------- /views/submissions/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('formbuilder::layout') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | Viewing Submission #{{ $submission->id }} for form '{{ $submission->form->name }}' 11 | 12 | 27 |
28 |
29 | 30 |
    31 | @foreach($form_headers as $header) 32 |
  • 33 | {{ $header['label'] ?? title_case($header['name']) }}: 34 | 35 | {{ $submission->renderEntryContent($header['name'], $header['type']) }} 36 | 37 |
  • 38 | @endforeach 39 |
40 |
41 |
42 | 43 |
44 |
45 |
46 |
Details
47 |
48 | 49 |
    50 |
  • 51 | Form: 52 | {{ $submission->form->name }} 53 |
  • 54 |
  • 55 | Submitted By: 56 | {{ $submission->user->name ?? 'Guest' }} 57 |
  • 58 |
  • 59 | Last Updated On: 60 | {{ $submission->updated_at->toDayDateTimeString() }} 61 |
  • 62 |
  • 63 | Submitted On: 64 | {{ $submission->created_at->toDayDateTimeString() }} 65 |
  • 66 |
67 |
68 |
69 |
70 |
71 | @endsection 72 | --------------------------------------------------------------------------------