Merge pull request #60 from G0dLik3/implementAttendeesQuestions

Made a start on implementing the 'Attendees Question' feature.
This commit is contained in:
Dave Earley 2016-03-27 20:46:48 +01:00
commit e7b29cf6d8
13 changed files with 624 additions and 21 deletions

View File

@ -0,0 +1,149 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreEventQuestionRequest;
use App\Models\Event;
use App\Models\Ticket;
use App\Models\Question;
use App\Models\QuestionType;
use File;
use Image;
use Validator;
use Illuminate\Http\Request;
class EventQuestionsController extends MyBaseController
{
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create(Request $request)
{
// Get the event ID.
$event_id = $request->get('event_id');
// Get the event or display a 'not found' warning.
$event = Event::findOrFail($event_id);
// Initialize an empty array for the question options.
$question_options = [
(object)[
'name' => '',
],
];
return view('ManageEvent.Modals.EditQuestion', [
'form_url' => route('event.question.store', [
'event_id' => $event_id,
]),
'event' => $event,
'question' => [],
'modal_id' => $request->get('modal_id'),
'question_types' => QuestionType::all(),
'question_options' => $question_options,
]);
}
/**
* Store a newly created resource in storage.
*
* @access public
* @param StoreEventQuestionRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreEventQuestionRequest $request)
{
// Get the event ID.
$event_id = $request->get('event_id');
// Get the event or display a 'not found' warning.
$event = Event::findOrFail($event_id);
// Create question.
$question = Question::createNew(false, false, true);
$question->title = $request->get('title');
$question->instructions = $request->get('instructions');
$question->is_required = $request->get('title');
$question->question_type_id = $request->get('question_type_id');
$question->save();
// Get options.
$options = $request->get('option');
// Add options.
if ($options && is_array($options)) {
foreach ($options as $option_name) {
if (trim($option_name) !== '') {
$question->options()->create([
'name' => $option_name,
]);
}
}
}
// Get tickets.
$ticket_ids = $request->get('tickets');
// Add ticket <-> question entry.
if ($ticket_ids && is_array($ticket_ids)) {
foreach ($ticket_ids as $ticket_id) {
Ticket::find($ticket_id)->questions()->attach($question->id);
}
}
$event->questions()->attach($question->id);
return response()->json([
'status' => 'success',
'message' => 'Successfully Created Question. Refreshing page...',
'runThis' => 'reloadPageDelayed();',
]);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

View File

@ -36,8 +36,11 @@ class MyBaseController extends Controller
*/
public function getEventViewData($event_id, $additional_data = [])
{
$event = Event::scope()->findOrFail($event_id);
return array_merge([
'event' => Event::scope()->findOrFail($event_id),
], $additional_data);
'event' => $event,
'questions' => $event->questions()->get(),
], $additional_data);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class StoreEventQuestionRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required',
'option' => 'array',
];
}
}

View File

@ -375,6 +375,11 @@ Route::group(['middleware' => ['auth', 'first.run']], function () {
'uses' => 'EventTicketQuestionsController@postCreateQuestion',
]);
/**
* Event questions.
*/
Route::resource('question', 'EventQuestionsController');
/*
* -------
* Attendees

View File

@ -16,6 +16,7 @@ class Question extends MyBaseModel
/**
* The events associated with the question.
*
* @access public
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function events()
@ -26,10 +27,22 @@ class Question extends MyBaseModel
/**
* The type associated with the question.
*
* @access public
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function question_types()
{
return $this->hasOne('\App\Models\QuestionType');
}
/**
* The options associated with the question.
*
* @access public
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function options()
{
return $this->hasMany('\App\Models\QuestionOption');
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class QuestionOption extends MyBaseModel
{
/**
* The attributes that are mass assignable.
*
* @access protected
* @var array
*/
protected $fillable = ['name'];
/**
* Indicates if the model should be timestamped.
*
* @access public
* @var bool
*/
public $timestamps = false;
/**
* The question associated with the question option.
*
* @access public
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function question()
{
return $this->belongsTo('\App\Models\Question');
}
}

View File

@ -61,7 +61,7 @@ class Ticket extends MyBaseModel
*/
public function questions()
{
return $this->belongsToMany('\App\Models\Question', 'ticket_question');
return $this->belongsToMany('\App\Models\Question');
}
/**

View File

@ -0,0 +1,113 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAttendeesQuestions extends Migration
{
/**
* Run the migrations.
*
* @access public
* @return void
*/
public function up()
{
/**
* Checkbox, dropdown, radio, text etc.
*/
Schema::create('question_types', function (Blueprint $table)
{
$table->increments('id');
$table->string('alias');
$table->string('name');
$table->boolean('has_options')->default(false);
$table->boolean('allow_multiple')->default(false);
});
/**
* The questions.
*/
Schema::create('questions', function (Blueprint $table)
{
$table->increments('id');
$table->string('title', 255);
$table->text('instructions');
$table->unsignedInteger('question_type_id');
$table->unsignedInteger('account_id')->index();
$table->tinyInteger('is_required')->default(0);
$table->timestamps();
$table->softDeletes();
$table->foreign('question_type_id')->references('id')->on('question_types');
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
});
/**
* Used for the questions that allow options (checkbox, radio, dropdown).
*/
Schema::create('question_options', function (Blueprint $table)
{
$table->increments('id');
$table->string('name');
$table->integer('question_id')->unsigned()->index();
$table->foreign('question_id')->references('id')->on('questions')->onDelete('cascade');
});
/**
* Event / Question pivot table.
*/
Schema::create('event_question', function(Blueprint $table)
{
$table->increments('id');
$table->integer('event_id')->unsigned()->index();
$table->integer('question_id')->unsigned()->index();
$table->foreign('event_id')->references('id')->on('events')->onDelete('cascade');
$table->foreign('question_id')->references('id')->on('questions')->onDelete('cascade');
});
/**
* Question / Ticket pivot table.
*/
Schema::create('question_ticket', function (Blueprint $table)
{
$table->increments('id');
$table->integer('question_id')->unsigned()->index();
$table->integer('ticket_id')->unsigned()->index();
$table->foreign('ticket_id')->references('id')->on('tickets')->onDelete('cascade');
$table->foreign('question_id')->references('id')->on('questions')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @access public
* @return void
*/
public function down()
{
$tables = [
'question_types',
'questions',
'question_options',
'event_question',
'question_ticket',
];
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
foreach ($tables as $table) {
Schema::drop($table);
}
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
}
}

View File

@ -19,5 +19,8 @@ class DatabaseSeeder extends Seeder
// $this->call('UserTableSeeder');
$this->call('CountriesSeeder');
$this->command->info('Seeded the countries!');
$this->call('QuestionTypesSeeder');
$this->command->info('Seeded the question types!');
}
}

View File

@ -0,0 +1,60 @@
<?php
use Illuminate\Database\Seeder;
class QuestionTypesSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @access public
* @return void
*/
public function run()
{
DB::table('question_types')->insert([
[
'id' => 1,
'alias' => 'text',
'name' => 'Single-line text box',
'has_options' => 0,
'allow_multiple' => 0,
],
[
'id' => 2,
'alias' => 'textarea',
'name' => 'Multi-line text box',
'has_options' => 0,
'allow_multiple' => 0,
],
[
'id' => 3,
'alias' => 'dropdown',
'name' => 'Dropdown (single selection)',
'has_options' => 1,
'allow_multiple' => 0,
],
[
'id' => 4,
'alias' => 'dropdown_multiple',
'name' => 'Dropdown (multiple selection)',
'has_options' => 1,
'allow_multiple' => 1,
],
[
'id' => 5,
'alias' => 'checkbox',
'name' => 'Checkbox',
'has_options' => 1,
'allow_multiple' => 1,
],
[
'id' => 6,
'alias' => 'radio',
'name' => 'Radio input',
'has_options' => 1,
'allow_multiple' => 0,
],
]);
}
}

View File

@ -8550,6 +8550,12 @@ $(function () {
},
error: function (data, statusText, xhr, $form) {
// Form validation error.
if (422 == data.status) {
processFormErrors($form, $.parseJSON(data.responseText));
return;
}
showMessage('Whoops!, it looks like something went wrong on our servers.\n\
Please try again, or contact support if the problem persists.');
@ -8589,24 +8595,7 @@ $(function () {
break;
case 'error':
$.each(data.messages, function (index, error) {
var $input = $(':input[name=' + index + ']', $form);
if ($input.prop('type') === 'file') {
$('#input-' + $input.prop('name')).append('<div class="help-block error">' + error + '</div>')
.parent()
.addClass('has-error');
} else {
$input.after('<div class="help-block error">' + error + '</div>')
.parent()
.addClass('has-error');
}
});
var $submitButton = $form.find('input[type=submit]');
toggleSubmitDisabled($submitButton);
processFormErrors($form, data.messages);
break;
default:
@ -8869,6 +8858,71 @@ $(function () {
});
function changeQuestionType(select)
{
var select = $(select);
var selected = select.find(':selected');
if (selected.data('has-options') == '1') {
$('#question-options').removeClass('hide');
} else {
$('#question-options').addClass('hide');
}
}
function submitQuestionForm()
{
$('#edit-question-form').submit();
}
function addQuestionOption()
{
var tbody = $('#question-options tbody');
var questionOption = $('#question-option-template').html();
tbody.append(questionOption);
}
function removeQuestionOption(removeBtn)
{
var removeBtn = $(removeBtn);
var tbody = removeBtn.parents('tbody');
if (tbody.find('tr').length > 1) {
removeBtn.parents('tr').remove();
} else {
alert('You must have at least one option.');
}
}
function processFormErrors($form, errors)
{
$.each(errors, function (index, error)
{
var $input = $(':input[name=' + index + ']', $form);
if ($input.prop('type') === 'file') {
$('#input-' + $input.prop('name')).append('<div class="help-block error">' + error + '</div>')
.parent()
.addClass('has-error');
} else {
$input.after('<div class="help-block error">' + error + '</div>')
.parent()
.addClass('has-error');
}
});
var $submitButton = $form.find('input[type=submit]');
toggleSubmitDisabled($submitButton);
}
function reloadPageDelayed()
{
setTimeout(function () {
location.reload();
}, 2000);
}
/**
*

View File

@ -524,7 +524,24 @@
</div>
</div>
<h4>Attendees questions</h4>
@if ($questions)
<table class="table">
<tbody>
@foreach ($questions as $question)
<tr>
<td>{{ $question->title }}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
<div class="panel-footer mt15 text-right">
<button class="loadModal btn btn-success" type="button" data-modal-id="EditQuestion" href="javascript:void(0);"
data-href="{{route('event.question.create', ['event_id' => $event->id])}}">
<i class="ico-question"></i> Add question
</button>
{!! Form::submit('Save Changes', ['class'=>"btn btn-success"]) !!}
</div>

View File

@ -0,0 +1,120 @@
<script id="question-option-template" type="text/template">
<tr>
<td><input class="form-control" name="option[]" type="text"></td>
<td width="50">
<i class="btn btn-danger ico-remove" onclick="removeQuestionOption(this);"></i>
</td>
</tr>
</script>
<div role="dialog" id="{{ $modal_id }}" class="modal fade" style="display: none;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-center">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 class="modal-title">
<i class="ico-question"></i>
Questions</h3>
</div>
<div class="modal-body">
<h3>
Existing Questions
</h3>
<div class="panel panel-default">
{!! Form::model($question, ['url' => $form_url, 'id' => 'edit-question-form', 'class' => 'ajax']) !!}
<div class="panel-body">
<div class="form-group">
<label for="question-title" class="required">
Question
</label>
{!! Form::text('title', '', [
'id' => 'question-title',
'class' => 'form-control',
'placeholder' => 'e.g.: What is your name?',
]) !!}
</div>
<div class="form-group">
<label for="question-type">
Question Type
</label>
<select id="question-type" class="form-control" name="question_type_id" onchange="changeQuestionType(this);">
@foreach ($question_types as $question_type)
<option data-has-options="{{$question_type->has_options}}" value="{{$question_type->id}}">
{{$question_type->name}}
</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="question-instructions">
Instructions
</label>
{!! Form::text('instructions', null, [
'id' => 'question-instructions',
'class' => 'form-control',
]) !!}
</div>
<fieldset id="question-options" {!! empty($question->has_options) ? ' class="hide"' : '' !!}>
<legend>Question Options</legend>
<table class="table">
<thead>
<tr>
<th colspan="2">Option name</th>
</tr>
</thead>
<tbody>
@foreach ($question_options as $question_option)
<tr>
<td><input class="form-control" name="option[]" type="text" value="{{ $question_option->name }}"></td>
<td width="50">
<i class="btn btn-danger ico-remove" onclick="removeQuestionOption(this);"></i>
</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td colspan="2">
<span id="add-question-option" class="btn btn-success btn-xs" onclick="addQuestionOption();">
<i class="ico-plus"></i>
Add another option
</span>
</td>
</tr>
</tfoot>
</table>
</fieldset>
<div class="form-group">
{!! Form::checkbox('is_required', 1, null, ['id' => 'is_required']) !!}
{!! Form::label('is_required', 'Make this a required question') !!}
</div>
<div class="form-group">
<label>
Require this question for ticket(s):
</label>
@foreach ($event->tickets as $ticket)
<br>
<input id="ticket_{{ $ticket->id }}" name="tickets[]" type="checkbox" value="{{ $ticket->id }}">
<label for="ticket_{{ $ticket->id }}">&nbsp; {{ $ticket->title }}</label>
@endforeach
</div>
</div>
{!! Form::close() !!}
</div>
</div> <!-- /end modal body-->
<div class="modal-footer">
<a href="" class="btn btn-danger" data-dismiss="modal">
Close
</a>
<a class="btn btn-success" href="javascript:void(0);" onclick="submitQuestionForm();">
Create Question
</a>
</div>
</div><!-- /end modal content-->
</div>
</div>