├── CONTRIBUTING.md
├── public
├── app.css
└── mix-manifest.json
├── resources
├── sass
│ ├── app.scss
│ └── base.scss
├── js
│ ├── event-bus.js
│ ├── main.styl
│ ├── models
│ │ ├── Field.js
│ │ ├── Submission.js
│ │ ├── Model.js
│ │ └── Form.js
│ ├── pages
│ │ ├── dashboard.vue
│ │ ├── fields
│ │ │ └── index.vue
│ │ └── forms
│ │ │ ├── index.vue
│ │ │ ├── fields
│ │ │ └── create.vue
│ │ │ └── edit.vue
│ ├── store
│ │ ├── index.js
│ │ └── modules
│ │ │ └── forms.js
│ ├── components
│ │ ├── FormActionsComponent.vue
│ │ ├── FieldsComponent.vue
│ │ ├── NavComponent.vue
│ │ ├── DialogComponent.vue
│ │ ├── Alert.vue
│ │ ├── FormsComponent.vue
│ │ ├── FormSubmissionsComponent.vue
│ │ └── FormFieldsComponent.vue
│ ├── routes.js
│ ├── app.js
│ └── base.js
└── views
│ ├── submissions
│ └── edit.blade.php
│ ├── forms
│ └── _form-questions.blade.php
│ ├── layouts
│ └── front.blade.php
│ └── layout.blade.php
├── screenshots
├── new_field.png
├── new_form.png
├── form_details.png
├── forms_list.png
├── field_details.png
├── front_end_form.png
└── submission_details.png
├── src
├── Fields
│ ├── Date.php
│ ├── Email.php
│ ├── File.php
│ ├── Text.php
│ ├── Number.php
│ ├── Password.php
│ ├── TextArea.php
│ ├── CheckBox.php
│ ├── Radio.php
│ ├── Select.php
│ └── FormField.php
├── User.php
├── Http
│ ├── Controllers
│ │ ├── SubmissionController.php
│ │ ├── DashboardController.php
│ │ ├── FieldController.php
│ │ ├── Controller.php
│ │ ├── FormController.php
│ │ ├── FormSubmissionController.php
│ │ └── FormFieldController.php
│ ├── Requests
│ │ ├── ListFormRequest.php
│ │ ├── DeleteFormRequest.php
│ │ ├── CreateFormRequest.php
│ │ ├── UpdateFormRequest.php
│ │ ├── CreateFormQuestionRequest.php
│ │ ├── UpdateFormQuestionRequest.php
│ │ └── CreateFormSubmissionRequest.php
│ └── routes.php
├── Services
│ ├── SubmissionService.php
│ ├── AnswerService.php
│ ├── FormService.php
│ └── QuestionService.php
├── Facades
│ └── FormFacade.php
├── Transformers
│ ├── FieldTypeTransformer.php
│ ├── AnswerTransformer.php
│ ├── SubmissionTransformer.php
│ ├── FormTransformer.php
│ ├── Transformer.php
│ └── FieldTransformer.php
├── Models
│ ├── Answer.php
│ ├── Submission.php
│ ├── Form.php
│ └── Question.php
├── Rules
│ └── ReCaptcha.php
├── Form.php
└── FormServiceProvider.php
├── .travis.yml
├── tests
├── Feature
│ ├── Form
│ │ ├── DeleteTest.php
│ │ ├── CreateTest.php
│ │ ├── UpdateTest.php
│ │ └── ListTest.php
│ └── Question
│ │ ├── DeleteTest.php
│ │ ├── UpdateTest.php
│ │ └── CreateTest.php
├── Unit
│ ├── SubmissionTest.php
│ ├── FormTest.php
│ ├── AnswerTest.php
│ ├── QuestionTest.php
│ └── Fields
│ │ └── FieldsRenderTest.php
├── TestCase.php
└── BaseTestCase.php
├── .gitignore
├── phpunit.xml.dist
├── composer.json
├── webpack.mix.js
├── README.md
├── package.json
├── config
└── laravel_forms.php
└── database
├── seeds
└── FormsDatabaseSeeder.php
├── factories
└── ModelFactory.php
└── migrations
└── create_form_tables.php
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/sass/app.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/sass/base.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/js/event-bus.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | export const EventBus = new Vue();
--------------------------------------------------------------------------------
/resources/js/main.styl:
--------------------------------------------------------------------------------
1 | @import '~vuetify/src/stylus/main' // Ensure you are using stylus-loader
--------------------------------------------------------------------------------
/screenshots/new_field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musonza/laravel-forms/HEAD/screenshots/new_field.png
--------------------------------------------------------------------------------
/screenshots/new_form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musonza/laravel-forms/HEAD/screenshots/new_form.png
--------------------------------------------------------------------------------
/screenshots/form_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musonza/laravel-forms/HEAD/screenshots/form_details.png
--------------------------------------------------------------------------------
/screenshots/forms_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musonza/laravel-forms/HEAD/screenshots/forms_list.png
--------------------------------------------------------------------------------
/screenshots/field_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musonza/laravel-forms/HEAD/screenshots/field_details.png
--------------------------------------------------------------------------------
/screenshots/front_end_form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musonza/laravel-forms/HEAD/screenshots/front_end_form.png
--------------------------------------------------------------------------------
/screenshots/submission_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musonza/laravel-forms/HEAD/screenshots/submission_details.png
--------------------------------------------------------------------------------
/public/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/app.js": "/app.js?id=080f3fb6c90b157f5680",
3 | "/app.css": "/app.css?id=d41d8cd98f00b204e980"
4 | }
5 |
--------------------------------------------------------------------------------
/src/Fields/Date.php:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | name: "Dashboard"
4 | };
5 |
6 |
7 |
8 | Dashboard TODO
9 |
10 |
--------------------------------------------------------------------------------
/src/User.php:
--------------------------------------------------------------------------------
1 |
2 | import FieldsComponent from "@/components/FieldsComponent";
3 | export default {
4 | name: "FormsPage",
5 |
6 | components: {
7 | FieldsComponent
8 | }
9 | };
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/resources/js/pages/forms/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
--------------------------------------------------------------------------------
/resources/js/components/FormActionsComponent.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 | Add Field
16 |
17 |
18 |
--------------------------------------------------------------------------------
/resources/js/models/Model.js:
--------------------------------------------------------------------------------
1 | import { Model as BaseModel } from 'vue-api-query'
2 |
3 | export default class Model extends BaseModel {
4 |
5 | // define a base url for a REST API
6 | baseURL() {
7 | return '';
8 | }
9 |
10 | // implement a default request method
11 | request(config) {
12 | return this.$http.request(config)
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Http/Controllers/SubmissionController.php:
--------------------------------------------------------------------------------
1 | delete();
12 |
13 | return response('', 201);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/resources/js/models/Form.js:
--------------------------------------------------------------------------------
1 | import Model from './Model';
2 | import Field from './Field';
3 | import Submission from './Submission';
4 |
5 | export default class Form extends Model {
6 | resource() {
7 | return 'forms';
8 | }
9 |
10 | fields() {
11 | return this.hasMany(Field);
12 | }
13 |
14 | submissions() {
15 | return this.hasMany(Submission);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Services/SubmissionService.php:
--------------------------------------------------------------------------------
1 | submission = $submission;
12 | }
13 |
14 | public function getById($id)
15 | {
16 | return $this->submission->findOrFail($id);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Facades/FormFacade.php:
--------------------------------------------------------------------------------
1 | attributes['class'] = "form-control";
13 | $attributes = $this->attributes($this->attributes);
14 | return "";
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Services/AnswerService.php:
--------------------------------------------------------------------------------
1 | form = $form;
13 | $this->answer = $answer;
14 | }
15 |
16 | public function getById($id)
17 | {
18 | return $this->answer->findOrFail($id);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Services/FormService.php:
--------------------------------------------------------------------------------
1 | form = $form;
12 | }
13 |
14 | public function create(array $data)
15 | {
16 | return $this->form->create($data);
17 | }
18 |
19 | public function getById($id)
20 | {
21 | return $this->form->findOrFail($id);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Http/Controllers/DashboardController.php:
--------------------------------------------------------------------------------
1 | $cssFile,
20 | ]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Transformers/FieldTypeTransformer.php:
--------------------------------------------------------------------------------
1 | $field,
13 | 'title' => $this->title($field),
14 | 'has_choices' => $fieldObj->hasChoices(),
15 | ];
16 | }
17 |
18 | protected function title($field)
19 | {
20 | return substr($field, strrpos($field, '\\') + 1);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Services/QuestionService.php:
--------------------------------------------------------------------------------
1 | form = $form;
13 | $this->question = $question;
14 | }
15 |
16 | public function create(array $data)
17 | {
18 | return $this->question->create($data);
19 | }
20 |
21 | public function getById($id)
22 | {
23 | return $this->question->findOrFail($id);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Http/Requests/ListFormRequest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
19 | }
20 |
21 | public function testDeleteSuccess()
22 | {
23 | $response = $this
24 | ->deleteJson(route('forms.destroy', $this->form->id))
25 | ->assertStatus(201);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Http/Controllers/FieldController.php:
--------------------------------------------------------------------------------
1 | fieldTransformer = $fieldTransformer;
17 | }
18 |
19 | public function index()
20 | {
21 | $fields = $this->fieldTransformer->transformCollection(config('laravel_forms.fields'));
22 |
23 | return response($fields);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/resources/js/routes.js:
--------------------------------------------------------------------------------
1 | export default [
2 | { path: '/', redirect: '/forms' },
3 | {
4 | path: '/dashboard',
5 | name: 'dashboard',
6 | component: require('./pages/dashboard')
7 | },
8 | {
9 | path: '/forms',
10 | name: 'forms-index',
11 | component: require('./pages/forms/index')
12 | },
13 | {
14 | path: '/forms/:id',
15 | name: 'forms-edit',
16 | component: require('./pages/forms/edit')
17 | },
18 | {
19 | path: '/fields',
20 | name: 'fields-index',
21 | component: require('./pages/fields/index')
22 | },
23 | {
24 | path: '/forms/:id/fields/create',
25 | name: 'formFieldCreate',
26 | component: require('./pages/forms/fields/create')
27 | },
28 | ];
--------------------------------------------------------------------------------
/src/Transformers/AnswerTransformer.php:
--------------------------------------------------------------------------------
1 | toArray(),
11 | [
12 | 'response' => $this->getResponse($answer),
13 | 'question' => $answer->question,
14 | ]
15 | );
16 | }
17 |
18 | public function getResponse($answer)
19 | {
20 | if ($answer->question->options && isset($answer->question->options[$answer->value])) {
21 | return $answer->question->options[$answer->value];
22 | }
23 |
24 | return $answer->value;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Fields/CheckBox.php:
--------------------------------------------------------------------------------
1 | attributes['class'] = "";
13 | $this->attributes['name'] = "{$this->attributes['name']}[]";
14 |
15 | $html = "";
16 |
17 | foreach ($this->options as $value => $label) {
18 | $html .= 'attributes($this->attributes)
19 | . ' value="'
20 | . $value
21 | . '"> '
22 | . $label
23 | . '
';
24 | }
25 | return $html;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Folders #
2 | ###########
3 | dist/
4 | vendor/
5 | node_modules/
6 | CVS/
7 | .idea
8 | deploy.php
9 | composer.lock
10 | # Compiled source #
11 | ###################
12 | *.com
13 | *.class
14 | *.dll
15 | *.exe
16 | *.o
17 | *.so
18 |
19 | # Packages #
20 | ############
21 | # it's better to unpack these files and commit the raw source
22 | # git has its own built in compression methods
23 | *.7z
24 | *.dmg
25 | *.gz
26 | *.iso
27 | *.jar
28 | *.rar
29 | *.tar
30 | *.zip
31 |
32 | # Logs and databases #
33 | ######################
34 | *.log
35 | *.sqlite
36 |
37 | # OS generated files #
38 | ######################
39 | .DS_Store
40 | .DS_Store?
41 | ._*
42 | .Spotlight-V100
43 | .Trashes
44 | ehthumbs.db
45 | Thumbs.db
46 |
47 | docs/
48 | phpDocumentor.phar
--------------------------------------------------------------------------------
/src/Fields/Radio.php:
--------------------------------------------------------------------------------
1 | attributes['class'] = "";
13 | $attributes = $this->attributes($this->attributes);
14 |
15 | $html = "";
16 |
17 | foreach ($this->options as $value => $label) {
18 | $html .= ' '
24 | . $label
25 | . '
';
26 | }
27 |
28 | return $html;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Http/Requests/CreateFormRequest.php:
--------------------------------------------------------------------------------
1 | 'Required',
11 | 'description' => '',
12 | 'status' => '',
13 | ];
14 |
15 | /**
16 | * Determine if the user is authorized to make this request.
17 | *
18 | * @return bool
19 | */
20 | public function authorize()
21 | {
22 | return true;
23 | }
24 |
25 | /**
26 | * Get the validation rules that apply to the request.
27 | *
28 | * @return array
29 | */
30 | public function rules()
31 | {
32 | return self::$rules;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Requests/UpdateFormRequest.php:
--------------------------------------------------------------------------------
1 | 'Required',
11 | 'description' => '',
12 | 'status' => '',
13 | ];
14 |
15 | /**
16 | * Determine if the user is authorized to make this request.
17 | *
18 | * @return bool
19 | */
20 | public function authorize()
21 | {
22 | return true;
23 | }
24 |
25 | /**
26 | * Get the validation rules that apply to the request.
27 | *
28 | * @return array
29 | */
30 | public function rules()
31 | {
32 | return self::$rules;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/routes.php:
--------------------------------------------------------------------------------
1 | 'web',
7 | 'namespace' => 'Musonza\Form\Http\Controllers',
8 | ], function () {
9 | Route::resource('fields', 'FieldController');
10 | Route::resource('forms', 'FormController');
11 | Route::resource('forms.fields', 'FormFieldController');
12 | Route::resource('forms.submissions', 'FormSubmissionController');
13 | Route::resource('submissions', 'SubmissionController');
14 | });
15 |
16 | Route::group([
17 | 'middleware' => 'web',
18 | 'namespace' => 'Musonza\Form\Http\Controllers',
19 | ], function () use ($dashboardPathPrefix) {
20 | Route::get($dashboardPathPrefix, 'DashboardController@index');
21 | });
22 |
--------------------------------------------------------------------------------
/resources/views/submissions/edit.blade.php:
--------------------------------------------------------------------------------
1 | @extends('laravel-forms::layouts.front')
2 |
3 | @section('content')
4 |
5 |
{{ $form['title'] }}
6 |
7 |
27 | @endsection
28 |
--------------------------------------------------------------------------------
/src/Fields/Select.php:
--------------------------------------------------------------------------------
1 | attributes($this->attributes);
15 | $html = "';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Models/Answer.php:
--------------------------------------------------------------------------------
1 | belongsTo(Question::class)->orderBy('position');
25 | }
26 |
27 | /**
28 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
29 | */
30 | public function submission()
31 | {
32 | return $this->belongsTo(Submission::class);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | {
9 | return state.forms;
10 | }
11 | };
12 |
13 | const BASEURL = ''; //http://127.0.0.1:8000';
14 |
15 | const actions = {
16 | loadForms({ commit }) {
17 | commit('LOADING', true);
18 | axios.get(BASEURL + '/forms')
19 | .then((response) => {
20 | commit('SET_FORMS', response.data);
21 | commit('LOADING', false);
22 | return response;
23 | });
24 | },
25 | };
26 |
27 | const mutations = {
28 | ['LOADING'](state, payload) {
29 | state.loading = payload;
30 | },
31 | ['SET_FORMS'](state, payload) {
32 | state.forms = payload;
33 | },
34 | };
35 |
36 | export default {
37 | state,
38 | getters,
39 | actions,
40 | mutations,
41 | };
--------------------------------------------------------------------------------
/tests/Feature/Form/CreateTest.php:
--------------------------------------------------------------------------------
1 | postJson(route('forms.store'), ['title' => 'Contact Form', 'description' => 'Our Form']);
13 |
14 | $response
15 | ->assertStatus(200)
16 | ->assertJson([
17 | 'title' => 'Contact Form',
18 | 'description' => 'Our Form',
19 | ]);
20 | }
21 |
22 | public function testFormRequiresTitle()
23 | {
24 | $response = $this->postJson(route('forms.store'), [])
25 | ->assertStatus(422)
26 | ->assertJson([
27 | 'message' => 'The given data was invalid.',
28 | ]);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Http/Requests/CreateFormQuestionRequest.php:
--------------------------------------------------------------------------------
1 | 'Required',
11 | 'field_type' => 'Required',
12 | 'help_text' => '',
13 | 'placeholder' => '',
14 | 'value' => '',
15 | 'columns_count' => '',
16 | ];
17 |
18 | /**
19 | * Determine if the user is authorized to make this request.
20 | *
21 | * @return bool
22 | */
23 | public function authorize()
24 | {
25 | return true;
26 | }
27 |
28 | /**
29 | * Get the validation rules that apply to the request.
30 | *
31 | * @return array
32 | */
33 | public function rules()
34 | {
35 | return self::$rules;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Http/Requests/UpdateFormQuestionRequest.php:
--------------------------------------------------------------------------------
1 | 'Required',
11 | 'field_type' => 'Required',
12 | 'is_required' => '',
13 | 'help_text' => '',
14 | 'placeholder' => '',
15 | 'default_value' => '',
16 | //'columns_count' => '',
17 | ];
18 |
19 | /**
20 | * Determine if the user is authorized to make this request.
21 | *
22 | * @return bool
23 | */
24 | public function authorize()
25 | {
26 | return true;
27 | }
28 |
29 | /**
30 | * Get the validation rules that apply to the request.
31 | *
32 | * @return array
33 | */
34 | public function rules()
35 | {
36 | return self::$rules;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Feature/Form/UpdateTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
19 | }
20 |
21 | public function testUpdateSuccess()
22 | {
23 | $response = $this->putJson(route('forms.update', $this->form->id), [
24 | 'title' => 'Contact Form2',
25 | 'label' => 'Contact Form2',
26 | 'description' => 'Our Form2',
27 | ]);
28 |
29 | $response
30 | ->assertStatus(200)
31 | ->assertJson([
32 | 'title' => 'Contact Form2',
33 | 'description' => 'Our Form2',
34 | ]);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/resources/views/forms/_form-questions.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | $count = 0;
3 | @endphp
4 |
5 |
34 |
35 | @endif
36 | @endforeach
37 |
--------------------------------------------------------------------------------
/tests/Feature/Question/DeleteTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
18 | $this->question = Form::createQuestion(
19 | [
20 | 'label' => 'First Name',
21 | 'description' => 'Description',
22 | 'field_type' => Text::class,
23 | 'form_id' => $this->form->id,
24 | ]
25 | );
26 | }
27 |
28 | public function testDeleteSuccess()
29 | {
30 | $response = $this
31 | ->deleteJson(route('forms.fields.destroy', [$this->form->id, $this->question->id]))
32 | ->assertStatus(201);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Unit/SubmissionTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
20 | $this->submission = $this->form->addSubmission([]);
21 | }
22 |
23 | public function testFormSubmissions()
24 | {
25 | $this->assertInstanceOf(Collection::class, $this->form->submissions);
26 | }
27 |
28 | public function testGetSubmissionById()
29 | {
30 | $submission = Form::submissionService()->getById(1);
31 | $this->assertEquals($this->submission->id, $submission->id);
32 | $this->assertInstanceOf(Submission::class, $submission);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Transformers/SubmissionTransformer.php:
--------------------------------------------------------------------------------
1 | $submission->id,
16 | 'form_id' => $submission->form_id,
17 | 'ip_address' => $submission->ip_address,
18 | 'response' => $submission->response,
19 | 'is_complete' => $submission->is_complete,
20 | 'created_at_readable' => $submission->created_at->diffForHumans(),
21 | 'created_at' => $submission->created_at,
22 | 'updated_at' => $submission->updated_at,
23 | ];
24 | }
25 |
26 | public function includeAnswers($submission)
27 | {
28 | return $this->collection($submission->answers, new AnswerTransformer());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Models/Submission.php:
--------------------------------------------------------------------------------
1 | 'array',
24 | 'is_complete' => 'boolean',
25 | ];
26 |
27 | /**
28 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
29 | */
30 | public function form()
31 | {
32 | return $this->belongsTo(Form::class);
33 | }
34 |
35 | /**
36 | * Submission has answers.
37 | *
38 | * @return \Illuminate\Database\Eloquent\Relations\HasMany
39 | */
40 | public function answers()
41 | {
42 | return $this->hasMany(Answer::class);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./tests/Feature
15 |
16 |
17 | ./tests/Unit
18 |
19 |
20 |
21 |
22 | src/
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/Models/Form.php:
--------------------------------------------------------------------------------
1 | hasMany(Question::class)->orderBy('position');
28 | }
29 |
30 | /**
31 | * A form has many submissions.
32 | *
33 | * @return \Illuminate\Database\Eloquent\Relations\HasMany
34 | */
35 | public function submissions()
36 | {
37 | return $this->hasMany(Submission::class);
38 | }
39 |
40 | public function addSubmission($submission)
41 | {
42 | return $this->submissions()->create($submission);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Rules/ReCaptcha.php:
--------------------------------------------------------------------------------
1 | post(
16 | 'https://www.google.com/recaptcha/api/siteverify',
17 | ['form_params' =>
18 | [
19 | 'secret' => config('laravel_forms.google_recaptcha_secret'),
20 | 'response' => $value,
21 | 'remoteip' => $_SERVER['REMOTE_ADDR'],
22 | ],
23 | ]
24 | );
25 |
26 | $body = json_decode((string) $response->getBody());
27 |
28 | return $body->success;
29 | }
30 |
31 | /**
32 | * Get the validation error message.
33 | *
34 | * @return string
35 | */
36 | public function message()
37 | {
38 | return 'Please ensure that you are a human!';
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/resources/js/components/FieldsComponent.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
32 |
33 | {{ props.item.id }} |
34 | {{ props.item.title }} |
35 | {{ props.item.has_choices }} |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/Transformers/FormTransformer.php:
--------------------------------------------------------------------------------
1 | $form->id,
16 | 'title' => $form->title,
17 | 'description' => $form->description,
18 | 'created_at' => $form->created_at,
19 | 'status' => [
20 | 'value' => (int)$form->status,
21 | 'label' => $statuses[$form->status]['label'],
22 | 'class' => $statuses[$form->status]['class'],
23 | ],
24 | 'statuses' => $statuses,
25 | 'submissions_count' => $form->submissions->count(),
26 | ];
27 | }
28 |
29 | public function includeQuestions($form)
30 | {
31 | return $this->collection($form->questions, new FieldTransformer());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Feature/Form/ListTest.php:
--------------------------------------------------------------------------------
1 | 'Contact Form1']);
14 | Form::create(['title' => 'Contact Form2']);
15 | Form::create(['title' => 'Contact Form3']);
16 | }
17 |
18 | public function testListForms()
19 | {
20 | // $response = $this->get(route('forms.index'));
21 |
22 | // $response->dump();
23 |
24 | $response = $this->getJson(route('forms.index'));
25 |
26 | $response
27 | ->assertStatus(200)
28 | ->assertJsonCount(3, 'data');
29 | }
30 |
31 | public function testGetForm()
32 | {
33 | $response = $this->getJson(route('forms.show', $id = 2));
34 |
35 | $response
36 | ->assertStatus(200)
37 | ->assertJson([
38 | 'title' => 'Contact Form2',
39 | ]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/resources/js/components/NavComponent.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ item.icon }}
29 |
30 |
31 |
32 | {{ item.title }}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/tests/Feature/Question/UpdateTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
18 | $this->data = [
19 | 'label' => 'First Name',
20 | 'description' => 'Description',
21 | 'field_type' => Text::class,
22 | 'form_id' => $this->form->id,
23 | ];
24 | $this->question = Form::createQuestion($this->data);
25 | }
26 |
27 | public function testUpdateSuccess()
28 | {
29 | $this->data['label'] = 'First Name Updated';
30 |
31 | $response = $this
32 | ->putJson(route('forms.fields.update', [$this->form->id, $this->question->id]), $this->data)
33 | ->assertStatus(200)
34 | ->assertJson([
35 | 'label' => 'First Name Updated',
36 | ]);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "musonza/laravel-forms",
3 | "authors": [
4 | {
5 | "name": "Tinashe Musonza",
6 | "email": "tinashemusonza@gmail.com",
7 | "role": "Developer"
8 | }
9 | ],
10 | "require": {
11 | "league/fractal": "^0.17.0",
12 | "laravel/framework": "~5.5.0|~5.6.0|~5.7.0|~5.8.0",
13 | "guzzlehttp/guzzle": "~6.0"
14 | },
15 | "require-dev": {
16 | "phpunit/phpunit": "^5.7|6.2|^7.0",
17 | "orchestra/testbench": "~3.3.0|~3.4.2|^3.5.0|~3.7.0",
18 | "orchestra/database": "~3.3.0|~3.4.2|^3.5.0|~3.7.0",
19 | "mockery/mockery": "^1.0.0",
20 | "spatie/phpunit-watcher": "dev-master",
21 | "orchestra/testbench-dusk": "^3.7@dev"
22 | },
23 | "minimum-stability": "dev",
24 | "autoload": {
25 | "psr-4": {
26 | "Musonza\\Form\\": "src/"
27 | }
28 | },
29 | "autoload-dev": {
30 | "psr-4": {
31 | "Musonza\\Form\\Tests\\": "tests"
32 | }
33 | },
34 | "scripts": {
35 | "test": "phpunit"
36 | },
37 | "extra": {
38 | "laravel": {
39 | "providers": [
40 | "Musonza\\Form\\FormServiceProvider"
41 | ]
42 | }
43 | },
44 | "license": "MIT"
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Unit/FormTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
19 | }
20 |
21 | public function testCreatesForm()
22 | {
23 | $this->assertDatabaseHas($this->tablePrefix . 'forms', ['id' => 1, 'title' => 'Contact Form']);
24 | }
25 |
26 | public function testGetFormById()
27 | {
28 | $form = Form::formService()->getById($this->form->id);
29 | $this->assertEquals($this->form->id, $form->id);
30 | }
31 |
32 | public function testDeleteForm()
33 | {
34 | $this->assertDatabaseHas($this->tablePrefix . 'forms', ['id' => 1, 'title' => 'Contact Form']);
35 |
36 | Form::formService()
37 | ->getById($this->form->id)
38 | ->delete();
39 |
40 | $this->assertDatabaseMissing($this->tablePrefix . 'forms', ['id' => 1, 'title' => 'Contact Form']);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | artisan('migrate', ['--database' => 'testbench']);
16 | $this->withFactories(__DIR__ . '/../database/factories');
17 | $this->migrate();
18 | $this->users = $this->createUsers(6);
19 | }
20 |
21 | /**
22 | * Define environment setup.
23 | *
24 | * @param \Illuminate\Foundation\Application $app
25 | *
26 | * @return void
27 | */
28 | protected function getEnvironmentSetUp($app)
29 | {
30 | parent::getEnvironmentSetUp($app);
31 |
32 | // Setup default database to use sqlite :memory:
33 | $app['config']->set('database.default', 'testbench');
34 | $app['config']->set('database.connections.testbench', [
35 | 'driver' => 'sqlite',
36 | 'database' => ':memory:',
37 | 'prefix' => '',
38 | ]);
39 | $app['config']->set('app.debug', true);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require('laravel-mix');
2 | const webpack = require('webpack');
3 |
4 | /*
5 | |--------------------------------------------------------------------------
6 | | Mix Asset Management
7 | |--------------------------------------------------------------------------
8 | |
9 | | Mix provides a clean, fluent API for defining some Webpack build steps
10 | | for your Laravel application. By default, we are compiling the Sass
11 | | file for the application as well as bundling up all the JS files.
12 | |
13 | */
14 |
15 | mix
16 | .options({
17 | uglify: {
18 | uglifyOptions: {
19 | compress: {
20 | drop_console: true,
21 | }
22 | }
23 | }
24 | })
25 | .setPublicPath('public')
26 | .js('resources/js/app.js', 'public')
27 | .sass('resources/sass/app.scss', 'public')
28 | .version()
29 | .webpackConfig({
30 | resolve: {
31 | symlinks: false,
32 | alias: {
33 | '@': path.resolve(__dirname, 'resources/js/'),
34 | }
35 | },
36 | plugins: [
37 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
38 | ],
39 | });
40 |
--------------------------------------------------------------------------------
/resources/views/layouts/front.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Forms
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | @foreach (['danger', 'warning', 'success', 'info'] as $msg)
19 | @if(Session::has('alert-' . $msg))
20 |
{{ Session::get('alert-' . $msg) }}
21 | @endif
22 | @endforeach
23 |
24 |
25 |
26 |
27 | @yield('content')
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Http/Requests/CreateFormSubmissionRequest.php:
--------------------------------------------------------------------------------
1 | [
33 | $form->googleRecaptchaEnabled() ? 'required' : '',
34 | new ReCaptcha,
35 | ],
36 | ];
37 | }
38 |
39 | /**
40 | * Get the error messages for the defined validation rules.
41 | *
42 | * @return array
43 | */
44 | public function messages()
45 | {
46 | return [
47 | 'g-recaptcha-response.required' => 'Captcha response is required.',
48 | ];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/resources/js/app.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Base from './base';
3 | import axios from 'axios';
4 | import Routes from './routes';
5 | import VueRouter from 'vue-router';
6 | import TreeView from 'vue-json-tree-view';
7 | import store from './store/index';
8 | import Vuetify from 'vuetify';
9 | import 'vuetify/dist/vuetify.min.css';
10 | import { Model } from 'vue-api-query';
11 | import VeeValidate from 'vee-validate'
12 |
13 | // inject global axios instance as http client to Model
14 | Model.$http = axios
15 | require('bootstrap');
16 | let token = document.head.querySelector('meta[name="csrf-token"]');
17 | if (token) {
18 | axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
19 | }
20 |
21 | Vue.use(Vuetify);
22 | Vue.use(VueRouter);
23 | Vue.use(TreeView);
24 | Vue.use(VeeValidate);
25 |
26 | Vue.component('nav-component', require('./components/NavComponent.vue'));
27 | Vue.component('alert', require('./components/Alert.vue'));
28 |
29 | const router = new VueRouter({
30 | routes: Routes,
31 | // mode: 'history',
32 | base: '/laravel-forms/',
33 | });
34 |
35 | Vue.mixin(Base);
36 |
37 | new Vue({
38 | el: '#laravel-forms',
39 | router,
40 | store,
41 | // render: (h) => h(App),
42 | data() {
43 | return {
44 | alert: {
45 | type: null,
46 | show: false,
47 | autoDismiss: 5000,
48 | message: '',
49 | title: '',
50 | confirmationAgree: null,
51 | },
52 | }
53 | },
54 | });
--------------------------------------------------------------------------------
/src/Transformers/Transformer.php:
--------------------------------------------------------------------------------
1 | setPaginator(new IlluminatePaginatorAdapter($paginator));
20 | }
21 |
22 | if ($meta) {
23 | $resource->setMeta($meta);
24 | }
25 |
26 | return $this->fractalManager($resource);
27 | }
28 |
29 | public function fractalManager($resource)
30 | {
31 | $fractal = new Manager();
32 |
33 | $fractal->setSerializer(new ArraySerializer());
34 |
35 | if ($includes = request('include', null)) {
36 | $fractal->parseIncludes($includes);
37 | }
38 |
39 | return $fractal->createData($resource)->toArray();
40 | }
41 |
42 | public function transformItem($item)
43 | {
44 | $resource = new Item($item, $this);
45 |
46 | return $this->fractalManager($resource);
47 | }
48 |
49 | abstract public function transform($item);
50 | }
51 |
--------------------------------------------------------------------------------
/resources/views/layout.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Laravel Forms
8 |
9 |
10 |
11 |
12 |
13 |
14 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/resources/js/components/DialogComponent.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 |
36 |
40 |
41 |
42 | {{ title }}
43 |
44 |
45 | {{ message }}
46 |
47 |
48 |
49 |
50 |
51 |
56 | Cancel
57 |
58 |
59 |
64 | Delete
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/Form.php:
--------------------------------------------------------------------------------
1 | formService = $formService;
21 | $this->questionService = $questionService;
22 | $this->answerService = $answerService;
23 | $this->submissionService = $submissionService;
24 | }
25 |
26 | public function create(array $data)
27 | {
28 | return $this->formService->create($data);
29 | }
30 |
31 | public function formService()
32 | {
33 | return $this->formService;
34 | }
35 |
36 | public function createQuestion(array $data)
37 | {
38 | return $this->questionService->create($data);
39 | }
40 |
41 | public function questionService()
42 | {
43 | return $this->questionService;
44 | }
45 |
46 | public function answerService()
47 | {
48 | return $this->answerService;
49 | }
50 |
51 | public function submissionService()
52 | {
53 | return $this->submissionService;
54 | }
55 |
56 | public function googleRecaptchaEnabled()
57 | {
58 | return config('laravel_forms.google_recaptcha_enabled');
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Forms
2 |
3 | [](https://travis-ci.org/musonza/laravel-forms)
4 | [](https://packagist.org/packages/musonza/laravel-forms)
5 |
6 |
7 |
8 | ## Installation
9 | 1. Install composer package
10 | ```sh
11 | composer require musonza/laravel-forms
12 | ```
13 |
14 | 1. Publish Assets
15 | ```sh
16 | php artisan vendor:publish
17 | ```
18 | 1. Add Form facade to `config/app.php`
19 | ```php
20 | 'Form' => Musonza\Form\Facades\FormFacade::class,
21 | ```
22 |
23 | 1. Run migrations
24 | ```sh
25 | php artisan migrate
26 | ```
27 |
28 | 1. Check the published file config/laravel_forms.php
29 | - You can enable / disable captcha
30 | - You can configure the path for your forms dashboard
31 | - You can add custom field types
32 |
33 | 1. Access dashboard at
34 |
35 | http//your-url.com/laravel-forms (you can change the path in config/laravel_forms.php)
36 |
37 | ## Adding a Form
38 |
39 |
40 | ## Form details
41 |
42 |
43 | ## Adding a Field
44 |
45 |
46 | ## Field details
47 |
48 |
49 | ## Sample Form Output
50 |
51 |
52 | ## Sample Submission
53 |
54 |
55 | ## TODO
56 | - Multi page forms
57 |
58 | ## Credits
59 | https://github.com/laravel/telescope for some of the front-end structuring
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "npm run development -- --watch",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "@vue/cli-plugin-babel": "^3.1.1",
14 | "@vue/cli-plugin-eslint": "^3.1.5",
15 | "@vue/cli-service": "^3.1.4",
16 | "axios": "^0.18",
17 | "babel-eslint": "^10.0.1",
18 | "bootstrap": "^4.0.0",
19 | "cross-env": "^5.1",
20 | "eslint": "^5.8.0",
21 | "eslint-plugin-vue": "^5.0.0-beta.5",
22 | "highlight.js": "^9.12.0",
23 | "jquery": "^3.2",
24 | "laravel-mix": "^2.0",
25 | "lodash": "^4.17.4",
26 | "moment": "^2.10.6",
27 | "moment-timezone": "^0.5.21",
28 | "popper.js": "^1.14.5",
29 | "sql-formatter": "^2.3.1",
30 | "stylus": "^0.54.5",
31 | "stylus-loader": "^3.0.1",
32 | "vue": "^2.5.7",
33 | "vue-cli-plugin-vuetify": "^0.4.6",
34 | "vue-json-tree-view": "^2.1.4",
35 | "vue-router": "^3.0.2",
36 | "vue-template-compiler": "^2.5.17",
37 | "vuetify-loader": "^1.0.7"
38 | },
39 | "dependencies": {
40 | "api-class": "0.0.2",
41 | "md5": "^2.2.1",
42 | "sortablejs": "^1.7.0",
43 | "vee-validate": "^2.1.3",
44 | "vue-api-query": "^1.2.0",
45 | "vuedraggable": "^2.16.0",
46 | "vuetify": "^1.3.9",
47 | "vuex": "^3.0.1"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/config/laravel_forms.php:
--------------------------------------------------------------------------------
1 | 'laravel-forms',
5 | 'dashboard_css_file' => 'app.css',
6 | /*
7 | |--------------------------------------------------------------------------
8 | | Field Types
9 | |--------------------------------------------------------------------------
10 | |
11 | | Here you may define add additional field types for use in your application.
12 | |
13 | */
14 | 'fields' => [
15 | Musonza\Form\Fields\CheckBox::class,
16 | Musonza\Form\Fields\Date::class,
17 | Musonza\Form\Fields\File::class,
18 | Musonza\Form\Fields\Password::class,
19 | Musonza\Form\Fields\Radio::class,
20 | Musonza\Form\Fields\Select::class,
21 | Musonza\Form\Fields\Text::class,
22 | Musonza\Form\Fields\TextArea::class,
23 | Musonza\Form\Fields\Email::class,
24 | Musonza\Form\Fields\Number::class,
25 | ],
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Form Statuses
29 | |--------------------------------------------------------------------------
30 | |
31 | |
32 | */
33 | 'form_statuses' => [
34 | 0 => [
35 | 'label' => 'Draft',
36 | 'class' => 'warning',
37 | ],
38 | 1 => [
39 | 'label' => 'Published',
40 | 'class' => 'success',
41 | ],
42 | 2 => [
43 | 'label' => 'Unpublished',
44 | 'class' => 'error',
45 | ],
46 | ],
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Google Recaptcha
50 | |--------------------------------------------------------------------------
51 | |
52 | |
53 | */
54 | 'google_recaptcha_enabled' => true,
55 | 'google_recaptcha_key' => env('GOOGLE_RECAPTCHA_KEY'),
56 | 'google_recaptcha_secret' => env('GOOGLE_RECAPTCHA_SECRET'),
57 | ];
58 |
--------------------------------------------------------------------------------
/src/Transformers/FieldTransformer.php:
--------------------------------------------------------------------------------
1 | $question->id,
11 | 'title' => $question->title,
12 | 'label' => $question->label,
13 | 'field_type' => $question->field_type,
14 | 'field_type_name' => $this->fieldTypeTitle($question->field_type),
15 | 'has_choices' => $this->hasChoices($question->field_type),
16 | 'help_text' => $question->help_text,
17 | 'placeholder' => $question->placeholder,
18 | 'render' => $question->field()->render(),
19 | 'is_required' => $question->is_required,
20 | 'description' => $question->description,
21 | 'validations' => $question->validations,
22 | 'options' => $question->options,
23 | 'options_text' => implode(PHP_EOL, $question->options),
24 | 'position' => $question->position,
25 | 'default_value' => $question->default_value,
26 | 'columns_count' => $question->columns_count ?? 12,
27 | ];
28 | }
29 |
30 | protected function fieldTypeTitle($field)
31 | {
32 | return substr($field, strrpos($field, '\\') + 1);
33 | }
34 |
35 | protected function hasChoices($field)
36 | {
37 | return app($field)->hasChoices();
38 | }
39 | }
40 |
41 | // "id": {
42 | // "id": 1,
43 | // "form_id": "1",
44 | // "title": "wqeqw",
45 | // "label": "qweqwe",
46 | // "place_holder": "qweqwe",
47 | // "help_text": null,
48 | // "is_required": true,
49 | // "description": "eqweqweqwe",
50 | // "field_type": "Musonza\\Form\\Fields\\CheckBox",
51 | // "validations": null,
52 | // "properties": null,
53 | // "options": [],
54 | // "created_at": "2018-10-13 16:33:29",
55 | // "updated_at": "2018-10-13 16:33:29"
56 | // }
57 |
--------------------------------------------------------------------------------
/src/Models/Question.php:
--------------------------------------------------------------------------------
1 | 'boolean',
31 | 'properties' => 'array',
32 | 'options' => 'array',
33 | 'position' => 'integer',
34 | ];
35 |
36 | protected $attributes = [
37 | 'options' => '{}',
38 | ];
39 |
40 | public function __construct(array $attributes = array(), $exists = false)
41 | {
42 | parent::__construct($attributes, $exists);
43 |
44 | // $this->initListify();
45 | }
46 |
47 | /**
48 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
49 | */
50 | public function form()
51 | {
52 | return $this->belongsTo(Form::class);
53 | }
54 |
55 | public function answers()
56 | {
57 | return $this->hasMany(Answer::class);
58 | }
59 |
60 | public function addValidations($validations)
61 | {
62 | $this->validations = $validations;
63 | $this->save();
64 | return $this;
65 | }
66 |
67 | /**
68 | * Get prefilled value for the field.
69 | *
70 | * @return mixed
71 | */
72 | public function getValueAttribute()
73 | {
74 | return "";
75 | }
76 |
77 | public function field()
78 | {
79 | return new $this->field_type($this, $this->options);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/Feature/Question/CreateTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
20 | }
21 |
22 | public function testCreateSuccess()
23 | {
24 | $data = [
25 | 'label' => 'First Name Label',
26 | 'description' => 'Description',
27 | 'field_type' => Text::class,
28 | ];
29 |
30 | $response = $this->postJson(route('forms.fields.store', $this->form->id), $data);
31 |
32 | $response
33 | ->assertStatus(200)
34 | ->assertJson([
35 | 'label' => 'First Name Label',
36 | 'description' => 'Description',
37 | 'field_type' => 'Musonza\Form\Fields\Text',
38 | ]);
39 | }
40 |
41 | public function testQuestionRequiresLabel()
42 | {
43 | $data = [
44 | 'description' => 'Description',
45 | 'field_type' => Text::class,
46 | ];
47 |
48 | $response = $this->postJson(route('forms.fields.store', $this->form->id), $data)
49 | ->assertStatus(422)
50 | ->assertJson([
51 | 'message' => 'The given data was invalid.',
52 | ]);
53 | }
54 |
55 | public function testQuestionRequiresFieldType()
56 | {
57 | $data = [
58 | 'label' => 'First Name Label',
59 | 'description' => 'Description',
60 | ];
61 |
62 | $response = $this->postJson(route('forms.fields.store', $this->form->id), $data)
63 | ->assertStatus(422)
64 | ->assertJson([
65 | 'message' => 'The given data was invalid.',
66 | ]);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/Unit/AnswerTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
21 | $this->question = Form::createQuestion(
22 | [
23 | 'title' => 'First Name',
24 | 'label' => 'First Name',
25 | 'description' => 'Description',
26 | 'field_type' => Text::class,
27 | 'form_id' => $this->form->id,
28 | ]
29 | );
30 |
31 | $submission = $this->form->addSubmission([]);
32 | $question = $this->form->questions()->first();
33 | $this->answer = $question->answers()->create([
34 | 'value' => 'Jane',
35 | 'submission_id' => $submission->id,
36 | ]);
37 | }
38 |
39 | public function testCreateFormQuestionAnswer()
40 | {
41 | $this->assertInstanceOf(Answer::class, $this->answer);
42 | $this->assertInstanceOf(Submission::class, $this->answer->submission);
43 | }
44 |
45 | public function testGetAnswerById()
46 | {
47 | $answer = Form::answerService()->getById(1);
48 | $this->assertEquals($this->answer->id, $answer->id);
49 | $this->assertInstanceOf(Answer::class, $answer);
50 | }
51 |
52 | public function testDeleteAnswer()
53 | {
54 | $this->assertDatabaseHas($this->tablePrefix . 'answers', ['id' => 1, 'value' => 'Jane']);
55 |
56 | Form::answerService()
57 | ->getById(1)
58 | ->delete();
59 |
60 | $this->assertDatabaseMissing($this->tablePrefix . 'answers', ['id' => 1, 'value' => 'Jane']);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/resources/js/components/Alert.vue:
--------------------------------------------------------------------------------
1 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
62 | {{ message }}
63 |
68 | Close
69 |
70 |
71 |
72 |
73 |
80 | {{ message }}
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/database/seeds/FormsDatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call(UsersTableSeeder::class);
19 | $fieldTypes = ['text', 'textarea', 'checkbox', 'radio', 'select'];
20 | $props = [];
21 | $props['choices'] = [
22 | [
23 | 'label' => 'Choice1',
24 | 'recode' => 1,
25 | ],
26 | [
27 | 'label' => 'Choice2',
28 | 'description' => 'This is a test description',
29 | 'attachment' => [
30 | "type" => "image",
31 | "href" => "http://example.com/img",
32 | ],
33 | ],
34 | ['label' => 'Choice3', 'recode' => 3],
35 | ];
36 |
37 | factory(Form::class, 55)->create()->each(function ($form) use ($props) {
38 | $form->questions()->save(factory(Question::class)->make([
39 | 'form_id' => $form->id,
40 | 'field_type' => Text::class,
41 | ]));
42 | $form->questions()->save(factory(Question::class)->make([
43 | 'form_id' => $form->id,
44 | 'field_type' => TextArea::class,
45 | ]));
46 | $form->questions()->save(factory(Question::class)->make([
47 | 'form_id' => $form->id,
48 | 'field_type' => Text::class,
49 | 'properties' => $props,
50 | ]));
51 | $form->questions()->save(factory(Question::class)->make([
52 | 'form_id' => $form->id,
53 | 'field_type' => Text::class,
54 | 'properties' => $props,
55 | ]));
56 | $form->questions()->save(factory(Question::class)->make([
57 | 'form_id' => $form->id,
58 | 'field_type' => Text::class,
59 | 'properties' => $props,
60 | ]));
61 | });
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/BaseTestCase.php:
--------------------------------------------------------------------------------
1 | increments('id');
19 | $table->string('name');
20 | $table->string('email')->unique();
21 | $table->string('password');
22 | $table->rememberToken();
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | protected function migrate()
28 | {
29 | (new CreateFormTables)->up();
30 | $this->migrateTestTables();
31 | }
32 |
33 | public function tearDown(): void
34 | {
35 | $this->rollbackTestTables();
36 | (new CreateFormTables)->down();
37 | // parent::tearDown();
38 | }
39 |
40 | public function createUsers($count = 1)
41 | {
42 | return factory(User::class, $count)->create();
43 | }
44 |
45 | protected function rollbackTestTables()
46 | {
47 | Schema::drop('users');
48 | }
49 |
50 | protected function getPackageProviders($app)
51 | {
52 | return [
53 | \Orchestra\Database\ConsoleServiceProvider::class,
54 | \Musonza\Form\FormServiceProvider::class,
55 | ];
56 | }
57 |
58 | protected function getPackageAliases($app)
59 | {
60 | return [
61 | 'Form' => \Musonza\Form\Facades\FormFacade::class,
62 | ];
63 | }
64 |
65 | protected function disableExceptionHandling($app)
66 | {
67 | $app->instance(ExceptionHandler::class, new class extends Handler
68 | {
69 | public function __construct()
70 | {
71 | }
72 |
73 | public function report(\Exception $e)
74 | {
75 | // no-op
76 | }
77 |
78 | public function render($request, \Exception $e)
79 | {
80 | throw $e;
81 | }
82 | });
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/FormServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishMigrations();
24 | $this->publishDatabaseSeeds();
25 | $this->publishConfig();
26 |
27 | require __DIR__ . '/Http/routes.php';
28 |
29 | $this->publishes([
30 | __DIR__ . '/../public' => public_path('vendor/laravel-forms'),
31 | ], 'laravel-forms-assets');
32 | }
33 |
34 | /**
35 | * Register application services.
36 | *
37 | * @return void
38 | */
39 | public function register()
40 | {
41 | $this->app->bind('form', function () {
42 | return $this->app->make(\Musonza\Form\Form::class);
43 | });
44 | }
45 |
46 | /**
47 | * Publish package's migrations.
48 | *
49 | * @return void
50 | */
51 | public function publishMigrations()
52 | {
53 | $timestamp = date('Y_m_d_His', time());
54 | $stub = __DIR__ . '/../database/migrations/create_form_tables.php';
55 | $target = $this->app->databasePath() . '/migrations/' . $timestamp . '_create_form_tables.php';
56 |
57 | $this->publishes([$stub => $target], 'laravel_forms.migrations');
58 | }
59 |
60 | /**
61 | * Publish database seeds.
62 | *
63 | * @return void
64 | */
65 | public function publishDatabaseSeeds()
66 | {
67 | $this->publishes([
68 | __DIR__ . '/../database/seeds' => database_path() . '/seeds',
69 | ], 'laravel_forms.database_seeds');
70 | }
71 |
72 | /**
73 | * Publish package's config file.
74 | *
75 | * @return void
76 | */
77 | public function publishConfig()
78 | {
79 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'laravel-forms');
80 | $this->publishes([
81 | __DIR__ . '/../config' => config_path(),
82 | ], 'laravel_forms.config');
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/resources/js/base.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | /**
4 | * Show an error message.
5 | */
6 | alertError(message) {
7 | this.$root.alert.type = 'error';
8 | this.$root.alert.message = message;
9 | this.$root.alert.show = true;
10 | },
11 |
12 | alertWarning(message) {
13 | this.$root.alert.type = 'warning';
14 | this.$root.alert.message = message;
15 | this.$root.alert.show = true;
16 | },
17 |
18 | /**
19 | * Show a success message.
20 | */
21 | alertSuccess(message, autoClose) {
22 | this.$root.alert.type = 'success';
23 | this.$root.alert.message = message;
24 | this.$root.alert.show = true;
25 | },
26 |
27 | /**
28 | * Show confirmation message.
29 | */
30 | alertConfirm(message, success, failure, title) {
31 | this.$root.alert.type = 'confirmation';
32 | this.$root.alert.autoClose = false;
33 | this.$root.alert.title = title;
34 | this.$root.alert.message = message;
35 | this.$root.alert.confirmationAgree = success;
36 | this.$root.alert.confirmationCancel = failure;
37 | this.$root.alert.show = true;
38 | },
39 |
40 | dismissAlert() {
41 | this.$root.alert.show = false;
42 | },
43 |
44 | formatErrorMessage(response) {
45 | let message = '';
46 | if (response.data) {
47 | let data = response.data;
48 | message += data.message;
49 |
50 | if (data.errors) {
51 | for (let [key, value] of Object.entries(data.errors)) {
52 | message += ' ' + value;
53 | break;
54 | }
55 | }
56 | }
57 | return message;
58 | },
59 |
60 | getConfirmationMessages() {
61 | return {
62 | 'delete_form': {
63 | 'title': 'Delete Form',
64 | 'message': 'Are you sure you want to delete this form? This action cannot be undone.'
65 | },
66 | 'delete_field': {
67 | 'title': 'Delete Field',
68 | 'message': 'Are you sure you want to delete this field? This action cannot be undone.'
69 | },
70 | 'delete_submission': {
71 | 'title': 'Delete Submission',
72 | 'message': 'Are you sure you want to delete this submission? This action cannot be undone.'
73 | },
74 | }
75 | }
76 | }
77 | };
--------------------------------------------------------------------------------
/database/factories/ModelFactory.php:
--------------------------------------------------------------------------------
1 | define(Musonza\Form\User::class, function (Faker $faker) {
11 | return [
12 | 'name' => $faker->name,
13 | 'email' => $faker->unique()->safeEmail,
14 | 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
15 | 'remember_token' => str_random(10),
16 | ];
17 | });
18 |
19 | $factory->define(Form::class, function (Faker $faker) {
20 | return [
21 | 'title' => $faker->sentence,
22 | 'description' => $faker->sentence,
23 | ];
24 | });
25 |
26 | $factory->define(Question::class, function (Faker $faker) {
27 | return [
28 | 'label' => $faker->sentence,
29 | 'description' => $faker->sentence,
30 | 'form_id' => function () {
31 | return factory(Form::class)->create()->id;
32 | },
33 | 'field_type' => Text::class,
34 | 'is_required' => true,
35 | ];
36 | });
37 |
38 | $factory->define(Submission::class, function (Faker $faker) {
39 | return [
40 | 'ip_address' => $faker->ipv4,
41 | 'response' => [
42 | 'field1' => [
43 | 'field_identifier' => 'field1',
44 | 'response_text' => $faker->sentence,
45 | ],
46 | 'field2' => [
47 | 'field_identifier' => 'field2',
48 | 'response_text' => $faker->paragraph,
49 | ],
50 | 'field3' => [
51 | 'field_identifier' => 'field3',
52 | 'response_text' => $faker->sentence,
53 | ],
54 | ],
55 | 'form_id' => function () {
56 | return factory(Form::class)->create()->id;
57 | },
58 | ];
59 | });
60 |
61 | $factory->define(SubmissionResponse::class, function (Faker $faker) {
62 | return [
63 | 'submission_id' => function () {
64 | return factory(Submission::class)->create()->id;
65 | },
66 | 'question_id' => function () {
67 | return factory(Question::class)->create()->id;
68 | },
69 | 'response_text' => $faker->sentence,
70 | ];
71 | });
72 |
--------------------------------------------------------------------------------
/tests/Unit/QuestionTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
19 | $this->question = Form::createQuestion(
20 | [
21 | 'label' => 'First Name',
22 | 'description' => 'Description',
23 | 'field_type' => Text::class,
24 | 'form_id' => $this->form->id,
25 | ]
26 | );
27 | }
28 |
29 | public function testCreatesQuestion()
30 | {
31 | $this->assertDatabaseHas($this->tablePrefix . 'questions', ['id' => 1]);
32 | }
33 |
34 | public function testGetQuestionById()
35 | {
36 | $question = Form::questionService()->getById(1);
37 | $this->assertEquals(1, $question->id);
38 | }
39 |
40 | public function testQuestionBelongsToForm()
41 | {
42 | $this->assertInstanceOf(\Musonza\Form\Models\Form::class, $this->question->form);
43 | }
44 |
45 | public function testUpdatesQuestion()
46 | {
47 | $this->question->update([
48 | 'label' => 'New Label',
49 | 'field_type' => TextArea::class,
50 | ]);
51 |
52 | $this->assertEquals('New Label', $this->question->label);
53 | $this->assertInstanceOf(TextArea::class, $this->question->field());
54 | }
55 |
56 | public function testDeleteQuestion()
57 | {
58 | $this->assertDatabaseHas($this->tablePrefix . 'questions', ['id' => 1]);
59 | Form::questionService()
60 | ->getById(1)
61 | ->delete();
62 | $this->assertDatabaseMissing($this->tablePrefix . 'questions', ['id' => 1]);
63 | }
64 |
65 | public function testAddQuestionValidations()
66 | {
67 | $validations = "required | email";
68 | $this->question->addValidations($validations);
69 | $this->assertEquals($validations, $this->question->validations);
70 | }
71 |
72 | public function testResolvesQuestionField()
73 | {
74 | $this->assertInstanceOf(Text::class, $this->question->field());
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/resources/js/pages/forms/fields/create.vue:
--------------------------------------------------------------------------------
1 |
62 |
63 |
64 |
65 | New Field
66 |
67 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/tests/Unit/Fields/FieldsRenderTest.php:
--------------------------------------------------------------------------------
1 | form = Form::create(['title' => 'Contact Form']);
22 | }
23 |
24 | /**
25 | * @dataProvider fieldsDataProvider
26 | */
27 | public function testFieldRender($field, $options, $expected)
28 | {
29 | $question = $this->createQuestion($field, $options);
30 |
31 | $this->assertEquals($expected, $question->field()->render());
32 | }
33 |
34 | public function fieldsDataProvider()
35 | {
36 | return [
37 | [
38 | TextArea::class, [],
39 | '',
40 | ],
41 | [
42 | Text::class, [],
43 | '',
44 | ],
45 | [
46 | Password::class, [],
47 | '',
48 | ],
49 | [
50 | Radio::class,
51 | ['male'],
52 | ' male
',
53 | ],
54 |
55 | [
56 | Select::class,
57 | ['male', 'female'],
58 | '',
59 | ],
60 | [
61 | Select::class,
62 | ['m' => 'male', 'f' => 'female'],
63 | '',
64 | ],
65 | ];
66 | }
67 |
68 | public function createQuestion($field, $options)
69 | {
70 | return Form::createQuestion(
71 | [
72 | 'label' => 'First Name',
73 | 'description' => 'Description',
74 | 'field_type' => $field,
75 | 'options' => $options,
76 | 'form_id' => $this->form->id,
77 | ]
78 | );
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/resources/js/components/FormsComponent.vue:
--------------------------------------------------------------------------------
1 |
60 |
61 |
62 |
63 | Create Form
64 |
65 |
66 | | {{ props.item.id }} |
67 | {{ props.item.title }} |
68 |
69 | {{ props.item.status.label }}
76 | |
77 | {{ props.item.submissions_count }} |
78 |
79 | visibility
80 |
81 | edit
82 |
83 | delete
84 | |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/Http/Controllers/FormController.php:
--------------------------------------------------------------------------------
1 | formTransformer = $formTransformer;
29 | $this->form = $form;
30 | }
31 |
32 | /**
33 | * List forms.
34 | *
35 | * @param ListFormRequest $request
36 | * @return \Illuminate\Http\Response
37 | */
38 | public function index(ListFormRequest $request)
39 | {
40 | $forms = FormModel::all();
41 |
42 | return response($this->formTransformer->transformCollection($forms));
43 | }
44 |
45 | /**
46 | * Gets the form by id.
47 | *
48 | * @param FormModel $form
49 | * @return \Illuminate\Http\Response
50 | */
51 | public function show(FormModel $form)
52 | {
53 | request()->query->add(['include' => 'questions']);
54 |
55 | return response($this->formTransformer->transformItem($form));
56 | }
57 |
58 | /**
59 | * Stores the created form.
60 | *
61 | * @param CreateFormRequest $request
62 | * @return \Illuminate\Http\Response
63 | */
64 | public function store(CreateFormRequest $request)
65 | {
66 | $form = $this->form->create($request->validated());
67 |
68 | return response($this->formTransformer->transformItem($form));
69 | }
70 |
71 | /**
72 | * Updates form details.
73 | *
74 | * @param UpdateFormRequest $request
75 | * @param FormModel $form
76 | * @return \Illuminate\Http\Response
77 | */
78 | public function update(UpdateFormRequest $request, FormModel $form)
79 | {
80 | $form->update($request->validated());
81 |
82 | return response($this->formTransformer->transformItem($form));
83 | }
84 |
85 | /**
86 | * Deletes a form.
87 | *
88 | * @param DeleteFormRequest $request
89 | * @param FormModel $form
90 | * @return \Illuminate\Http\Response
91 | * @throws \Exception
92 | */
93 | public function destroy(DeleteFormRequest $request, FormModel $form)
94 | {
95 | $form->delete();
96 |
97 | return response('', 201);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Fields/FormField.php:
--------------------------------------------------------------------------------
1 | question = $question;
26 | $this->name = "field_{$question->id}";
27 | $this->options = $options;
28 | $this->fieldHtmlId = $this->nameToId();
29 | }
30 |
31 | protected function input()
32 | {
33 | $this->attributes['type'] = $this->controlType;
34 | $this->attributes['value'] = $this->question->value;
35 | $this->attributes['placeholder'] = $this->question->placeholder;
36 | $this->attributes = $this->attributes($this->attributes);
37 |
38 | $input = 'attributes . '>';
39 |
40 | //dd($input);
41 |
42 | return 'attributes . '>';
43 | }
44 |
45 | /**
46 | * [attributes description]
47 | * @param array $attributes [description]
48 | * @return [type] [description]
49 | */
50 | protected function attributes(array $attributes)
51 | {
52 | $html = array();
53 |
54 | foreach ((array) $attributes as $key => $value) {
55 | if (is_numeric($key)) {
56 | $key = $value;
57 | }
58 | if ($value !== null) {
59 | $html[] = $key . '="' . e($value) . '"';
60 | }
61 | }
62 | return empty($html) ? '' : ' ' . implode(' ', $html);
63 | }
64 |
65 | /**
66 | * [nameToId description]
67 | * @return [type] [description]
68 | */
69 | protected function nameToId()
70 | {
71 | return str_replace(array('.', '[]', '[', ']'), array('_', '', '_', ''), $this->name);
72 | }
73 |
74 | /**
75 | * [render description]
76 | * @return [type] [description]
77 | */
78 | public function render()
79 | {
80 | if (!isset($this->attributes['name'])) {
81 | $this->attributes['name'] = $this->name;
82 | }
83 |
84 | if ($this->question->is_required) {
85 | $this->attributes['required'] = true;
86 | }
87 |
88 | if (!isset($this->attributes['id'])) {
89 | $this->attributes['id'] = $this->nameToId();
90 | }
91 |
92 | $this->attributes['class'] = "form-control";
93 |
94 | return $this->input();
95 | }
96 |
97 | public function hasChoices()
98 | {
99 | return $this->hasChoices;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Http/Controllers/FormSubmissionController.php:
--------------------------------------------------------------------------------
1 | formTransformer = $formTransformer;
36 | $this->submissionTransformer = $submissionTransformer;
37 | $this->form = $form;
38 | }
39 |
40 | public function index(FormModel $form)
41 | {
42 | $submissions = $this->submissionTransformer->transformCollection($form->submissions()->orderBy('id', 'DESC')->get());
43 | $data = ['form' => $this->formTransformer->transformItem($form), 'submissions' => $submissions];
44 |
45 | return response($data);
46 | }
47 |
48 | public function create(Request $request, FormModel $form)
49 | {
50 | request()->query->add(['include' => 'questions']);
51 |
52 | $form = $this->formTransformer->transformItem($form);
53 |
54 | if (request()->wantsJson()) {
55 | return response($form);
56 | }
57 |
58 | $googleRecaptchaEnabled = $this->form->googleRecaptchaEnabled();
59 |
60 | return view('laravel-forms::submissions.edit', compact('form', 'googleRecaptchaEnabled'));
61 | }
62 |
63 | public function store(CreateFormSubmissionRequest $request, FormModel $form)
64 | {
65 | $data = $request->except([
66 | 'g-recaptcha-response',
67 | '_token'
68 | ]);
69 |
70 | $submission = $form->addSubmission($data);
71 | $answers = [];
72 |
73 | foreach ($data as $key => $value) {
74 | // if ($value) {
75 | $questionId = str_replace('field_', '', $key);
76 | $answers[$questionId]['question_id'] = $questionId;
77 | $answers[$questionId]['value'] = $value;
78 | $answers[$questionId]['submission_id'] = $submission->id;
79 | // }
80 | }
81 |
82 | $submission->answers()->insert($answers);
83 |
84 | // Do this in an event listener
85 | $submission->is_complete = true;
86 | $submission->save();
87 |
88 | $this->flashSuccess('Your submission was stored');
89 |
90 | return back();
91 | }
92 |
93 | public function show(FormModel $form, Submission $submission)
94 | {
95 | request()->query->add(['include' => 'answers']);
96 |
97 | return response($this->submissionTransformer->transformItem($submission));
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/database/migrations/create_form_tables.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('title');
19 | $table->string('description')->nullable();
20 | $table->unsignedInteger('status')->default(0);
21 | $table->boolean('enable_captcha')->default(true);
22 | $table->timestamps();
23 | });
24 |
25 | Schema::create('mc_questions', function (Blueprint $table) {
26 | $table->increments('id');
27 | $table->unsignedInteger('form_id');
28 | $table->string('label')->nullable();
29 | $table->string('placeholder')->nullable();
30 | $table->string('help_text')->nullable();
31 | $table->boolean('is_required')->default(true);
32 | $table->text('description')->nullable();
33 | $table->text('field_type');
34 | $table->text('validations')->nullable();
35 | $table->json('properties')->nullable();
36 | $table->json('options')->default();
37 | $table->text('default_value')->nullable();
38 | $table->unsignedInteger('columns')->default(12);
39 | $table->unsignedInteger('position')->nullable();
40 | $table->timestamps();
41 |
42 | $table->foreign('form_id')
43 | ->references('id')
44 | ->on('mc_forms')
45 | ->onDelete('cascade');
46 | });
47 |
48 | Schema::create('mc_answers', function (Blueprint $table) {
49 | $table->increments('id');
50 | $table->unsignedInteger('question_id');
51 | $table->unsignedInteger('submission_id');
52 | $table->text('value');
53 | $table->timestamps();
54 |
55 | $table->foreign('question_id')
56 | ->references('id')
57 | ->on('mc_questions')
58 | ->onDelete('cascade');
59 |
60 | $table->foreign('submission_id')
61 | ->references('id')
62 | ->on('mc_submissions')
63 | ->onDelete('cascade');
64 | });
65 |
66 | Schema::create('mc_submissions', function (Blueprint $table) {
67 | $table->increments('id');
68 | $table->unsignedInteger('form_id');
69 | $table->unsignedInteger('user_id')->nullable();
70 | $table->json('ip_address')->nullable();
71 | $table->json('response')->nullable();
72 | $table->boolean('is_complete')->default(false);
73 | $table->timestamps();
74 |
75 | $table->foreign('form_id')
76 | ->references('id')
77 | ->on('mc_forms')
78 | ->onDelete('cascade');
79 | });
80 | }
81 |
82 | /**
83 | * Reverse the migrations.
84 | *
85 | * @return void
86 | */
87 | public function down()
88 | {
89 | Schema::dropIfExists('mc_forms');
90 | Schema::dropIfExists('mc_questions');
91 | Schema::dropIfExists('mc_answers');
92 | Schema::dropIfExists('mc_submissions');
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Http/Controllers/FormFieldController.php:
--------------------------------------------------------------------------------
1 | fieldTransformer = $fieldTransformer;
28 | $this->fieldTypeTransformer = $fieldTypeTransformer;
29 | }
30 |
31 | public function index(FormModel $form)
32 | {
33 | $fields = $form->questions()->orderBy('position')->get();
34 |
35 | $fields = $this->fieldTransformer->transformCollection($fields);
36 |
37 | return response($fields);
38 | }
39 |
40 | public function create(FormModel $form)
41 | {
42 | $fieldTypes = $this->fieldTypes();
43 |
44 | $field = [];
45 |
46 | return view('laravel-forms::forms.fields.create', compact('form', 'fieldTypes', 'field'));
47 | }
48 |
49 | public function show(FormModel $form, Question $field)
50 | {
51 | return response($field);
52 | }
53 |
54 | public function store(CreateFormQuestionRequest $request, FormModel $form)
55 | {
56 | $data = $request->all();
57 | $data['options'] = [];
58 |
59 | if ($request->options) {
60 | $options = $this->normalizeOptions($request->options);
61 | $data['options'] = $options;
62 | }
63 |
64 | $field = $form->questions()->create($data);
65 | return response($field);
66 | }
67 |
68 | public function destroy(Request $request, FormModel $form, Question $field)
69 | {
70 | $field->delete();
71 | return response('', 201);
72 | }
73 |
74 | public function edit(FormModel $form, Question $field)
75 | {
76 | $field = $this->fieldTransformer->transformItem($field);
77 | $fieldTypes = $this->fieldTypes();
78 | return view('laravel-forms::forms.fields.edit', compact('form', 'field', 'fieldTypes'));
79 | }
80 |
81 | public function update(UpdateFormQuestionRequest $request, FormModel $form, Question $field)
82 | {
83 | $data = $request->validated();
84 | $data['options'] = [];
85 |
86 | if ($request->options) {
87 | $options = $this->normalizeOptions($request->options);
88 | $data['options'] = $options;
89 | }
90 |
91 | if ($request->position && $request->position != $field->position) {
92 | $field->insertAt($request->position);
93 | }
94 |
95 | $field->update($data);
96 |
97 | return response($this->fieldTransformer->transformItem($field));
98 | }
99 |
100 | protected function normalizeOptions($options)
101 | {
102 | $options = array_unique($options);
103 | return array_values($options);
104 | }
105 |
106 | protected function fieldTypes($value = '')
107 | {
108 | return $this->fieldTypeTransformer->transformCollection(config('laravel_forms.fields'));
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/resources/js/components/FormSubmissionsComponent.vue:
--------------------------------------------------------------------------------
1 |
57 |
58 |
59 |
64 |
65 |
66 | | {{ props.item.id }} |
67 | {{ props.item.created_at_readable }} |
68 |
69 |
75 | Complete
76 | In Progress
77 |
78 | |
79 |
80 |
81 |
82 |
87 |
98 |
103 |
{{ answer.question.label }}
104 | {{ answer.response }}
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/resources/js/pages/forms/edit.vue:
--------------------------------------------------------------------------------
1 |
109 |
110 |
111 |
112 |
Form #{{ formModel.id }}
113 |
New Form
114 |
115 | Fields
116 |
117 | Submissions
118 |
119 | {{ formModel.submissions_count }}
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
Details
129 |
130 |
131 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
--------------------------------------------------------------------------------
/resources/js/components/FormFieldsComponent.vue:
--------------------------------------------------------------------------------
1 |
107 |
108 |
109 |
110 |
Add Field
111 |
112 |
Fields
113 |
114 |
115 |
120 |
121 |
122 | :::
123 | {{ field.label }}
124 |
125 |
126 |
127 |
128 |
129 |
139 |
146 |
147 |
154 |
155 |
156 |
157 |
158 |
167 |
168 |
169 | {{ data.item }}
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | file_copy
180 |
181 | Duplicate
182 |
183 |
184 |
185 |
186 | delete
187 |
188 | Delete
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
213 |
214 |
--------------------------------------------------------------------------------